javascript基礎-原型鏈與繼承

陳小瓦發表於2019-05-05

我理解的繼承,是為了解決程式碼重複浪費空間與編寫精力的問題,如有兩個物件,

// person1
var person1={
    name:'tom',
    say(){
        console.log(this.name);
    }
}

// person2
var person2={
    name:'jerry',
    say(){
        console.log(this.name);
    }
}
複製程式碼

這兩個物件都有相同的name屬性和say()方法,只是name屬性值不同,造成了程式碼的重複浪費,因此提出了節省程式碼的方式:

工程模式

function person(name){
    var obj=new Object();
    obj.name=name;
    obj.say=function(){
        console.log(obj.name);
    };
    return obj;
}

var person1=person("tom");
var person2=person("jerry");
複製程式碼

建構函式

function Person(name){
    this.name=name;
    this.say=function(){
        console.log(this.name);
    };
}
var person1=new Person("tom");
var person2=new Person("jerry");
複製程式碼

其實兩種方式大同小異,因為在使用new操作符來建立一個例項物件時,產生了以下4個步驟:

  1. 新建一個物件:var obj=new Object();
  2. 將建構函式中的this指向該物件,對該物件進行賦值obj.name='name
  3. 將該物件的__proto__指向建構函式的原型obj.__proto__=Person.prototype
  4. 返回該物件
    其中1、2、4步就是工程模式中的步驟,只是多了第3步。

這兩種產生物件的方法,雖然節省了程式碼的書寫量,但在記憶體上仍然消耗相同的空間,每建立一個新的例項物件仍然要建立新的屬性和方法。所以就有了原型。

原型

(1)首先,js裡所有的函式都有一個prototype屬性,該屬性是一個物件;同時js裡面所有的物件(除去基本型別number,string,boolean,null和undefined之外的所有)都有一個__proto__屬性,所以一個函式有prototype__proto__兩個屬性,可以通過console.dir(fn)檢視。

function Person(name){
    this.name=name;
}
console.dir(Person);
複製程式碼

javascript基礎-原型鏈與繼承
(2)prototype裡有個構造器constructor,指向的就是該建構函式,所有的物件都是由建構函式例項化得到的,現在我們來看一下剛才講new操作符時的第3個步驟:

  1. 將該物件的__proto__指向建構函式的原型obj.__proto__=Person.prototype 用圖來表示就是:

javascript基礎-原型鏈與繼承
因為所有的例項物件的__proto__屬性都指向建構函式的prototype物件,所以可以把共享的方法寫在prototype裡,這樣只需要建立一個方法就可以了。

function Person(name){
    this.name=name;
}
Person.prototype.say=function(){
    console.log(this.name);
}

var p1=new Person("tom");
p1.say();   // tom
var p1=new Person("jerry");
p1.say();   // jerry
複製程式碼

原型鏈

有了原型的概念,先給出原型鏈的概念:例項物件在使用屬性或者呼叫方法時,如果自己沒有,則會往上一級級查詢prototype物件,直到找到為止,如果最終也找不到則報錯,就拿上面講的,p1自己沒有say方法,但是原型物件裡面有該方法,所以可以呼叫。

javascript基礎-原型鏈與繼承

原型鏈繼承

有了原型鏈的概念,我們就可以實現繼承了,即讓子類建構函式的prototype指向父類的一個例項物件。這樣通過原型鏈的查詢就可以繼承到父類的方法,我們通常需要繼承的都是方法。

javascript基礎-原型鏈與繼承

function Person(name){
    this.name=name;
}
Person.prototype.say=function(){
    console.log(this.name);
}
// 子類建構函式
function Student(name){
    this.name=name;
}
// 將子類新增到原型鏈中
Student.prototype=new Person("tom");
// 子類自己的原型方法必須在改變原型指向後新增
Student.prototype.play=function(){
    console.log(this.name+" play");
}

var s1=new Student("jerry");
s1.say(); // 原型鏈上的方法 jerry
s1.play();  // 自己原型上的方法 jerry play 
// this一直指向都是s1,跟例項物件tom沒有關係
複製程式碼

例項方法、靜態方法、原型方法和內部方法

function Fn(){
    // 例項方法,只能通過例項物件.的形式呼叫
    this.work=function(){
        console.log("work");
    }
    // 內部方法 只能內部呼叫
    function learn(){}
};
// 靜態方法,只能通過函式名.的形式呼叫
Fn.say=function(){
    console.log("say")
}
// 原型方法,只能例項.的形式呼叫
Fn.prototype.play=function(){
    console.log("play")
}
Fn.say();  // say
Fn.play(); // 報錯
Fn.work(); // 報錯
var f1=new Fn();
f1.say(); // 報錯
f1.play(); // play
f1.work(); // work
複製程式碼

我們可以使用console.dir(Person)檢視一下:

javascript基礎-原型鏈與繼承
可以看到靜態方法和原型方法,例項方法與內部方法看不到。
其實這個問題是我在面試頭條的時候暴露出來的,感謝面試小哥哥為我講解,當時是有兩個問題,怎麼判斷是陣列,怎麼讓不是陣列的元素呼叫splice方法,然後我就回答成了:

[1,2,3].isArray()
Array.splice.call(obj)
複製程式碼

完美搞錯,真感謝那個面試小哥哥還耐心地給我講解(捂臉羞愧)。
其實列印以下建構函式console,dir(Array)就可以看到

javascript基礎-原型鏈與繼承
isArray是靜態方法,splice是原型方法,所以正確的應該是:

Array.isArray([1,2,3]);
[].splice.call(obj); // []是Array的一個例項化物件
複製程式碼

instanceof操作符

l instanceof R 就是判斷l的原型鏈上是否有R.prototype

s1 instanceof Student // true
s1 instanceof Person  // true
複製程式碼

缺點

父類原型上的引用屬性會被子類們共享,一個子類更改了,其餘的也會被更改;
子類例項無法向父類建構函式傳參

建構函式繼承

建構函式可以解決向父類建構函式傳參的問題,但沒有辦法繼承父類原型上的方法。

function Person(name){
    this.name=name;
}
Person.prototype.say=function(){
    console.log("say");
}

function Student(name,age){
    Person.call(this,name);
    this.age=age;
}

var s1=new Student("xixi",12);
s1.name; // xixi
s1.age; // 12
s1.say(); // 報錯
複製程式碼

組合繼承

即使用建構函式來繼承屬性,使用原型來繼承原型方法

function Person(name){
    this.name=name;
}
Person.prototype.say=function(){
    console.log("say");
}

function Student(name,age){
    // 繼承屬性
    Person.call(this,name);
    this.age=age;
}
// 繼承方法
Student.prototype=new Person();
var s1=new Student("xixi",12);
s1.name; // xixi
s1.age; // 12
s1.say(); // say
複製程式碼

相關文章