1、对象的构建
// 方法一 function ajquery (name) { this.name = name; this.sayName = function() { return this.name; } return this; } // 方法二 function ajquery(name) { this.name = name; } ajquery.prototype = { sayName : function () { return this.name; } }
上面是两种建立类的方式,虽然最后实现的效果是一致的可是在性能上确实不一致的,当咱们实例化三个ajquery对象的时候,对于方法一,每个实例都有一个sayName方法,这样就浪费了内存,增长了开销,方法二则是吧sayName放到了原型链上了,这样每个实例对象都能共享这个方法了(是一个不错的选择呢),只是经过scope链接到原型链上查找,这样就无形之中也就是多了一层做用域链的查找;javascript
jquery的对象在性能上考虑,因此就必须采用原型链了呢css
jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context ); } jQuery.fn = jQuery.prototype = { init:function(){ return this }, jquery: version, constructor: jQuery, // ……………… } var a = $() ;
2、 jquery中的对象的构造器的分离
new一个实例的过程html
1. 建立一个空对象 2. 将构造函数的做用域赋给这个对象,这个this就指向了这个对象了 3. 执行构造函数中的代码 4. 返回这个对象
new主要是吧原型链跟实例的this链接起来java
var $$ = ajquery = function (selector) { this.selector = selector; return this; } ajquery.fn = ajquery.prototype = { getSelector: function () { return this.selector; }, constructor: ajquery } var a = new $$('aaa'); a.getSelector();
var $$ = ajquery = function (selector) { if(!(this instanceof ajquery)) { return new ajquery(selector); } this.selector = selector; return this; }
var $$ = ajquery = function(selector) { return new ajquery.fn.init(selector); } ajquery.fn = ajquery.prototype = { name: 'meils', init: function (selector){ console.log(this); }, constructor: ajquery }
init是ajQuery原型上做为构造器的一个方法,那么其this就不是ajQuery了,因此this就彻底引用不到ajQuery的原型了,因此这里经过new把init方法与ajQuery给分离成2个独立的构造器。jquery
3、 方法的链式调用
$('input[type="button"]') .eq(0).click(function() { alert('点击我!'); }).end().eq(1) .click(function() { $('input[type="button"]:eq(0)').trigger('click'); }).end().eq(2) .toggle(function() { $('.aa').hide('slow'); }, function() { $('.aa').show('slow'); }); // end()方法是将最近的筛选操做还原为前一步操做的状态
看这个代码的结构,咱们或多或少都能猜到其含义:数组
☑ 找出type类型为button的input元素安全
☑ 找到第一个按钮,并绑定click事件处理函数app
☑ 返回全部按钮,再找到第二个jquery插件
☑ 为第二个按钮绑定click事件处理函数异步
☑ 为第三个按钮绑定toggle事件处理函数
jquery的核心理念是写的少,办的多。
jquery.fn.init = function() { return this; } jquery.fn.get = function() { return this; }
// 经过返回this来实现链式操做,由于返回当前实例的this,从而又能够访问本身的原型了,这样的就节省代码量,提升代码的效率,代码看起来更优雅。可是这种方法有一个问题是:全部对象的方法返回的都是对象自己,也就是说没有返回值,因此这种方法不必定在任何环境下都适合。
虽然Javascript是无阻塞语言,可是他并非没阻塞,而是不能阻塞,因此他须要经过事件来驱动,异步来完成一些本须要阻塞进程的操做,这样处理只是同步链式,除了同步链式还有异步链式,异步链式jQuery从1.5开始就引入了Promise,jQuery.Deferred后期再讨论。
4、插件接口的设计
jQUery为插件的编写提供了两个接口,一种时 $.extend()
将其做为静态方法处理,另外一种时将方法挂在到 $.fn
上,做为jquery的原型对象的方法。
fn与jQuery实际上是2个不一样的对象,在以前有讲解:jQuery.extend 调用的时候,this是指向jQuery对象的(jQuery是函数,也是对象!),因此这里扩展在jQuery上。而jQuery.fn.extend 调用的时候,this指向fn对象,jQuery.fn 和jQuery.prototype指向同一对象,扩展fn就是扩展jQuery.prototype原型对象。这里增长的是原型方法,也就是对象方法了。因此jQuery的API中提供了以上2个扩展函数。
5、插件开发
jquery插件的开发模式一共有三种。
第一种方式仅仅只是在$的命名空间下建立了一个静态方法,能够直接使用$来调用执行便可。而不须要使用$('selector')来选中DOM对象再来调用方法。
第三种方法是用来编写更高级的插件使用的,通常不使用。
第二种方法是咱们平时开发所,最经常使用的一种形式,稍后咱们将仔细学习该方法
$.extend({ sayName: function(name) { console.log(name); } }) $.sayName('meils'); // 直接使用 $.sayName 来调用 // 经过$.extend()向jQuery添加了一个sayHello函数,而后经过$直接调用。到此你能够认为咱们已经完成了一个简单的jQuery插件了。
这种方法用来定义一些辅助方法,仍是有用的。
$.extend({ log: function(message) { var now = new Date(), y = now.getFullYear(), m = now.getMonth() + 1, //!JavaScript中月分是从0开始的 d = now.getDate(), h = now.getHours(), min = now.getMinutes(), s = now.getSeconds(), time = y + '/' + m + '/' + d + ' ' + h + ':' + min + ':' + s; console.log(time + ' My App: ' + message); } }) $.log('initializing...'); //调用
1、基本使用
$.fn.setColor = function() { this.css('color', 'red'); } //这里的this指向调用咱们这个方法的那个对象
2、each()遍历
咱们也能够对咱们获取到的元素的集合的每个元素使用咱们的方法。
$.fn.addUrl = function() { this.css('css', 'red'); this.each(function() { $(this).append('i'); }) }
jquery选择器
选中的是一个集合,所以能够$.each()
来遍历每个元素,在each
内部,this
`指的就是每个DOM元素了,若是须要调用jquery的方法,那么就须要在用$
来包装一下了;
3、链式调用
$.fn.setSelector = function() { this.css('color', 'red'); return this.each(function() { }) }
4、参数的接收
咱们会使用$.extend()来处理咱们参数,由于这个方法若是传入多个参数后,他会把这些合并到第一个上。若是存在有同名参数,那么遵循后面的覆盖前面的。
// 利用这一点,咱们能够在插件里定义一个保存插件参数默认值的对象,同时将接收来的参数对象合并到默认对象上,最后就实现了用户指定了值的参数使用指定的值,未指定的参数使用插件默认值。 $.fn.setStyle = function(options) { var default = { color: 'red', fontSize: '15px' } var setting = $.extend(default, optioins); return this.css({ 'color': setting.color, 'fontSize': setting.fontSize }) }
上面的参数处理有一点不足,咱们把default也改变了,万一咱们接下来还要使用该怎么办呢,因此咱们还须要在作修改
$.fn.setStyle = function(options) { var default = { color: 'red', fontSize: '15px' } var setting = $.extend({}, default, optioins); return this.css({ 'color': setting.color, 'fontSize': setting.fontSize }) }
5、面向对象开发插件
你可能须要一个方法的时候就去定义一个function,当须要另一个方法的时候,再去随便定义一个function,一样,须要一个变量的时候,毫无规则地定义一些散落在代码各处的变量。
仍是老问题,不方便维护,也不够清晰。固然,这些问题在代码规模较小时是体现不出来的。
// 若是将须要的重要变量定义到对象的属性上,函数变成对象的方法,当咱们须要的时候经过对象来获取,一来方便管理,二来不会影响外部命名空间,由于全部这些变量名还有方法名都是在对象内部。 var beautify = function(ele, option) { this.$element = this.ele; this.default = { 'color': 'red', 'fontSize': '12px', 'textDecoration':'none' } this.ops = $.extend({}, default, option); } beautify.prototype = { setStyle : function() { return this.$element.css({ 'color': this.options.color, 'fontSize': this.options.fontSize, 'textDecoration': this.options.textDecoration }) // return this对象,实现链式调用 } } $.fn.myPlugin = function(option) { var beautify = new beautify(this, option); beautify.setStyle(); } /// 使用 $('a').myPlugin({ 'color': '#2C9929', 'fontSize': '20px' });
6、解决命名冲突
由于随着你代码的增多,若是有意无心在全局范围内定义一些变量的话,最后很难维护,也容易跟别人写的代码有冲突。
好比你在代码中向全局window对象添加了一个变量status用于存放状态,同时页面中引用了另外一个别人写的库,也向全局添加了这样一个同名变量,最后的结果确定不是你想要的。因此不到万不得已,通常咱们不会将变量定义成全局的。
一个最好的方法就是始终使用自调用的匿名函数来包裹你的代码,这样,就能够彻底放心的使用本身的变量了。
绝对不会有命名冲突。
;(function() { var beautify = function(ele, option) { this.$element = this.ele; this.default = { 'color': 'red', 'fontSize': '12px', 'textDecoration':'none' } this.ops = $.extend({}, default, option); } beautify.prototype = { setStyle : function() { return this.$element.css({ 'color': this.options.color, 'fontSize': this.options.fontSize, 'textDecoration': this.options.textDecoration }) // return this对象,实现链式调用 } } $.fn.myPlugin = function(option) { var beautify = new beautify(this, option); beautify.setStyle(); } })();
var foo=function(){ //别人的代码 }//注意这里没有用分号结尾 //开始咱们的代码。。。 (function(){ //咱们的代码。。 alert('Hello!'); })();
因为前面的代码没有加;
号 , 而后咱们的插件加载出错了,因此咱们在咱们插件的最开始加一个;
号来强制前面的结束。
咱们能够将一些系统变量传入咱们的插件,这样能够缩短变量的做用域链,将其做为咱们插件内部的局部变量来使用,这样能够大大提升咱们的速度和性能。
;(function($, window, document, undefined) { var beautify = function(ele, option) { this.$element = this.ele; this.default = { 'color': 'red', 'fontSize': '12px', 'textDecoration':'none' } this.ops = $.extend({}, default, option); } beautify.prototype = { setStyle : function() { return this.$element.css({ 'color': this.options.color, 'fontSize': this.options.fontSize, 'textDecoration': this.options.textDecoration }) // return this对象,实现链式调用 } } $.fn.myPlugin = function(option) { var beautify = new beautify(this, option); beautify.setStyle(); } })(jQuery, window,document,);
一个安全,结构良好,组织有序的插件编写完成。
6、版本回溯
jquery能过够方便的获取到DOM元素,而且可以遍历它们。你知道这些背后的原理吗?
经过对sizzle的分析,咱们知道jquery获取到的是一个jquery对象,是一个包装容器。
你会看到在上面有一个prevObject
对象。
在jquery内部维护着一个jquery对象栈,遍历方法每一次遍历都会找到一个新的元素,每个元素都是一个jquery对象,而后jquery会把这些元素都放到这个栈中。(入栈)
$('ul').find('li'); // 这句话能够拆分,第一个jquery对象是$('ul'); 第二个jquery对象是$('ul').find('li'); // 首先将$('ul')入栈 // 而后将li集合(类数组对象)入栈
由于栈中的每个元素都是一个jquery对象,每个jquery对象都有着三个属性,分别时context
、selector
、prevObject
, prevObject
属性就是指向对象栈中的前一个元素的。
这个prevObject
就比较有意思了。它指向前一个元素。
举例子了:
<ul id="aaron"> parent <li>child</li> </ul> var aaron = $("#aaron"); aaron.find('li').click(function(){ alert(1); //1 }) // 若是咱们想在返回父级 aaron.find('li').click(function(){ alert(1); //1 }).end().click(function() { alert(2); }) // end()方法就是进行了回溯,
jquery为咱们操纵对象栈提供了两个有用的方法
这两个方法都是进行版本的回退。
返回最近的一次筛选的上一个状态。回到最近的一个"破坏性"操做以前。即,将匹配的元素列表变为前一次的状态。
<p><span>Hello</span>,how are you?</p> $('p').find('span').end(); // 选取全部的p元素,查找并选取span子元素,而后再回过来选取p元素
end()
方法返回的就是一个prevObject
;
<ul> <li>list item 1</li> <li>list item 2</li> <li class="third-item">list item 3</li> <li>list item 4</li> <li>list item 5</li> </ul> $('li.third-item').nextAll().addBank() .css(); // 初始选择位于第3项,初始化堆栈集合只包含这项。调用.nextAll() 后将第4和第5项推入堆栈。最后,调用.addBack() 合并这两个组元素在一块儿,建立一个jQuery对象,指向全部三个项元素(按照文档中的顺序):{[<li.third-item>,<li>,<li> ]} // [1] li.third-item 入栈 // [2] 四、5入栈 // [3] addBank 合并重组
.addBack()方法致使前一组遍历堆栈中的DOM元素被添加到当前组。
来举例子喽
// First Example $("div.before-addback").find("p").addClass("background"); // p元素添加背景 // Second Example $("div.after-addback").find("p").addBack().addClass("background"); // 选中的p元素以及前一个元素合并到栈中,所有添加背景
利用这个DOM元素栈能够减小重复的查询和遍历的操做,而减小重复操做也正是优化jQuery代码性能的关键所在。
在对对象栈的操做中,用到了一个pushStack()
pushStack 生成了一个新 jQuery 对象 ret,ret 的 prevObject 属性是指向调用 pushStack 函数的那个 jQuery 对象的,这样就造成了一个栈链,其它原型方法如 find、nextAll、filter 等均可以调用 pushStack 函数,返回一个新的 jQuery 对象并维持对象栈。
jQuery.fn.pushStack = function (elems) { // 将 elems 合并到 jQuery 对象中 var ret = jQuery.merge(this.constructor(), elems); // 实现对象栈 ret.prevObject = this; // 返回 return ret; }
jQuery.fn.end = function() { return this.prevObject || this.constructor; // 若是存在以前的jquery对象就返回它,若是不存在上一个,就返回当前的jQuery对象。 }
jQuery.fn.addBank = function(selector) { return this.add(selector == null ? this.prevObject : this.prevObject.filter(selector)); // 若是参数为空,就把当前的jquery对象的上一个jQuery对象一块儿合并为一个新的对象。若是指定了参数,那么就在上一个对象中查找这个对象。 }
<ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> </ul> var li = $('ul').find('li'); console.log(li); //结果以下图