一文搞懂JavaScript原型鏈(看完絕對懂)

Destiny本尊發表於2019-03-25

學習目標

  • 原型
  • 原型鏈
  • 原型指向改變後是如何新增方法和屬性
  • 原型指向改變後的原型鏈
  • 例項物件的屬性和原型物件的屬性重名
  • 通過原型繼承
  • 組合繼承
  • 拷貝繼承

一,原型

問題: 請看以下程式碼,如果我們要建立100個物件,應該怎麼建立?

function Person(name, sex) {
    this.name = name;
    this.sex = sex;
    this.drink () {
        console.log('我想喝手磨咖啡!!')
    }
}
for (let i = 0; i < 100; i++) {
    var per = new Person('蘇大強', '男');
    per.drink();
}
複製程式碼

從上面的程式碼可以看出,如果我們要建立100個Person物件,這樣要開一百個記憶體空間,每次都要呼叫drink()函式,由於drink()函式都是一樣的,每個記憶體空間裡都有它太過於浪費空間,那我們怎樣才能避免這種情況,減少記憶體呢?我們接下來引入原型prototype

function Person(name, sex) {
    this.name = name;
    this.sex = sex;
}
//為原型新增方法
Person.prototype.drink = function () {
        console.log('我想喝手磨咖啡!!')
    }
    //例項化物件
let per = new Person('蘇大強', '男');
per.drink();
複製程式碼

我們運用了原型prototype,可以共享資料,減少記憶體空間。

二,原型鏈

我們既然清楚了原型,那我們再來看看原型鏈。首先我們列印一下建構函式Person和例項物件per。

console.dir(Person);//建構函式
console.dir(per);//例項物件
複製程式碼

一文搞懂JavaScript原型鏈(看完絕對懂)
從圖上看,建構函式中的prototype中的屬行和例項物件per中的__proto__中的屬性一模一樣,那我們想想它們相等嗎?我們可以驗證一下。

console.log(per.__proto__ === Person.prototype);
複製程式碼

一文搞懂JavaScript原型鏈(看完絕對懂)
由此我們可以判斷出,建構函式Person中的prototype原型和例項物件per中的__proto__原型指向是相同的,我們一般是先有建構函式再有例項物件,例項物件由建構函式建立,所以說例項物件中的__proto__原型指向的是建構函式中的原型prototype

例項物件中__proto__是原型,瀏覽器使用的。建構函式中的prototype是原型,程式設計師使用的

那接下來我們看一幅圖來看看原型鏈到底是什麼?

一文搞懂JavaScript原型鏈(看完絕對懂)
我們來分析分析整張圖

  • 首先,建構函式中的prototype屬性指向自己的原型物件
  • 然後,原型物件中的構造器指向的是,原型物件所在的建構函式
  • 再然後,例項物件中的__proto__指向的是,它所在建構函式中prototype屬性所指向的原型物件

所以從上圖我們可以得到以下幾點:

  • 例項物件的原型指向了建構函式中prototype屬性所指向的原型物件,所以例項物件和原型物件之間有關係,它和建構函式是一個間接的關係。
  • 我們從程式碼中也可以得出,例項物件可以直接訪問原型物件中的屬性或方法。
  • 例項物件和原型物件之間有關係,它們的關係是通過原型__proto__來連線的。

最終我們可以得出,原型鏈:它是一種關係,例項物件和原型物件之間的關係,關係是通過原型__proto__來聯絡的

三,原型指向改變後是如何新增方法和屬性

原型改變新增方法也無非就是兩種:1.在原型改變前新增加方法。2.在原型改變以後新增方法。

首先,我們來看第一種:

function Person(name, sex) {
    this.name = name;
    this.sex = sex;
}
Person.prototype.drink = function () {
        console.log('我想喝水!!')
    }
function Student(name, sex) {
    this.name = name;
    this.sex = sex;
}
Student.prototype.eat = function () {
    console.log('我想吃東西!!')
}

//改變原型指向
Student.prototype = new Person('人', '男');
let stu = new Student('學生', '女');
stu.drink();
stu.eat();
複製程式碼

我們來執行以下:

一文搞懂JavaScript原型鏈(看完絕對懂)
我們可以看到圖中的資訊,stu.eat()不是一個函式,剛才我們明明將eat()新增到了Student的原型上,怎麼現在報錯了? 原因是:由於Student的原型指向改變了,它指向了new Person('人', '男'),並且Person的原型上並沒有eat(),所以報錯,那麼第一種情況在原型改變之前新增是錯誤的!

我們再來看第二種情況:在原型改變之後新增方法。

function Person(name, sex) {
    this.name = name;
    this.sex = sex;
}
Person.prototype.drink = function () {
        console.log('我想喝水!!')
    }
function Student(name, sex) {
    this.name = name;
    this.sex = sex;
}

//改變原型指向
Student.prototype = new Person('人', '男');
//為原型新增方法
Student.prototype.eat = function () {
    console.log('我想吃東西!!')
}
let stu = new Student('學生', '女');
stu.drink();
stu.eat();
複製程式碼

我們來執行以下:

一文搞懂JavaScript原型鏈(看完絕對懂)
可以看出來,兩個方法都被成功的調運了,所以說:如果原型指向改變了,那麼就應該在原型改變指向之後新增原型方法。

四,原型指向改變後的原型鏈

那麼,當原型指向改變之後,原型鏈會發生怎樣的改變呢? 那我們來們分析以下:

  • 原型指向改變之前
  • 原型指向改變之後

我們先來分析原型指向改變之前:

//人的建構函式
function Person(name) {
    this.name = name;
}
//為原型新增方法
Person.prototype.drink = function () {
        console.log('我想喝水!!')
    }
//學生的建構函式
function Student(name) {
    this.name = name;
}
//為原型新增方法
Student.prototype.eat = function () {
    console.log('我想吃東西!!')
}
//例項物件
let per = new Person('老師');
let stu = new Student('學生');

console.dir(Person);//建構函式
console.dir(per);//例項物件
console.dir(Student);//建構函式
console.dir(stu);//例項物件
複製程式碼

我們執行以下這段程式碼:

一文搞懂JavaScript原型鏈(看完絕對懂)
請看每個prototype和__proto__,我們可以得到它們的原型鏈圖:

一文搞懂JavaScript原型鏈(看完絕對懂)
由此圖,我們可以看出,原型沒有改變之前,例項物件的__proto__都指向自己建構函式中prototype屬性所指向的原型物件。

我們再來看看原型指向改變之後:

//人的建構函式
function Person(name) {
    this.name = name;
}
//為原型新增方法
Person.prototype.drink = function () {
        console.log('我想喝水!!')
    }
//學生的建構函式
function Student(name) {
    this.name = name;
}
//為原型新增方法
Student.prototype.eat = function () {
    console.log('我想吃東西!!')
}
//改變學生的原型指向
Student.prototype = new Person('老師');
//例項物件
let stu = new Student('學生');

console.dir(Person);//建構函式
console.dir(new Person('老師'))//例項物件
console.dir(Student.prototype)//Student的原型物件
console.dir(Student);//建構函式
console.dir(stu);//例項物件
複製程式碼

我們來看看執行結果:

一文搞懂JavaScript原型鏈(看完絕對懂)
我們來分析分析:

一文搞懂JavaScript原型鏈(看完絕對懂)
我們程式碼和圖結合來看,當Student.prototype = new Person('老師');之後,①學生建構函式的prototype屬性會斷開指向原型物件,②原型物件中的構造器也會斷開指向建構函式,③例項物件的__proto__會斷開指向原型物件 這裡的序號沒有任何意義,相當於起的名字!!! 還沒有完,我們再來看圖:

一文搞懂JavaScript原型鏈(看完絕對懂)
當原型指向改變之後,學生的建構函式中的prototype屬性指向了new Person('老師');,隨後,學生的例項化物件中的__proto__屬性指向了學生建構函式中prototype屬性所指向的new Person('老師');

原型鏈改變完畢!

五,例項物件的屬性和原型物件的屬性重名

當例項物件中的屬性和原型物件中的屬性重名時應該先訪問那個? 我們來看一看程式碼:

//人的建構函式
function Person(age, sex) {
      this.age = age;
      this.sex = sex;
    }
//為原型新增屬性
Person.prototype.sex = "女";
//例項化物件
var per = new Person(10,"男");

console.log(per.sex);
複製程式碼

看圖:

一文搞懂JavaScript原型鏈(看完絕對懂)
從程式碼的執行結果來看,當例項物件中的屬性和原型中的屬性重名時,它會先訪問例項物件中的屬性。

如果在例項物件中找不到呢?我們來看程式碼:

function Person(age) {
      this.age = age;
    }
//為原型新增屬性
Person.prototype.sex = "女";
//例項化物件
var per = new Person(10);

console.log(per.sex);
複製程式碼

我們來看執行結果:

一文搞懂JavaScript原型鏈(看完絕對懂)
從這段程式碼,我們可以看出,當例項物件中訪問不到屬性時,它會向上查詢原型物件上的屬性

六,通過原型繼承

    //js中通過原型來實現繼承
    
    //人的建構函式
    function Person(name, age, sex) {
      this.name = name;
      this.sex = sex;
      this.age = age;
    }
    //為原型新增方法
    Person.prototype.eat = function () {
      console.log("人吃東西");
    };
    Person.prototype.sleep = function () {
      console.log("人在睡覺");
    };
    Person.prototype.play = function () {
      console.log("生活就是編程式碼!");
    };

    
    //學生的建構函式
    function Student(score) {
      this.score = score;
    }
    //改變學生的原型的指向即可==========>學生和人已經發生關係
    Student.prototype = new Person("小明", 10, "男");
    //為原型新增方法
    Student.prototype.study = function () {
      console.log("學習很累很累的哦.");
    };


    var stu = new Student(100);
    console.log(stu.name);
    console.log(stu.age);
    console.log(stu.sex);
    stu.eat();
    stu.play();
    stu.sleep();
    console.log("下面的是學生物件中自己有的");
    console.log(stu.score);
    stu.study();
複製程式碼

看執行結果:

一文搞懂JavaScript原型鏈(看完絕對懂)
原型繼承的精髓就是:使用原型,改變原型的指向進行繼承。

七,組合繼承


    //組合繼承:原型繼承+借用建構函式繼承
    
    //人的建構函式
    function Person(name, age, sex) {
      this.name = name;
      this.age = age;
      this.sex = sex;
    }
    Person.prototype.sayHi=function () {
      console.log("你好嗎?");
    };
    function Student(name, age, sex, score) {
      //借用建構函式:屬性值重複的問題
      Person.call(this, name, age, sex);
      this.score = score;
    }
    //改變原型指向----繼承
    Student.prototype = new Person();//不傳值
    Student.prototype.eat = function () {
      console.log("吃東西");
    };
    //例項物件
    var stu = new Student("金仔", 20, "男", "100分");
    console.log(stu.name, stu.age, stu.sex, stu.score);
    stu.sayHi();
    stu.eat();
    
    var stu2=new Student("含仔", 20, "女", "100分");
    console.log(stu2.name, stu2.age, stu2.sex, stu2.score);
    stu2.sayHi();
    stu2.eat();
複製程式碼

看執行結果:

一文搞懂JavaScript原型鏈(看完絕對懂)
組合繼承的精髓:首先在Student建構函式中使用call()函式將屬性傳入Person建構函式中,並改變this,然後改變Student的原型指向

八,拷貝繼承

    function Person() {};
    Person.prototype.age = 10;
    Person.prototype.sex = "男";
    Person.prototype.height = 100;
    Person.prototype.play = function () {
      console.log("玩耍!");
    };
    var Student = {};
    //Person的構造中有原型prototype,prototype就是一個物件,那麼裡面,age,sex,height,play都是該物件中的屬性或者方法

    for (let key in Person.prototype) {
      Student[key] = Person.prototype[key];
    }
    console.dir(Student);
    Student.play();
複製程式碼

請看執行結果:

一文搞懂JavaScript原型鏈(看完絕對懂)
至此,本片文章的全部內容完畢!本人是一個前端新人,本片文章若哪裡有不正確的地方,請各位前端大佬不吝斧正!我們們攜手共同進步!再次感謝!

另外,本人將要參加實習找工作,若是那位大佬覺得小學弟可以的話,請給小學弟一個機會。郵箱:1441398660@qq.com

相關文章