面試中的必問痛點-原型和原型鏈以及繼承

1. 背景

前端面試經(jīng)常會問到這個問題,這是一個比較讓人回答起來比較頭疼的問題,為什么頭疼呢,因為javaScript在被創(chuàng)造出來的時候就具備了和現(xiàn)有的強語言有了很大的區(qū)別,因為出發(fā)點就是一個腳本語言,而并非想java等語言,所以js的原型就相對比較復雜。想要搞明白原型和原型鏈首先我們需要搞明白兩個問題:

  1. 搞清楚prototype__proto__是什么
  2. 搞清楚prototype__proto__的關系和指向問題

2. 原型的定義和作用

這個面試要考的

定義:原型就是指函數(shù)的prototype屬性所引用的對象,這個對象就是原型
作用:原型的作用就是為了實現(xiàn)對象之間的數(shù)據(jù)共享
es5獲取對象原型的標準方法是Object.getPrototypeOf()

舉例來說明一下原型的作用

用構造函數(shù)生成實例對象,有一個缺點,那就是無法共享屬性和方法。
    function DOG(name) {
      this.name = name;
      this.species = '犬科';
    }
    var dogA = new DOG('大毛');
  var dogB = new DOG('二毛');
    dogA.species = '貓科';
    console.log(dogA.species) //=>貓科
    console.log(dogB.species) //=>貓科
    console.log(dogA)
    console.log(dogB)

運行結果:
image.png

結論:每一個實例對象,都有自己的屬性和方法的副本。這不僅無法做到數(shù)據(jù)共享,也是極大的資源浪費。

優(yōu)化代碼使用prototype
function DOG(name) {
      this.name = name;
    }
    DOG.prototype = { species : '犬科' };
    var dogA = new DOG('大毛');
  var dogB = new DOG('二毛');
    dogA.species = '貓科';
    console.log(dogA.species) //=>貓科
    console.log(dogB.species) //=>貓科
    console.log(dogA)
    console.log(dogB)

結果:
image.png

1.實例對象需要共享的屬性和方法,都放在這個對象里面;那些不需要共享的屬性和方法,就放在構造函數(shù)里面
2.實例對象一旦創(chuàng)建,將自動引用prototype對象的屬性和方法。也就是說,實例對象的屬性和方法,分成兩種,一種是本地的,另一種是引用的。

3. prototype__proto__的區(qū)別

先搞清楚一個概念

所有的構造函數(shù)都是需要用new來實例化
那么Object,F(xiàn)unction,Array,String,String,Data,Boolean等等都是構造函數(shù)

測試一下

function fun1() {}
    var obg = {}
    console.dir(fun1)
    console.log("=======================")
    console.dir(obg)
    console.log("=======================")
    console.dir(Array)
image.png

什么意思呢?就是為了說明一個問題 (很重要)

函數(shù)擁有prototype__proto__屬性
對象只擁有__proto__屬性

為什么函數(shù)具有prototype屬性,上面已經(jīng)簡單說明了說明,詳情請看Javascript繼承機制的設計思想

說明:__proto__不是一個規(guī)范屬性, 但是這個屬性已在ES6語言規(guī)范中標準化,不推薦使用,但是我們可以利用它來學習原型的原理,建議使用Object.getPrototypeOf/Reflect.getPrototypeOfObject.setPrototypeOf/Reflect.setPrototypeOf,但是現(xiàn)代瀏覽器基本都支持包括 IE11

該屬性沒有寫入 ES6 的正文,而是寫入了附錄,原因是proto前后的雙下劃線,說明它本質上是一個內部屬性,而不是一個正式的對外的 API ,只是由于瀏覽器廣泛支持,才被加入了 ES6

5. 原型鏈

定義:當原型對象找不到需要的屬性時通過內部屬性prototype__proto__向上查找,一次類推就構成了一個鏈,這個構成對象的鏈就叫做原型鏈

Object.prototype是對象的最終原型,絕大多數(shù)對象最終都會繼承自Object.prototype,而Object.prototype的原型是null。
JavaScript的原型鏈邏輯遵從以下通用規(guī)則

通用規(guī)則

1.對象有__proto__屬性,函數(shù)有prototype屬性
2.對象是由函數(shù)生成的
3.生成對象時, 對象的__proto__ 屬性指向的就是函數(shù)的 prototype 屬性

  • 一般情況
    // 創(chuàng)建空對象時,實際上是我們用Object函數(shù)來生成的對象
    var obj = {}
    console.log(obj.__proto__ === Object.prototype) //true
    // 我們也可以顯式的使用Object函數(shù)來創(chuàng)建對象:
    var obj1 = Object()
    console.log(obj1.__proto__ === Object.prototype) //true
    var obj2 = new Object()
    console.log(obj2.__proto__ === Object.prototype) //true
    // 當我們使用函數(shù)來創(chuàng)建自定義的對象時,上面的規(guī)則同樣適用:
    function Fun() { }
    var f1 = new Fun()
    console.log(f1.__proto__ === Fun.prototype) //true
  • 函數(shù)對象
  //函數(shù)作為一種特殊的對象, 如果函數(shù)做為對象使用,上面的規(guī)則依然有效
  //函數(shù)對象是由Function函數(shù)生成的
  function Func(){}
  console.log(Func.__proto__===Function.prototype) //true

  //函數(shù)本身也是由Function本身
  console.log(Function.__proto__===Function.prototype) //true

  Object函數(shù)既然是函數(shù),那生成它的函數(shù)自然是Function函數(shù),上面的規(guī)則依然有效
  console.log(Object.__proto__===Function.prototype) //true
  • 特殊情況一

先來打印下Object.prototype看看

console.log(Object.prototype)  

打印出一堆方法,其實這些方法都是javaScript的默認方法,但是奇怪的是Object.prototype 屬性中沒有__proto__屬性

console.log(Object.prototype.__proto__) // null  

這就是Object函數(shù)特殊情況了:Object.prototype.proto === null,我們知道,這就是JavaScript原型鏈的終點了。如果Object.prototype.proto === Object.prototype,想想原型鏈沒有了終點,太可怕了,所以在原型鏈的最頂端,JavaScript規(guī)定了Object.prototype.proto === null。

  • 特殊情況二

先來打印下Function.prototype看看

console.log(Function.prototype)
// ? () { [native code] }

函數(shù)內部是[native code],也就是系統(tǒng)編譯好的二進制代碼函數(shù),這就暫時沒法深究了。現(xiàn)在讓我們來看看我們最關心的proto屬性:

console.log(Function.prototype.__proto__)

打印的結果竟然和Object.prototype結果一樣,測試下

console.log(Function.prototype.__proto__ === Object.prototype ) //true

竟然真的一樣。又回到了第一種特使情況 我的那個天啊
這就是原型鏈懂了嗎

5. 原型繼承

我們平時一般說的繼承,是原型繼承,不是改變構造函數(shù)的原型

寫個簡單的例子看看

      function User(){}
      User.prototype.name = function(){
        console.log('我是User');
      }
      function Admin(){}
      Admin.prototype = User.prototype
      var admin = new Admin()
      admin.name()
      //我是User

看起來好像沒有問題,我們拿到了User的name方法,但是我在如果重新一下Admin的name方法呢

      function User(){}
      User.prototype.name = function(){
        console.log('我是User');
      }
      function Admin(){}
      Admin.prototype = User.prototype
      Admin.prototype.name = function(){
        console.log('我是Admin');
      }
      var admin = new Admin()
      var user = new User()
      admin.name()  //我是Admin
      user.name()   //我是Admin

問題來的竟然user.name()也打印出來了我是Admin,這是一件非常糟糕的事情,
那要怎么做呢

function User(){}
      User.prototype.name = function(){
        console.log('我是User');
      }
      function Admin(){}
      // 記住一定原型繼承,不是改變構造函數(shù)的原型
      Admin.prototype.__proto__ = User.prototype
      Admin.prototype.name = function(){
        console.log('我是Admin');
      }
      var admin = new Admin()
      var user = new User()
      admin.name()  //我是Admin
      user.name()   //我是User

6. 如何實現(xiàn)原型繼承

學習繼承那就先要有個父類,在js中實際上是沒有類的概念,在es6中class雖然很像類,但實際上只是es5上語法糖而已

    //這是一個基類或者說是父類
    function Admin(name) {
      this.name = name || 'admin'
      this.add = function () {
        console.log('我是:' + this.name + ",我有新增功能");
      }
    }
    Admin.prototype.Edit = function (val) {
      console.log('我是:' + this.name + ",我的功能是編輯" + val);
    }
1.原型鏈繼承

將父類的實例作為子類的原型

    function User(){
      this.name = '李四'
    }
    User.prototype = new Admin()
    User.prototype.constructor = User
    var user = new User()
    user.add()  // 我是:李四,我有新增功能
    console.log(user instanceof Admin) //true

優(yōu)點:簡單易于實現(xiàn),父類的新增的實例與屬性子類都能訪問
缺點:
1.可以在子類中增加實例屬性,如果要新增加原型屬性和方法需要在new 父類構造函數(shù)的后面;
2.無法實現(xiàn)多繼承
3.創(chuàng)建子類實例時,不能向父類構造函數(shù)中傳參數(shù)
無法實現(xiàn)多繼承

2.借用構造函數(shù)繼承
3.組合繼承(組合原型鏈繼承和借用構造函數(shù)繼承)(常用)
4.實例繼承(原型式繼承)
5.寄生式繼承
6.寄生組合式繼承(常用)
7.es6繼承

5. ES5繼承和ES6繼承的區(qū)別

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。