原型
原型是什么
每个函数都有一个原型对象(prototype),它是一个属性也是一个指针,指向一个对象。
当所有对象有一共有的属性时,就可以用prototype添加,不用一个一个的重复添加。
Person.prototype.name = "Nicholas";
Person.prototype.age = 23;
function Person(){
}
var person1 = new Person();
原型的属性
- constructor
创建了构造函数后,系统默认其对象有constructor属性。这个属性指向原函数。
Person.prototype.constructor == Person;
相应的,在每一个使用构造函数新建的实例中,也存在一个指针,指向构造函数的原型对象。这个指针可以为[[prototype]]或__proto__。
- isPrototypeOf
确定传入的对象的原型是否是当前的对象。
var result = Person.prototype.isprototypeOf(person1);//true
- getPrototypeOf
ES5新添加的属性,可以返回实例对象的原型。
var result = Object.getPrototypeOf(person1);//Person.prototype
原型的增删查改
虽然我们能用对象实例去访问对象原型中的值,但是不能使用实例去改变原型中已有的属性和值。
Person.prototype.name = "Nicholas";
Person.prototype.age = 23;
function Person(){
}
var person1 = new Person();
var person2 = new Person();
person1.name = 'Greg';
console.log(person1.name);
当像这样使用实例强制改变原型中的值时,原型中的值会被覆盖,
这种情况被称为"属性遮蔽 (property shadowing)。
此时再访问 person1 . name,返回值为‘Greg’;
原有的原型值没有并没有改变。
当delete person1.name时,再次访问person1 . name,返回为‘Nicholas’;
对象字面量创建原型
为了减少代码量,有一种更简便的方法来写对象的原型。
function Person(){
}
Person.prototype = {
name : 'Nicholas',
age : 23,
job : 'teacher'
}
var person1 = new Person();
var person2 = new Person();
但是这种写法也有缺点:此时原型的constructor属性不再指向Person
这时constructor指向Object。
如果constructor不能改变,可以通过以下方法特意设置其值。
function Person(){
}
Person.prototype = {
constructor : Person,
name : 'Nicholas',
age : 23,
job : 'teacher'
}
原型的重写
关于创建原型实例和修改原型的问题。
- 当先创建实例,后添加属性值时:
function Person(){
}
var person1 = new Person();
Person.prototype.name = 'Nicholas';
console.log(person1.name);//'Nicholas'
尽管原型赋值在后,但是由于实例与原型松散的连接,所以在寻找person1.name中后从实例找到原型中。所以可以随时给原型添加属性和方法。
2. 当改变重写原型对象时。
function Person(){
}
var person1 = new Person();
Person.prototype = {
name : 'Nicholas', //上面讲到当使用这种方法创建原型时,
age : 23, //会改变原型constructor的指向
job : 'teacher'
}
console.log(person1.name); //因此现在找不到person1.name,返回undefined
原型链
二话不说,先看代码
Grand.prototype.lastName = 'Nicholas';
function Grand(){
}
var grand = new Grand();
Father.prototype = grand;
function Father(){
this.name = 'xiaoming';
}
var father = new Father();
son.prototype = father;
function Son(){
this.hobbit = 'havana';
}
var son = new Son();
由上可以看出原型链的实现方法。
此时原型对象包含一个指向另一个原型的指针,则另一个原型中也包含着一个指向另一个构造函数的指针。当另一个原型又是另一个原型的实例,如此层层递进,就构成原型与实例的链条。
而原型链的终端则是Object.prototype。
简而言之,上面的代码中:Son继承了Father,Father继承了Grand,Grand继承了Object。
虽然绝大多数对象都继承自Object.prototype,但是还是例外。
我们可以构建出不继承自Object的对象
使用Object.create(null)即可
谨慎定义
在原型链中定义方法时,不能使用对象字面量创建原型方法。
function Father(){
this.name = 'xiaoming';
}
var father = new Father();
function Son(){
this.hobbit = 'havana';
}
son.prototype = father;
//这时添加方法,会切断son和father之间的连接,原型链被切断了
son.prototype = {
action : function(){
return 'walk';
}
}
var son = new Son();
原型链的问题
含有引用值的原型属性会被所有实例共享,这就造成了原型链的缺陷。
让我们来看一个栗子。
function Father(){
this.TshirtColors = ['red','blue','yellow'];
}
var father = new Father();
function Son(){
this.hobbit = 'havana';
}
son.prototype = father;
var son1 = new Son();
var son2 = new Son();
son1.TshirtColors.push('black');
alert(son1.TshirtColors); //'red','blue','yelllow','black'
alert(son2.TshirtColors); //'red','blue','yelllow','black'
关于解决这个问题的方法,将在下次博客介绍。
原型实战
// 1
Person.prototype.name = 'sunny';
function Person(){
}
var person = new Person();
person.peototype = {
name : 'cherry'
}
// 2
Person.prototype.name = 'sunny';
function Person(){
}
person.peototype = {
name : 'cherry'
}
var person = new Person();
请比较这两块代码的输出。