类式继承

原理

类的原型对象的作用就是为类的原型添加公有属性和公有方法,但类不能直接访问这些属性和方法,必须通过原型prototype来访问。而我们实例化一个父类的时候,新创建的对象复制了父类的构造函数内的属性与方法,并且将原型__proto__指向了父类的原型对象,这样就拥有了父类原型对象上的属性和方法,并且这个新创建的对象可直接访问到父类原型对象上的属性与方法,同样也可以访问从父类构造函数中复制的属性和方法。

var Parent = function() {
    this.member = ['father', 'mother'];
};

var Child = function() {};
Child.prototype = new Parent();

// test:
var child1 = new Child();
var child2 = new Child();

console.log(child1.member);     // ["father", "mother"];
console.log(child2.member);        // ["father", "mother"];

child1.member.push('uncle');

console.log(child1.member);        // ["father", "mother", "uncle"];
console.log(child2.member);        // ["father", "mother", "uncle"];

缺陷

1、由于子类通过其原型prototype对父类实例化,继承了父类,所以说父类中的公有属性要是引用类型,就会在子类中被所有实例共用,因此一个子类的实例更改子类原型从父类构造函数中继承出来的公有属性就会影响到其他子类。

2、由于子类实现的继承是靠其原型prototype对父类的实例化实现,因此在创建父类的时候,是无法向父类传递参数的,因而实例化父类的时候也无法对父类构造函数内的属性进行初始化。

构造函数(窃取)继承

原理

由于call方法可以更改函数的作用域,因此在子类中,对父类调用这个方法就是将子类中的变量在父类中执行一遍,
由于父类中是给this绑定属性的,因此子类自然就继承了父类中的公有属性。

var Parent = function() {
    this.member = ['father', 'mother'];
    this.speak = function() {
        console.log('Chinese!');
    }
};
Parent.prototype = {
    constructor: Parent,
    say: function() {
        console.log('Hi!');
    }
};

var Child = function() {
    Parent.call(this);
};

// test:
var child1 = new Child();
var child2 = new Child();

console.log(child1.member);     // ["father", "mother"];
console.log(child2.member);        // ["father", "mother"];


console.log(child1.speak());    // Chinese!
console.log(child1.say());        // Uncaught TypeError: child1.say is not a function


child1.member.push('uncle');

console.log(child1.member);        // ["father", "mother", "uncle"];
console.log(child2.member);        // ["father", "mother"];

缺陷

由于这种类型的继承没有涉及原型prototype,所以父类的原型方法自然不会被子类继承,而如果要想被子类继承就必须要放在构造函数中,这样创建出来的每个实例都会单独拥有一份而不能共用,这样就违背了代码复用的原则.

组合继承

原理

在子类构造函数中执行父类构造函数,在子类原型上实例化父类,融合了类式继承和构造函数继承两者的优点。并过滤了其缺点。

var Parent = function() {
    this.member = ['father', 'mother'];
    this.speak = function() {
        console.log('Chinese!');
    }
};
Parent.prototype = {
    constructor: Parent,
    say: function() {
        console.log('Hi!');
    }
};

var Child = function() {
    Parent.call(this);
};
Child.prototype = new Parent();

// test:
var child1 = new Child();
var child2 = new Child();

console.log(child1.member);     // ["father", "mother"];
console.log(child2.member);        // ["father", "mother"];


console.log(child1.speak());    // Chinese!
console.log(child1.say());        // Hi!


child1.member.push('uncle');

console.log(child1.member);        // ["father", "mother", "uncle"];
console.log(child2.member);        // ["father", "mother"];

缺陷

在子类构造函数中执行了一遍父类构造函数,在实现子类原型的类式继承时又调用了一遍父类构造函数,因此调用了两遍构造函数。

原型式继承

原理

对类式继承的一个封装

// 声明一个过渡对象继承父对象, 并返回过渡对象的实例
function inheritObject(o) {
    function F() {};
    F.prototype = o;
    return new F();
};

var book = {
    name: 'web',
    type: ['html', 'css']
};

// test:
var html5Book = inheritObject(book);
html5Book.name = 'html5Book';
html5Book.type.push('html5');

var jsBook = inheritObject(book);
jsBook.name = 'jsBook';
jsBook.type.push('js');

console.log(html5Book.name);
console.log(html5Book.type);    // ["html", "css", "html5", "js"];

console.log(jsBook.name);
console.log(jsBook.type);        // ["html", "css", "html5", "js"];

缺陷

与类式继承一样, 父类对象中的值类型被复制, 引用类型的属性被共用.

寄生式继承

原理

对原型继承的第二次封装, 并且在第二次封装过程中对继承的对象进行扩展,
这样新创建的对象不仅仅有父类中的属性和方法, 而且还添加新的属性和方法.
寄生式继承依托于原型继承模式同时也是为了寄生组合式继承模式的实现。

function inheritObject(o) {
    // 声明一个过渡对象继承父对象, 并返回过渡对象的实例
    function F() {};
    F.prototype = o;
    return new F();
}

var book = {
    name: 'web',
    type: ['html', 'css']
};

function createBook(obj) {
    var o = new inheritObject(obj);
    o.getName = function() {
        console.log('webBook');
    };
    return o;
}

var newBook = createBook(book);
console.log(newBook.name);            // web
console.log(newBook.type);            // ['html', 'css']
console.log( newBook.getName() );    // webBook

寄生组合式继承

原理

对子类赋予父类原型的一个引用.即需要父类的原型对象的一个副本, 而这副本可以通过原型继承得到,
但因为这样直接赋值给子类会造成父类原型对象复制得到的复制对象p中的constructor指向不是子类对象,
因此需要对复制对象p做一次增强, 修复其constructor属性指向不正确的问题, 最后将得到的复制对象p赋值给子类的原型, 这样子类的原型就继承了父类的原型并且没有执行父类的构造函数.

function inheritObject(o) {
    // 声明一个过渡对象继承父对象, 并返回过渡对象的实例
    function F() {};
    F.prototype = o;
    return new F();
}

function inheritPrototype(subClass, superClass) {
    // 复制一份父类的原型副本保存在变量中
    var p = inheritObject(superClass.prototype);
    // 修正因为重写子类原型导致子类的constructor属性被修改
    p.constructor = subClass;
    // 设置子类原型
    subClass.prototype = p;
}

var Parent = function(language) {
    this.language = language;
    this.member = ['father', 'mother'];
    this.speak = function() {
        console.log(this.language);
    }
};
Parent.prototype = {
    constructor: Parent,
    say: function() {
        console.log('Hi!');
    }
};

var Child = function(language, name) {
    Parent.call(this, language);
    this.name = name;
};

inheritPrototype(Child, Parent);

// test:
var child1 = new Child('English', 'xiaoming');
var child2 = new Child('japanese', 'xiaoli');

child1.member.push('uncle');

console.log( child1.speak() );        // English
console.log( child1.say() );        // Hi!
console.log( child1.member );        // ["father", "mother", "uncle"]

console.log( child2.speak() );        // English
console.log( child2.say() );        // Hi!
console.log( child2.member );        // ["father", "mother"]

Child.prototype.getName = function() {
    console.log('child~');
};

var child3 = new Child();
console.log( child3.getName() );    // child~
console.log( child3.say() );        // Hi~

Child.prototype = {
    getMember: function() {
        console.log(this.member);
    }
};

var child4 = new Child();
console.log( child4.getMember() );    // ["father", "mother"]
console.log( child4.say() );        // Uncaught TypeError: child3.say is not a function

缺陷

子类再想添加方法必须通过prototype.对象, 通过点语法的形式一个一个添加方法, 否则直接赋予对象就会覆盖从父类原型继承的对象.

单继承

单继承 属性复制

var extend = function(target, source) {
    // 遍历源对象的属性
    for(var property in source) {
        // 将源对象中的属性复制到目标对象中
        target[property] = source[property];
    }
    //返回目标对象
    return target;
};

多继承

多继承 属性复制

var mix = function() {
    var i = 1,                        // 从第二个参数起为被继承的对象
        len = arguments.length,        // 获取参数长度
        target = arguments[0],        // 第一个对象为目标对象
        arg;                        // 缓存参数对象
    for(; i < len; i++) {
        // 缓存当前对象
        arg = arguments[i];
        // 遍历被继承对象中的属性
        for(var property in arg) {
        // 将被继承对象中的属性复制到目标对象中
            target[property] = arg[property];
        }
    }
    // 返回目标对象
    return target;
};

引路人
146 声望12 粉丝