在 JavaScript 中,每当定义一个对象(或函数)时候,对象中都会包含一些预定义的属性,其中一个属性就是原型对象 prototype。
var myObject = function( name ) {
this.name = name;
return this;
}; console.log(typeof myObject.prototype); // object myObject.prototype.getName = function() {
return this.name;
};
上面的代码创建了一个函数,然后赋值给 myObject
。如果我名调用 myObject()
,它将返回window
对象。因为它是在全局作用域内定义的,而且它还没有被实例化,所以 this
直接指向全局对象:
console.log(myObject() === window); // true
原型链
JavaScript 中定义或实例化任何一个对象的时候,它都会被附加一个名为 __proto__
的隐藏属性,原型链正是依靠这个属性才得以形成。但是千万别直接访问 __proto__
属性,因为有些浏览器并不支持直接访问它。另外 __proto__
和 对象的 prototype
属性也不是一回事,它们各自有各自的用途。
怎么理解呢?其实,当我们创建 myObject
函数时,实际上是创建了一个 Function
类型的对象:
console.log(typeof myObject); // function
这里要说明一下,Function
是 JavaScript 中预定义的一个对象,所以它也有自己预定义的属性(如length
和 arguments
)和方法(如 call
和 apply
),当然也有 __proto__
,以此实现原型链。也就是说,JavaScript 引擎内可能有类似如下的代码片段:
Function.prototype = {
arguments: null,
length: 0,
call: function() {
// secret code
},
apply: function(){
// secret code
},
...
};
事实上,JavaScript 引擎代码不可能这样简单,这里只是描述一下原型链是如何工作的。
我们定义了一个函数 myObject
,它还有一个参数 name
,但是并没有给它任何其它属性,例如length
或者其它方法,如 call
。那么下面这段代码为啥能正常执行呢?
console.log(myObject.length); // 结果:1,是参数的个数
这是因为我们定义 myObject
时,同时也给它定义了一个 __proto__
属性,并赋值为Function.prototype
(参考前面的代码片段),所以我们能够像访问其它属性一样访问myObject.length
,即使我们并没有定义这个属性,因为它会顺着 __proto__
原型链往上去找length
,最终在 Function
里面找到了。
那为什么找到的 length
属性的值是 1,而不是 0 呢,是什么时候给它赋值的呢?由于 myObject
是 Function
的一个实例:
console.log(myObject instanceof Function); // true
console.log(myObject === Function); // false
当实例化一个对象的时候,对象的 __proto__
属性会被赋值为其构造者的原型对象,在本示例中就是 Function
,此时构造器回去计算参数的个数,改变 length
的值。
console.log(myObject.__proto__ === Function.prototype); // true
而当我们用 new
关键字创建一个新的实例时,新对象的 __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 个新的对象。
prototype 的典型示例
用过 jQuery 或者 Prototype 库的朋友可能知道,这些库中通常都会有 trim
这个方法。
示例:
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, '');
};
trim
用法:
' foo bar '.trim(); // 'foo bar'
但是这样做又有一个缺点,因为比较新版本的浏览器中的 JavaScript 引擎在 String 对象中本身就提供了 trim
方法, 那么我们自己定义的 trim
就会覆写它自带的 trim
。其实,我们在定义 trim 方法之前,可以做个简单的检测,看是否需要自己添加这个方法:
if(!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, '');
};
}
检查一下,如不存在 trim
这个方法,定义一个。
via http://net.tutsplus.com/tutorials/javascript-ajax/prototypes-in-javascript-what-you-need-to-know/