你還沒學會javascript原型和原型鏈嗎?

屏幕快照 2019-11-14 01.18.10.png

前言

在前端這塊領域,原型與原型鏈是每個前端er必須掌握的概念。咱們屢次在面試或者一些技術博客裏面看見這個概念。因而可知,這個玩意對於前端來講有多重要。其實它自己理解起來不難,可是不少剛入行前端的同窗,看到prototype__proto__理解起來仍是有點吃力,而後腦子裏面就亂成一鍋粥,就像我同樣。可是這是很正常的事情,沒什麼大不了的,就像咱們想要學會跑步,那麼咱們就必須先學會走路。任何事情都是有個過程的。因此如今就跟我一塊兒來攻克這個難點吧。經過這篇文章你將掌握如下知識點:javascript

  • 理解 __proto_;
  • 理解 prototype;
  • 理解javascript對象的概念;
  • 理解原型和原型鏈;
  • 理解javascript的概念;
  • 理解new的實現;
  • 理解instanceof的實現;
  • 理解javascript的繼承;
  • 加深對javascript這門語言的理解。

這也是本篇文章的寫做思路。前端

對象

那麼咱們就從對象這一律念開始提及,其實對象這一律念相信你們並不陌生。有一種說法是「javasrcript中萬物皆是對象」,其實這個說法是錯誤的,一個很簡單的例子,javasript中簡單基本類型(string、boolean、number、null、undefined、symbol)自己就不是對象。其實javasript中對象主要分爲函數對象普通對象。其中:java

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

這些都是函數對象,他們同時也被稱爲內置對象函數對象自己其實就是一個純函數,javascript用他們來模擬。普通對象就很簡單了,就是咱們常見的對象:面試

const obj = {
    name: 'juefei',
    desc: 'cool'
}
複製代碼

可能說到這,你仍是沒法理解到底啥是函數對象,啥是普通對象,那咱們就一塊兒來看看下面的代碼:函數

const obj1 = {};
const obj2 = new Object();
function func1() {

}
const obj3 = new func1();
const func2 = new function() {

}
const func3 = new Function()
複製代碼

接着咱們來分別打印一下他們:學習

console.log(obj1);  // object
console.log(obj2);  // object
console.log(obj3);  // object
console.log(func1);  // function
console.log(func2);  // function
console.log(func3);  // function
複製代碼

因此能夠看見,obj1obj2、,obj3是普通對象,他們都是Object的實例,而func1func2func3則都是Function的實例,稱爲函數對象。咱們再看看:ui

console.log(typeof Object);  //f unction
console.log(typeof Function); // function
複製代碼

你是否是驚呆了,原來ObjectFunction都是 Function的實例。 因此咱們得出一個結論就是:this

  • 只要是Function的實例,那就是函數對象,其他則爲普通對象

一樣咱們也能夠看出,不只 Object函數對象,就連 Function 自己也是函數對象,由於咱們經過 console.log(typeof Function); 得知 FunctionFunction 的實例。是否是又開始有點繞了?沒事,到這一步你就記住咱們剛剛的結論就算完成目標:spa

  • 只要是Function的實例,那就是函數對象,其他則爲普通對象

那麼說到對象,咱們從上面能夠看出,一個對象是經過構造函數 new 出來的,這其實跟原型原型鏈有很大的關係,那麼原型原型鏈究竟是用來幹嗎的呢?prototype

原型

涉及到這兩個概念,咱們就必須先來介紹兩個東西: __proto__prototype ,這兩個變量能夠說,在 javascript 這門語言裏面隨處可見,咱們無論他三七二十一,咱們先來看一張表:

對象類型 __proto__ prototype
普通對象
函數對象

因此,請你先記住如下結論:

  • 只有函數對象prototype 屬性,普通對象 沒有這個屬性。

  • 函數對象普通對象 都有 __proto__這個屬性。

  • prototype__proto__都是在建立一個函數或者對象會自動生成的屬性。

接着咱們來驗證一下:

function func (){  //func稱爲構造函數

}
console.log( typeof func.prototype); // object
console.log(typeof func.__proto__);  // function
複製代碼
const obj = {}
console.log(typeof obj.__proto__) //object
console.log(typeof obj.prototype) //undefined (看見了吧,普通對象真的沒有 prototype 屬性)
複製代碼

因此就驗證了咱們剛剛的結論:

  • 只有函數對象prototype 屬性,普通對象 沒有這個屬性
  • 函數對象普通對象 都有 __proto__這個屬性。
  • prototype__proto__都是在建立一個函數或者對象會自動生成的屬性。

你看我又重複寫了一遍,我不是爲了湊字數,是爲了你加深記憶,這對於咱們接下來的篇幅很重要。 接着咱們來看看下面的代碼:

console.log(obj.__proto__ === Object.prototype); // true
console.log(func.__proto__ === Function.prototype); // true
複製代碼

因此咱們又得出以下結論:

  • 實例的 __proto__屬性主動指向構造的 prototype;
  • prototype 屬性被 __proto__ 屬性 所指向。

這就是prototype 屬性和 __proto__ 屬性的區別與聯繫。 這可能又有點繞了,來多看幾遍這一節,多背一下咱們的結論。咱們繼續。
那麼問題來了,既然func是一個函數對象,函數對象是有 prototype 屬性的,那麼func.prototype.__proto__等於啥呢?
爲了解決這個問題,咱們來思考一下:
首先,咱們看看func.prototype 是啥:

console.log(typeof func.prototype); //object
複製代碼

好,咱們知道了,func.prototype 是一個對象,那既然是對象,那 func.prototype 那不就是 Object的實例嗎?那也就是說,func.prototype.__proto__屬性確定是指向 Object.prototype 咯! 好,咱們來驗證一下:

console.log(func.prototype.__proto__ === Object.prototype); //true
複製代碼

看見沒有,就是這樣的。那看到這裏,咱們應該也知道當咱們這建立一個構造函數的時候,javascript是如何幫咱們自動生成__proto__prototype屬性的。哈哈沒錯就是這樣:

//咱們手動建立func函數
function func() {}
//javascript悄悄咪咪執行如下代碼:
func._proto = Function.prototype; //實例的 __proto__ 屬性主動指向構造的 prototype
func.prototype = {
    constructor: func,
    __proto: Object.prototype //咱們剛剛纔在上面驗證的,你別又忘記了
}
複製代碼

我還專門爲你畫了個圖(夠貼心不老鐵):

原型.png

因此prototype又被稱爲顯式原型對象,而__proto__又被稱爲隱式原型對象。

hi,看到這裏,你是否是有種腦子開了光的感受。哈哈,因此到如今你應該已經理解原型的概念了,若是你還不理解,那就把上述章節再看一遍。最好拿個紙筆出來跟着畫一畫,順便拿出電腦把示例代碼敲一敲。好,整理一下頭腦,接下來咱們來看看什麼又是原型鏈

原型鏈

再介紹這個概念以前,咱們先來看以下代碼:

function Person = function(name,desc){
    this.name = name;
    this.desc = desc;
} //***1****//
Person.prototype.getName = function(){
    return this.name;
}//***2****//
Person.prototype.getDesc = function(){
    return this.desc;
}//***3****//

const obj = new Person('juefei','cool');//***4****//
console.log(obj);//***5****//
console.log(obj.getName);//***6****//
複製代碼

接下來咱們來逐步解析一下:

  1. 建立了一個構造函數 Person,此時,Person.portotype自動建立,其中包含了 constructor__proto__兩個屬性;
  2. 給對象 Person.prototype 新增了一個方法 getName;
  3. 給對象 Person.prototype 新增了一個方法 getDesc;
  4. 根據構造函數 Person 新建一個實例: obj(在建立實例的時候,構造函數會自動執行);
  5. 打印實例 obj :
{
    name: 'juefei',
    desc: 'cool'
}
複製代碼

根據上面一節的結論,咱們得出:

obj.__proto__ = Person.prototype;
複製代碼
  1. 執行到第6步時,因爲在實例 obj 上面找不到 getName()這個方法,因此它就會自動去經過自身的 __proto__ 繼續向上查找,結果找到了 Person.prototype ,接着它發現,恰好 Person.prototype 上面有getName()方法,因而找到了這個方法,它就中止了尋找。 怎麼樣,是否是有一種環環相扣的感受?他們造成一個鏈了,沒錯,這就是原型鏈

咱們得出以下結論:
在訪問一個對象(假設這個對象叫obj)的屬性/方法時,若在當前的對象上面找不到,則會嘗試經過obj.__proto__去尋找,而 obj.__proto__ 又指向其構造函數(假設叫objCreated)的 prototype,因此它又自動去 objCreated.prototype 的屬性/方法上面去找,結果仍是沒找到,那麼就訪問 objCreated.prototype.__proto__繼續往上面尋找,直到找到,則中止對原型鏈對尋找,若最終仍是沒能找到,則返回 undefined 。 一直沿着原型鏈尋找下去,直到找到 Object.prototype.__proto__,指向 null,因而返回 undefined了。

是否是天然而然就理解了。我又給你畫了個圖(請對照着上面👆那個圖看):

原型鏈.png

接下來咱們再來增長一些概念:

  1. 任何內置函數對象自己的 __proto__屬性都指向 Function的原型對象,即: Function.prototype;
  2. 除了 Object.prototype.__proto__指向 null ,全部的內置函數對象的原型對象的 __proto__屬性 ( 內置函數對象.prototype.__proto__),都指向Object

咱們得出以下終極原型鏈的圖:

原型鏈終極圖.png

針對這個圖,我最終給出咱們常常看見那個原型鏈的圖:

prototype.jpg

好好對比一下,拿出紙和筆畫一畫,根據上面章節的講解,相信你很容易就能明白。

javascript中的

剛剛咱們終於明白什麼是 原型原型鏈。下面咱們根據上面的概念來說解一下javascript中的。 咱們知道,在面向對象的語言中,類能夠被實例化屢次,這個實例化是指咱們能夠根據構造函數去獨立複製多個獨立的實例,這些實例之間是獨立的。可是實際上在 javascript 卻不是這樣的,由於它不是這種複製機制。咱們不能建立一個類的多個實例,咱們只能建立這個類的多個對象,由於他們都是經過原型原型鏈關聯到同一個對象。因此在 javascript 中 ,都是經過原型原型鏈來實現的,它實際上是一種委託方式

new的實現

瞭解了上面javascript中的的概念,那咱們應該很容易就理解new的過程,其核心無非就是執行原型鏈的連接:

function myNew(Cons,...args){
    let obj = {};
    obj.__proto__ = Cons.prototype; //執行原型連接
    let res = Cons.call(obj,args);
    return typeof res === 'object' ? res : obj;
}
複製代碼

instanceof的實現

那麼學習了原型原型鏈instanceof的實現確定也很簡單了,它也是經過原型原型鏈來實現的:

function myInstanceof(left,right){
      let rightProto = right.prototype;
      let leftValue = left.__proto__;
      while(true){
          if(leftValue === null){
              return false;
          }
          if(leftValue === rightProto){
              return true;
          }
          leftValue = leftValue.__proto__;
      }
  }
複製代碼

我就不講解過程了,由於我知道你確定能看懂,哈哈。

javascript的繼承

咱們都知道繼承也是經過原型原型鏈來實現的,那我在這裏介紹兩種常見的繼承方式:

  1. 組合繼承:
//組合式繼承
//經過call繼承Parent的屬性,並傳入參數
//將Child的原型對象指向Parent的實例,從而繼承Parent的函數
 function Parent(value){
     this.val = value;
 }
 Parent.prototype.getValue = function(){
     console.log(this.val);
 }
 function Child(value){
     Parent.call(this,value);//繼承Parentd的屬性
 }
 Child.prototype = new Parent();
複製代碼
  1. 寄生組合式繼承:
//寄生組合式繼承
//經過call繼承Parent的屬性,並傳入參數
//經過Object.create()繼承Parent的函數
  function Parent(value){
      this.val = value;
  }
  Parent.prototype.getValue = function(){
      console.log(this.val);
  }
  function Child(value){
      //繼承Parentd的屬性
      Parent.call(this,value);
  }
  Child.prototype = Object.create(Parent.prototype,{
      constructor:{
          value:Child,
          writable:true,
          configurable:true,
          enumerable:false
      }
  })
複製代碼

總結

  1. 若 A 經過 new 建立了 B,則 B.__proto__ = A.prototype
  2. 執行B.a,若在B中找不到a,則會在B.__proto__中,也就是A.prototype中查找,若A.prototype中仍然沒有,則會繼續向上查找,最終,必定會找到Object.prototype,假若還找不到,由於Object.prototype.__proto__指向null,所以會返回undefined
  3. 原型鏈的頂端,必定有 Object.prototype.__proto__ ——> null。

因而可知,原型原型鏈是如此的強大,但願看完這篇文章,你再也不會對他們感到恐懼。 寫完這篇已經近凌晨兩點,若是你以爲這篇文章對你有些許收穫,請點贊支持!!

參考資料: << 你不知道的javascript 上卷 >>

相關文章
相關標籤/搜索