面不面試的,你都得懂原型和原型鏈

新蜂商城開源倉庫(內涵 Vue 2.x 和 Vue 3.x 的 H5 商城開源代碼,帶服務端 API 接口):github.com/newbee-ltdjavascript

Vue 3.x + Vant 3.x + Vue-Router 4.x 高仿微信記帳本開源地址(帶服務端 API 接口):github.com/Nick930826/…java

前言

不要爲了面試而去背題,匆匆忙忙的,不只學不進去,背完了幾天後立刻會忘記。git

image.png

你可能會說,「沒辦法,這不是爲了能找份工做嘛!」。我想說的是,「那你沒開始找工做的時候,咋很差好學習呢。」github

好了,上述扯的這些,意思就是讓你們不要作收藏家,不要把好文收藏了,就放在收藏夾裏吃灰!面試

下面爲你們簡單闡述我對原型和原型鏈的理解,如果以爲有說的不對的地方,還望直接把頁面關閉了,別在我這篇文章上繼續浪費時間。(逃)微信

image.png

四個規則

咱們先來了解下面引用類型的四個規則:markdown

一、引用類型,都具備對象特性,便可自由擴展屬性。函數

二、引用類型,都有一個隱式原型 __proto__ 屬性,屬性值是一個普通的對象。oop

三、引用類型,隱式原型 __proto__ 的屬性值指向它的構造函數的顯式原型 prototype 屬性值。學習

四、當你試圖獲得一個對象的某個屬性時,若是這個對象自己沒有這個屬性,那麼它會去它的隱式原型 __proto__(也就是它的構造函數的顯式原型 prototype)中尋找。

引用類型:Object、Array、Function、Date、RegExp。這裏我姑且稱 proto 爲隱式原型,沒有官方中文叫法,你們都瞎叫居多。

下面咱們逐一驗證上面幾個規則,就會慢慢地理解原型和原型鏈。

規則一

引用類型,都具備對象特性,便可自由擴展屬性:

const obj = {}
const arr = []
const fn = function () {}

obj.a = 1
arr.a = 1
fn.a = 1

console.log(obj.a) // 1
console.log(arr.a) // 1
console.log(fn.a) // 1
複製代碼

這個規則應該比較好理解,Date 和 RegExp 也同樣,就不贅述了。

規則二

引用類型,都有一個隱式原型 __proto__ 屬性,屬性值是一個普通的對象:

const obj = {};
const arr = [];
const fn = function() {}

console.log('obj.__proto__', obj.__proto__);
console.log('arr.__proto__', arr.__proto__);
console.log('fn.__proto__', fn.__proto__);
複製代碼

image.png

規則三

引用類型,隱式原型 __proto__ 的屬性值指向它的構造函數的顯式原型 prototype 屬性值:

const obj = {};
const arr = [];
const fn = function() {}

obj.__proto__ == Object.prototype // true
arr.__proto__ === Array.prototype // true
fn.__proto__ == Function.prototype // true
複製代碼

規則四

當你試圖獲得一個對象的某個屬性時,若是這個對象自己沒有這個屬性,那麼它會去它的隱式原型 __proto__(也就是它的構造函數的顯式原型 prototype)中尋找:

const obj = { a:1 }
obj.toString
// ƒ toString() { [native code] }
複製代碼

首先, obj 對象並無 toString 屬性,之因此能獲取到 toString 屬性,是遵循了第四條規則,從它的構造函數 Object 的 prototype 裏去獲取。

一個特例

我試圖想推翻上面的規則,看下面這段代碼:

function Person(name) {
  this.name = name
  return this // 其實這行能夠不寫,默認返回 this 對象
}

var nick = new Person("nick")
nick.toString
// ƒ toString() { [native code] }
複製代碼

按理說, nick 是 Person 構造函數生成的實例,而 Person 的 prototype 並無 toString 方法,那麼爲何, nick 能獲取到 toString 方法?

這裏就引出 原型鏈 的概念了, nick 實例先從自身出發反省本身,發現並無 toString 方法。找不到,就往上走,找 Person 構造函數的 prototype 屬性,仍是沒找到。構造函數的 prototype 也是一個對象嘛,那對象的構造函數是 Object ,因此就找到了 Object.prototype 下的 toString 方法。

image.png

上述尋找的過程就造成了原型鏈的概念,我理解的原型鏈就是這樣一個過程。也不知道哪一個人說過一句,JavaScript 裏萬物皆對象。從上述狀況看來,好像是這麼個理。🤔

一張圖片

用圖片描述原型鏈:

image.png

最後一個 null,設計上是爲了不死循環而設置的, Object.prototype 的隱式原型指向 null

一個方法

instanceof 運算符用於測試構造函數的 prototype 屬性是否出如今對象原型鏈中的任何位置。 instanceof 的簡易手寫版,以下所示:

// 變量R的原型 存在於 變量L的原型鏈上
function instance_of (L, R) {    
  // 驗證若是爲基本數據類型,就直接返回 false
  const baseType = ['string', 'number', 'boolean', 'undefined', 'symbol']
  if(baseType.includes(typeof(L))) { return false }

  let RP = R.prototype;  // 取 R 的顯示原型
  L = L.__proto__; // 取 L 的隱式原型
  while (true) {
    if (L === null) { // 找到最頂層
      return false;
    }
    if (L === RP) { // 嚴格相等
      return true;
    }
    L = L.__proto__;  // 沒找到繼續向上一層原型鏈查找
  }
}
複製代碼

咱們再來看下面這段代碼:

function Foo(name) {
  this.name = name;
}
var f = new Foo('nick')

f instanceof Foo // true
f instanceof Object // true
複製代碼

上述代碼判斷流程大體以下:

一、 f instanceof Foof 的隱式原型 __proto__ 和 Foo.prototype ,是相等的,因此返回 true 。

二、 f instanceof Objectf 的隱式原型 __proto__ ,和 Object.prototype 不等,因此繼續往上走。 f 的隱式原型 __proto__ 指向 Foo.prototype ,因此繼續用 Foo.prototype.__proto__ 去對比 Object.prototype ,這會兒就相等了,由於 Foo.prototype 就是一個普通的對象。

再一次驗證萬物皆對象。。。。

總結

經過四個特性、一個例子、一張圖片、一個方法,你們應該對原型和原型鏈的關係有了大概的認知。個人認知就是,原型鏈就是一個過程,原型是原型鏈這個過程當中的一個單位,貫穿整個原型鏈。就好像你要是看完了不點個贊,我能夠順着網線找到你。

相關文章
相關標籤/搜索