译者: 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 myObject
function, 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.
如果你有关于原型的任何问题,在评论区让我知道,我会知无不言言无不尽。