面試之JS篇 - 原型與原型鏈

不吃早餐發表於2019-03-12

本文主題

  • 建立物件有幾種方法
  • 原型、建構函式、例項、原型鏈的關係
  • instanceof 原理
  • 例項題(考察)

面試之JS篇 - 原型與原型鏈

建立物件的幾種方法

// 通過字面量
const obj1 = { name: 'guodada' }
const obj11 = new Object({ name: 'guodada' })

// 通過建構函式
function Pershon() {
  this.name = 'guodada'
}
const obj2 = new Pershon()

// Object.create
const obj3 = Object.create({ name: 'guodada' })
複製程式碼

原型、建構函式、例項、原型鏈之間的關係

這裡著重點講原型、建構函式、例項、原型鏈他們之間的關係,因為這也是面試常問、也是容易混淆的點。

建構函式 和 new 做了什麼?

函式被 new 關鍵字呼叫時就是建構函式。

new 關鍵字的內部實現機制(舉例說明):

function Person(name) {
  this.name = name
}

const person = new Person('guodada')
複製程式碼
  • 建立一個新物件, 他繼承於 Person.prototype
  • 建構函式 Person 被執行,相應的引數傳入,同時上下文(this)會被指定為這個新的例項。
  • 執行建構函式中的程式碼;
  • 返回新物件
var obj = {} // 建立一個空物件
obj.__proto__ = constructor.prototype //新增 __proto__ 屬性,並指向建構函式的 prototype 屬性。
constructor.call(this) // 繫結this
return obj
複製程式碼

(建議看下去再回來看 new 操作符做了什麼。。。)

prototype

每一個函式都有一個 prototype 屬性。這個屬性指向函式的原型物件。

Person.prototype // {constructor: Pershon(),__proto__: Object}
複製程式碼

__proto__

那麼我們該怎麼表示例項與例項原型 ?

每一個 JavaScript 物件(除了 null )都具有的一個屬性,叫__proto__,這個屬性會指向該物件的原型

person.__proto__ === Person.prototype // true
複製程式碼

constructor

既然例項物件和建構函式都可以指向原型,那麼原型是否有屬性指向建構函式或者例項呢?

Person.prototype.constructor === Person
複製程式碼

總結一下建構函式、例項原型、和例項之間的關係

Person.prototype // 建構函式['prototype'] 指向函式原型
person.__proto__ === Person.prototype // 例項['__proto__'] 指向函式原型
Person.prototype.constructor === Person // 函式原型['constructor'] 指向建構函式
複製程式碼

面試之JS篇 - 原型與原型鏈

原型鏈

每一個例項都包含一個指向原型物件的 __proto__ 指標,依賴這條關係,層層遞進,就形成了例項與原型的鏈條。

function Person() {}

Person.prototype.name = 'Kevin'

var person = new Person()

person.name = 'Daisy'
console.log(person.name) // Daisy

delete person.name
console.log(person.name) // Kevin
複製程式碼

在這個例子中,我們給例項物件 person 新增了 name 屬性,當我們列印 person.name 的時候,結果自然為 Daisy

但是當我們刪除了 personname 屬性時,讀取 person.name,從 person 物件中找不到 name 屬性就會從 person 的原型也就是 person.__proto__ ,也就是 Person.prototype 中查詢,幸運的是我們找到了 name 屬性,結果為 Kevin

原型的終點是 null,因為 null 沒有 proto 屬性。

關係圖也可以更新為:

面試之JS篇 - 原型與原型鏈

順便還要說一下,圖中由相互關聯的原型組成的鏈狀結構就是原型鏈,也就是藍色的這條線。

instanceof 原理

js 的基本型別有 String, Undefined, Boolean, Number, Null, Symbol, 我們一般可以通過 typeof 來判斷值的型別

typeof 1 === 'number'
typeof function() {} === 'function'
typeof null === 'object' // 注意!

// 判斷引用型別
typeof {} === 'object'
typeof [] === 'object'
複製程式碼

而引用型別的判斷這是通過 instanceof ,用來判斷例項是不是另一個物件的引用.

person instanceof Person // true
複製程式碼

原理就是: 例項['proto'] === 建構函式['prototype'], 但是值得注意的是 instanceof 會通過原型鏈繼續往下找。

person instanceof Object // true

person.__proto__ === Person.prototype // true
person.__proto__.constructor === Person // true
複製程式碼

經典例項題如下

function A() {
  B = function() {
    console.log(10)
  }
  return this
}

A.B = function() {
  console.log(20)
}

A.prototype.B = function() {
  console.log(30)
}

var B = function() {
  console.log(40)
}

function B() {
  console.log(50)
}

A.B()
B()
A().B()
B()
new A.B()
new A().B()
// 請在瀏覽器環境下執行
複製程式碼

上述題目答案是多少呢,大家不妨試試。在看下去(ps 這題還涉及到了執行上下文的概念--考察了函式宣告和函式表示式)

答案就在筆者之前寫過的文章中 通過一道面試題來學習原型/原型鏈-函式宣告/函式表示式

思考完揭曉答案

  • A.B() => 在 A 原型物件上找到 A.B = function() { console.log(20) } answer 20
  • B() => 同名的函式表示式和函式宣告同時存在時 總是執行表示式 answer 40
  • A().B()
    • A() 執行函式 A ==> 1.變數 B 重新賦值函式 2.返回 this(window)
    • .B()執行全域性下的 B 函式 已經被重新賦值 所以輸出 10
  • B() => 上面的程式碼執行過 A 函式了,此時全域性下的 B 函式輸出 10
  • new A.B() => new 執行了 A.B = function () {console.log(20)};
  • new A().B()
    • new 執行建構函式 A => objA.__proto__ = A.prototype
    • .B() 在 A 的原型物件中查詢 B; A.prototype 指向函式的原型物件
    • A.prototype.B = function () {console.log(30)} 輸出 30
A.B() // 20
B() // 40
A().B() // 10
B() // 10
new A.B() // 20
new A().B() // 30
複製程式碼

如有不對之處,請指出~

參考 JavaScript深入之從原型到原型鏈

相關文章