prototype在初学阶段简直神一般的存在,让人闻风丧胆。这篇文章简单介绍下prototype,以及浏览器中的__proto__。
简单地说,prototype就是为原型对象提供了一个开放接口,让我们可以方便的为对象的实例扩展属性和方法。
先看一下对象的静态方法和实例方法。
function Person () {
};
Person.sayHello = function () { //定义一个静态方法
console.log("Hello!");
};
var p = new Person();
Person.sayHello(); // Hello!
p.sayHello(); // p.sayHello is not a function
由此可以看出,对象的静态方法不能被对象实例调用。例如String对象的fromCharCode()方法,正确的调用方法应该是 String.fromCharCode(),而使用 myString.fromCharCode()会报错。
如果我们想给对象的实例添加方法,可以在构造函数中使用"this"来定义:
var Person = function () {
this.sayHello = function () {
console.log("Hello!");
}
};
var p = new Person();
p.sayHello(); // Hello!
构造函数模式虽然好用,但有个很大的缺点,那就是每个方法都会在每个实例上重新创建一遍。例如,我们创建两个Person实例,p1和p2都有一个名为sayHello()的方法,但这两个方法不是同一个Function的实例。
var Person = function () {
this.sayHello = function () {
console.log("Hello!");
}
};
var p1 = new Person();
var p2 = new Person();
console.log(p1.sayHello == p2.sayHello); // false
因为JavaScript中函数也是对象,因此每定义一个函数就会实例化一个Function对象,造成了不必要的内存开销。另外,使用this创建实例方法也并不总是可行的。例如我们想给Date对象实例扩展一个format()方法,我们总不能直接修改Date的源码吧,而用prototype就很简单了:
Date.prototype.format = function () {
//do something...
}
var time = new Date();
time.format();
下面说一下__proto__。当调用构造函数创建一个新实例后,该实例内部会包含一个指针,指向构造函数的原型对象。这个指针在ECMA-262第5版上叫[[Prototype]],虽然没有标准方式访问[[Prototype]],但在Firefox、Safari和Chrome等浏览器上都实现了一个__proto__属性来访问它。当解析器查找实例上的某个属性时,如果没有查找到,就会在__proto__上查找,而__proto__指向构造函数的原型对象,这就是多个对象实例共享原型的属性和方法的基本原理。
简单的说:p.__proto__ === Person.prototype, 但也有例外,那就是使用Object.creat创建对象的时候。
function Person () {
};
var p1 = new Person();
var p2 = Object.create(Person)
console.log(Person.prototype === p1.__proto__) // true
console.log(Person.prototype === p2.__proto__) // false
console.log(Person.prototype.constructor === p2.__proto__) // true
一般情况下,使用new 关键字创建的实例,其__proto__等于其原型对象的prototype ,而使用Object.create()创建的实例,其__proto__等于其原型对象的构造函数。