实例详解jQuery的无new构建

jQuery的无new构建

jQuery框架的核心就是从HTML文档中匹配元素并对其执行操做、

回想一下使用 jQuery 的时候,实例化一个 jQuery 对象的方法:

// 无 new 构造
$('#test').text('Test');
  
// 固然也可使用 new
var test = new $('#test');
test.text('Test');
大部分人使用 jQuery 的时候都是使用第一种无 new 的构造方式,直接 $('') 进行构造,这也是 jQuery 十分便捷的一个地方。

当咱们使用第一种无 new 构造方式的时候,其本质就是至关于 new jQuery(),那么在 jQuery 内部是如何实现的呢?看看:

(function(window, undefined) {
  var
  // ...
  jQuery = function(selector, context) {
    // The jQuery object is actually just the init constructor 'enhanced'
    return new jQuery.fn.init(selector, context, rootjQuery);
  },
  
  jQuery.fn = jQuery.prototype = {
    init: function(selector, context, rootjQuery) {
      // ...
    }
  }
  jQuery.fn.init.prototype = jQuery.fn;
})(window);
 没看懂?不要紧,咱们一步一步分析。

函数表达式和函数声明

在ECMAScript中,建立函数的最经常使用的两个方法是函数表达式和函数声明,二者期间的区别是有点晕,由于ECMA规范只明确了一点:函数声明必须带有标示符(Identifier)(就是你们常说的函数名称),而函数表达式则能够省略这个标示符: 
//函数声明:
function 函数名称 (参数:可选){ 函数体 }
//函数表达式:
function 函数名称(可选)(参数:可选){ 函数体 }
因此,能够看出,若是不声明函数名称,它确定是表达式,可若是声明了函数名称的话,如何判断是函数声明仍是函数表达式呢?

ECMAScript是经过上下文来区分的,若是function foo(){}是做为赋值表达式的一部分的话,那它就是一个函数表达式,

若是function foo(){}被包含在一个函数体内,或者位于程序的最顶部的话,那它就是一个函数声明
function foo(){} // 声明,由于它是程序的一部分 var bar = function foo(){}; // 表达式,由于它是赋值表达式的一部分 new function bar(){}; // 表达式,由于它是new表达式 (function(){ function bar(){} // 声明,由于它是函数体的一部分 })(); 还有一种函数表达式不太常见,就是被括号括住的(function foo(){}),他是表达式的缘由是由于括号 ()是一个分组操做符,它的内部只能包含表达式 再来看jQuery源码:
(function(window, undefined) { /... })(window) 能够将上面的代码结构分红两部分:(function(){window, undefined}) 和 (window) , 第1个()是一个表达式,而这个表达式自己是一个匿名函数, 因此在这个表达式后面加(window)就表示执行这个匿名函数并传入参数window。 原型 prototype 认识一下什么是原型? 在JavaScript中,原型也是一个对象,经过原型能够实现对象的属性继承,JavaScript的对象中都包含了一个" [[Prototype]]"内部属性,这个属性所对应的就是该对象的原型。 对于"prototype"和"__proto__"这两个属性有的时候可能会弄混,"Person.prototype"和"Person.__proto__"是彻底不一样的。 在这里对"prototype"和"__proto__"进行简单的介绍: 1.对于全部的对象,都有__proto__属性,这个属性对应该对象的原型 2.对于函数对象,除了__proto__属性以外,还有prototype属性,当一个函数被用做构造函数来建立实例时,该函数的prototype属性值将被做为原型赋值给全部对象实例(也就是设置实例的__proto__属性) function Person(name, age){ this.name = name; this.age = age; } Person.prototype.getInfo = function(){ console.log(this.name + " is " + this.age + " years old"); }; //调用 var will = new Person("Will", 28); will.getInfo();//"Will is 28 years old" 闭包 闭包的定义: 当一个内部函数被其外部函数以外的变量引用时,就造成了一个闭包。 闭包的做用: 在了解闭包的做用以前,咱们先了解一下 javascript中的GC机制: 在javascript中,若是一个对象再也不被引用,那么这个对象就会被GC回收,不然这个对象一直会保存在内存中。 在上述例子中,B定义在A中,所以B依赖于A,而外部变量 c 又引用了B, 因此A间接的被 c 引用, 也就是说,A不会被GC回收,会一直保存在内存中。为了证实咱们的推理,看以下例子: function A(){ var count = 0; function B(){ count ++; console.log(count); } return B; } var c = A(); c();// 1 c();// 2 c();// 3 count是A中的一个变量,它的值在B中被改变,函数B每执行一次,count的值就在原来的基础上累加1。所以,A中的count一直保存在内存中。 这就是闭包的做用,有时候咱们须要一个模块中定义这样一个变量:但愿这个变量一直保存在内存中但又不会“污染”全局的变量,这个时候,咱们就能够用闭包来定义这个模块 在看jQuery源码: (function(window, undefined) { var // ...   jQuery = function(selector, context) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init(selector, context, rootjQuery); }, jQuery.fn = jQuery.prototype = { init: function(selector, context, rootjQuery) { // ... } } jQuery.fn.init.prototype = jQuery.fn; })(window); 咱们知道了 什么是闭包:当一个内部函数被其外部函数以外的变量引用时,就造成了一个闭包。 jQuery.fn的init 函数被jQuery 的构造函数调用了,这里造成了一个闭包。 构造函数及调用代码: // ...   jQuery = function(selector, context) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init(selector, context, rootjQuery); }, 问题关键来了。 如何实现无new构建 JavaScript是函数式语言,函数能够实现类,类就是面向对象编程中最基本的概念 var aQuery = function(selector, context) { //构造函数 } aQuery.prototype = { //原型 name:function(){}, age:function(){} } var a = new aQuery(); a.name(); 这是常规的使用方法,显而易见jQuery不是这样玩的 要实现这样,那么jQuery就要当作一个类,那么$()应该是返回类的实例才对 按照jQuery的抒写方式 $().ready() $().noConflict() 要实现这样,那么jQuery就要当作一个类,那么$()应该是返回类的实例才对 因此把代码改一下: var aQuery = function(selector, context) { return new aQuery(); } aQuery.prototype = { name:function(){}, age:function(){} } 经过new aQuery(),虽然返回的是一个实例,可是也能看出很明显的问题,死循环了! 那么如何返回一个正确的实例? 在javascript中实例this只跟原型有关系 那么能够把jQuery类看成一个工厂方法来建立实例,把这个方法放到aQuery.prototye原型中 var aQuery = function(selector, context) { return aQuery.prototype.init(selector); } aQuery.prototype = { init:function(selector){ return this; } name:function(){}, age:function(){} } 当执行aQuery() 返回的实例: 很明显aQuery()返回的是aQuery类的实例,那么在init中的this其实也是指向的aQuery类的实例 问题来了init的this指向的是aQuery类,若是把init函数也看成一个构造器,那么内部的this要如何处理? var aQuery = function(selector, context) { return aQuery.prototype.init(selector); } aQuery.prototype = { init: function(selector) { this.age = 18 return this; }, name: function() {}, age: 20 } aQuery().age //18 由于this只是指向aQuery类的,因此aQuery的age属性是能够被修改的。 这样看似没有问题,其实问题很大的 为何是new jQuery.fn.init? 看以下代码: var aQuery = function(selector, context) { return aQuery.prototype.init(selector); } aQuery.prototype = { init: function(selector) { if(selector=="a") this.age = 18 return this; }, name: function() {}, age: 20 } aQuery("a").age //18 aQuery("b").age //18 当我调用 传入"a"的时候,修改age=18,及aQuery("a").age 的值为18 可是当我 传入"b"的时候 并没又修改 age的值,我也但愿获得默认age的值20,可是aQuery("b").age 的值为18. 由于在 调用aQuery("a").age 的时候age被修改了。 这样的状况下就出错了,因此须要设计出独立的做用域才行。 jQuery框架分隔做用域的处理
jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init( selector, context, rootjQuery ); }, 很明显经过实例init函数,每次都构建新的init实例对象,来分隔this,避免交互混淆 咱们修改一下代码: var aQuery = function(selector, context) { return new aQuery.prototype.init(selector); } aQuery.prototype = { init: function(selector) { if(selector=="a") this.age = 18 return this; }, name: function() {}, age: 20 } aQuery("a").age //18 aQuery("b").age //undefined aQuery("a").name() //Uncaught TypeError: Object [object Object] has no method 'name' 又出现一个新的问题, age :undefined, name() :抛出错误,没法找到这个方法,因此很明显new的init跟jquery类的this分离了 怎么访问jQuery类原型上的属性与方法? 作到既能隔离做用域还能使用jQuery原型对象的做用域呢,还能在返回实例中访问jQuery的原型对象实现的关键点 // Give the init function the jQuery prototype for later instantiation jQuery.fn.init.prototype = jQuery.fn; 咱们再改一下: var aQuery = function(selector, context) { return new aQuery.prototype.init(selector); } aQuery.prototype = { init: function(selector) { if(selector=="a") this.age = 18 return this; }, name: function() { return age; }, age: 20 } aQuery.prototype.init.prototype = aQuery.prototype; aQuery("a").age //18 aQuery("b").age //20 aQuery("a").name() //20 最后在看一下jQuery源码: (function(window, undefined) { var // ...   jQuery = function(selector, context) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init(selector, context, rootjQuery); }, jQuery.fn = jQuery.prototype = { init: function(selector, context, rootjQuery) { // ... } } jQuery.fn.init.prototype = jQuery.fn; })(window); 是否是明白了? 哈哈哈~~~ 在简单说两句: 大部分人初看 jQuery.fn.init.prototype = jQuery.fn 这一句都会被卡主,非常不解。可是这句真的算是 jQuery 的绝妙之处。理解这几句很重要,分点解析一下: 1)首先要明确,使用 $('xxx') 这种实例化方式,其内部调用的是 return new jQuery.fn.init(selector, context, rootjQuery) 这一句话,也就是构造实例是交给了 jQuery.fn.init() 方法取完成。 2)将 jQuery.fn.init 的 prototype 属性设置为 jQuery.fn,那么使用 new jQuery.fn.init() 生成的对象的原型对象就是 jQuery.fn ,因此挂载到 jQuery.fn 上面的函数就至关于挂载到 jQuery.fn.init() 生成的 jQuery 对象上,全部使用 new jQuery.fn.init() 生成的对象也可以访问到 jQuery.fn 上的全部原型方法。 3)也就是实例化方法存在这么一个关系链 1.jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype ; 2.new jQuery.fn.init() 至关于 new jQuery() ; 3.jQuery() 返回的是 new jQuery.fn.init(),而 var obj = new jQuery(),因此这 2 者是至关的,因此咱们能够无 new 实例化 jQuery 对象。

 

jQuery的无new构建javascript

jQuery框架的核心就是从HTML文档中匹配元素并对其执行操做、java

回想一下使用 jQuery 的时候,实例化一个 jQuery 对象的方法:jquery

?
1
2
3
4
5
6
// 无 new 构造
$( '#test' ).text( 'Test' );
  
// 固然也可使用 new
var test = new $( '#test' );
test.text( 'Test' );

大部分人使用 jQuery 的时候都是使用第一种无 new 的构造方式,直接 $('') 进行构造,这也是 jQuery 十分便捷的一个地方。编程

当咱们使用第一种无 new 构造方式的时候,其本质就是至关于 new jQuery(),那么在 jQuery 内部是如何实现的呢?看看:闭包

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
( function (window, undefined) {
   var
   // ...
   jQuery = function (selector, context) {
     // The jQuery object is actually just the init constructor 'enhanced'
     return new jQuery.fn.init(selector, context, rootjQuery);
   },
  
   jQuery.fn = jQuery.prototype = {
     init: function (selector, context, rootjQuery) {
       // ...
     }
   }
   jQuery.fn.init.prototype = jQuery.fn;
})(window);

 没看懂?不要紧,咱们一步一步分析。框架

函数表达式和函数声明函数

在ECMAScript中,建立函数的最经常使用的两个方法是函数表达式和函数声明,二者期间的区别是有点晕,由于ECMA规范只明确了一点:函数声明必须带有标示符(Identifier)(就是你们常说的函数名称),而函数表达式则能够省略这个标示符: this

?
1
2
3
4
//函数声明:
function 函数名称 (参数:可选){ 函数体 }
//函数表达式:
function 函数名称(可选)(参数:可选){ 函数体 }

因此,能够看出,若是不声明函数名称,它确定是表达式,可若是声明了函数名称的话,如何判断是函数声明仍是函数表达式呢?spa

ECMAScript是经过上下文来区分的,若是function foo(){}是做为赋值表达式的一部分的话,那它就是一个函数表达式,.net

若是function foo(){}被包含在一个函数体内,或者位于程序的最顶部的话,那它就是一个函数声明。

?
1
2
3
4
5
6
function foo(){} // 声明,由于它是程序的一部分
var bar = function foo(){}; // 表达式,由于它是赋值表达式的一部分
new function bar(){}; // 表达式,由于它是new表达式
( function (){
  function bar(){} // 声明,由于它是函数体的一部分
})();

还有一种函数表达式不太常见,就是被括号括住的(function foo(){}),他是表达式的缘由是由于括号 ()是一个分组操做符,它的内部只能包含表达式

 再来看jQuery源码:

?
1
2
3
( function (window, undefined) {
   /...
})(window)

能够将上面的代码结构分红两部分:(function(){window, undefined}) 和 (window) ,

第1个()是一个表达式,而这个表达式自己是一个匿名函数,

因此在这个表达式后面加(window)就表示执行这个匿名函数并传入参数window。

原型 prototype

认识一下什么是原型?

在JavaScript中,原型也是一个对象,经过原型能够实现对象的属性继承,JavaScript的对象中都包含了一个" [[Prototype]]"内部属性,这个属性所对应的就是该对象的原型。

对于"prototype"和"__proto__"这两个属性有的时候可能会弄混,"Person.prototype"和"Person.__proto__"是彻底不一样的。

在这里对"prototype"和"__proto__"进行简单的介绍:

    1.对于全部的对象,都有__proto__属性,这个属性对应该对象的原型

    2.对于函数对象,除了__proto__属性以外,还有prototype属性,当一个函数被用做构造函数来建立实例时,该函数的prototype属性值将被做为原型赋值给全部对象实例(也就是设置实例的__proto__属性)

?
1
2
3
4
5
6
7
8
9
10
function Person(name, age){
   this .name = name;
   this .age = age;
}
Person.prototype.getInfo = function (){
   console.log( this .name + " is " + this .age + " years old" );
};
//调用
var will = new Person( "Will" , 28);
will.getInfo(); //"Will is 28 years old"

闭包

闭包的定义:

当一个内部函数被其外部函数以外的变量引用时,就造成了一个闭包。

闭包的做用:

在了解闭包的做用以前,咱们先了解一下 javascript中的GC机制:

在javascript中,若是一个对象再也不被引用,那么这个对象就会被GC回收,不然这个对象一直会保存在内存中。

在上述例子中,B定义在A中,所以B依赖于A,而外部变量 c 又引用了B, 因此A间接的被 c 引用,

也就是说,A不会被GC回收,会一直保存在内存中。为了证实咱们的推理,看以下例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
function A(){
   var count = 0;
   function B(){
     count ++;
     console.log(count);
   }
   return B;
}
var c = A();
c(); // 1
c(); // 2
c(); // 3

count是A中的一个变量,它的值在B中被改变,函数B每执行一次,count的值就在原来的基础上累加1。所以,A中的count一直保存在内存中。

这就是闭包的做用,有时候咱们须要一个模块中定义这样一个变量:但愿这个变量一直保存在内存中但又不会“污染”全局的变量,这个时候,咱们就能够用闭包来定义这个模块

在看jQuery源码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
( function (window, undefined) {
   var
   // ...
  jQuery = function (selector, context) {
     // The jQuery object is actually just the init constructor 'enhanced'
     return new jQuery.fn.init(selector, context, rootjQuery);
   },
   jQuery.fn = jQuery.prototype = {
     init: function (selector, context, rootjQuery) {
       // ...
     }
   }
   jQuery.fn.init.prototype = jQuery.fn;
})(window);
 

咱们知道了 什么是闭包:当一个内部函数被其外部函数以外的变量引用时,就造成了一个闭包。

jQuery.fn的init 函数被jQuery 的构造函数调用了,这里造成了一个闭包。
构造函数及调用代码:

?
1
2
3
4
5
// ...
  jQuery = function (selector, context) {
     // The jQuery object is actually just the init constructor 'enhanced'
     return new jQuery.fn.init(selector, context, rootjQuery);
   },

问题关键来了。

如何实现无new构建

JavaScript是函数式语言,函数能够实现类,类就是面向对象编程中最基本的概念

?
1
2
3
4
5
6
7
8
9
10
var aQuery = function (selector, context) {
     //构造函数
}
aQuery.prototype = {
   //原型
   name: function (){},
   age: function (){}
}
var a = new aQuery();
a.name();
 

这是常规的使用方法,显而易见jQuery不是这样玩的

要实现这样,那么jQuery就要当作一个类,那么$()应该是返回类的实例才对

按照jQuery的抒写方式

?
1
2
$().ready()
$().noConflict()

要实现这样,那么jQuery就要当作一个类,那么$()应该是返回类的实例才对

因此把代码改一下:

?
1
2
3
4
5
6
7
var aQuery = function (selector, context) {
     return new aQuery();
}
aQuery.prototype = {
   name: function (){},
   age: function (){}
}

经过new aQuery(),虽然返回的是一个实例,可是也能看出很明显的问题,死循环了

那么如何返回一个正确的实例?

在javascript中实例this只跟原型有关系

那么能够把jQuery类看成一个工厂方法来建立实例,把这个方法放到aQuery.prototye原型中

?
1
2
3
4
5
6
7
8
9
10
var aQuery = function (selector, context) {
     return aQuery.prototype.init(selector);
}
aQuery.prototype = {
   init: function (selector){
     return this ;
   }
   name: function (){},
   age: function (){}
}

当执行aQuery() 返回的实例:

很明显aQuery()返回的是aQuery类的实例,那么在init中的this其实也是指向的aQuery类的实例

问题来了init的this指向的是aQuery类,若是把init函数也看成一个构造器,那么内部的this要如何处理?

?
1
2
3
4
5
6
7
8
9
10
11
12
var aQuery = function (selector, context) {
     return aQuery.prototype.init(selector);
}
aQuery.prototype = {
   init: function (selector) {
     this .age = 18
     return this ;
   },
   name: function () {},
   age: 20
}
aQuery().age //18

 由于this只是指向aQuery类的,因此aQueryage属性是能够被修改的。

这样看似没有问题,其实问题很大的

为何是new jQuery.fn.init?

看以下代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var aQuery = function (selector, context) {
     return aQuery.prototype.init(selector);
}
aQuery.prototype = {
   init: function (selector) {
     if (selector== "a" )
       this .age = 18
     return this ;
   },
   name: function () {},
   age: 20
}
aQuery( "a" ).age //18
aQuery( "b" ).age //18
 

当我调用 传入"a"的时候,修改age=18,及aQuery("a").age 的值为18

可是当我  传入"b"的时候 并没又修改 age的值,我也但愿获得默认age的值20,可是aQuery("b").age 的值为18.

由于在 调用aQuery("a").age 的时候age被修改了。

这样的状况下就出错了,因此须要设计出独立的做用域才行。

jQuery框架分隔做用域的处理

?
1
2
3
4
jQuery = function ( selector, context ) {
     // The jQuery object is actually just the init constructor 'enhanced'
     return new jQuery.fn.init( selector, context, rootjQuery );
   },

很明显经过实例init函数,每次都构建新的init实例对象,来分隔this,避免交互混淆

咱们修改一下代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var aQuery = function (selector, context) {
     return new aQuery.prototype.init(selector);
}
aQuery.prototype = {
   init: function (selector) {
     if (selector== "a" )
       this .age = 18
     return this ;
   },
   name: function () {},
   age: 20
}
aQuery( "a" ).age //18
aQuery( "b" ).age //undefined
aQuery( "a" ).name() //Uncaught TypeError: Object [object Object] has no method 'name'

又出现一个新的问题,

age  :undefined,

name() :抛出错误,没法找到这个方法,因此很明显new的init跟jquery类的this分离了

怎么访问jQuery类原型上的属性与方法?

     作到既能隔离做用域还能使用jQuery原型对象的做用域呢,还能在返回实例中访问jQuery的原型对象?

实现的关键点

?
1
2
// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;

咱们再改一下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var aQuery = function (selector, context) {
     return new aQuery.prototype.init(selector);
}
aQuery.prototype = {
   init: function (selector) {
     if (selector== "a" )
       this .age = 18
     return this ;
   },
   name: function () {
      return age;
   },
   age: 20
}
aQuery.prototype.init.prototype = aQuery.prototype;
 
aQuery( "a" ).age //18
aQuery( "b" ).age //20
aQuery( "a" ).name()  //20

最后在看一下jQuery源码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
( function (window, undefined) {
   var
   // ...
  jQuery = function (selector, context) {
     // The jQuery object is actually just the init constructor 'enhanced'
     return new jQuery.fn.init(selector, context, rootjQuery);
   },
   jQuery.fn = jQuery.prototype = {
     init: function (selector, context, rootjQuery) {
       // ...
     }
   }
   jQuery.fn.init.prototype = jQuery.fn;
})(window);

是否是明白了?

哈哈哈~~~

在简单说两句:

大部分人初看 jQuery.fn.init.prototype = jQuery.fn 这一句都会被卡主,非常不解。可是这句真的算是 jQuery 的绝妙之处。理解这几句很重要,分点解析一下:

1)首先要明确,使用 $('xxx') 这种实例化方式,其内部调用的是 return new jQuery.fn.init(selector, context, rootjQuery) 这一句话,也就是构造实例是交给了 jQuery.fn.init() 方法取完成。

2)将 jQuery.fn.init 的 prototype 属性设置为 jQuery.fn,那么使用 new jQuery.fn.init() 生成的对象的原型对象就是 jQuery.fn ,因此挂载到 jQuery.fn 上面的函数就至关于挂载到 jQuery.fn.init() 生成的 jQuery 对象上,全部使用 new jQuery.fn.init() 生成的对象也可以访问到 jQuery.fn 上的全部原型方法。

3)也就是实例化方法存在这么一个关系链 

    1.jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype ;

    2.new jQuery.fn.init() 至关于 new jQuery() ;

    3.jQuery() 返回的是 new jQuery.fn.init(),而 var obj = new jQuery(),因此这 2 者是至关的,因此咱们能够无 new 实例化 jQuery 对象。

相关文章
相关标签/搜索