说到继承,想必都不陌生。学过java的童鞋最清楚不过了,面向对象的一大特性就是继承。竟然js有也可以面向对象,那么js继承怎么实现呢?
构造函数继承
继承从字面量上讲首先要有父子关系,这样才能叫继承,孩子继承父亲,然后孩子就有了父亲的特性和方法,我们很容易就能想到js的关键字call、apply,通过改变上下文的指向来调用不是自己的方法的方法。我们来看代码具体怎么实现:
function Animal(kind) {
this.kind = kind;
this.eat = function (food) {
console.log(this.name + 'eat ' + food);
}
}
function Dog(name, color, kind) {
Animal.apply(this, arguments); //这一句必须写在构造函数最前面,因为写在后面会让
this.name = name;
this.color = color;
}
var dog1 = new Dog('大黄', '黑色', '狗');
console.log(dog1);
console.log(dog1.eat('骨头'));
console.log(dog1.constructor); //Dog
console.log(dog1 instanceof Animal); //false
console.log(dog1 instanceof Dog); //true
console.log(dog1.eat === dog2.eat); //false
当然用apply也可以,不过参数要对应Animal.call(this, name, color, kind);但是这一句必须在子类构造函数的开头写,不然父类的参数赋值会覆盖掉子类的值;另外这种方法的继承子类并不是父类的实例,也不完全符合继承的要求;并且最重要的一点就是性能问题,没实例化一次所有属性都实例一次,没有达到继承共享的特性,造成浪费。
原型链继承
谈到js继承当然少不了原型链,原型链继承是js继承的主要手段。js里面类就是对象,每一个对象都有一个prototype属性,对象调用方法和属性首先会在自身上查找有没有对应的方法和属性,没有就会去查找prototype上有没有,在没有继续往上找,知道Object上都没有就会报错---undefined。那么我们继续看代码如何实现:
function Animal() {
this.kind = '动物';
}
Animal.prototype = {
eat: function (food) {
console.log(this.name + 'eat ' + food);
}
}
function Cat(name, color) {
this.name = name;
this.color = color;
}
Cat.prototype = new Animal(); //这样会把父类对象上的属性也实例一次,造成不必要的内存开销
Cat.prototype.constructor = Cat; //这一句是为矫正constructor属性,不然会造成继承混乱
var cat1 = new Cat('大黄', '黄色');
var cat2 = new Cat('大花', '花色');
console.log(cat1.constructor); //Cat
console.log(Animal.prototype.constructor == Cat.prototype.constructor); //true
console.log(cat1.eat === cat2.eat); //true
console.log(cat1 instanceof Cat); //true
console.log(cat2 instanceof Cat); //true
console.log(cat1 instanceof Animal); //true
这样看起来似乎很棒,基本没什么毛病,子类即是父类的实例也是子类的实例,并且父类原型上的方法和子类实例的方法公用内存地址,节省开销,也可以复用,但是Cat.prototype = new Animal()这一句;会实例化父亲一次,这样必然就会把父亲的所有属性也实例一次,虽然子类不必要但是也会有父亲的的属性,显然也不是很好,并且还需要手动矫正constructor的指向;
那么我们试试不实例化父类实例直接复制不就可以解决了吗?我们继续看代码:
Cat.prototype = Animal.prototype;
只需要加一句就可以改变不需要实例化父类,看样子很不错,然而细细想,如果直接把父类的prototype赋值给子类,那么子类的prototype有修改那么父类必然也会修改,很显然这不是个好办法。
拷贝继承
由上面直接赋值给子类,我们可以这样改进:
function extend(Child, Parent) {    
var p = Parent.prototype;    
var c = Child.prototype;    
for (var i in p) {      
c[i] = p[i];      
}
c.uber = p;  
}
这样既不需要手动矫正constructor的指向了,但是如果是复杂数据结构,这样的浅拷贝很显然会导致父子的数据会产生共享,子改变数据会影响父,反之也是;继续改进,通过递归实现深拷贝
function deepCopy(p, c) {    
var c = c || {};    
for (var i in p) {      
if (typeof p[i] === 'object') {        
c[i] = (p[i].constructor === Array) ? [] : {};        
deepCopy(p[i], c[i]);      
} else {         
c[i] = p[i];      
}    
}    
return c;  
}
像这样,显然没什么问题,但是循环递归,属性多了,效率也不会太高。我们继续研究。
原型继承升级版
竟然原型继承会实例父亲并且会给子类附上父类的属性,那么我们可不可以用一个空函数来作为中介,先把父类的prototype存起来,在赋给子类;
function extend (Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.superClass = Parent,prototype;
}
项目地址
github: github.com/lspCoder/js…
参考文献
初来掘进,各位大佬多多关照。。。。
我的博客地址:lspcoder.github.io/