【翻译】JavaScript中的原型对象prototype
在 JavaScript 中,每定义一个对象的时候,对象都会带有一些预定义好的属性,其中之一便是 prototype
。
什么是 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
__proto__
在 JavaScript 中,当定义或实例化任何一个对象的时候,都会为其附加一个隐藏属性 __proto__
,但是千万不要直接访问 __proto__
属性,因为有些浏览器并不支持直接访问它。并且,__proto__
和 prototype
也不是一回事。
当创建 myObject
函数的时候,我们定义了一个类型对象 Function
。
console.log(typeof myObject); // function
Function
是 JavaScript 中的预定义对象,并且其拥有自己的属性(如 length
和 argument
)和方法(如 call
和 apply
)。同样,它也拥有自己的原型对象以及隐藏属性 __proto__
,这就意味着,在 JavaScript 中,可能会有如下形式的代码:
Function.prototype = {
arguments: null,
length: 0,
call: function(){
// secret code
},
apply: function(){
// secret code
}
...
}
当然,肯定没有这么简单,这只是说明原型链是如何工作的。
当定义 myObject
函数,并且只为其添加参数 name
,但是却可以访问它的 length
或其它方法
console.log(myObject.length); // 1 (being the amount of available arguments)
这是为什么呢?当我们定义 myObject
函数的时候,它就创建了一个 __proto__
属性并且将值设置为 Function.prototype
。所以,当我们访问 myObject.length
的时候,它先会在 myObject
中查找 length
,没有找到,然后继续沿着 __proto__
原型链向上查找,最后再 Function
中找到并返回它。
你可能想知道为什么 length
为 1 却不为 0 ,或者是其他任何数字。这是因为 myObject
事实上是 Function
的一个实例。
console.log(myObject instanceof Function); // true
console.log(myObject === Function); // false
当创建对象的实例时,__proto__
属性被更新为指向构造函数的原型,在这种情况下,原型是 Function
。
console.log(myObject.__proto__ === Function.prototype) // true
另外,当创建一个新的 Function
对象时,构造函数中的本地代码 Function
将计算参数的数量,并相应地更新 this.length
,在这种情况下,它是 1。
如果我们使用 new
关键字创建的新实例 myObject
的时候,新对象的 __proto__
将赋值为 myObject.prototype
,因为现在的构造函数为 myObject
,而非 Function
。
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
其实这相当于把原型对象当做一个蓝本,然后可以根据这个蓝本创建 N 个新的对象。
为什么要使用原型
比如说,我们正在开发一个画布游戏,需要有几个(可能几百)对象。每个对象都需要自己的属性,如 x 和 y 坐标, width , height 等等。
我们可以这样做:
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
等等其他可能需要的方法。这种方式当然不可取,因为浏览器可能会为每一个对象创建内存分配而运行缓慢或者是直接崩溃。
那么,如何使用 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 个 GameObjects
的数组,它们都共享相同的原型和 draw
方法的定义,从而大大节省了应用程序中的内存。
当我们调用该draw
方法时,它将引用完全相同的函数(即它们所共有的函数)。
var GameLoop = function() {
for(gameObject in arrayOfGameObjects) {
gameObject.draw();
}
};
更新当前的原型对象
你可能用过类似的 JavaScript 库,如 Prototype ,它便用的是如下方式。
可以看下这个简单示例:
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, ‘’);
};
然后,就可以这样使用这个方法
" foo bar ".trim(); // "foo bar"
当然,这样写还是有一些小缺点,在低版本的浏览器中,使用该方法是没有问题的,但是在比较新的浏览器中的 JavaScript 引擎已经实现了trim
方法,那么我们自己写的就会覆盖掉本身已经实现的,所以在自己定义 trim
方法之前,可以做一个检测。
if(!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, ‘’);
};
}
- 本博客所有文章除特别声明外,均可转载和分享,转载请注明出处!
- 本文地址:https://www.leevii.com/?p=336