一、我们从一个简单的构造函数+原型程序开始
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>