1. 程式人生 > >JavaScript的原型和原型鏈 (二)

JavaScript的原型和原型鏈 (二)

constructor建構函式

我們昨天說建立物件的三種方式,第二種是通過new建立物件

let  obj = new Object()//建立一個空物件等同於 var obj = {}
console.log(obj.constructor === Object)//true

Object就是一個建構函式,是js內建的建構函式,上面的例子中Object就是obj的建構函式,這個例子似乎不太明顯,我們繼續看

function Person(name){
    this.name = name
}
let LJJ = new Person("劉家軍")
console.log
(LJJ.constructor === Person)//true

我們自定義了一個建構函式Person,Person是LJJ的建構函式,LJJ的建構函式就是Person
建構函式與其他函式的唯一區別,就在於呼叫它們的方式不同。不過,建構函式畢竟也是函式,不存在定義建構函式的特殊語法。任何函式,只要通過 new 操作符來呼叫,那它就可以作為建構函式;而任何函式,如果不通過 new 操作符來呼叫,那它跟普通函式也不會有什麼兩樣。
建構函式在建立時有一個約定,如果是建構函式,那麼首字母要大寫,普通函式首字母小寫

constructor和prototype

constructor和我們昨天討論的prototype有什麼聯絡嗎?
觀察如下程式碼的輸出

function Person(name) {
    this.name = name;
}
let LJJ = new Person("劉家軍");

console.log(Person.prototype)

輸出:
在這裡插入圖片描述
Person.prototype是Person的原型物件
繼續貼程式碼

function Person(name) {
    this.name = name;
}
let LJJ = new Person("劉家軍");
 console.log(Person.prototype.constructor === Person) // true

在預設情況下,所有原型物件都會自動獲得一個 constructor(建構函式)屬性,這個屬性包含一個指向 prototype 屬性所在函式的指標。就拿前面的例子來說,Person.prototype.constructor 指向 Person。
我們得出以下結論
原型物件中的constructor屬性,指向該原型物件對應的建構函式
也就是說上面的例子,Person的原型物件是Person.prototype,原型物件(Person.prototype)中有一個constructor屬性,這個constructor屬性指向原型物件(Person.prototype)對應的建構函式Person,用一行程式碼概括

 console.log(Person.prototype.constructor === Person) // true

以上就是constructor和prototype的關係
我們注意到原型物件(Person.prototype)中還存在一個屬性__proto__,這又是什麼?它和prototype,constructor又有什麼關聯呢?

隱式原型(___ proto___)

那麼__proto__是什麼?每個物件都會在其內部初始化一個屬性,就是__proto__。
Firefox、Safari 和 Chrome 的每個物件上都有這個屬性 ,而在其他瀏覽器中是完全不可見的(為了確保瀏覽器相容性問題,不要直接使用 proto 屬性,此處只為演示)。我們繼續看程式碼

let obj = new Object()
console.log(obj.__proto__ === Object.prototype) // true
let Fun = new Function()
console.log(Fun.__proto__ === Function.prototype) // true
let arr = new Array()
console.log(arr.__proto__ === Array.prototype) // true
let str = new String()
console.log(str.__proto__ === String.prototype) // true
let bool = new Boolean
console.log(bool.__proto__ === Boolean.prototype) // true
function MyFun() {
    console.log("我是劉家軍");
}
var LJJ = new MyFun()
console.log(LJJ.__proto__ === MyFun.prototype) // true

再重複一次:Array,String,Function,Boolean,Object都是js內建的建構函式,MyFun是自定義的建構函式
只有函式物件才存在prototype
所有物件(除了Object.prototype)都存在__proto__

剛才我們討論過,普通物件都是通過函式建立的
根據以上我們得出結論:
普通物件__proto__指向當前函式物件的原型,
你可能發現了,有一個矛盾的地方,所有物件都存在__proto__,只有普通物件的__proto__指向當前函式物件的原型,那函式物件的__proto__指向哪裡呢?繼續看程式碼

function MyFun() {
    console.log("我是劉家軍")
}
console.log(Boolean.__proto__) // ƒ () { [native code] }
console.log(Function.__proto__) // ƒ () { [native code] }
console.log(String.__proto__) // ƒ () { [native code] }
console.log(Array.__proto__) // ƒ () { [native code] }
console.log(Object.__proto__) // ƒ () { [native code] }
console.log(MyFun.__proto__) // ƒ () { [native code] }

函式物件的__proto__輸出的都是ƒ () { [native code] }
函式內部是[native code],也就是系統編譯好的二進位制程式碼函式,我們不對此做研究
上面說到,所有物件都有__proto__,原型物件也是物件,
我們得出結論
原型物件也存在__proto__
結合以上我門又一次得出結論
原型物件的__proto__指向當前函式物件的原型,
還是繼續看程式碼,便於理解*

console.log('劉家軍'.__proto__ === String.prototype) // true
console.log(String.prototype.__proto__ === Object.prototype) // true
//等量代換,得出一下結論
console.log('劉家軍'.__proto__.__proto__ === Object.prototype) // true
//自此形成了一條鏈,===>原型鏈

解釋一下如上程式碼,
"劉家軍"是字串型別,"劉家軍"的建構函式是String(), 所以"劉家軍"的__proto__指向String的原型
String()是js的內建建構函式,繼承自Object,也就是說Object是頂端,是原型鏈的頂端,既然是頂端,所以:

console.log(Object.prototype.__proto__) // null

Object的原型物件是不存在__proto__的

總結

所有物件(不包括Object.prototype)有__proto__屬性,函式物件有prototype屬性;
物件由函式生成;
生成物件時,物件的__proto__屬性指向當前函式的prototype屬性。
Object.prototyp處於原型鏈的頂端,不存在原型,不繼承任何屬性,其他原型物件都是普通物件,普通物件都具有原型,所有的內建建構函式(以及大部分自定義建構函式)都具有一個繼承自Object.prototype的原型,例如Date.prototype的 屬性繼承自Object.prototype,因此有new Date()建立的Date物件的屬性同時繼承自Date.prototype和Object.prototype,這一系列的原型物件就是所謂的原型鏈。