本文主題
- 建立物件有幾種方法
- 原型、建構函式、例項、原型鏈的關係
- 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' })
複製程式碼
原型、建構函式、例項、原型鏈之間的關係
這裡著重點講原型、建構函式、例項、原型鏈他們之間的關係,因為這也是面試常問、也是容易混淆的點。
建構函式 和 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'] 指向建構函式
複製程式碼
原型鏈
每一個例項都包含一個指向原型物件的
__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
。
但是當我們刪除了 person
的 name
屬性時,讀取 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 20B()
=> 同名的函式表示式和函式宣告同時存在時 總是執行表示式 answer 40A().B()
A()
執行函式 A ==> 1.變數 B 重新賦值函式 2.返回 this(window).B()
執行全域性下的 B 函式 已經被重新賦值 所以輸出 10
B()
=> 上面的程式碼執行過 A 函式了,此時全域性下的 B 函式輸出 10new 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
複製程式碼
如有不對之處,請指出~