理解JavaScript中的原型链-Prototypes

waterhydroxyl 发布于

源文章来源Prototypes in JavaScript
译者: Leeyw 理解JavaScript中的原型链-Prototypes

Prototypes in JavaScript

  • JavaScript中的Prototypes

When you define a function within JavaScript, it comes with a few pre-defined properties; one of these is the illusive prototype. In this article, I’ll detail what it is, and why you should use it in your projects.

  • 当你在JavaScript中定义一个方法时,它(方法)会预设一些属性;其中之一就是让人迷惑的原型。在这篇文章中,我将详细解释它(原型链)是什么,你为何需要在您的项目中使用它。

What is Prototype?

  • 原型是什么?

The prototype property is initially an empty object, and can have members added to it - as you would any other object.

  • 原型中的属性最初是一个空的对象,而且能添加成员-就像你添加其他任何对象一样。
1
2
3
4
5
6
7
8
9
10
11
var myObject = function(name){
this.name = name;
return this;
};


console.log(typeof myObject.prototype); // object

myObject.prototype.getName = function(){
return this.name;
}

In the snippet above, we’ve created a function, but if we call myObject(), it will simply return the window object, because it was defined within the global scope. this will therefore return the global object, as it has not yet been instantiated (more on this later).

  • 在上面的代码片段中,我们创建了一个方法,但如果我们调用 myObject() ,它将仅仅返回 window 对象,因为它是在全局作用域下的。this 也因此返回全局对象,因为它还没有实例化(稍后详细解释)。
1
console.log(myObject() === window); // true
  • “神秘”链

    Every object within JavaScript has a “secret” property.

  • 在JavaScript中所有对象都有一个“神秘”链
  • 在我们继续了解之前,我想要先论述“secret”(“神秘”)链在原型中的作用。

The __proto__ property shouldn’t be confused with an object’s prototype, as they are two separate properties; that said, they do go hand in hand. It’s important to make this distinction, as it can be quite confusing at first! What does this mean exactly? Let me explain. When we created the myObject function, we were defining an object of type Function.

  • (__proto__属性) ≠ (对象中的 prototype属性),事实上它们是两个单独的属性;即便如此,它们也go hand in hand(携手并进,一荣俱荣..)。重要的是把它们两个区别开(并不同属),因为第一次接触它们两个属性会让你混淆!准确的说是什么意思呢?让我解释一下。当我们创建myObject方法时,我们是定义了一个类型为 Function 的对象。

    1
    console.log(typeof myObject); // function
  • 对于那些未知的,在JavaScript中 Function 是一个预定义的对象,并且,其结果是,他们都有自己的属性(例如 length   和 arguments) 和方法 (例如. call  和 apply),是的,它也有自己的原型对象,和 “神秘”的__proto__ 链一样。这是什么意思呢。在JavaScript engine的某个地方,有一些代码,可能类似于下面的代码块:

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

In truth, it probably wouldn’t be quite so simplistic; this is merely to illustrate how the prototype chain works.

  • 实际上,它可能不会这么简单;);这仅仅是为了说明(prototype chain)原型链是如何工作的

So we have defined myObject as a function and given it one argument, name; but we never set any properties, such as length or methods, such as call. So why does the following work?

  • 因此我们定义了一个方法 myObject 并给他一个参数, name ;但是我们从未设置过任何属性,诸如 length 或者方法,诸如 call 。所以为什么下面(code)会执行呢?
    1
    2
    3
    4
    5
    function myObject(sdadas) {

    }
    console.log(myObject.length);// 1(being the amount of available arguments)
    // (存在于这个方法的参数的数量)
  • 这是因为,我们在定义 myObject 时,它创建了一个 __proto__ 属性并设置了它自身的值为 Function.prototype (以上面的代码中作为例证)。因此,我们调用 myObject.length  时,会在 myObject  的属性中寻找 length 但没有找到;然后向上查询原型链(中的length方法), 通过 __proto__ link ,寻找属性(length)并返回它。

You might be wondering why length is set to 1 and not 0 - or any other number for that fact. This is because myObject is in fact an instance of Function.

  • 你也许想知道为什么 length 中设置为 1 而非 0 —— 或其他任何数值(对于这个结果来说)。这是因为 myObject 事实上是 Function 的一个实例化对象。
    1
    2
    console.log(myObject instanceof Function); // true
    console.log(myObject === Function); // false

    When an instance of an object is created, the __proto__ property is updated to point to the constructor’s prototype, which, in this case, is Function.

  • 我们在创建一个实例化对象时, proto 属性的(内容)更新是因为原型的构造函数,至此,为 Function 。
    1
    console.log(myObject.__proto__ === Function.prototype) // true

    Additionally, when you create a new Function object, the native code inside the Function constructor will count the number of arguments and update this.length accordingly, which, in this case, is 1.

  • 加之,我们在创建一个(实例化)新 Function 的对象时, Function 构造函数中的代码将计算参数数量并相应的更新 this.length 。至此,为 1

    If, however, we create a new instance of myObject using the new keyword, __proto__ will point to myObject.prototype as myObject is the constructor of our new instance.

  • 然而,如果,我们创建一个新的myObject 的实例化 使用 new 关键词,(新实例化对象的)__proto__ 会指向 myObject 的 myObject .prototype 的构造函数(的返回内容)。
    1
    2
    var myInstance = new myObject(“foo”);
    console.log(myInstance.__proto__ === myObject.prototype); // true

    In addition to having access to the native methods within the Function.prototype, such as call and apply, we now have access to myObject’s method, getName

  • 除了访问 Function .prototype 的原生方法外,诸如 call 和 apply ,我们还能访问 myObject 的方法,getName()。
    1
    2
    3
    4
    5
    6
    console.log(myInstance.getName()); // foo

    var mySecondInstance = new myObject(“bar”);

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

    As you can imagine, this is quite handy, as it can be used to blueprint an object, and create as many instances as needed - which leads me onto the next topic!

  • 正如你想的那样,这非常方便,因为它可以 blueprint(绘制) 一个对象(的蓝图)。

    Why is Using Prototype Better?

  • 为什么使用原型更好?

Say, for instance, that we are developing a canvas game and need several (possibly hundreds of) objects on the screen at once. Each object requires its own properties, such as x and y coordinates, width,height, and many others.

  • 说,例如,我们在开发一款 canvas 的游戏并且需要几个(可能数以百计)对象在屏幕上,每个对象都需要有自己的属性,诸如 x 和 y 的坐标,width,height 和其它更多属性。

    We might do it as follows:

  • 我们可以这样做,如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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);
    }
    ...

    … do this 98 more times …

  • …这样做98次…

    What this will do is create all these objects within memory - all with separate definitions for methods, such as draw and whatever other methods may be required. This is certainly not ideal, as the game will bloat the browsers allocated JavaScript memory, and make it run very slowly… or even stop responding.

  • 我们所有创建的对象都放在内存中——所有单独定义的方法,诸如 draw 和可能需要的其他方法。但这并不理想,因为游戏会膨胀浏览器分配给JavaScript的内存,这让它运行的非常缓慢…甚至停止响应。

    While this probably wouldn’t happen with only 100 objects, it still can serve to be quite a performance hit, as it will need to look up one hundred different objects, rather than just the single prototype object.

  • 虽然这种情况大概不会发生——只有100个对象;),但他仍然能带来一定的性能影响,因为它会访问100个不同的对象,而不是只有一个 prototype 对象。

    How to Use Prototype

    ` 怎么使用原型

    To make the application run faster (and follow best practices), we can (re)define the prototype property of the GameObject; every instance of GameObject will then reference the methods within GameObject.prototype as if they were their own methods.

  • 让应用程序运行的更快(并且遵循最优实践方式),我们能(重新)定义 GameObject 的prototype属性;每个GameObject 的实例在 GameObject.prototype 引用的方法都和使用它自身的方法别无二样。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // define the GameObject constructor function
    // 定义GameObject 的构造方法
    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的原型对象
    GameObject.prototype = {
    x: 0,
    y: 0,
    width: 5,
    width: 5,
    draw: function() {
    myCanvasContext.fillRect(this.x, this.y, this.width, this.height);
    }
    };

    We can then instantiate the GameObject 100 times.

  • 于是,我们能(轻松的)实例化100个GameObject对象
    1
    2
    3
    4
    5
    6
    var x = 100,
    arrayOfGameObjects = [];

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

    Now we have an array of 100 GameObjects, which all share the same prototype and definition of the draw method, which drastically saves memory within the application.

  • 现在我们有了一个有100个GameObject对象元素的数组,它们都共享同一个prototype并且定义了 draw 方法,这大大节省了项目所使用的内存。

When we call the draw method, it will reference the exact same function.

  • 当我们调用 draw 方法时,他将引用相同的方法。
    1
    2
    3
    4
    5
    6

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

    Prototype is a Live Object

  • Prototype 是一个 Live 对象

    An object’s prototype is a live object, so to speak. This simply means that, if, after we create all our GameObject instances, we decide that, instead of drawing a rectangle, we want to draw a circle, we can update our GameObject.prototype.draw method accordingly.

  • 对象的prototype是一个 Live 对象,可以说。这就意味着,如果,我们创建所有的GameObject实例化对象之后,我们决定,不是绘制一个矩形,我们想绘制一个圆形。因此我们能更新我们的GameObject.prototype.draw 方法。
    1
    2
    3
    GameObject.prototype.draw = function() {
    myCanvasContext.arc(this.x, this.y, this.width, 0, Math.PI*2, true);
    }

    And now, all the previous instances of GameObject and any future instances will draw a circle.

  • 至此,所有以前实例化 GameObject 对象和未来将要实例化 GameObject 对象都将绘制圆。

    Updating Native Objects Prototypes

  • 更新对象原型(自身)

    Yes, this is possible. You may be familiar with JavaScript libraries, such as Prototype, which take advantage of this method.

  • 是的,这是有可能的。你或许熟悉JavaScript库,诸如Prototype,利用这个方法(prototype)(更新原型)。

    Let’s use a simple example:

  • 让我们举一个简单的例子:
    1
    2
    3
    4
    String.prototype.trim = function() {
    return this.replace(/^\s+|\s+$/g, ‘’);
    };
    // tips: 以上正则表达式为替换所有(全局)开头为1个或n个空白字符组成或结尾为1个或多个空白字符组成的字符串替换成''(空字符串)

    We can now access this as a method of any string:

  • 我们现在能访问字符串(String)的这个方法:
    1
    “ foo bar   “.trim(); // “foo bar”

    There is a minor downside to this, however. For example, you may use this in your application; but a year or two down the road, a browser may implement an updated version of JavaScript that includes a native trim method within the String‘s prototype. This means that your definition of trim will override the native version! Yikes! To overcome this, we can add a simple check before defining the function.

  • 这儿有一个小缺点,无论怎样,例如,你可以在你的应用程序中使用它;但一两年过后,浏览器可能会更新JavaScript的新版本,这其中或许就包括String’s原型中的 trim 方法。这意味着重定义的 trim 将会覆盖你本地的版本! 可恶! 咱们得克服它,我们能添加一个简单的方法:检查之前定义的函数:
    1
    2
    3
    4
    5
    6

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

    Now, if it exists, it will use the native version of the trim method.

  • 现在,如果它存在,它将使用本地的trim 方法。

    As a rule of thumb, it’s generally considered a best practice to avoid extending native objects. But, as with anything, rules can be broken, if needed.

  • 一般来说,这种方法通常被认为是最佳实践方式,避免重新声明本机对象(已有)。但是,都一样,规则可以被打破,如果有必要的话。

    Conclusion

  • 结论

    Hopefully, this article has shed some light on the backbone of JavaScript that is prototype. You should now be on your way to creating more efficient applications.

  • 希望,这篇文章解释清楚了JavaScript的骨干prototype。你现在应该创建更加高效的应用程序。

    If you have any questions regarding prototype, let me know in the comments, and I’ll do my best to answer them.

  • 如果你有关于原型的任何问题,在评论区让我知道,我会知无不言言无不尽。