js原型與原型鏈萬文總結(jié)詳解(一文搞懂原型鏈!)
前言
作為一個初入前端的小白,原型鏈的概念太多,一直覺得難以理解,對整個原型鏈的了解非常模糊。理解原型鏈?zhǔn)巧钊雽W(xué)習(xí)js的一小步,在參考諸多大佬文章后,整理筆記如下:
一,原型
原型與原型鏈,首先我們要知道什么是原型。在開始了解原型之前,賣個關(guān)子先認(rèn)識下js中的對象
1,對象
對象(Object)是一種復(fù)合數(shù)據(jù)類型,它是一種無序的鍵值對集合。對象用于存儲和傳遞多個值,每個值都有一個鍵(key)與之關(guān)聯(lián)。
對象的鍵是字符串類型,值可以為任意數(shù)據(jù)類型(數(shù)字,字符串,布爾)和其他對象??傊褪俏覀兘?jīng)常用到的 鍵值對啦 {key: value} (對象又分為函數(shù)對象與普通對象,此處不贅述,以免繞暈,放到【 第五點 進(jìn)階 】下面)
對象的創(chuàng)建方式 以下是幾種常見的對象創(chuàng)建方式:
第一種: 對象字面量方式 var obj1 = { name: "Jack", age: 26, } 第二種: Object構(gòu)造函數(shù)模式 var obj2 = new Object() obj2.name = "Jack" obj2.age = 26 第三種: 構(gòu)造函數(shù)模式 function Test(name, age){ this.name = name this.age = age this.say = function(){ console.log('我能說話') } } var obj3 = new Test('Jack', 26) var obj4 = new Test('Rose', 25)
日常中我們最常用的應(yīng)該是字面量方式,那為什么會出現(xiàn)第三種構(gòu)造函數(shù)方式呢?
想想看我們需要多個obj1,obj2的時候,字面量方式我們需要重復(fù)代碼去創(chuàng)建對象;而使用構(gòu)造函數(shù)的方式只需要寫一遍屬性和方法,我們就可以通過new關(guān)鍵字,new出多個不同的對象。
試試在控制臺打印如下obj3.say === obj4.say,false 看起來調(diào)用的是同一個函數(shù)方法,實際并不相等。因為他們的內(nèi)存地址是不同的,每個new出來的obj3 和 obj4 都包含一份獨立的屬性和方法(可能導(dǎo)致浪費內(nèi)存)
function Test(){ this.name = 'rose' this.say = function(){ console.log('我能說話') } } var obj3 = new Test() var obj4 = new Test() obj3.say === obj4.say // false obj3.name === obj4.name // true
你可能會問為什么obj3.name和obj4.name是相等的呢?剛才不是說的內(nèi)存不同獨立的屬性和方法嗎? 要理解這個行為,可以大致參考下面這種情況:
const a = { value: 10 }; const b = { value: 10 }; console.log(a.value === b.value); // 輸出 true,因為屬性值相同 console.log(a === b); // 輸出 false,因為是不同的對象
同樣的邏輯,對比obj3.name的時候比較的是name這個屬性值,而obj3.name輸出值為rose,所以rose比較值obj4.name也是rose,是相同的
現(xiàn)在我們來說說構(gòu)造函數(shù)方式,上面例子中的 obj3 和 obj4 都是 Test的實例(也叫實例對象),而Test是 obj3 和 obj4 的構(gòu)造函數(shù)。 實例都有一個構(gòu)造函數(shù)屬性(constructor)指向構(gòu)造函數(shù),通過構(gòu)造函數(shù)創(chuàng)建的對象擁有構(gòu)造函數(shù)內(nèi)部定義的屬性和方法
function Test(name, age){ this.name = name this.age = age } Test.prototype.say = function(){ console.log('我能說話') } var obj3 = new Test('Jack', 26) var obj4 = new Test('Rose', 25) // constructor屬性指向構(gòu)造函數(shù) console.log(obj3.constructor === Test) // true console.log(obj4.constructor === Test) // true console.log(obj4.constructor === obj3.constructor) // true
記住以下兩個概念:
(1)Test是構(gòu)造函數(shù)
(2)obj3 和 obj4 是構(gòu)造函數(shù)Test的 實例,實例的屬性constructor指向構(gòu)造函數(shù)Test
2,原型(原型對象)
上面的例子中, obj3 和 obj4 都需要調(diào)用 Test 中的say()方法,我們有沒有辦法將公共方法放到一個公共的地方呢? 這時候有請公共的原型(prototype)登場
在js中,每一個對象(函數(shù)也是對象)都有一個特殊的屬性叫做原型(prototype),它指向另一個對象,這個對象(Test.prototype)被稱為原型對象, 原型對象是用來共享屬性和方法的
Test.prototype 就叫原型對象
打印Test.prototype可以看到上圖中原型對象存在一個constructor屬性,指向Test
Test.prototype.constructor === Test // true
原型對象:
(1),原型對象有一個constructor屬性指向構(gòu)造函數(shù)本身(Test)。
(2),原型對象是一個普通的對象,它包含屬性和方法。
(3),原型對象的屬性和方法會被繼承到所有通過原型鏈與它相連的對象。
簡單來說,原型對象是一個普通對象,屬性和方法會被繼承到其他對象,而每個對象都有一個原型(prototype),用來繼承其他對象的屬性和方法。
此時,我們就可以把say方法放到這個原型對象上, obj3 和 obj4 就可以訪問這個方法,再不用寫到Test中去重復(fù)占用內(nèi)存,所有new出來的實例都可以使用此方法
我們再來打印 obj3.say === obj4.say,為true, 證明obj3和obj4調(diào)用的就是同一個方法
function Test(name, age){ this.name = name this.age = age } Test.prototype.say = function(){ console.log('我能說話') } var obj3 = new Test('Jack', 26) var obj4 = new Test('Rose', 25) obj3.say() // 我能說話 obj4.say() // 我能說話 console.log(obj3.say === obj4.say) // true
構(gòu)造函數(shù)和實例之間就初步構(gòu)成了這樣一個關(guān)系,如圖:
二,隱式原型__proto__
1,__proto__
在js中,每個對象都有一個“ __proto__ ”屬性(左右兩邊兩個短下劃線),這個__proto__就被稱為隱式原型。(記住這點)
實例對象當(dāng)然也是對象,也存在__proto__屬性
console.log(obj3.__proto__ === Test.prototype) // true
打印以上obj3.__proto__ === Test.prototype結(jié)果為true,所以:
(1),每個js對象都有一個隱藏的原型對象屬性__proto__,它指向創(chuàng)建它的構(gòu)造函數(shù)的原型對象(Test.prototype)
(2),__proto__存在的意義在于為原型鏈查找提供方向,原型鏈查找靠的是__proto__,而不是prototype(畫重點,后面要考!!?。?/strong>
實例對象obj3通過__proto__指向了Test的原型對象(Test.prototype),如圖:(Test.prototype.constructor :從Test先指向原型對象Test.prototype在.constructor指回Test,繞了一圈,圖中就不列舉)
2,考你一下
前面提到的幾個概念理解清楚,再來看看下面的列子是否清楚
function Test(name, age){ this.name = name this.age = age } Test.prototype.say = function(){ console.log('我能說話') } var obj3 = new Test('Jack', 26) 1, 構(gòu)造函數(shù)是? 實例是? 2, obj3.constructor === Test true or false? 3, obj3.__proto__ === Test ? 4, Test.prototype === obj3.__proto__ ? 5, obj3.__proto__.constructor === Test ? // 1, Test obj3 2,true 3,false 4,true 5,true
三,原型鏈
1,Object.prototype
在上面第二點中,每個js對象都有一個隱藏的原型對象屬性__proto__
那Test的原型對象Test.prototype會不會也有一個隱式原型__proto__呢? 控制臺輸出如下:
Test.prototype當(dāng)然也存在一個屬性__proto__,而這個Test.prototype.__proto__到底是誰呢?
Test.prototype.__proto__ === Object.prototype // true
(1) Test.prototype的隱式原型(__proto__)就是Object.prototype
(2) 所有的對象,包括構(gòu)造函數(shù)的原型對象,最終都繼承自 Object.prototype,這是js原型鏈的頂點
Object.prototype是從哪里來的呢? 當(dāng)然是由Object的屬性prototype指向來的。Object.prototype同樣也會存在屬性 constructor指回Object(【目錄 2,原型 原型對象】中提到)
此時的關(guān)系圖:
2,鏈
在控制臺打印Object.prototype,會發(fā)現(xiàn) Object.prototype也是一個對象
既然它也是對象,它也存在隱式屬性__proto__。想想看,如果Object.prototype.__proto__再去指向某個對象的原型(prototype),那整條線就顯得無窮無盡,一直找下去
js代碼在創(chuàng)建時我們的開發(fā)者當(dāng)然考慮到了,Object.prototype作為原型鏈的頂端,位于原型鏈的最末端。因此,它不再有自己的原型,所以O(shè)bject.prototype.__proto__ 指向null,表示原型鏈的終點
原型鏈的終點是null
Object.prototype.__proto__ === null
這個時候終于到達(dá)了終點,形成了這樣一個關(guān)系圖(一整個鏈接在一起):
每個對象都有一個原型(prototype),它指向另外一個對象,而指向的對象又存在屬性(_proto_)指向另外一個對象。當(dāng)我們訪問對象(obj3)的屬性時,會先在對象定義的屬性中進(jìn)行查找,沒找到就會沿著__proto__一路向上查找,最終形成一個鏈?zhǔn)浇Y(jié)構(gòu),這整個鏈?zhǔn)浇Y(jié)構(gòu)就叫做原型鏈
如果在原型鏈中找到了這個屬性,就返回找到的屬性值;如果整個原型鏈都沒找到這個屬性值,則返回 undefined,沒找到方法直接報錯(not a function)
四,練習(xí)一下
到了這里應(yīng)該對整個原型鏈有了自己的認(rèn)知,其實只要記住以下幾個概念,就可以試著自己畫出整個關(guān)系圖
1,在js中,每一個對象(函數(shù)也是對象)都有一個特殊的屬性叫做原型(prototype),它指向另一個對象,這個對象被稱為原型對象, 原型對象是用來共享屬性和方法的 2,對象有一個屬性(__proto__)指向構(gòu)造函數(shù)的原型對象,構(gòu)造函數(shù)的原型對象也存在__proto__ 3,原型鏈的頂端是Object.prototype 4,原型鏈的終點是null
看看下面的題是否清楚:
function Test(name, age){ this.name = name this.age = age } Test.prototype.say = function(){ console.log('我能說話') } var obj3 = new Test('Jack', 26) var obj4 = new Test('Rose', 24) 1, Test.prototype === ( ) ? 2, obj3.__proto__.__proto__ === ( ) ? 3, obj3.__proto__ === obj4.__proto__ ? 4, Test.prototype.__proto__ === ( ) ? 5, obj4.__proto__.constructor === ( ) ? 6, Object.prototype.__proto__ === ( ) ? 7, obj3.say === obj4.say ? // 1, obj3.__proto__ 或 obj4.__proto 2,Object.prototype 3, true (二者都由Test new出來,在原型鏈上都指向 Test.prototype) // 4, Object.prototype 5, Test 6, null (終點) 7,true (同問題3)
要是不清楚可以在結(jié)合關(guān)系圖捋一捋
五,進(jìn)階
1,普通對象與函數(shù)對象
在 js 中,有兩種主要類型的對象:普通對象和函數(shù)對象。普通對象最常見,通過"{ }"創(chuàng)建的就是普通對象;通過new Function出來的就是函數(shù)對象(函數(shù)聲明、函數(shù)表達(dá)式創(chuàng)建的為函數(shù)對象),我們可以用typeof來區(qū)分 (注意:這里函數(shù)聲明式和表達(dá)式不要和對象字面量方式混淆)
f1,f2,f3都是函數(shù)對象, Object 也是函數(shù)對象, b1,2,3為普通對象;
簡單理解,普通對象就是我們最長見的 { } 鍵值對; 函數(shù)對象通常包含了一個function
function f1(){} var f2 = function(){} var f3 = new Function('name') var b1 = new f1() var b2 = {name: 'Rose'} var b3 = new Object() typeof f1 // 'function' typeof f2 //'function' typeof f3 //'function' typeof b1 //'object' typeof b2 //'object' typeof b3 //'object' typeof Object // 'Function'
這是再來看我們上面的例子就很清晰了, obj3 為普通對象, Test為函數(shù)對象 (不信F12控制臺打開 typeof試試)
function Test(name, age){ this.name = name this.age = age } var obj3 = new Test('Jack', 26)
在上面【2 原型對象】我們提到過每一個對象(函數(shù)也是對象)都有一個特殊的屬性叫做原型(prototype) obj3是對象,但是它沒有prototype的這個原型屬性(不信控制臺試試)
所以這話不夠完整,只有 函數(shù)對象才具有 prototype 這個原型屬性
2,原型鏈機制
在【二,隱式原型】中,提到過: __proto__存在的意義在于為原型鏈查找提供方向。 看你是不是忘記了吧
為什么會說提供查找方向呢,看看下面兩個例子:
左邊在構(gòu)造函數(shù)Test的原型對象(Test.prototype)上定義了sex, Test.sex為undefined ( 注意是Test.sex 不是 obj.sex); 右邊在Object.prototype上定義sex ,Test.sex 能獲取到值; 為什么在Test.prototype上定義,Tset.sex不能通過原型鏈到Test.prototype上去找到sex屬性;而定義到頂點Object.prototype上,又能通過原型鏈找到了
看了大佬的一些解答分析,定義到Test.prototype上的時候,Test.sex并沒有通過原型鏈查找,而是檢查Test自身是否定義該屬性,沒有所以是undefined。感覺解釋會有點說不通,定義到Object.prototype,不也應(yīng)該是Test自身檢查,也會檢查不到,但我們能輸出值,證明的確是順著原型鏈去查找到了Object.prototype上
why ?
來看看ai對此的回答
我們在左邊例子中,Test本身并沒有直接定義'sex'屬性,所以查找失敗返回undefined, 如果要到原型對象上查找 正確的方式應(yīng)該是 Test.prototype.sex 這結(jié)論很好沒任何問題,但是為毛右邊的例子能獲取到呢 ?不要給我岔開話題啊喂....
為了找到答案,我一度去翻遍全網(wǎng),問遍ai,得到結(jié)論大概都是:" 原型鏈從實例對象開始查找,不是從構(gòu)造函數(shù)開始查找,構(gòu)造函數(shù)不具備相同的原型鏈機制" 這回答非常好,可是還是沒解決我的問題?。。。?nbsp; 不具備相同的原型鏈機制,為什么定義到Object.prototype上就能獲取到了呢? 反復(fù)詢問ai,就開始給我繞圈子了....... 費解
終究還是得靠自己 先捋一下
既然__proto__才是原型鏈查找的方向,同時對象都有__proto__這個屬性,那構(gòu)造函數(shù)Test是屬于函數(shù)對象,函數(shù)對象也是對象 那是否Test也會存在__proto__這個屬性呢? 在上面的原型鏈圖中并沒有指出這個屬性 請往下看
3,F(xiàn)unction的原型
在第二點【隱式原型__proto__】中,我們提到__proto__指向創(chuàng)建它的構(gòu)造函數(shù)的原型對象。
function Test(name, age){ this.name = name this.age = age } var obj = new Test('Jack', 26) Test.__proto__ === Function.prototype // true
(1)構(gòu)造函數(shù)Test的隱式原型( __proto__)指向 Function.prototype, 函數(shù)對象的__proto__指向Function.prototype
至于為什么,js在設(shè)計時決定了構(gòu)造函數(shù)本身是函數(shù),當(dāng)然也可以通過指向 Function.prototype來訪問原型鏈上的屬性和方法,讓構(gòu)造函數(shù)也能參與到原型鏈中來(雖然不建議通過構(gòu)造函數(shù)訪問)
Function會有原型(prototype),當(dāng)然也有隱式原型(__proto__),打印這兩個原型,會發(fā)現(xiàn)二者互相相等 Function.prototype === Function.__proto__ 是不是很神奇? 看起來像是自己創(chuàng)造了自己
針對 Function.prototype === Function.__proto__, 諸多大佬對此有各種不同的解釋,以下拋出兩個觀點僅供參考:
1,F(xiàn)unction 也是對象, 由new Function創(chuàng)建,所以自己創(chuàng)造自己
2,F(xiàn)unction作為一個內(nèi)置對象,代碼運行前就存在,并非自己創(chuàng)造自己,先有的Function,然后實現(xiàn)上把原型指向了Function.prototype 以此來保持和其他函數(shù)一致,表明一種關(guān)系
對象都擁有隱式原型(__proto__),F(xiàn)unction.prototype當(dāng)然也存在__proto__,打印出來看看
好像有點眼熟? 是不是和Object.prototype打印的有點像 Function.prototype是函數(shù)對象,按照剛得出的結(jié)論,函數(shù)對象的__proto__應(yīng)該指向Function.prototype(即Function.prototype.__proto === Function.prototype),但是自己指向自己并沒有意義。 別忘記Object.prototype才是原型鏈的頂點,F(xiàn)unction.prototype存在于原型鏈中必然會與Object.prototype存在關(guān)聯(lián),指向Object.prototype能保證原型鏈存在終點,所以Function.prototype.__proto__ === Object.prototype
再來看原型鏈關(guān)系,這時候就成了這樣:
(2)如果在深究一點,Object也是函數(shù)對象,Object.__proto__ 也會指向Function.prototype
(3)構(gòu)造函數(shù)Test也有constructor屬性,這個屬性指向創(chuàng)建該函數(shù)的構(gòu)造函數(shù);如果自己沒有定義構(gòu)造函數(shù),會指向到 Function (Test.constructor === Function)
原型鏈關(guān)系就成了這樣:
針對上面 【2,原型鏈機制】 中的問題也有了答案,在構(gòu)造函數(shù)Test上訪問Object.prototype中的屬性時,其實是順著Test.__proto__這條路徑從Function去訪問 空口無憑,證據(jù)如下,看看會輸出什么
function Test(name, age){ this.name = name this.age = age } var obj = new Test('Jack', 26) Object.prototype.price = 2000 Function.prototype.price = 300 Test.price
Test在自身沒有找到price,順著Test的__proto__到Function.prototype上找到了price = 300,所以直接返回 300;若Function.prototype上沒有price,才會進(jìn)一步順著__proto__找到Object.prototype
在實際使用中,要獲取定義到Test.prototype上的屬性,還可以用原型對象Test.prototype.price訪問;不過建議還是通過實例(obj)來訪問具體的屬性(obj.name),而不是構(gòu)造函數(shù)Test.name訪問,畢竟實例new出來的目的就是為了調(diào)用構(gòu)造函數(shù)上的方法屬性
真相大白,再一次證明__proto__存在的意義在于為原型鏈查找提供方向,原型鏈查找靠的是__proto__,而不是prototype
六,練習(xí)一下
1,
function Test(){} var obj1 = new Test() 1, console.log( obj1.__proto__ === Test.prototype ) 2, obj1.__proto__.__proto__ === ()? 3, Object.prototype.__proto__ === ()? 4, console.log( Test.prototype === obj1.__proto__ ) 5, Test.__proto__ === Function.prototype ? 6, Function.prototype.__proto__ === () ? 7, Funcion.prototype === Function.__proto__ ? // 1,true 2,Object.prototype 3,null 4,true 5,true 6,Object.prototype 7,true
2,
var Test = function(){} Test.prototype.name = 'Rose' var obj1 = new Test() Test.prototype = {name: 'Rose', age: 26} var obj2 = new Test() obj1.name obj1.age obj2.name obj2.age // 'Rose' undefined 'Rose' 26 // Test.prototype = {} 將Test.prototype.name 覆蓋
3,
function Test(){} Object.prototype.a = function(){ console.log(1) } Function.prototype.b = function(){ console.log(2) } var obj = new Test() obj.a() obj.b() Test.a() Test.b() // 1 (not a function) 1 2 // 不太清楚再去看看Function的原型
七,總結(jié)
1,每個對象均存在隱式原型(__proto__),函數(shù)對象才有prototype屬性
2,__proto__存在的意義在于為原型鏈查找提供方向,原型鏈查找靠的是__proto__,而不是prototype
3,函數(shù)對象的__proto__都指向Function.prototype
4,每個對象都有一個隱式原型屬性(__proto__),多個原型通過__proto__鏈接在一起形成的鏈?zhǔn)浇Y(jié)構(gòu)就是原型鏈
到此這篇關(guān)于js原型與原型鏈的文章就介紹到這了,更多相關(guān)js原型與原型鏈詳解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript中數(shù)組去重的辦法總結(jié)
你是否在面試的過程中被考到過給你一個數(shù)組讓你去掉重復(fù)項呢,下面小編就來總結(jié)一下對于數(shù)組去重這道簡單的面試題時,我們可以回答的方法有什么吧2023-06-06swiperjs實現(xiàn)導(dǎo)航與tab頁的聯(lián)動
這篇文章主要為大家詳細(xì)介紹了swiperjs實現(xiàn)導(dǎo)航與tab頁的聯(lián)動,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-12-12JavaScript結(jié)合HTML DOM實現(xiàn)聯(lián)動菜單
這篇文章主要為大家詳細(xì)介紹了JavaScript結(jié)合HTML DOM實現(xiàn)聯(lián)動菜單,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04js隨機生成字母數(shù)字組合的字符串 隨機動畫數(shù)字
本篇文章給大家分享的js隨機生成字母數(shù)字組合的字符串,js隨機生成動畫數(shù)字,包括常用的產(chǎn)生隨機數(shù)的用法,需要的朋友可以參考下2015-09-09js復(fù)制內(nèi)容到剪貼板代碼,js復(fù)制代碼的簡單實例
下面小編就為大家?guī)硪黄猨s復(fù)制內(nèi)容到剪貼板代碼,js復(fù)制代碼的簡單實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10Javascript 修改String 對象 增加去除空格功能(示例代碼)
這篇文章主要介紹了Javascript 修改String 對象 增加去除空格功能(示例代碼)。需要的朋友可以過來參考下,希望對大家有所幫助2013-11-11