了解javascript编程中的Prototype
当你定义javascript方法的时候,会产生一些预定义的属性,其中一个比较让人迷惑的属性就是prototype。在本文中,我们将详细介绍什么是Prototype,并且为什么使用prototype。
什么是prototype?
prototype属性初始时是一个空的对象,可以添加对象 ,你可以添加任何对象到它里面去。
- var myObject = function(name){
- this.name = name;
- return this;
- };
- console.log(typeof myObject.prototype); // object
- myObject.prototype.getName = function(){
- return this.name;
- };
复制代码
在以上这段代码中,我们创建了一个方法,但是如果我们调用myObject(),将会返回window对象,因为它被定义在全局范围中。 this将会返回全局对象,因为没有被实例化。
- console.log(myObject() === window); // true
复制代码
秘密的连接
每一个javascript中的对象都有一个秘密属性。
在我们继续之前,我想讨论一下决定prototype工作方式的“秘密”连接。
每一个javascript对象在定义或者实例化的时候都会添加一个秘密的属性,叫__proto__,这决定了prototype链如何被访问。然而,在你的应用中访问这个__proto__属性绝对不是一个好主意,因为不是所有浏览器都可访问。
__prototype__属性在一个对象的prototype中不应该被弄混了, 因为它有两个分开的属性;意味着他们都是手拉手来使用的。对于弄清楚这个很重要。因为最开始的时候肯定比较令人迷惑。 那究竟什么意思呢? 这里我们解析一下。 当我们创建myObject方法时,我们定义了一个Function类型的对象。
- console.log(typeof myObject); // function
复制代码
如果你不知道的话, Function是一个javascript预定义的对象,这样的话,拥有自己的属性(例如,length和arguments)和方法(例如,call和apply)。这意味着,在javascript的引擎中,这里有类似如下代码的部分:
- Function.prototype = {
- arguments: null,
- length: 0,
- call: function(){
- // secret code
- },
- apply: function(){
- // secret code
- }
- ...
- }
复制代码
当然可能没有这么简单;不过这里只是演示prototype的链式如何工作的。
因此当我们定义myObject为一个方法并且提供一个参数name;但是并不设置其它属性和方法,例如length,和call,那么如下代码为什么可以工作?
- console.log(myObject.length); // 1 (being the amount of available arguments)
复制代码
这是因为我们定义了myObject,它创建了__proto__属性并且设置数值为Function.prototype。因此,当我们访问myObject.lengh,会寻找myObject的属性,调用lenght,但是找不到。于是它将会沿链式向上,通过__proto__ link来找到属性并且返回。
你可能会问,为什么会返回1而不是0或者其它数值,因为myObject其实是Function的一个实例。
- console.log(myObject instanceof Function); // true console.log(myObject === Function); // false
复制代码
当一个对象的实例创建后,__proto__属性将被更新然后指向构建器(constructor)的prototype,这里是Funciton。
- console.log(myObject.__proto__ === Function.prototype) // true
复制代码
而且当你创建一个新的Function对象,Function构建器的本地代码将会计算参数的个数并且更新this.length。这样你得到了1。
然而,如果我们使用new来创建一个新的myObject实例,__proto__将会指向myObject.prototype作为我们新的实例的构建器。
- var myInstance = new myObject(“foo”);
- console.log(myInstance.__proto__ === myObject.prototype); // true
复制代码
除了能够访问Function的Prototype中的本地方法call和apply,我们也可以访问myObject的方法getName。
- console.log(myInstance.getName()); // foo
- var mySecondInstance = new myObject(“bar”);
- console.log(mySecondInstance.getName()); // bar
- console.log(myInstance.getName()); // foo
复制代码
正如你猜到的,这个非常的实用,可以用来生成一个对象的基本设计图,创建尽可能多的对象。下一个主题中我们将介绍。
为什么使用prototype更好?
比方说,我们要开发一个画布游戏需要几个对象。每一个对象都拥有自己的属性,例如,x和y参数,宽和高,其它等等。
- var GameObject1 = {
- x: Math.floor((Math.random() * myCanvasWidth) + 1),
- y: Math.floor((Math.random() * myCanvasHeight) + 1),
- width: 10,
- height: 10,
- draw: function(){
- myCanvasContext.fillRect(this.x, this.y, this.width, this.height);
- }
- ...
- };
- var GameObject2 = {
- x: Math.floor((Math.random() * myCanvasWidth) + 1),
- y: Math.floor((Math.random() * myCanvasHeight) + 1),
- width: 10,
- height: 10,
- draw: function(){
- myCanvasContext.fillRect(this.x, this.y, this.width, this.height);
- }
- ...
- };
复制代码
创建这种对象98次... ...
这样做会在内存中创建所有的对象 - 所有都使用分开的方法定义 ,例如draw,还有其它的方法。这肯定不够理想,因为这个游戏会让javascript占用浏览器大量内存,运行会变得非常缓慢,甚至崩溃。
当然这种情况不会在创建100个对象而出现, 但是仍旧会对性能有不小的伤害。因为它会访问100个不同的对象,而不是一个prototype对象。
如何使用Prototype?
为了让程序运行的更快(或者使用最佳实践),我们可以重新定义GameObject的prototype属性;每一个实例都会参考GameObject.prototype,正如使用自己方法一样。
- // define the GameObject constructor function
- var GameObject = function(width, height) {
- this.x = Math.floor((Math.random() * myCanvasWidth) + 1);
- this.y = Math.floor((Math.random() * myCanvasHeight) + 1);
- this.width = width;
- this.height = height;
- return this;
- };
- // (re)define the GameObject prototype object
- GameObject.prototype = {
- x: 0,
- y: 0,
- width: 5,
- width: 5,
- draw: function() {
- myCanvasContext.fillRect(this.x, this.y, this.width, this.height);
- }
- };
复制代码
我们可以实例GameObject 100次。
- var x = 100,
- arrayOfGameObjects = [];
- do {
- arrayOfGameObjects.push(new GameObject(10, 10));
- } while(x--);
复制代码
现在我们拥有了一个100个对象的数组,所有对象都共享同样的prototype和draw方法定义。
当我们调用draw方法。会参考同样一个方法:
- var GameLoop = function() {
- for(gameObject in arrayOfGameObjects) {
- gameObject.draw();
- }
- };
复制代码
Prototype是一个活动的对象
一个对象的prototype是一个活动的对象。简单来说,在我们创建了我们的Gameobject实例后,如果我们不想绘制矩形,我们可以更新我们的GameObject.prototype.draw方法来绘制一个圆形。
- GameObject.prototype.draw = function() {
- myCanvasContext.arc(this.x, this.y, this.width, 0, Math.PI*2, true);
- }
复制代码
现在如果再调用draw方法将会都绘制圆形了。
更新本地对象prototype
没错,这也有可能。你可能比较熟悉某些javascript类库,例如,Prototypes ,这个类库就利用了这个方法。
我们看一个简答的例子:
- String.prototype.trim = function() {
- return this.replace(/^\s+|\s+$/g, ‘’);
- };
复制代码
我们可以使用字符串来调用这个方法:
- "foo bar ".trim(); // “foo bar”
复制代码
这里有一个负面的影响。例如,你可以在你的web应用中使用这个方法,但是1,2年后,一个浏览器可能也会在String的prototype中实现类似的本地方法trim。这意味着你的trim定义会覆盖本地版本。为了解决这个问题,我们可以添加一个简单的检查,如下:
- if(!String.prototype.trim) {
- String.prototype.trim = function() {
- return this.replace(/^\s+|\s+$/g, ‘’);
- };
- }
复制代码
现在如果存在本地版本,将会使用本地版本的trim方法。
一般的规律来说,我们要避免扩展本地对象。但是,必要情况下,规矩是可以被打破的。
转自GBin1
html5.js 通过扩展js的prototype 让不支持html5的浏览器支持html5函数 (持续新增) - kkdashu
(function(_window){ //数组新增某些方法 //forEach方法从头到尾遍历数组,为每个数组元素调用指定函数并提供当前元素,当前索引与数组当做参数传给函数 Array.prototype.forEach||(Array.prototype.forEach=function(fun){ for(var i=0;i<this.length;i++){ fun(this[i],i,this); } }) //map方法遍历数组并调用指定函数,最后返回一个数组,数组由所有函数返回值组成 Array.prototype.map||(Array.prototype.map=function(fun){ var returnArray=[]; for(var i=0;i<this.length;i++){ returnArray.push(fun(this[i],i,this)); } return returnArray; }) //filter相当一个数组的帅选器,遍历数组并调用指定函数,并返回一个新数组,如果函数返回true则添加返回的数组。 Array.prototype.filter||(Array.prototype.filter=function(fun){ var returnArray=[]; for(var i=0;i<this.length;i++){ if(fun(this[i],i,this)){ returnArray.push(this[i]); } } return returnArray; }) //every 遍历数组调用指定函数,当所有函数都返回true 则every返回true否则返回false。 Array.prototype.every||(Array.prototype.every=function(fun){ if(this.length==0) return true; //every应该尽早结束循环 for(var i=0;i<this.length;i++){ if(!fun(this[i],i,this)){ return false; } } return true; }) //some 遍历数组调用指定函数,当有一个函数返回true就返回true,当所有函数都返回false就返回false Array.prototype.some||(Array.prototype.some=function(fun){ if(this.length==0) return false; //some应该尽早结束循环 for(var i=0;i<this.length;i++){ if(fun(this[i],i,this)){ return true; } } return false; }) //reduce 遍历数组调用指定函数(函数需要2个参数),每次把调用函数的返回值与下一个元素当做函数参数继续调用。 //对数组进行求和等操作很实用 Array.prototype.reduce||(Array.prototype.reduce=function(fun,initval){ if(this.length==0) return ""; if(this.length==1){ return initval?fun(initval,this[0]):this[0]; } var val=initval, firstindex=initval?0:2; if(!initval){ val=fun(this[0],this[1]); } for(var i=firstindex;i<this.length;i++){ val=fun(val,this[i]); } return val; }) //reduceRight 与reduce一样 只是从右到左遍历数组 Array.prototype.reduceRight||(Array.prototype.reduceRight=function(fun,initval){ if(this.length==0) return ""; if(this.length==1){ return initval?fun(initval,this[0]):this[0]; } var val=initval, firstindex=initval?this.length-1:this.length-3; if(!initval){ val=fun(this[this.length-1],this[this.length-2]); } for(var i=firstindex;i>=0;i--){ val=fun(val,this[i]); } return val; }) })(window);