首發:https://zhuanlan.zhihu.com/p/91439580
原型,原型鏈屬於js語言的基礎,近期面試了很多同學,發現有很大一部分並不清楚原型,隱式原型,原型鏈之間的關係,加上自己在業務中分析源碼遇到的一些問題,秉着知識回顧和總結的初心,詳細總結下js原型相關的知識,有不足之處,還望大家補充。
幾個概念:
原型:prototype
隱式原型:__proto__
本文將從創建對象的不同方法中引出原型鏈查找規則
創建對象的5個方法
在講創建方法之前先來看下Object例子
const object_shadow = Object;
console.log(object_shadow)
console.log(typeof object_shadow) // function
console.log(typeof new object_shadow()) //object
調試上面的代碼,可以得到如下結果:
object_shadow沒有constructor,沒有prototype
object_shadow的__proto__等於function (){}
=> function(){}的constructor等於 function Function(){}
=> function Function(){}的prototype 等於function() {}
可以得到:object_shadow._proto_ = object_shadow.constructor.prototype
說明:object_shadow在查找constructor沒找到時,會通過__proto__向上查找,由於object_shadow並沒有constructor,本質object_shadow會通過__proto__向上查找
另外Object是一個函數類型,所以可以使用new關鍵字
接下來看5中創建對象的方法
1,字面量創建對象
const test = {
title:'字面量創建對象'
}
調試test:
從結果可以看出直接創建,_proto_指向Object
2,構造函數創建
function a(){
this.title = "構造函數創建對象"
}
const A = new a()
執行過程:
創建一個字面量對象var obj = {}
設置新對象的constructor屬性爲構造函數的名稱
指定obj的隱式原型等於a的prototype obj._proto_ = a.prototype
執行構造函數中的代碼,構造函數中的this指向new出對象 a.call(obj)
將初始化完畢的新對象地址,保存到等號左邊的變量中, 即賦值給A
調試A
可以看出:A._proto_ = A.constructor.prototype
3,Object.create()創建對象
const create_test = Object.create(test)
const create_a = Object.create(a)
create_test不滿足 create_test._proto_ = create_test.constructor.prototype
create_a不滿足 create_a._proto_ = create_a.constructor.prototype
console.log(create_a.title)//會得到undefined
Object.create(o)執行過程
// 創造一個新匿名函數
var F = function () {};
// 給該匿名函數的原型指向o
F.prototype = o;
// 返回該匿名函數的實例
return new F();
Object.create創建對象的過程包括了new
通過create創建的對象的_proto_指向構造函數F的prototype,通過上面的打印結果也可以看出。
4,class創建對象
class b {
title(){
return 'class創建對象'
}
}
class創建的對象默認有prototype屬性,不需要通過_proto_向上查找
5,extends創建對象
class extends_b extends b{}
//下面三個均報錯
class extends_A extends A{}//TypeError: Class extends value #<Object> is not a constructor or null
class extends_test extends test{}/TypeError: Class extends value #<Object> is not a constructor or null
class extends_create_test extends create_test{}//TypeError: Class extends value #<Object> is not a constructor or null
extends創建對象 的 _proto_和prototype均指向被繼承對象
由上面5種創建方法可以看出
1,class創建對象的方式,對象默認有prototype屬性
2,Object.create的創建的對象沒有prototype屬性,只有_proto_屬性指向傳入的對象
3,new a()創建方式 _proto_屬性指向constructor爲a的對象,也沒有prototype屬性
4,字面量創建對象_proto_屬性指向Object,也沒有沒有prototype屬性
原型鏈查找規則
來看一個例子:
const test = {
title:'字面量創建對象'
}
test.constructor.prototype.happy = 'constructor.prototype.happy'
test.constructor.prototype.pro_l = 'constructor.prototype.pro_l'
test.__proto__.pro_l = '_l'
console.log(test.pro_l)//'_l'
console.log(test.happy)//'constructor.prototype.pro_l'
create_a.constructor.prototype.__proto__.constructor.prototype.index = 3;
console.log(create_a.index)//3
test對象上沒有happy,pro_l屬性,默認向__proto_查找,此時如果_proto_上有屬性,則獲取,如果沒有__proto_會指向_proto_的constructor的prototype,也就是會查找其構造函數的prototype,如果沒有,繼續查找當前_proto_的_proto_
再看一個例子
//A是上面的對象
A.__proto__.constructor.prototype.test_a = 'pro_test_a'
A.__proto__.__proto__.test_a = 'test_a'
console.log(A.test_a)//pro_test_a
如果A.__proto__.constructor.prototype.test_a不存在,則取A.__proto__.__proto__.test_a
//A是上面的對象
//A.__proto__.constructor.prototype.test_a = 'pro_test_a'
A.__proto__.__proto__.test_a = 'test_a'
console.log(A.test_a)//test_a
constructor的prototype上沒有屬性時,繼續查找當前__proto__的_proto_
如圖:
總結:
1,對象的_proto_和A.constructor.prototype不一定相等,比如Object.create()就不是
2,{}創建對象效率是最高的,不需要執行構造函數,_proto_直接指向Object
3,new 一個對象的過程,新對象的_proto_指向該構造函數的prototype
4,Object.create()對象內部包含了new 運算符,新對象的_proto_指向傳入的對象
5,new和Object.create()的區別,_proto_是否指向傳入的對象
6,原型鏈查找
對象的prototype>_proto_>對象的constructor.prototype>_proto_._proto_