一塊兒學習面向對象——繼承

類式繼承

原理

類的原型對象的做用就是爲類的原型添加公有屬性和公有方法,但類不能直接訪問這些屬性和方法,必須經過原型prototype來訪問。而咱們實例化一個父類的時候,新建立的對象複製了父類的構造函數內的屬性與方法,而且將原型__proto__指向了父類的原型對象,這樣就擁有了父類原型對象上的屬性和方法,而且這個新建立的對象可直接訪問到父類原型對象上的屬性與方法,一樣也能夠訪問從父類構造函數中複製的屬性和方法。css

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"];

缺陷

一、因爲子類經過其原型prototype對父類實例化,繼承了父類,因此說父類中的公有屬性要是引用類型,就會在子類中被全部實例共用,所以一個子類的實例更改子類原型從父類構造函數中繼承出來的公有屬性就會影響到其餘子類。html

二、因爲子類實現的繼承是靠其原型prototype對父類的實例化實現,所以在建立父類的時候,是沒法向父類傳遞參數的,於是實例化父類的時候也沒法對父類構造函數內的屬性進行初始化。html5

構造函數(竊取)繼承

原理

因爲call方法能夠更改函數的做用域,所以在子類中,對父類調用這個方法就是將子類中的變量在父類中執行一遍,
因爲父類中是給this綁定屬性的,所以子類天然就繼承了父類中的公有屬性。web

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"];

缺陷

在子類構造函數中執行了一遍父類構造函數,在實現子類原型的類式繼承時又調用了一遍父類構造函數,所以調用了兩遍構造函數。this

原型式繼承

原理

對類式繼承的一個封裝prototype

// 聲明一個過渡對象繼承父對象, 並返回過渡對象的實例
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"];

缺陷

與類式繼承同樣, 父類對象中的值類型被複制, 引用類型的屬性被共用.code

寄生式繼承

原理

對原型繼承的第二次封裝, 而且在第二次封裝過程當中對繼承的對象進行擴展,
這樣新建立的對象不單單有父類中的屬性和方法, 並且還添加新的屬性和方法.
寄生式繼承依託於原型繼承模式同時也是爲了寄生組合式繼承模式的實現。htm

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;
};
相關文章
相關標籤/搜索