理解JavaScript中的原型链-Prototypes

源文章来源Prototypes in JavaScript

译者: Leeyw

 

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.

原型中的属性最初是一个空的对象,而且能添加成员-就像你添加其他任何对象一样。

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  也因此返回全局对象,因为它还没有实例化(稍后详细解释)。

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

 

The Secret Link

“神秘”链

Every object within JavaScript has a “secret” property.

在JavaScript中所有对象都有一个“神秘”链

Before we continue, I'd like to discuss the “secret” link that makes prototype work the way it does.

在我们继续了解之前,我想要先论述“secret”("神秘")链在原型中的作用。

Every object within JavaScript has a “secret” property added to it when it is defined or instantiated, named __proto__; this is how the prototype chain is accessed. However, it is not a good idea to access __proto__ within your application, as it is not available in all browsers.

当你定义一个实例化对象时会有一个“神秘”属性添加进这个对象,这个"神秘"属性名字叫__proto__; 这就是原型链的存储之地。然而,在你当前应用中访问__proto__并不是一个好主意,因为这个属性(__proto__)再某些浏览器中不存在(不兼容)。

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 myObjectfunction, we were defining an object of type Function.

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

console.log(typeof myObject); // function

For those unaware, Function is a predefined object in JavaScript, and, as a result, has its own properties (e.g. length and arguments) and methods (e.g. call and apply). And yes, it, too, has its own prototype object, as well as the secret __proto__ link. This means that, somewhere within the JavaScript engine, there is a bit of code that could be similar to the following:

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

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)会执行呢?

console.log(myObject.length); // 1 (being the amount of available arguments)
// (存在于这个方法的参数的数量)

 This is because, when we defined myObject, it created a __proto__ property and set its value to Function.prototype (illustrated in the code above). So, when we access myObject.length, it looks for a property of myObject called length and doesn’t find one; it then travels up the chain, via the __proto__ link, finds the property and returns it.

这是因为,我们在定义 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 的一个实例化对象。

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 。

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 的构造函数(的返回内容)。

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.。 

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:

我们可以这样做,如下:

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 引用的方法都和使用它自身的方法别无二样。

// 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对象

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 方法时,他将引用相同的方法。

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 方法。

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:

让我们举一个简单的例子:

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)的这个方法:

“ 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 将会覆盖你本地的版本! 可恶! 咱们得克服它,我们能添加一个简单的方法:检查之前定义的函数:

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.

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值