原型
一、原型定义:原型是function对象的一个属性,它定义了构造函数制造出来的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
先定义一个构造函数,Person.prototype这个属性就是这个构造函数的原型,这个属性天生就有,并且这个属性的值也是一个对象
function Person() {
}
我们可以在prototype上面添加一些属性和方法,每一个构造出来的对象都可以继承这些属性和方法。
function Person() {
}
Person.prototype.name = 'Tylor';
var oPerson = new Person();
console.log(oPerson.name);//Tylor
虽然每一个对象是独立的,但是他们都有共同的祖先,当我们访问这个对象的属性的时候,如果他没有这个属性,才会向上找它的原型,然后在原型上访问这个属性。
function Person() {
this.money = 100
}
Person.prototype = {
money: 200
}
var oPerson = new Person();
console.log(oPerson.money); //100
delete oPerson.money; //删除oPerson上的money属性
console.log(oPerson.money); //200
这里oPerson对象因为自身有一个money属性,所以就不会到原型上寻找money属性,而是查询自身的money属性,因此打印的是100,但是当我们删除自身的money属性之后,他就会到原型上去寻找money属性,因此就打印200
二、利用原型的特点,可以提取共有属性。
提取共有属性过程:我们可以把每一个对象(被同一个构造函数构造出来的)的共有属性不写在构造函数里面,而是提取到原型(构造函数的prototype)上
优点:这样我们用构造函数构造大量的对象的时候就不需要走多次构造函数里面的赋值语句,而是只走一遍构造函数原型上的赋值语句,这些对象通过继承的方式拿到这些属性。
三、对象如何查看原型(_proto_)
前面提到了构造函数可以通过.prototype的方式查看构造函数的原型,那么我们如何查看(以通过构造函数构造【new】出来对象为例)对象的原型呢?
由于构造函数在构造对象(new)的时候,会隐式的创建一个this对象,这个this对象里面有一个默认的属性就做_proto_属性,这个属性的指向就是这个对象的原型。
function Person() {
// new Person() 后发生
// this = {
// _proto_:Person.prototype
// }
this.money = 100
}
查找过程:当查找自身没有的属性时,就会先查找这个_proto_属性,然后这个属性指向了原型,所以就到原型上面继续查找属性了。
注意:prototype是构造函数的属性,_proto_是对象的属性。
原型链
有了原型,原型还是一个对象,那么这个名为原型的对象自然还有原型,这样的原型上还有原型的结构就构成了原型链。
绝大部分的对象最终都会继承自Object.prototype这个对象。
GrandFather.prototype.firstName = 'Zhang';
function GrandFather(){
this.name = 'grandfather';
this.sex = 'male';
}
var grandfrather = new GrandFather();
Father.prototype = grandfather;
function Father(){
this.name = 'father';
this.money = 100;
}
var father = new Father();
Son.prototype = father;
function Son(){
this.name = 'son';
}
var son = new Son();
Father创造出来的每一个对象都继承自grandfather这个对象, Son创造出来的每一个对象都继承自father这个由Father创造出来的对象,这样son就可以继承上面Father和GrandFather的所有属性值。
当我们查找son上的属性时,如果son自身有这个属性,那么就打印出来,如果没有就向上查找原型father,有就打印,如果father上还没有这个属性,就会继续向上查找grandfoo,如果有就输出,如果没有就返回undefined。(是undefined的原因和当我们在全局声明一个变量没有赋值直接打印一样,顺着原型链最终会找到Object这个对象如果他没有自然就是undefined)
console.log(son.name);//son
console.log(son.money);//100
console.log(son.sex)//male
这种链式的查询的结构就叫做原型链。
那么这种链式查询有没有终点呢?我们写的GrandFather是不是这个原型链的终点?
由测试可以看出,其实我们的GrandFather.prototype上面还有原型就是一个对象包含了许多对象,这个对象上面就没有原型了。
其实绝大部分的对象最终都会继承Object.prototype这个对象。
如果我们没有规定某个对象的原型(比如字面量创建对象时),他们的原型就会是Object.prototype这个对象。
由此可见原型链的终点一般都是object.prototype这个对象,但是并不是所有的对象都有原型(Object.create(null)可以创建出没有原型的对象)。
当我们使用Object.create()方法构造对象时,需要写一个参数,这个参数就是我们构造对象的原型,如果我们想要构造 var obj = {};这样一个空对象,那么就需要写:
var obj = Object.create(Object.prototype)
控制台打印:
当然我们也可以自定义一个对象让它成为原型
var obj = Object.create({name:'Tylor'})
控制台打印:
通过控制台打印发现虽然自定义这个对象作为了obj的原型,但是这个对象的原型也是Object.prototype
但是当我们将参数写为null的时候,我们就构造出来了一个没有原型的空对象
原型链上属性的增删改查
其实我们在前面一直使用着这些方法,这里说一下原型上的引用值。当我们通过一个对象改变了原型上的引用值类型的属性的话,那么所有对象的这个属性都会随之改变。
Person.prototype.arr = [1,2,3];
function Person(){
}
var person1 = new Person();
var person2 = new Person();
person1.arr.push(4);
console.log(person2.arr);//[1,2,3,4]
删除
Person.prototype.name = 'father';
function Person(){
this.name = 'son';
}
delete person.name;
console.log(person.name);//father
这个时候person对象上面没有了name属性,那么依据我们前面所说的当自身没有这个属性的时候就会向原型上查询这个属性的说法,我们再次删除是不是就可以删除原型上的属性了?答案当然是否定的。
Person.prototype.name = 'father';
function Person(){
this.name = 'son';
}
delete person.name;
console.log(person.name);//father
delete preson.name;
console.log(person.name);//father
由此可见,对象并不能删除原型上的属性。