[译]JavaScript中的Prototype

当你在JavaScript中定义一个函数的同时也会生成一些内置的属性;其中之一就是原型(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_的“隐秘”属性就被添加到了上面;原型链就是这样被访问到的。但是,因为并不是所有的浏览器都支持,所以尝试去访问_proto_并不是一个好主意。

请不要将对象中_proto_属性和prototype对象搞混淆了,因为它们是两个完全独立的属性;也就是说,它们俩是分别协作的。第一次接触它们可能会让人困惑,但是进行这种区分是很重要的!那这到底意味着什么?请让我接着解释。当我们创建myObject函数时,我们定义了一个类型为Function的对象。

console.log(typeof myObject); // function

对于那些不知道的朋友,Fuction是一个内置的JavaScript对象,正因如此,它有一些自己的属性(e.g. length and arguments)和函数(e.g. call and apply)。并且,是的,它也有自己的prototype对象和一个“隐秘的”_proto_连接。这意味着在JavaScript执行引擎中的某个地方,存在着类似下面几行的代码。

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

事实上,那些代码可能不会如此的简单;这只不过是去说明原型链是怎样运作的。

现在我们已经将myObject定义为了一个函数,并且给了它一个参数,name;但是我们还没有给它设置任何属性,比如说length或者别的方法,比如说call,那为什么下面的代码可以正常运行呢?

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

这是因为,当我们定义了myObject的时候,它创建了一个_proto_属性,并且将它的值设置为了Function.prototype(在上面的代码中说明了)。所以,当我们访问myObject.length时,它会去寻找myObject对象的length属性,但是没有找到它;于是它就沿着通过_proto_link这个链条,找到了这个属性然后返回了它。

你可能在想为什么length会被设置为1而不是0 - 或者其他任何的数字呢?这是因为myObject事实上是Function的一个实例。

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

另外,当你创建一个新的Function对象时,Functino构造器中内置的一些代码会对参数进行计数,并且依据它去更新this.length。也就是说,在这个例子中它是1

但是如果,我们用关键字new创建了一个myObject的新实例,_proto_将会指向myObject.prototype因为myObject就是我们新实例的构造器。

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

除了可以去访问Function.prototype已经有的方法,比如说callapply,我们现在还可以访问myObject的方法,getName

console.log(myInstance.getName()); // foo

var mySecondInstance = new myObject(“bar”);

console.log(mySecondInstance.getName()); // bar
console.log(myInstance.getName()); // foo

正如你能想到的,这可以让我们非常的方便地设计对象,同时根据需要创建足够多的实例。- 也就是我下一个话题要说的!

为什么用prototype更好

假如说,我们要开发一个canvas游戏,这个游戏需要很多(也有可能很多很多)对象。每个对象都需要它们自己的属性,比如说xy坐标,widthheight,还有其他的属性。

我们可能会像下面这样写:

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

为了让我们的应用运行得更加快速(同时遵循最佳做法),我们可以重新定义GameObjectprototype属性;每次实例化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);
    }
};

我们可以接着这样初始化GameObjcet对象100次

var x = 100,
arrayOfGameObjects = [];

do {
    arrayOfGameObjects.push(new GameObject(10, 10));
} while(x--);

现在我们有了一个包含100个GameObjects实例的数组,它们都共享一个同样的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);
}

那么现在,所有之前的GameObject实例和任何今后的实例都会画圆。

更新内置对象的prototype

是的,这是完全可能的。你可能对JavaScript库很熟悉,比如说Prototype,正是利用了这种方法。

让我们来看一个简单的小例子:

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

我们现在可以通过任何字符串使用这个方法:

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

然而,这样做有一个不太好的地方。举个例子,你可能会在你的应用中这么做;然而,在未来的一或者两年里,浏览器可能会实现或者更新JavaScript里String.prototypetrim。那样就将会使你定义的trim覆盖掉内置的trim!为了避免这样的情况发生,我们可以在定义函数前添加一个简单的检查代码。

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

现在,如果内置方法中已经存在trim,那么将会使用内置的trim方法。
根据经验法则,通常会避免对内置对象进行拓展。但是,如果我们需要,任何法则都是可以被打破的

小结

希望这篇文章能展现出些许JavaScript的核心内容 – 也就是prototype。快去创造更加高效的应用吧!

如果你有任何关于prototype的问题,请给我留言,我会竭尽所能回答它们。

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

如有翻译错误,请给我留言,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值