且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

JavaScript中的Prototypes

更新时间:2022-09-27 22:08:59

Prototype是什么?

    prototype属性是一个初始化的空对象,像其它对象一样,它也可以添加成员属性和方法。

JavaScript中的Prototypes


The Secret Link

    每个JavaScript对象都有一个隐藏的属性,当这个对象被定义或者实例化的时候,这个属性会被添加到该对象中,这个属性的名称叫“__proto__”,它道出了原型链是如何被访问的。然而,在你的应用中直接方法__proto__不是一个好主意,因为所有浏览器都不认识它。

    不要把__proto__属性和对象的prototype弄混了,它们是两个不同的属性。也就是说,它们是手拉着手齐头并进的。把它们区分开是很重要的,因为它们确实有点不容易理解。这是什么意思呢?让我来解释一下。当我们创建myObject函数时,我们实际上是定义了一个Function类型的对象。

JavaScript中的Prototypes

    Function是JavaScript中预定义的一个对象,它有自己的属性(比如:length和arguments)和方法(比如:call和apply),它也有自己的prototype对象和__proto__属性。也就是说,在JavaScript引擎的中有大量的代码类似下面这样:

Function.prototype = {
    arguments: null,
    length: 0,
    call: function(){
        // secret code
    },
    apply: function(){
        // secret code
    }
    ...
}

    实际上,可能不是像上面这么简单,这里仅仅是阐述原型链是如何工作的。

    我们定义myObject作为一个函数并且给它一个参数name,从来没有给它设置任何诸如length这样的属性已经call这样的方法。既然是这样,那么为什么下面的语句还能正常输出呢?

1
console.log(myObject.length); // 1 (being the amount of available arguments)

    这是因为,当我们定义myObject时,它创建了一个__proto__属性并且把这它的值设为Function.prototype。所以,当我们访问myObject.length时,它就在myObject中查找名为length的属性,它发现没找到,于是顺着这条链,通过__proto__链,找到这样一个属性,然后返回它。

    你可能想要知道为什么length被设置为1而不是0或者其它数呢?其实是因为,myObject是Function的一个实例。

JavaScript中的Prototypes

    当一个对象的实例被创建时,__proto__属性被更新为指向构造器的prototype属性,在这个例子中,构造器的原型是Function。

JavaScript中的Prototypes

    另外,当你创建一个新的Function对象时,本地的Function构造器将记下参数个数并且相应的更新this.length,在本例中,是1。

    如果,我们用new关键字创建一个myObject的实例,那么,__proto__将指向myObject.prototype,因为此时myObject是我们新创建的实例的构造器。

JavaScript中的Prototypes    除此之外,还可以访问Function.prototype的本地方法,比如call和apply。现在我们访问myObject的getName方法:

JavaScript中的Prototypes


Why is Using Prototype Better?

    有一个例子,我们准备开发一个游戏并且一次需要大量的在屏幕上的对象,每个对象有自己的属性,比如横坐标、纵坐标、宽度、高度等等。

我们可能这样写:

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);
    }
    ...
};

    这样写将会在内存中创建所有这些定义的对象,这不是一个好主意,因为这个将占用大量的浏览器内存,并且运行会非常慢,甚至停止响应。

    当然,只有100个对象的话,可能不会发生这样的情况,但是仍然会对性能有影响,因为它需要查找100个不同的对象,而不仅仅是单个prototype对象。


How to User Prototype
    为了让这个应用运行更快,我们可以重新定义GameObject的prototype属性,每个GameObject的实例将引用定义在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个GameObject实例的数组,所有实例有相同的prototyp,并且都有draw方法,这样一来,就能大幅减少应用所占用的内存资源。

    当调用draw方法的时候,将关联相同的函数

var GameLoop = function() {
    for(gameObject in arrayOfGameObjects) {
        gameObject.draw();
    }
};



Prototype is a Live Object

    可以这么说,一个对象的prototype是一个灵活的对象。可以简单的这样理解,在我们创建完所有我们需要的GameObject实例以后,我们能够决定是画矩形还是画圆,只要相应地改变GameObject.prototype.draw方法即可达到这个目的。

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

现在,所有实例将画圆。


Updating Native Objects Prototypes
看下面的例子:

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

现在,我们能够对任意字符串使用trim方法,就像这样:

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

    对于这个例子,你可以在你的应用中使用它,但是随着时间的推移,浏览器可能实现了它,并且在

JavaScript的新版本中String对象的原型中包含了一个内置的trim方法。这就意味着你定义的trim方法会覆盖JavaScript中String对象内置的trim方法。为了避免这种情况,我们在定义这样一个函数之前可以先检查一下。   

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

    现在,如果JavaScript内置了trim方法,浏览器将使用内置的trim方法。

    一般来讲,***的方式是避免扩展内置对象,但是,如果有必要的话,规则也是可以打破的。


原文:http://code.tutsplus.com/tutorials/prototypes-in-javascript-what-you-need-to-know--net-24949


下面我总结一下:

  1. Function对象是JavaScript的内置对象,它有自己的属性、方法、prototype以及__prototype__

  2. 所有对象都是Function的实例

  3. JavaScript中的Function相当于Java中的Object类,JavaScript中的对象相当于Java中的类,JavaScript中的实例相当于Java中的对象(或者叫实例)。(反正我是这么理解的)

  4. 当我们定义一个对象或者创建一个对象的实例的时候,一个名字叫“__proto__”的属性会被添加到对象中。通过__proto__可以访问对象的原型链。__proto__对浏览器来说是不可用的,所以不能直接方法它。

  5. 对象的prototype是一个初始化的空对象,可以想其它对象一样给它添加属性和方法。

  6. prototype属性使我们有能力给对象添加属性和方法。

  7. 对象的__proto__属性与prototype属性是两个不同的属性,但又密不可分。千万别把它们弄混了。

  8. 定义一个对象时,它的__proto__属性的值是构造器的prototype属性,即__proto__指向了构造器的prototype。当我们通过new关键字创建对象的实例时,这个新生成的实例的__proto__指向了这个对象的prototype,因为对于新的实例来说,它的构造器是构造它的这个对象,即new后面的那个。

  9. 关于__proto__和prototype,我是这样理解的:通过prototype,我们可以向当前对象添加方法和属性,通过__proto__,我们可以访问这个对象的原型链,也就是说,它提供了一种方式让我们可以访问父对象(即构造它的对象)。总之呢就是,prototype指向当前对象,__proto__指向父对象。

  10. 每个对象的实例都要关联对象的prototype中的方法

  11. 感觉prototype有点像Java中的Class。(纯属个人愚见)


JavaScript中的Prototypes

本文转自   手不要乱摸  51CTO博客,原文链接:http://blog.51cto.com/5880861/1674090