js本身并不是一个面向对象的语言。只能用原型来实现继承。梳理一下js实现继承的几种的方式
修改原型
function parent (name, age) {
this.name = name;
this.age = age;
}
parent.prototype.hi = function () {
alert('hi! I am' + this.name);
}
function child(name, age) {
this.name = name;
this.age = age;
this.type = 'child';
}
child.prototype = new parent();
var x = new child('nikke', 12);
x.hi();
这样会有一个缺点,child.prototype = new parent();
这一句等同于直接将原型指向一个对象,如果原型对象的某个属性是引用类型,一旦有子类重写这个属性,将会直接重写原型对象,导致其余的子类上所有的这个属性全部发生改变。
function parent (name, age) {
this.name = name;
this.age = age;
this.skill = ['js', 'css'];
}
parent.prototype.hi = function () {
alert('hi! I am' + this.name);
}
function child(name, age) {
this.name = name;
this.age = age;
this.type = 'child';
}
child.prototype = new parent();
var x = new child('nikke', 12);
var y = new child('lily', 13);
console.log(y.skill);
console.log(x.skill);
y.skil.push('ps');
console.log(y.skill);
console.log(x.skill);
console.log(x.prototype === y.prototype)
// ["js", "css"]
// ["js", "css"]
// ["js", "css", "ps"]
// ["js", "css", "ps"]
// true
由于skill来自于原型属性,并且x和y的原型是同一个对象,而skill的值是个引用类型,不会再单独分配一个存储空间,所以修改y的skill直接导致x的也被修改。
构造super方法
将父类的构造函数置于子类中,充当super方法。
function parent (name, age) {
this.name = name;
this.age = age;
this.skill = ['js', 'css'];
}
parent.prototype.hi = function () {
alert('hi! I am' + this.name);
}
function child(name, age) {
parent.apply(this, arguments);
this.type = 'child';
}
var x = new child('nikke', 12);
var y = new child('lily', 13);
console.log(y.skill);
console.log(x.skill);
y.skill.push('ps');
console.log(y.skill);
console.log(x.skill);
console.log(x.hi());
// ["js", "css"]
// ["js", "css"]
// ["js", "css", "ps"]
// ["js", "css"]
这一次没有引用类型的bug了,但是又带来了另外一个问题,hi方法丢失了,父类的原型属性并没有被继承过来
构造函数与原型组合
function parent (name, age) {
this.name = name;
this.age = age;
this.skill = ['js', 'css'];
}
parent.prototype.hi = function () {
alert('hi! I am' + this.name);
}
parent.prototype.superProp = ['dom', 'bom'];
function child(name, age) {
parent.apply(this, arguments);
this.type = 'child';
}
child.prototype = new parent();
var x = new child('nikke', 12);
var y = new child('lily', 13);
console.log(y.skill);
console.log(x.skill);
console.log(x.superProp);
console.log(y.superProp);
y.skill.push('ps');
y.superProp.push('http');
console.log(y.skill);
console.log(x.skill);
console.log(x.superProp);
console.log(y.superProp);
console.log(x.hi());
// ["js", "css"]
// ["js", "css"]
// ["dom", "bom"]
// ["dom", "bom"]
// ["js", "css", "ps"]
// ["js", "css"]
// ["dom", "bom", "http"]
// ["dom", "bom", "http"]
目前为止,之前的问题都解决了,看起来很完美,但是新的问题又出来了,如果某个属性是来自父类原型上的话,一旦某个子类修改它,会导致其余所有的子类的该属性发生变化,看看上面代码中的superProp。而且会两次调用父类构造函数。
原型式继承
function extend (o){
function F (){};
F.prototype = o;
return new F();
}
var parent = function () {
this.name = 'fd';
this.skill = ['ps', 'css'];
}
parent.prototype.hi = function () {
alert('hi ' + this.name)
}
var o = new parent();
var x = extend(o);
var y = extend(o);
console.log(x);
console.log(y);
x.skill.push('js')
console.log(x);
console.log(y);
这中方法本质上与第一种没区别,依然有引用类型共享的问题,他所做的仅仅是一次父类属性的复制。es5中的Object.create()方法所做的事情与上面的extend函数一样
复制原型
function parent(name, age) {
this.name = name;
this.age = age;
this.skill = ['js', 'css'];
}
parent.prototype.hi = function () {
alert(this.name)
}
parent.prototype.superProp = ['dom', 'bom'];
function child(name, age, gender) {
parent.call(this, name, age);
this.gender = gender
}
function inherit(oParent, oChild) {
var oProto = (function (o) {
var F = function () {
};
F.prototype = o;
return new F();
})(oParent.prototype);
oChild.prototype = oProto;
oChild.prototype.constructor = oChild;
}
inherit(parent, child);
var x = new child('kily', 12, 'male');
var y = new child('bob', 23, 'male');
x.superProp.push('http');
x.skill.push('html');
console.log(x.superProp);
console.log(y.superProp);
console.log(x.skill);
console.log(y.skill);
// ["dom", "bom", "http"]
// ["dom", "bom", "http"]
// ["js", "css", "html"]
// ["js", "css"]
这种方法先是用父类的构造函数来创建私有属性,然后再复制父类的原型,相比之前的方法,只调用了一次父类构造函数,对于继承的父类自身的属性,并不会有引用类型的bug,但是对于父类原型上的引用类型属性,依然无能为力。