现在的位置: 首页 > 综合 > 正文

了解javascript编程中的Prototype

2016年02月27日 ⁄ 综合 ⁄ 共 7074字 ⁄ 字号 评论关闭

了解javascript编程中的Prototype

2012-5-21 17:46|发布者:
html5cn
|原作者: Leigh Kaszick|评论: 0

摘要: 当你定义javascript方法的时候,会产生一些预定义的属性,其中一个比较让人迷惑的属性就是prototype。在本文中,我们将详细介绍什么是Prototype,并且为什么使用prototype。 什么是prototype? prototype属性 ...

       当你定义javascript方法的时候,会产生一些预定义的属性,其中一个比较让人迷惑的属性就是prototype。在本文中,我们将详细介绍什么是Prototype,并且为什么使用prototype。

       什么是prototype?

       prototype属性初始时是一个空的对象,可以添加对象 ,你可以添加任何对象到它里面去。

  1. var myObject = function(name){
  2.     this.name = name;
  3.     return this;
  4. };
  5. console.log(typeof myObject.prototype); // object
  6. myObject.prototype.getName = function(){
  7.     return this.name;
  8. };

复制代码

       在以上这段代码中,我们创建了一个方法,但是如果我们调用myObject(),将会返回window对象,因为它被定义在全局范围中。 this将会返回全局对象,因为没有被实例化。

  1. console.log(myObject() === window); // true

复制代码

       秘密的连接

       每一个javascript中的对象都有一个秘密属性。

       在我们继续之前,我想讨论一下决定prototype工作方式的“秘密”连接。

       每一个javascript对象在定义或者实例化的时候都会添加一个秘密的属性,叫__proto__,这决定了prototype链如何被访问。然而,在你的应用中访问这个__proto__属性绝对不是一个好主意,因为不是所有浏览器都可访问。

       __prototype__属性在一个对象的prototype中不应该被弄混了, 因为它有两个分开的属性;意味着他们都是手拉手来使用的。对于弄清楚这个很重要。因为最开始的时候肯定比较令人迷惑。 那究竟什么意思呢? 这里我们解析一下。 当我们创建myObject方法时,我们定义了一个Function类型的对象。

  1. console.log(typeof myObject); // function

复制代码

       如果你不知道的话, Function是一个javascript预定义的对象,这样的话,拥有自己的属性(例如,length和arguments)和方法(例如,call和apply)。这意味着,在javascript的引擎中,这里有类似如下代码的部分:

  1. Function.prototype = {
  2.     arguments: null,
  3.     length: 0,
  4.     call: function(){
  5.         // secret code
  6.     },
  7.     apply: function(){
  8.         // secret code
  9.     }
  10.     ...
  11. }

复制代码

       当然可能没有这么简单;不过这里只是演示prototype的链式如何工作的。

       因此当我们定义myObject为一个方法并且提供一个参数name;但是并不设置其它属性和方法,例如length,和call,那么如下代码为什么可以工作?

  1. 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的一个实例。

  1. console.log(myObject instanceof Function); // true  console.log(myObject === Function); // false

复制代码

       当一个对象的实例创建后,__proto__属性将被更新然后指向构建器(constructor)的prototype,这里是Funciton。

  1. console.log(myObject.__proto__ === Function.prototype) // true

复制代码

       而且当你创建一个新的Function对象,Function构建器的本地代码将会计算参数的个数并且更新this.length。这样你得到了1。

       然而,如果我们使用new来创建一个新的myObject实例,__proto__将会指向myObject.prototype作为我们新的实例的构建器。

  1. var myInstance = new myObject(“foo”);
  2. console.log(myInstance.__proto__ === myObject.prototype); // true

复制代码

       除了能够访问Function的Prototype中的本地方法call和apply,我们也可以访问myObject的方法getName。

  1. console.log(myInstance.getName()); // foo
  2. var mySecondInstance = new myObject(“bar”);
  3. console.log(mySecondInstance.getName()); // bar
  4. console.log(myInstance.getName()); // foo

复制代码

       正如你猜到的,这个非常的实用,可以用来生成一个对象的基本设计图,创建尽可能多的对象。下一个主题中我们将介绍。

       为什么使用prototype更好?

       比方说,我们要开发一个画布游戏需要几个对象。每一个对象都拥有自己的属性,例如,x和y参数,宽和高,其它等等。

  1. var GameObject1 = {
  2.     x: Math.floor((Math.random() * myCanvasWidth) + 1),
  3.     y: Math.floor((Math.random() * myCanvasHeight) + 1),
  4.     width: 10,
  5.     height: 10,
  6.     draw: function(){
  7.         myCanvasContext.fillRect(this.x, this.y, this.width, this.height);
  8.     }
  9.    ...
  10. };
  11. var GameObject2 = {
  12.     x: Math.floor((Math.random() * myCanvasWidth) + 1),
  13.     y: Math.floor((Math.random() * myCanvasHeight) + 1),
  14.     width: 10,
  15.     height: 10,
  16.     draw: function(){
  17.         myCanvasContext.fillRect(this.x, this.y, this.width, this.height);
  18.     }
  19.     ...
  20. };

复制代码

       创建这种对象98次... ...

       这样做会在内存中创建所有的对象 - 所有都使用分开的方法定义 ,例如draw,还有其它的方法。这肯定不够理想,因为这个游戏会让javascript占用浏览器大量内存,运行会变得非常缓慢,甚至崩溃。

       当然这种情况不会在创建100个对象而出现, 但是仍旧会对性能有不小的伤害。因为它会访问100个不同的对象,而不是一个prototype对象。

       如何使用Prototype?

       为了让程序运行的更快(或者使用最佳实践),我们可以重新定义GameObject的prototype属性;每一个实例都会参考GameObject.prototype,正如使用自己方法一样。

  1. // define the GameObject constructor function
  2. var GameObject = function(width, height) {
  3.     this.x = Math.floor((Math.random() * myCanvasWidth) + 1);
  4.     this.y = Math.floor((Math.random() * myCanvasHeight) + 1);
  5.     this.width = width;
  6.     this.height = height;
  7.     return this;
  8. };
  9. // (re)define the GameObject prototype object
  10. GameObject.prototype = {
  11.     x: 0,
  12.     y: 0,
  13.     width: 5,
  14.     width: 5,
  15.     draw: function() {
  16.         myCanvasContext.fillRect(this.x, this.y, this.width, this.height);
  17.     }
  18. };

复制代码

       我们可以实例GameObject 100次。

  1. var x = 100,
  2. arrayOfGameObjects = [];
  3. do {
  4.     arrayOfGameObjects.push(new GameObject(10, 10));
  5. } while(x--);

复制代码

       现在我们拥有了一个100个对象的数组,所有对象都共享同样的prototype和draw方法定义。

       当我们调用draw方法。会参考同样一个方法:

  1. var GameLoop = function() {
  2.     for(gameObject in arrayOfGameObjects) {
  3.         gameObject.draw();
  4.     }
  5. };

复制代码

       Prototype是一个活动的对象

       一个对象的prototype是一个活动的对象。简单来说,在我们创建了我们的Gameobject实例后,如果我们不想绘制矩形,我们可以更新我们的GameObject.prototype.draw方法来绘制一个圆形。

  1. GameObject.prototype.draw = function() {
  2.     myCanvasContext.arc(this.x, this.y, this.width, 0, Math.PI*2, true);
  3. }

复制代码

       现在如果再调用draw方法将会都绘制圆形了。

       更新本地对象prototype

       没错,这也有可能。你可能比较熟悉某些javascript类库,例如,Prototypes ,这个类库就利用了这个方法。

       我们看一个简答的例子:

  1. String.prototype.trim = function() {
  2.     return this.replace(/^\s+|\s+$/g, ‘’);
  3. };

复制代码

       我们可以使用字符串来调用这个方法:

  1. "foo bar   ".trim(); // “foo bar”

复制代码

       这里有一个负面的影响。例如,你可以在你的web应用中使用这个方法,但是1,2年后,一个浏览器可能也会在String的prototype中实现类似的本地方法trim。这意味着你的trim定义会覆盖本地版本。为了解决这个问题,我们可以添加一个简单的检查,如下:

  1. if(!String.prototype.trim) {
  2.     String.prototype.trim = function() {
  3.         return this.replace(/^\s+|\s+$/g, ‘’);
  4.     };
  5. }

复制代码

       现在如果存在本地版本,将会使用本地版本的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);

抱歉!评论已关闭.