一、原型
寫在前面:
任何對象都有原型。
函數也是對象,所以函數也有原型。
1.什么是原型
? ? ? ?在 JavaScript 中,對象有一個特殊的隱藏屬性?[[Prototype]]
,它要么為?null
,要么就是對另一個對象的引用,該對象被稱為“原型”。所以,被?[[Prototype]]
?鏈接所引用的對象就是該對象的“原型”。
? ? ? ?我們可以通過Object.getPrototypeOf/Object.setPrototypeOf(推薦寫法)或者是__proto__去設置原型,__proto__?是?[[Prototype]]?的 getter/setter,我們可以用Object.getPrototypeOf/Object.setPrototypeOf?來取代?__proto__?去 get/set 原型,也可以繼續使用__proto__去訪問原型。?
? ? ?例如,在下面的示例中,通過Object.create()將對象b的原型設置為a,實現原型式繼承,此時對象a就是對象b的原型。
const a = {name: "小王",};// Object.create()是一個靜態方法,它創建一個新對象,使用現有的對象作為新創建的對象的proto。// 在這個例子中,我們使用對象a作為原型來創建新對象b。這意味著b的原型是a`。const b = Object.create(a);// Object.getPrototypeOf()方法返回指定對象的原型(即內部[[Prototype]]屬性的值)。console.log(Object.getPrototypeOf(b));//{name: '小王'}// proto是一個非標準的屬性,但在許多環境中都可用,它提供了對對象原型的直接訪問。這里,b.__proto__也返回a,即{name: '小王'}`。console.log(b.__proto__);//{name: '小王'}console.log(a); //{name: '小王'}console.log(b.name); //小王
圖像示例:
2.原型的作用
- 實現繼承:原型鏈是JavaScript中實現對象繼承的主要機制。當一個對象試圖訪問一個屬性時,如果它自身沒有這個屬性,JavaScript會在它的原型鏈上查找這個屬性,直到找到這個屬性或者到達鏈的盡頭(
null
)。通過這種方式,原型允許對象繼承其他對象的屬性和方法。 - 共享屬性和方法:通過原型,我們可以定義對象的共享屬性和方法。這意味著所有對象實例都可以訪問和修改這些屬性和方法。這在創建大量具有相同屬性和方法的對象時非常有用,因為它可以避免在每個對象實例中重復定義這些屬性和方法。
- 動態修改和擴展:由于原型是一個對象,我們可以在運行時動態地修改和擴展它。這允許我們在不修改原始構造函數的情況下,為所有對象實例添加新的屬性和方法。這種靈活性使得原型成為JavaScript中一個非常強大的工具。
- 代碼重用和模塊化:通過創建具有特定原型的對象,我們可以實現代碼的重用和模塊化。這有助于降低代碼的復雜性,提高代碼的可讀性和可維護性。
二、原型鏈
直接上圖:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?原型鏈圖
講解:
1.構造函數
? ? ?構造函數是一種特殊的函數,通常用于初始化新創建的對象實例。每個構造函數都有一個prototype
屬性,這個屬性是一個指針,指向一個對象(原型對象),該對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。當訪問一個對象的屬性時,如果這個對象自身沒有這個屬性,JavaScript會查找這個對象的原型(即[[Prototype]]
指向的對象),以此類推,直到找到這個屬性或者到達原型鏈的盡頭(null
)。我們可以使用new
關鍵字來調用一個構造函數,它會創建一個新的空對象,然后將這個新對象的內部鏈接([[Prototype]]
)指向構造函數的prototype
屬性所引用的對象。構造函數的實例和構造函數的原型對象有一個constructor屬性指向構造函數。在上面原型鏈圖中,因為對象b繼承了實例a,所以b.constructor也指向構造函數A。當然,構造函數也是一個對象,所以它也有原型,通常是Function.prototype。
2.Object.prototype和Function.prototype
? ? ? ?Object.prototype是所有JavaScript對象(除了null)的原型。換句話說,幾乎所有的對象都會從Object.prototype繼承屬性和方法,比如.toString(),?.hasOwnProperty(), 和?.constructor等。
? ? ? ? ?Function.prototype是所有函數的原型,包括那些被用作構造函數的函數。這意味著所有的函數都會從Function.prototype繼承屬性和方法,如.apply(),?.call(), 和?.bind()等。
? ? ? ? ? 所以在查找原型時,在null之前函數會找到Function.prototype,所有對象都會找到Object.prototype。
3.理解原型鏈
? ? ? ?原型鏈是一種實現繼承的機制。在上面的原型鏈圖可以看出,通過把一個對象的原型指向另一個對象,可以讓這個對象訪問另一個對象的屬性,最終形成了一個鏈條一樣的結構。在原型鏈中查找屬性或方法時,JavaScript 會從當前對象開始,沿著原型鏈(即?__proto__
?鏈)向上查找,直到找到相應的屬性或方法或直到到達?Object.prototype
?的原型(即?null
)。如果找不到,則返回?undefined
。
4.代碼展示
希望下面的代碼可以幫你理解上面的原型鏈圖。
<script>function A() {this.name = "a";}A.prototype.say = function () {return "hello";};const a = new A();const b = Object.create(a);const c = Object.create(b);console.log(c.say()); //hello,因為c.__proto__指向b,b.__proto__指向a,所以c可以訪問到a的原型鏈上的方法console.log(c.name); //a,因為c.__proto__指向b,b.__proto__指向a,所以c可以訪問到a的原型鏈上的屬性console.log(c.age); //undefined,因為在原型鏈上沒找到age屬性console.log(c.__proto__.__proto__.__proto__.__proto__.__proto__); //nullconsole.log(A.prototype === a.__proto__); //true,它們指向同一個原型對象console.log(c.__proto__); //A {}console.log(b.__proto__); //A {}console.log(c === b); //false,c和b的原型鏈長度是不一樣的,也就是說{}展開的內容是不一樣的</script>
三、總結
1.是個對象就有原型
2.對象的原型是個對象或者null
3.被?[[Prototype]]
?鏈接所引用的對象就是該對象的“原型”
4.構造函數.prototype指向一個原型對象,這個對象是構造函數的實例的原型
5.通過Object.getPrototypeOf或者__proto__逐級訪問形成的鏈條叫做原型鏈