實例詳解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 對象。

相關文章
相關標籤/搜索