Prototypes in JavaScript

JavaScript 是" 入易深难 " 但也就是这个深才给程序员带来了无限乐趣. Prototype 是Javascript非常重要属性之一.所以如果有不清楚或想知道请翻看以下文章.

(英文好的建议直接阅读原文)

又是一篇深入理解JavaScript的好文篇, 再次分享.希望您看完之后有豁然开朗的感觉.(仅供个人学习使用). 如需转载请注明地址/作者 谢谢.

英文原文地址 : http://net.tutsplus.com/tutorials/javascript-ajax/prototypes-in-javascript-what-you-need-to-know/

 

======================Enein翻译并学习着...=======================

 

在文章开头, 作者就说了关于新手对于'prototype'的神秘感. 在定义function初function里就会有很多属性. 其中的prototype是比较重要和不是很好理解的.这篇文章主要就是 详细介绍prototype以及在项目中的实际应用.

 

What is Prototype?  

  prototype 是初始化Object就存在的属性, 可以为其再次加入属性.

var myObject = function(name){
    this.name = name;
    return this;
};
 
console.log(typeof myObject.prototype); // object
 
myObject.prototype.getName = function(){
    return this.name;
};

  在上面的代码中, 我们创建了一个function, 但当我们调用myObject()的时候, 它会返回window对象, 因为它被定义在全局作用域下, this 代表全局对象.同样它也没有被实例化(后面会有更详细的介绍).

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

The Secret Link  

  我们继续之前的讨论, 看看这个'神秘的'prototype是怎么工作的.

每个object在JavaScript定义或实例化的时候都会存在一个内置属性"__proto__"(两个下划线), 这使得原型链可以被访问, 无论如何在你的应用程序中直接使用"__proto__"都不是最好的.

它不是在所有的浏览器中都是有效的. 

  这个__proto__不能和对象的prototype相混淆, 它们是两个不同的属性. 它们的关系很紧密, 所以最重要的是能了解它们并区别它们.当我创建function的时候, 我们是定义了一个Funtcion类型的Object 

console.log(typeof myObject); // function

"Function 在JavaScript中是内置对象"  因此它有它自己的属性(e.g length 和 arguments) 和 方法(e.g call 和 apply), 同样它也是有它自己的object("__proto__"), 这意味着, 在JavaScript 解析引擎中, 类似这样的:

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

事实上没有这个简单, 这里只是简单的介绍一下原型链的结构.

  上面我们有给myObject设置一个name方法, 没有在给其它的了, 但它为什么还能调用 length call apply ... 

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

这是因为, 在我们定义myObject的时候, 它会创建一个 __proto__属性并且会为Function.prototype设置它的值. 所以, 当你访问 myObject.length 的时候, 它会尝试去 myObject 里找属性并调用, 但没有找到, 然后它就会去它的原型链上去找, 通过 __proto__ 链, 找到并返回数据. 

  你可能对 myObject.length 返回1 不是很理解, 为什么不是2,3或其它的数字, 这是因为 myObject 事实上是 Function的一个实例.

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

  当一个对象创建的时候, 这个 __proto__ 是被更新并指向这个构造器的 prototype , 在上面的案例中, 就是 Funtiion :

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

   另外, 在你创建一个新的Function对象的时候, 在Function 构造器的代码内部会统计可用参数的数量(上面的function(name)参数为一个所以)并且更新 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

  你可以想你一下这是很方便的, 因为它可以很方便的去'操作'一个对象, 创建你自己需要的实例. --> Next Top;

Why Is Use Prototype Better? 

  举个例子, 我们要开发一个canvas游戏, 我可能会需要很多个对象立即出现在屏幕上, 每个对象都会有它的属性, 像 x, y坐标 width, height等等.

Like this:

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 次. 会有很多个对象在内存里被创建 --- 所有的方法都分开定义, 为方便被其它接口调用. 很明显这不是一个好办法 做为游戏这会导致JavaScript的内存溢出/甚至非常慢/或Down掉.

当然 仅100个对象是不太有可能会发生的, 但它还是会影响一部分性能, 因为它需要'遍历'100个不同的对象, 而不是一个prototype对象.

How to Use Prototype? 

  要想使应用程序最快, 最好的实践就是我们要定义一个 prototype 属性 GameObject; 每个 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对象, 所有的对象都共享prototype.draw方法, 这样大大减少了内存. 在我们调用 draw 方法的时候, 我们引用的其实是同一个function.

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

Prototype is a Live Object  

  Prototype是一个灵活的对象, 举例来说: 我想改变这之前的draw 我要画个长方形或一个圆. 我只要改变一下:

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

那么我上面的100Object都会画圆, 之后的实例也会.

Updating Native Objects Prototype  

  这是可能的, 如果你熟悉 Javascript库比如 Prototype, 你就会知道怎么利用好这一点.

 让我们看一下简单的实例:

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

 

现在我们任何的string都可以调用该方法:

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

这里存在一个小缺陷, 就是随着浏览器时代的发展,有的JavaScript引擎的 trim已经是String的 prototype 属性了, 如果我们这样写会覆盖native方法, 所以我们要在之前加入个判断.

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

"正常情况下, 最好不要重写原生态的方法, 但是在一些特殊的情况下, 规则是可以被破坏的" 

Conclusion 

   这篇文章希望是有所帮助对您, 介绍的主要还是Javascript的骨干属性prototype , 如果理解了prototype 你就可以写出更有效的应用程序.

如果你还有对prototype的问题, 请写在 comments 里我很高兴为您解答.

 

======================Enein翻译并学习着...=======================

 

posted @ 2012-08-23 16:50  Enein  阅读(1737)  评论(3编辑  收藏  举报