javascript基礎-原型鏈與繼承

我理解的繼承,是爲了解決代碼重複浪費空間與編寫精力的問題,若有兩個對象,面試

// 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個步驟:bash

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

這兩種產生對象的方法,雖然節省了代碼的書寫量,但在內存上仍然消耗相同的空間,每建立一個新的實例對象仍然要建立新的屬性和方法。因此就有了原型。函數

原型

(1)首先,js裏全部的函數都有一個prototype屬性,該屬性是一個對象;同時js裏面全部的對象(除去基本類型number,string,boolean,null和undefined以外的全部)都有一個__proto__屬性,因此一個函數有prototype__proto__兩個屬性,能夠經過console.dir(fn)查看。ui

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

(2) prototype裏有個構造器 constructor,指向的就是該構造函數,全部的對象都是由構造函數實例化獲得的,如今咱們來看一下剛纔講new操做符時的第3個步驟:

  1. 將該對象的__proto__指向構造函數的原型obj.__proto__=Person.prototype 用圖來表示就是:

由於全部的實例對象的 __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方法,可是原型對象裏面有該方法,因此能夠調用。this

原型鏈繼承

有了原型鏈的概念,咱們就能夠實現繼承了,即讓子類構造函數的prototype指向父類的一個實例對象。這樣經過原型鏈的查找就能夠繼承到父類的方法,咱們一般須要繼承的都是方法。spa

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)查看一下:prototype

能夠看到靜態方法和原型方法,實例方法與內部方法看不到。
其實這個問題是我在面試頭條的時候暴露出來的,感謝面試小哥哥爲我講解,當時是有兩個問題,怎麼判斷是數組,怎麼讓不是數組的元素調用splice方法,而後我就回答成了:

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

完美搞錯,真感謝那個面試小哥哥還耐心地給我講解(捂臉羞愧)。
其實打印如下構造函數console,dir(Array)就能夠看到3d

isArray是靜態方法, splice是原型方法,因此正確的應該是:

Array.isArray([1,2,3]);
[].splice.call(obj); // []是Array的一個實例化對象
複製代碼

instanceof操做符

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

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
複製代碼
相關文章
相關標籤/搜索