-
Notifications
You must be signed in to change notification settings - Fork 0
Description
关于原型链
我们都知道javascript的继承是采用原型链的方式,也就是一个构造函数的显式原型(prototype)等于其实例的隐式原型(proto)。来一起通过下面的代码看一下。
function Dog(name) {
this.name = name;
}
// Dog.prototype其实也是一个对象,这个对象是Object构造函数的实例
Dog.prototype.bark = function() {
console.log('wangwang!');
}
Dog.prototype.sayName = function() {
console.log(this.name);
}
var dog = new Dog('wangcai');
dog.bark(); // wangwang!
dog.sayName(); // wangcai实例上并没有bark与sayName方法,但是却能调用,那么很明显这个调用的是构造函数上的显示原型(prototype)的bark方法。
规则上如果调用一个实例的方法,那么会先在实例上检查有没有此方法,如果有就调用自己的方法,如果没有则顺着实例的__proto__去寻找原型上面的方法,如果原型上面依然没有则继续顺着__proto__寻找,所以类似一条长链,我们就称之为原型链。这个原型链的尽头是Object.prototype因为此原型的__proto__为null,所有的对象最后都继承于Object.prototype,包括函数。
关于继承
由于我们这次要说的是关于混合继承的问题,在此只讨论最常见的混合继承方式。
混合继承是一种由借用构造函数与原型链混用的继承方式,他的好处是又能继承原构造函数的属性,也能继承原构造函数原型上的方法。比如除了上面我们创建的Dog,我们希望再创建一个Cat,Cat可以继承Dog上的name属性与原型上的bark与sayName方法。
function Cat(name) {
Dog.call(this, name);
}
Cat.prototype = new Dog();
// 当然,Cat的叫声肯定不是wangwang了,所以这里我们改写一下bark
Cat.prototype.bark = function() {
console.log('miaomiao!');
}
var cat = new Cat('mimeng');
cat.bark(); // miaomiao!
cat.sayName(); //mimeng这里首先我们借用了Dog这个构造函数,将Dog的this改变成了Cat的实例,让cat具有name属性,然后让Cat的显示原型等于Dog的实例,此时就完成了一条基于原型链的继承。
我们可以想一下,当我们调用cat的sayName方法时,cat会检索自己身上有没有sayName,很明显没有,然后顺着cat.__proto__向上寻找,找到了Cat.prototype,此时的Cat.__prototype__等于new Dog()也就是Dog的实例,但这个实例上依然没有sayName这个方法,所以沿着此实例的__proto__找到了Dog.prototype,这样就找到了sayName方法然后调用。他们其实是这样一种关系
// 寻找sayName方法
cat --继承于--> Cat.prototype --等于--> new Dog() --继承于--> Dog.prototype
但是寻找bark的形式则与上面不一样,因为我们是在Cat.prototype上重写了bark方法,所以如果调用bark则寻找到Cat.prototype就会停止寻找并调用。
有一点值得注意,我们是先Cat.prototype = new Dog(),然后才重写了bark方法,这两个顺序并不能调换,因为如果调换位置,则它的意思是先在原对象上改写了bark方法,然后将Cat.prototype重新指向了一个新对象,这个新对象上并没有bark方法。
混合继承的一点问题
其实上面的代码是有一点问题的,我们先在Dog这个构造函数上动一下手脚
function Dog(name) {
console.log(name);
this.name = name;
}将Dog中第一行添加一个console,打开控制台看看输出的什么~
// undefined
// mimeng
很明显控制台输出了两条语句,因为其实console.log被执行了两遍,第一遍是在Cat.prototype = new Dog()中被执行,因为并没有传递任何参数,所以name为undefined,第二遍则是在实例化Cat的时候,
我们的Dog中由于只是单纯的打印了name,并没有对name进行任何操作,所以并没有报错,但是一旦name进行一些比如字符串的操作,此函数就会报错。所以我们一起将混合继承的方式改进一下。
// es5
Cat.prototype = Object.create(Dog.prototype);这里我们将实例化Dog替换成了Object.create(),它会创建一个继承参数对象的对象,这样就不会去执行Dog构造函数中内部的代码。如果环境不兼容es5的话我们也可以自己实现一个create函数
function create(obj) {
if (Object.prototype.toString.call(obj) !== '[object Object]') {
throw new Error('need an object type');
}
function FakerPrototype() {}
FakerPrototype.prototype = obj;
return new FakerPrototype();
}
Cat.prototype = create(Dog.prototype);实现原理其实还蛮简单的,我们只是构造了一个空的函数,让这个函数的prototype指向要继承的对象,然后实例化这个空的构造函数,这样自然不存在问题啦!