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

本文主題git

  • 建立對象有幾種方法
  • 原型、構造函數、實例、原型鏈的關係
  • instanceof 原理
  • 實例題(考察)

建立對象的幾種方法

// 經過字面量
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' })
複製代碼

原型、構造函數、實例、原型鏈之間的關係

這裏着重點講原型、構造函數、實例、原型鏈他們之間的關係,由於這也是面試常問、也是容易混淆的點。github

構造函數 和 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 屬性。這個屬性指向函數的原型對象。post

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

__proto__

那麼咱們該怎麼表示實例與實例原型 ?學習

每個 JavaScript 對象(除了 null )都具備的一個屬性,叫__proto__,這個屬性會指向該對象的原型ui

person.__proto__ === Person.prototype // true
複製代碼

constructor

既然實例對象和構造函數均可以指向原型,那麼原型是否有屬性指向構造函數或者實例呢?this

Person.prototype.constructor === Person
複製代碼

總結一下構造函數、實例原型、和實例之間的關係spa

Person.prototype // 構造函數['prototype'] 指向函數原型
person.__proto__ === Person.prototype // 實例['__proto__'] 指向函數原型
Person.prototype.constructor === Person // 函數原型['constructor'] 指向構造函數
複製代碼

原型鏈

每個實例都包含一個指向原型對象的 __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 屬性。

關係圖也能夠更新爲:

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

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深刻之從原型到原型鏈

相關文章
相關標籤/搜索