JavaScript繼承的思想實現

JavaScript作爲一門語法比較鬆散的語言,在ES6之前並沒有像C++/Java等傳統OO語言一樣有class關鍵字,也不能通過private,public等關鍵字來限定權限。本篇就介紹一下JavaScript是如何實現繼承的(js的繼承說白了只是一種思想上的繼承,在代碼級別並沒有像java的天然繼承,在編寫js的時候採用oo思想,能更簡單的優化和擴展代碼)。

JavaScript的繼承可以分爲兩類: 


基於對象的繼承 
基於類型的繼承

基於對象的繼承 
基於對象的繼承也叫原型繼承。我們知道通過JavaScript字面量創建的對象都會連接到Object.prototype,因此我們用Object.prototype來實現繼承。本質上是摒棄類,不調用構造函數,而是用Object.create(),直接讓新對象繼承舊對象的屬性。例如:

var person = {
    name: "Jack",
    getName: function () { return this.name; }
}var p1 = Object.create(person);
console.log(p1.getName());    //Jack

代碼很簡單,person有一個屬性和一個方法。對象p1通過Object.create()來繼承,第一個參數prototype指向person的prototype,這樣對象p1就繼承了person的屬性和方法。 
Object.create()還可以指定第二個參數,即數據屬性,將其添加到新對象中。數據屬性可設4個描述符value, writable,enumerable,configurable 。後3個看名字也能猜出意思,不指定的話默認爲false。因爲和本篇關係不大,就不跑題了,只看看設置value的情況:

var p2 = Object.create(person, {
    name: {        value: "Zhang"
    }
});
console.log(p2.getName());    //Zhang

用Object.create()相當於創建了一個全新的對象,你可以給該對象任意新增,重載它的屬性和方法:

var person = {
    name: "Jack",
    getName: function () { return this.name; },
    getAge: function() { return this.age; } //注意並沒有age這個成員變量,依賴子類實現}var p3 = Object.create(person);
p3.name = 'Rose';
p3.age = 17;
p3.location = '上海';
p3.getLocation = function() { return this.location; }

console.log(p3.getName());    
Roseconsole.log(p3.getAge());     //17
console.log(p3.getLocation());    //上海

在person中並沒有age這個屬性,因此你調用person.getAge();將得到undefined。但在對象p3裏新定義了age這個屬性,於是就能正確地調用基類的getAge方法。另外子類重載了name的值,且新定義了location屬性和getLocation方法。結果如上所示,不贅述。

基於類型的繼承 
基於類型的繼承是通過構造函數依賴於原型的繼承,而非依賴於對象。例如:

function Person(name) {
    this.name = name;    this.getName = function () { return this.name; };  
}function Student(name, age) {
    Person.call(this, name);    
    this.age = age;    
    this.getAge = function () { 
        return this.age; 
     }; 
}
Student.prototype = new Person();    //需要通過new來訪問基類的構造函數
var p = new Person('Cathy');
var s = new Student('Bill', 23);
console.log(p.getName());    
Cathyconsole.log(s.getName());    
Billconsole.log(s.getAge());     //23

Student繼承自Person。name雖然是在基類Person裏被定義的,但用new調用Person的構造函數後,this將被綁定到子類Student對象上,因此name最終是定義在子類Student對象上的。結果如上所示,不贅述。 
保護隱私 
之所以定義getName,getAge等方法就是不想讓用戶直接訪問name,age等屬性。可惜上面兩種繼承均無法保護隱私,均可像p.name,p.age這樣直接訪問屬性。如果認爲這些屬性的隱私非常重要,希望模擬出OO語言中private屬性的效果,可以用函數模塊化。 
所謂函數模塊化,本質上就是在函數內新建一個對象,新對象的方法裏使用參數對象的屬性,然後將新對象返回。此時新對象裏是沒有參數對象的屬性的,達到了保護隱私的目的。代碼如下:

var person = function(spec) {
    var that = {};        //新對象
    that.getName = function () { return spec.name; };  //使用參數的屬性
    that.getAge = function() { return spec.age; };  //使用參數的屬性
    return that;        //返回新對象
}
var p4 = person({name: 'Jane', age: 20});

console.log(p4.name);    //undefined
console.log(p4.age);     //undefined
console.log(p4.getName());    //Jane
console.log(p4.getAge());     //20

因爲函數person返回的是新對象that,而that裏並沒有name和age屬性,因此直接訪問會得到undefined。只能通過that暴露出的兩個接口來獲取name和age。 
進一步實現多層繼承也非常方便,效果如下,不贅述:

var student = function(spec) {
    var that = person(spec);        //新對象繼承自person
    that.getRole = function() { return 'student'; };  //新對象增加方法
    that.getInfo = function() {
        return spec.name + ' ' + spec.age + ' ' + that.getRole();
    };    
    return that;    //返回新對象
};
var p5 = student({name:'Andy', age:12});

console.log(p5.name);       //undefined
console.log(p5.getName());  //Andy
console.log(p5.getRole());  //student
console.log(p5.getInfo());  //Andy 12 student

更多資源可以訪問:去轉盤;或者加QQ羣參與js,css的討論學習(QQ羣:512245829)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章