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,但是对于父类原型上的引用类型属性,依然无能为力。