原型鏈?zhǔn)?a href="http://m.babyishan.com/tag/javascript">JavaScript實(shí)現(xiàn)繼承和屬性查找的核心機(jī)制。javascript中每個(gè)對(duì)象都有指向其原型對(duì)象的內(nèi)部鏈接,構(gòu)成原型鏈。訪問(wèn)對(duì)象屬性時(shí),若自身無(wú)此屬性,則沿原型鏈向上查找,直至找到或到達(dá)NULL。函數(shù)的prototype屬性指向構(gòu)造出對(duì)象的原型,對(duì)象的__proto__屬性(推薦用Object.getprototypeof())指向其構(gòu)造函數(shù)的prototype,而原型對(duì)象的constructor指向關(guān)聯(lián)構(gòu)造函數(shù)。例如,person1.greet()通過(guò)person1.__proto__(即person.prototype)找到方法。繼承通過(guò)將子類原型設(shè)為父類實(shí)例實(shí)現(xiàn),如student.prototype = object.create(person.prototype)。理解原型鏈對(duì)代碼設(shè)計(jì)、性能優(yōu)化、排查問(wèn)題等至關(guān)重要。避免原型鏈污染的方法包括不修改內(nèi)置原型、使用object.create(null)創(chuàng)建無(wú)原型對(duì)象、用object.freeze()凍結(jié)對(duì)象。
原型鏈機(jī)制是JavaScript實(shí)現(xiàn)繼承的核心方式,它允許對(duì)象訪問(wèn)自身沒(méi)有的屬性或方法,轉(zhuǎn)而去查找其原型對(duì)象,直至找到或到達(dá)原型鏈的頂端(null)。理解原型鏈對(duì)于深入掌握JS至關(guān)重要。
解決方案
JavaScript中,每個(gè)對(duì)象都有一個(gè)指向其原型對(duì)象的內(nèi)部鏈接,這個(gè)鏈接就是原型鏈。當(dāng)我們?cè)噲D訪問(wèn)一個(gè)對(duì)象的屬性時(shí),如果對(duì)象自身沒(méi)有這個(gè)屬性,JS引擎會(huì)沿著原型鏈向上查找,直到找到該屬性或到達(dá)原型鏈的終點(diǎn)——null。
-
prototype 屬性: 每個(gè)函數(shù)(除了箭頭函數(shù))都有一個(gè)prototype屬性,它指向一個(gè)對(duì)象,這個(gè)對(duì)象通常被稱為原型對(duì)象。
-
__proto__ 屬性: 每個(gè)對(duì)象(除了 null )都有一個(gè) __proto__ 屬性,它指向創(chuàng)建該對(duì)象的構(gòu)造函數(shù)的原型對(duì)象。注意,__proto__ 已經(jīng)被標(biāo)記為不推薦使用,推薦使用 Object.getPrototypeOf() 和 Object.setPrototypeOf() 來(lái)訪問(wèn)和修改對(duì)象的原型。
-
constructor 屬性: 原型對(duì)象默認(rèn)有一個(gè)constructor屬性,指向關(guān)聯(lián)的構(gòu)造函數(shù)。
理解這三個(gè)屬性之間的關(guān)系是理解原型鏈的關(guān)鍵。例如:
function Person(name) { this.name = name; } Person.prototype.greet = function() { console.log("Hello, my name is " + this.name); }; const person1 = new Person("Alice"); person1.greet(); // 輸出 "Hello, my name is Alice" console.log(person1.__proto__ === Person.prototype); // true console.log(Person.prototype.constructor === Person); // true
在這個(gè)例子中,person1對(duì)象的__proto__屬性指向Person.prototype,因此person1可以訪問(wèn)Person.prototype上的greet方法。
原型鏈的查找過(guò)程:
當(dāng)訪問(wèn)person1.greet()時(shí),JS引擎首先檢查person1對(duì)象自身是否具有g(shù)reet屬性。如果沒(méi)有,則沿著person1.__proto__(即Person.prototype)向上查找。因?yàn)镻erson.prototype上有g(shù)reet方法,所以找到了并執(zhí)行。如果Person.prototype上也沒(méi)有g(shù)reet方法,會(huì)繼續(xù)沿著Person.prototype.__proto__向上查找,直到找到或到達(dá)原型鏈的頂端null。
如何利用原型鏈實(shí)現(xiàn)繼承?
JavaScript通過(guò)原型鏈實(shí)現(xiàn)繼承。子類構(gòu)造函數(shù)的原型指向父類的一個(gè)實(shí)例,這樣子類就可以繼承父類的屬性和方法。
function Student(name, studentId) { Person.call(this, name); // 調(diào)用父類構(gòu)造函數(shù),繼承屬性 this.studentId = studentId; } Student.prototype = Object.create(Person.prototype); // 關(guān)鍵:設(shè)置子類原型為父類原型的一個(gè)實(shí)例 Student.prototype.constructor = Student; // 修正constructor指向 Student.prototype.study = function() { console.log(this.name + " is studying."); }; const student1 = new Student("Bob", "12345"); student1.greet(); // 繼承自Person student1.study(); // Student自身的方法
這里,Object.create(Person.prototype)創(chuàng)建了一個(gè)新對(duì)象,該對(duì)象的原型是Person.prototype。然后,將Student.prototype指向這個(gè)新對(duì)象,從而實(shí)現(xiàn)了繼承。 需要注意的是,需要手動(dòng)修正Student.prototype.constructor的指向,否則它仍然指向Person。
為什么理解原型鏈對(duì)于javascript開發(fā)至關(guān)重要?
原型鏈?zhǔn)荍avaScript中實(shí)現(xiàn)繼承和共享屬性的關(guān)鍵機(jī)制。不理解原型鏈,很難理解JS中對(duì)象創(chuàng)建、屬性查找和繼承的工作方式,進(jìn)而影響代碼的編寫效率和質(zhì)量。它直接影響著你如何設(shè)計(jì)對(duì)象,如何組織代碼,以及如何解決一些常見的JS問(wèn)題,例如:
- 性能優(yōu)化: 理解原型鏈可以避免不必要的屬性查找,提高代碼性能。
- 代碼復(fù)用: 通過(guò)原型鏈實(shí)現(xiàn)繼承,可以有效地復(fù)用代碼,減少代碼冗余。
- 框架設(shè)計(jì): 很多JS框架都依賴原型鏈來(lái)實(shí)現(xiàn)其核心功能。
- 排查問(wèn)題: 當(dāng)遇到屬性訪問(wèn)問(wèn)題時(shí),可以通過(guò)原型鏈來(lái)追蹤屬性的來(lái)源。
__proto__、prototype 和 constructor 之間的區(qū)別是什么?
- prototype:是函數(shù)才有的屬性,指向該函數(shù)作為構(gòu)造函數(shù)創(chuàng)建的對(duì)象的原型。
- __proto__:是對(duì)象才有的屬性,指向創(chuàng)建該對(duì)象的構(gòu)造函數(shù)的原型。
- constructor:是原型對(duì)象才有的屬性,指向關(guān)聯(lián)的構(gòu)造函數(shù)。
三者之間存在關(guān)聯(lián):對(duì)象的__proto__屬性指向其構(gòu)造函數(shù)的prototype屬性,而構(gòu)造函數(shù)的prototype屬性的constructor屬性又指向該構(gòu)造函數(shù)本身。
如何避免原型鏈污染?
原型鏈污染是指修改了內(nèi)置對(duì)象的原型,導(dǎo)致所有基于該原型創(chuàng)建的對(duì)象都受到影響。這可能會(huì)導(dǎo)致意想不到的錯(cuò)誤和安全問(wèn)題。
例如,如果修改了Object.prototype,那么所有對(duì)象都會(huì)受到影響。
避免原型鏈污染的一些方法:
- 不要直接修改內(nèi)置對(duì)象的原型: 盡量避免修改Object.prototype、Array.prototype等內(nèi)置對(duì)象的原型。
- 使用 Object.create(null) 創(chuàng)建無(wú)原型對(duì)象: 如果需要?jiǎng)?chuàng)建一個(gè)完全干凈的對(duì)象,可以使用Object.create(null),它創(chuàng)建的對(duì)象沒(méi)有原型鏈。
- 使用 Object.freeze() 凍結(jié)對(duì)象: 可以使用Object.freeze()凍結(jié)對(duì)象,使其無(wú)法被修改。
- 注意第三方庫(kù): 使用第三方庫(kù)時(shí),要仔細(xì)檢查其代碼,避免其修改內(nèi)置對(duì)象的原型。
理解原型鏈?zhǔn)浅蔀橐幻麅?yōu)秀的JavaScript開發(fā)者的基石。希望以上內(nèi)容能夠幫助你更深入地理解JS原型鏈機(jī)制。