内功修行
—了解原型(prototype)
传统的OO语言有类的概念,js就没有,但是js也是可以实现面向对象编程的,比如:通过原型。
那什么是原型呢?
简单的说,我们创建的每一个函数都会有一个原型(prototype)属性,这个属性是一个指针,指向原型(prototype)对象。嗯,没错,就是这么一回事。
使用原型对象可以让所有的实例对象共享它包含的属性和方法,而不会像工厂模式或者构造函数模式那样,明明方法可以是通用的,可是每一个创建的对象实例都要各自复制一份。
举个栗子...
但使用原型模式的话,同一个方法是被不同对象实例所引用的,所以上面的结果会返回ture.
对的,这就是原型模式实现的面向对象编程。
一般来说,会把属性定义在构造函数里面,把方法定义在原型里面,原型当中定义的方法具有public属性,而构造函数当中定义的方法由于函数作用域的限制,具有private属性,
外界不能直接访问。
可以看到对象实例的创建是通过new + 构造函数名 来实现的,但你知道对象创建的过程中都发生了什么吗?
通过new创建的对象实例,会生成一个指向函数原型对象的指针,不过在这里不是prototype,是[[prototype]](或者叫—proto—),同时this指针会绑定到该对象实例上。
对象实例会把构造函数中定义的初始化后的属性或者方法复制下来,作为它自己的一部分,但是呢原型中定义的方法仍是属于原型自身的,不归对象实例所有。对象只是引用。
当对象需要调用原型中定义的方法时,会通过[[prototype]]指针去原型中寻找。
了解了这些,我们就可以利用原型式编程来模拟面向对象编程了。
炸裂·原型式编程!
心法:全局为一,逻辑为二
面向对象编程不能像面向过程编程一样,一边写一边再想着下一步要写什么,在一开始就应该要有一个全局的构思。
先想一下即将编写的组件它拥有什么样的功能特性,然后再去一步步的实现它。下面就以一个简单的拖拽组件的开发为例。
可拖拽组件拥有什么功能特性?可以拖拽?这个我知道,那要再细分呢?
有三个吧,我觉得。
我们需要:
1 拖拽对象在鼠标摁下还没松开的时候,能够记下自身的位置信息;
2 在鼠标摁下并移动的同时,重新定位自身的位置;
3 鼠标松开的时候,鼠标再怎么移动,拖拽对象也不会移动了;
这样的话,我们能写出这样的架构了:
4 function Drag(id) 5 { 6 var _this=this; 7 this.disX=0; 8 this.disY=0; 9 this.oDiv=document.getElementById(id); 10 this.oDiv.onmousedown=function(ev) 11 { 12 _this.down(ev); 13 }; 14 } 15 Drag.prototype.down=function(ev) 16 { 17 var _this=this; 18 console.log("down!"); 19 this.oDiv.onmousemove=function(ev)//事件对象ev中包含着所有与事件有关的信息 20 { 21 _this.move(ev); 22 }; 23 }; 24 Drag.prototype.move=function(ev) 25 { 26 var _this=this; 27 console.log("move!"); 28 document.onmouseup=function() 29 { 30 _this.up(); 31 }; 32 }; 33 Drag.prototype.up=function() 34 { 35 console.log("up!"); 36 };
这种方式把属性定义在构造函数里,方法定义在原型下,作为组件的每一个的功能特性。
可以像上面那样让每一个方法被触发的时候都打印出一些东西作为测试,如果每一次操作都能得到相应的响应的话,说明你的组织结构是没有问题的,接下来就可以进行下一步,
往相应的方法里书写具体的实现了。
最后的实现在这里:
function Drag(id) { var _this=this; this.disX=0; this.disY=0; this.oDiv=document.getElementById(id); this.oDiv.onmousedown=function(ev) { _this.down(ev); return false;//拖拽的时候不会选中其它DIV。。。 }; } Drag.prototype.down=function(ev) { var _this=this; var oEvent=ev||event; this.disX=oEvent.clientX-this.oDiv.offsetLeft; this.disY=oEvent.clientY-this.oDiv.offsetTop; document.onmousemove=function(ev)//事件对象ev中包含着所有与事件有关的信息 { _this.move(ev); }; }; Drag.prototype.move=function(ev) { var _this=this; var oEvent=ev||event; this.oDiv.style.left=oEvent.clientX-this.disX+‘px‘; this.oDiv.style.top=oEvent.clientY-this.disY+‘px‘; document.onmouseup=function() { _this.up(); }; }; Drag.prototype.up=function() { document.onmousemove=null; };
相比于结构化编程,面向对象的方式看起来是不是更厉害!
我知道你看完这个已经能够变身超级赛亚人第三阶了,
我隔着屏幕都能感受到你那强得让我尿裤子的气,但请先不要变身,如果你还想变得更强的话,先修习属性技能-真·冰之闭包吧!
进阶!
—修习属性技能 真·冰之闭包
闭包在原型式编程中会经常使用到,就比如在上面的代码中就创建了三个闭包。所以了解闭包的一些特性对于我们的编程是很有利的,还能避免一些闭包带来的陷阱。
再举个栗子...
我们希望能把0-9这10个数字打印出来,但实际上每个函数都返回10.
因为每个函数的作用域链都保存着其父函数(Nico函数)的活动对象,所以它们引用的都是同一个变量 i 。当父函数数返回后,变量 i 的值是10,此时每个函数引用的都是同一个变
量对象,所以在每个函数内部 i 的值都是10.
还有奇怪的是,闭包有个特点,就是在其父函数被返回销毁之后,因为闭包的存在,它的父函数内部定义的变量不会被销毁,而是会一直存活下来。我以前一直觉得迷惑,直到有
一天,我想呢,大概是因为这个样子吧...
父函数内部的变量一直是被闭包所引用着的,我想它们大概也和baby5一样很缺爱吧,因为觉得被别人需要着,所以很开心,所以很感动,所以想要多帮自己的恩人再多做一点事,
因为这样的信念支撑着它们继续存活了下来,明明躯体已经被毁灭了,灵魂却因为爱和感恩而活下来。
呜呜...是不是很感动~
那上面的问题怎么解决呢?
可以这样写...
也可以这样...
都能按自己希望的打印出0-9.
嗯...好了,现在你可以变身超级赛亚人第三阶了。
觉醒!
—真·烈焰之继承
觉醒:真·烈焰之继承
跟大多数觉醒技能一样,冷却时间较长,平时并不常用,但一旦使用总能得到很让人满意的效果。
使用继承能最大程度的实现代码复用,下面是通过继承实现的子类拖拽。
//继承 function DragChild(id) { Drag.call(this,id);//属性继承利用call方法 } for(var i in Drag.prototype){ DragChild.prototype[i]=Drag.prototype[i]; } DragChild.prototype.move=function(ev) { var _this=this; var oEvent=ev||event; var left=oEvent.clientX - this.disX; var top=oEvent.clientY - this.disY; if(left<0) { left=0; } else if(left > document.documentElement.clientWidth - this.oDiv.offsetWidth) { left = document.documentElement.clientWidth - this.oDiv.offsetWidth; } if(top<0) { top=0; } else if(top > document.documentElement.clientHeight - this.oDiv.offsetHeight) { top = document.documentElement.clientHeight - this.oDiv.offsetHeight } this.oDiv.style.left=left+‘px‘; this.oDiv.style.top=top+‘px‘; document.onmouseup=function() { _this.up(); }; };
一般实现属性的继承可以在子类中调用父类构造函数的call方法,将父类构造函数的this替换为其他对象。
function DragChild(id)
{
Drag.call(this,id);//属性继承利用call方法
}
原型的继承在这里用的是
for(var i in Drag.prototype){
DragChild.prototype[i]=Drag.prototype[i];
}
通过for in实现拷贝继承。
最好不要用——
DragChild.prototype=Drag.prototype;
像这样子直接赋值,因为在js中,值的直接赋值是通过拷贝的,但对象的直接赋值则是引用,也就是说如果你这样做的话,当你修改子类或者父类的prototype对象时,同时会影响到另一方。
这显然不是我们想要的。
当然也可以通过原型链继承的方式——
把父类创建的对象实例直接赋值给子类的原型对象,这样就形成了创说中的原型链了...
原文:http://www.cnblogs.com/tianheila/p/5033574.html