Javascript深入理解之继承与原型链
1.构造函数、原型、实例的关系
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针[[Prototype]]
function Foo() {
this.a = 123;
}
Foo.prototype.b = 456;
let AA = new Foo();
console.dir(Foo.prototype);
console.dir(Foo.prototype.constructor);
console.dir(AA.__proto__);
// Foo为构造函数
// AA为实例
// Foo.prototype为原型对象
// Foo.prototype的constructor指向构造函数
// AA的__proto__指向原型对象
2.原型链
关于原型链的定义,我们来看一下MDN里面的定义“在 javaScript 中,每个对象都有一个指向它的原型(prototype)对象的内部链接。这个原型对象又有自己的原型,直到某个对象的原型为 null 为止(也就是不再有原型指向),组成这条链的最后一环。这种一级一级的链结构就称为原型链(prototype chain)。”
3.原型链实例与继承
function Foo() {
this.nameOne = 123;
this.toOne = function() {
return this.nameOne;
}
}
function FooTwo() {
this.nameTwo = 456;
this.toTwo = function() {
return this.nameTwo;
}
}
FooTwo.prototype = new Foo();
/* 当我们对对象的prototype属性进行替换时,可能会对对象constructor属性产生一定的副作用,因此,在对prototype进行替换后,要对对象的constructor进行重置,避免陷入原型陷进。*/
FooTwo.prototype.constructor = FooTwo;
let aa = new FooThree();
console.log(aa.nameOne); // => 123
console.log(aa.nameTwo); // => 456
console.log(aa.toOne()); // => 132
console.log(aa.toTwo()); // => 456
关于原型陷阱请看[JavaScript深入理解之原型](http://blog.csdn.net/zza000000/article/details/54142510)
上例中:定义了两个类型:Foo和FooTwo,每个类型分别有一个属性和方法,且FooTwo继承了Foo,继承是通过创建Foo的实例,并将该实例赋给Foo.prototype实现的。实现的本质是重写原型对象,代之以一个新类型的实例,即原来存在于Foo的实例中的方法现在也存在于FooTwo.prototype中了,这样就继承了Foo的属性和方式,实现了继承。通俗一点说,A构造函数要继承B构造函数的属相和方法,则将B的实例赋值给A的prototype属性。
看下面一个例子
function Foo() {
this.name = 123;
this.toString = function() {
return this.name;
}
}
function FooTwo() {
this.nameTwo = 456;
}
function FooThree() {
this.name = 789;
this.toStr = function() {
return this.name;
}
}
FooTwo.prototype = new Foo();
FooThree.prototype = new FooTwo();
FooTwo.prototype.constructor = FooTwo;
FooThree.prototype.constructor = FooThree;
let aa = new FooThree();
console.log(aa.nameTwo); // => 456
console.log(aa.toString()); // => 789
console.log(aa.toStr()); // => 789
// aa同时是三个构造器的实例
console.log(aa instanceof Foo); // => true
console.log(aa instanceof FooTwo); // => true
console.log(aa instanceof FooThree); // => true
上例中:两个输出都打印789,因为在继承中,继承的方法和属性, 而且是单纯的方法和属性,FooThree实际上是下面这样:
function FooThree() {
this.nameTwo = 456;
this.name = 789; // 因为我们设置constructor指向FooThree
this.toStr = function() {
return this.name;
}
}
4.构造函数模式继承
function Foo(name) {
this.name = name;
this.say() = function() {
return this.name;
}
}
function FooTwo() {
// 继承Foo,同时还传递参数
Foo.call(this, 123);
}
let aa = new FooTwo();
console.log(aa.name); // => 123
console.log(aa.say()); //TypeError: say is not a function
构造函数模式缺点在于只能继承属性,不能继承方法
5.组合继承
function Foo(name) {
this.name = name;
}
Foo.prototype.say = function() {
return this.name
}
function FooTwo(name, age) {
// 继承属性
Foo.call(this, name);
this.age = age;
}
// 继承方法
FooTwo.prototype = new Foo();
FooTwo.prototype.constructor = FooTwo;
FooTwo.prototype.say = function() {
return this.name;
}
let aa = new FooTwo('aa', 26);
console.log(aa.name); // => aa
console.log(aa.age); // => 26
console.log(aa.say()); // => aa
5.其它继承方式还有:原型式继承、寄生式继承等