一、我們從一個簡單的構造函數+原型程序開始
1 var G = function(){}; 2 G.prototype = { 3 length : 5, 4 size : function(){ 5 return this.length; 6 } 7 }
上例是個非常簡單的程序,如果需要調用,我們可以用new的方式
1 var G = function () { 2 if( this instanceof G ) { 3 return this; 4 }else { 5 return new G(); 6 } 7 };
把G的構造函數改造一下,判斷this是否是當前G函數的實例,如果是,直接返回,如果不是,返回new G() 這樣根據原型對象的查找原則,就能確保調用到size方法
完整的代碼:
1 var G = function () { 2 if( this instanceof G ) { 3 return this; 4 }else { 5 return new G(); 6 } 7 }; 8 G.prototype = { 9 length: 5, 10 size: function () { 11 return this.length; 12 } 13 } 14 console.log( G().size() );
在jquery框架中,他是怎么做的?
1 var G = function () { 2 return G.fn; 3 }; 4 G.fn = G.prototype = { 5 length: 5, 6 size: function () { 7 return this.length; 8 } 9 } 10 console.log( G.prototype.size() ); //5 11 console.log( G().size() ); //5
在jquery中, 為函數G增加一個屬性fn( 記住:在js中函數是對象,這其實就是給對象增加了一個屬性 ),然后在G函數中返回 G.fn 就是就是返回G.prototype
那么用G().size 其實就是相當於調用 G.prototype.size()
二、在jquery中,這個構造函數一般是用來選擇元素的。
G返回的是G的原型對象,我們要增加選擇元素的功能,自然而然,就是往原型對象上添加:
1 var G = function ( id ) { 2 return G.fn.init( id ); 3 }; 4 G.fn = G.prototype = { 5 init : function( id ){ 6 return document.getElementById( id ); 7 }, 8 length: 5, 9 size: function () { 10 return this.length; 11 } 12 }
向G的原型對象上添加一個init方法, 然后在構造函數中調用
1 window.onload = function(){ 2 console.log( G( 'box' ) ); 3 G('box').style.backgroundColor = 'red'; 4 // G('box').size(); //報錯,無法鏈式調用 5 } 6 7 <div id="box">ghost wu tell you how to learn design pattern</div>
雖然通過init方法,能夠選擇到dom元素,但是不能實現鏈式調用, 因為G('box')返回的是一個dom對象, 而在dom對象上是沒有size這個方法的,因為size是G.prototype上的
所以要實現鏈式調用,就要確保init方法返回的是G的實例或者G.prototype, 這個時候,this就可以派上用場了
1 <script> 2 var G = function (id) { 3 return G.fn.init(id); 4 }; 5 G.fn = G.prototype = { 6 init: function (id) { 7 this[0] = document.getElementById(id); 8 this.length = 1; 9 return this; 10 }, 11 length: 0, 12 size: function () { 13 return this.length; 14 } 15 } 16 window.onload = function () { 17 console.log(G('box')); 18 console.log( G('box2').size() ); 19 } 20 </script>
1 <div id="box">ghost wu tell you how to learn design pattern</div> 2 <div id="box2">id為box2的第二個div</div>
把選擇到的元素放在this中, 這個時候的this指向的是G.fn,G.prototype?
因為在構造函數中,是這樣調用的: G.fn.init( id ), 我把G.fn標成紅色, 也就是相當於G.fn是一個對象,沒錯他確實就是一個對象G.prototype,所以在init中的this指向的就是
init方法前面的對象( G.fn, G.prototype ).
三、this覆蓋
接下來,就會產生一個問題, this共用之后,元素選擇就會產生覆蓋
1 <script> 2 var G = function (id) { 3 return G.fn.init(id); 4 }; 5 G.fn = G.prototype = { 6 init: function (id) { 7 this[0] = document.getElementById(id); 8 this.length = 1; 9 console.log( this === G.fn, this === G.prototype, this ); 10 return this; 11 }, 12 length: 0, 13 size: function () { 14 return this.length; 15 } 16 } 17 18 window.onload = function(){ 19 console.log( G( 'box' ) ); 20 console.log( G( 'box2' ) ); 21 } 22 </script> 23 24 <div id="box">ghost wu tell you how to learn design pattern</div> 25 <div id="box2">id為box2的第二個div</div>
調用兩次構造函數G 去獲取元素的時候, this[0] 現在都指向了 id為box2的元素, 把第一次G('box')選擇到的id為box的元素覆蓋了,產生覆蓋的原因是this共用,那么我們
可以通過什么方法把this分開呢?不同的this指向不同的實例? 用什么? 恩,對了, 用new,每次new一個構造函數就會生成新的實例
四、解決this覆蓋與鏈式調用
1 <script> 2 var G = function (id) { 3 return new G.fn.init(id); 4 }; 5 G.fn = G.prototype = { 6 init: function (id) { 7 this[0] = document.getElementById(id); 8 this.length = 1; 9 return this; 10 }, 11 length: 0, 12 size: function () { 13 return this.length; 14 } 15 } 16 window.onload = function(){ 17 console.log( G( 'box' ) ); 18 console.log( G( 'box2' ) ); 19 } 20 </script> 21 <div id="box">ghost wu tell you how to learn design pattern</div> 22 <div id="box2">id為box2的第二個div</div>
通過構造函數中new G.fn.init( id ) 的方式,每次生成一個新的實例,但是產生了一個新的問題,不能鏈式調用, 因為init中的this發生了改變,不再指向( G.fn, G.prototype ).
1 var G = function (id) { 2 return new G.fn.init(id); 3 }; 4 G.fn = G.prototype = { 5 init: function (id) { 6 this[0] = document.getElementById(id); 7 this.length = 1; 8 return this; 9 }, 10 length: 0, 11 size: function () { 12 return this.length; 13 } 14 } 15 G.fn.init.prototype = G.fn;
加上G.fn.init.prototype = G.fn;我們就修正了this的覆蓋與鏈式調用問題
五、擴展選擇器
上面支持id選擇器,我們只需要在init函數加上其他類型的擴展就可以了,比如,我這里增加了一個標簽選擇器
1 <!DOCTYPE html> 2 <html lang="en"> 3 4 <head> 5 <meta charset="UTF-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <meta http-equiv="X-UA-Compatible" content="ie=edge"> 8 <title>Document</title> 9 <style> 10 div,p { 11 border:1px solid red; 12 margin: 10px; 13 padding: 10px; 14 } 15 </style> 16 <script> 17 var G = function ( selector, context ) { 18 return new G.fn.init( selector, context ); 19 }; 20 G.fn = G.prototype = { 21 constructor : G, 22 init: function ( selector, context ) { 23 this.length = 0; 24 context = context || document; 25 if ( selector.indexOf( '#' ) == 0 ) { 26 this[0] = document.getElementById( selector.substring( 1 ) ); 27 this.length = 1; 28 }else { 29 var aNode = context.getElementsByTagName( selector ); 30 for( var i = 0, len = aNode.length; i < len; i++ ){ 31 this[i] = aNode[i]; 32 } 33 this.length = len; 34 } 35 this.selector = selector; 36 this.context = context; 37 return this; 38 }, 39 length: 0, 40 size: function () { 41 return this.length; 42 } 43 } 44 G.fn.init.prototype = G.fn; 45 46 window.onload = function(){ 47 console.log( G('#box')[0] ); 48 var aP = G('p', G('#box')[0]); 49 // var aP = G('p'); 50 // var aP = G('#p1'); 51 for( var i = 0, len = aP.size(); i < len; i++ ){ 52 aP[i].style.backgroundColor = 'blue'; 53 } 54 } 55 </script> 56 </head> 57 58 <body> 59 <div id="box"> 60 <p>跟着ghostwu學習設計模式</p> 61 <p>跟着ghostwu學習設計模式</p> 62 <p>跟着ghostwu學習設計模式</p> 63 <p>跟着ghostwu學習設計模式</p> 64 </div> 65 <p id="p1">跟着ghostwu學習設計模式</p> 66 <p>跟着ghostwu學習設計模式</p> 67 </body> 68 69 </html>