咱们建立的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含能够由特定类型的全部实例共享的属性和方法。看以下例子:chrome
function Person(){ } Person.prototype.name = 'ccc' Person.prototype.age = 18 Person.prototype.sayName = function (){ console.log(this.name); } var person1 = new Person() person1.sayName() // --> ccc var person2 = new Person() person2.sayName() // --> ccc console.log(person1.sayName === person2.sayName) // --> true
根据上面代码,看下图:
浏览器
须要理解三点:函数
注意:person1 和person2 实例与构造函数之间没有直接的关系。测试
在以前咱们提到,全部实现中没法访问到[[prototype]],那咱们如何知道实例和原型对象之间是否存在关系呢?这里能够经过两个方法来判断:this
console.log(Person.prototype.isPrototypeOf(person1)) // --> true
console.log(Object.getPrototypeOf(person1) === Person.prototype) // --> true
前面咱们提到过,原型最初只包含constructor属性,而该属性也是共享的,所以能够经过对象实例访问。虽然能够经过对象实例访问保存在原型中的值,但却不能经过对象实例重写原型中的值。若是咱们在实例中添加了一个属性,而改属性与实例原型中的一个属性同名,那就会在实例上建立该属性并屏蔽原型中的那个属性。以下:firefox
function Person() {} Person.prototype.name = "ccc"; Person.prototype.age = 18; Person.prototype.sayName = function() { console.log(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = 'www' // 在person1中添加一个name属性 person1.sayName() // --> 'www'————'来自实例' person2.sayName() // --> 'ccc'————'来自原型' console.log(person1.hasOwnProperty('name')) // --> true console.log(person2.hasOwnProperty('name')) // --> false delete person1.name // --> 删除person1中新添加的name属性 person1.sayName() // -->'ccc'————'来自原型'
咱们如何判断一个属性,究竟是实例上的属性仍是原型上的属性?这里能够经过hasOwnProperty()方法来检测一个属性是存在于实例中仍是存在于原型中。(此方法继承于Object)prototype
下图详细分析了上面例子在不一样状况下的实现与原型的关系:(省略了Person构造函数的的关系)
指针
咱们不可能总像以前的例子同样,没添加一个属性和方法就要敲一遍,Person.prototype。为了减小没必要要的输入,更常见的方法是像下面这样:code
function Person(){} Person.prototype ={ name: 'ccc', age: 18, sayName: function () { console.log(this.name) } }
在上面代码中,咱们将Person.prototype设置为等于一个以对象字面量形式建立的新对象。最终结果相同,但有一个例外,constructor属性再也不指向Person了。前面咱们介绍过,每建立一个函数,就会同时建立它的prototype对象,这个对象也会自动得到constructor属性。可是在咱们使用的新语法中,本质上彻底重写了默认的prototype对象,所以constructor属性也就变成了新对象的constructor属性(指向Object构造函数),再也不指向Person函数了。此时,尽管instanceof操做符还能返回正确的结果,但经过constructor已经没法肯定对象的类型了。以下:对象
var person1 = new Person() console.log(person1 instanceof Object) // --> true console.log(person1 instanceof Person) // --> true console.log(person1.constructor === Person) // --> false console.log(person1.constructor === Object) // --> true
这里用instanceof操做符测试Object和Person仍然返回true,constructor属性则等于Object,不等于Person了,若是constructor真的很重要能够像下面这样写:
function Person(){} Person.prototype ={ constructor: Person, // --> 重设 name: 'ccc', age: 18, sayName: function () { console.log(this.name) } }
可是这会引发一个新问题,用上述方式重置constructor属性会致使它的[[Enumerable]]特性被设置为true。而默认状况下,原生的constructor属性是不可枚举的。所以若是你要使用兼容ECMAscript5的JavaScript引擎,能够试一试Object.defineProperty()。
function Person(){} Person.constructor = { name: 'ccc', age: 18, sayName: function(){ console.log(this.name) } } // 重设构造函数,只适用于ECMAscript5兼容的浏览器 Object.defineProperty(Person.constructor, "constructor", { enumerable: false, value: Person })
因为原型中查找值的过程是一次搜索,所以咱们对原型对象所作的任何修改都能当即从实例上反映出来。好比:
function Person(){} var person1 = new Person() Person.prototype.sayHi= function(){ console.log('hi') } person1.sayHi()
上述代码咱们先建立了一个Person实例,并将其保存在person1中,而后在Person.prototype中添加了sayHi()方法。即便person1是添加新方法以前建立的,但它仍然能够访问这个方法。缘由是实例与原型之间的松散的链接关系。
尽管能够随时为原型添加属性和方法,并当即可以在实例中反映出来。可是若是重写整个原型对象,那么状况就不同了。看以下代码:
function Person(){} var person1 = new Person() Person.prototype = { name: 'ccc', age: 18, sayName: function(){ console.log(this.name) } } person1.sayName() // --> error
看下图分析:
调用构造函数时为实例添加了一个指向最初原型的[[prototype]]指针,而把原型修改成另一个对线更久等于切断了构造函数与最初原型之间的联系。请记住:实例中的指针仅指向原型,而不指向构造函数。
原型链的内容明天再写,今天有点晚了。