摘要:二原型和原型鏈其實,原型這個名字本身就很容易產(chǎn)生誤解,原型在百度詞條中的釋義是指原來的類型或模型。對象的原型是通過創(chuàng)建的對象,其原型是而的原型是,從而兩個原型對象和有了繼承關(guān)系三和這是容易混淆的兩個屬性。
關(guān)于原型和原型鏈的介紹,網(wǎng)上數(shù)不勝數(shù),但能講清楚這兩個概念的很少,大多數(shù)都是介紹各種對象、屬性之間如何指來指去,最后的結(jié)果就是箭頭滿天飛,大腦一團糟。本文將從這兩個概念的命名入手,用通俗易懂的語言,幫助你理解這兩個東西到底是何方神圣。
一. 背景知識JavaScript和Java、C++等傳統(tǒng)面向?qū)ο蟮木幊陶Z言不同,它是沒有類(class)的概念的(ES6 中的class也只不過是語法糖,并非真正意義上的類),而在JavaScript中,一切皆是對象(object)。在基于類的傳統(tǒng)面向?qū)ο蟮木幊陶Z言中,對象由類實例化而來,實例化的過程中,類的屬性和方法會拷貝到這個對象中;對象的繼承實際上是類的繼承,在定義子類繼承于父類時,子類會將父類的屬性和方法拷貝到自身當(dāng)中。因此,這類語言中,對象創(chuàng)建和繼承行為都是通過拷貝完成的。但在JavaScript中,對象的創(chuàng)建、對象的繼承(更好的叫法是對象的代理,因為它并不是傳統(tǒng)意義上的繼承)是不存在拷貝行為的。現(xiàn)在讓我們忘掉類、忘掉繼承,這一切都不屬于JavaScript。
二. 原型和原型鏈其實,原型這個名字本身就很容易產(chǎn)生誤解,原型在百度詞條中的釋義是:指原來的類型或模型。按照這個定義解釋的話,對象的原型是對象創(chuàng)建自身的模子,模子具備的特點對象都要具有,這儼然就是拷貝的概念。我們已經(jīng)說過, JavaScript的對象創(chuàng)建不存在拷貝,對象的原型實際上也是一個對象,它和對象本身是完全獨立的兩個對象。既然如此,原型存在的意義又是什么呢?原型是為了共享多個對象之間的一些共有特性(屬性或方法),這個功能也是任何一門面向?qū)ο蟮木幊陶Z言必須具備的。A、B兩個對象的原型相同,那么它們必然有一些相同的特征。
JavaScript中的對象,都有一個內(nèi)置屬性[[Prototype]],指向這個對象的原型對象。當(dāng)查找一個屬性或方法時,如果在當(dāng)前對象中找不到定義,會繼續(xù)在當(dāng)前對象的原型對象中查找;如果原型對象中依然沒有找到,會繼續(xù)在原型對象的原型中查找(原型也是對象,也有它自己的原型);如此繼續(xù),直到找到為止,或者查找到最頂層的原型對象中也沒有找到,就結(jié)束查找,返回undefined。可以看出,這個查找過程是一個鏈?zhǔn)降牟檎遥總€對象都有一個到它自身原型對象的鏈接,這些鏈接組成的整個鏈條就是原型鏈。擁有相同原型的多個對象,他們的共同特征正是通過這種查找模式體現(xiàn)出來的。
在上面的查找過程,我們提到了最頂層的原型對象,這個對象就是Object.prototype,這個對象中保存了最常用的方法,如toString、valueOf、hasOwnProperty等,因此我們才能在任何對象中使用這些方法。
1.字面量方式當(dāng)通過字面量方式創(chuàng)建對象時,它的原型就是Object.prototype。雖然我們無法直接訪問內(nèi)置屬性[[Prototype]],但我們可以通過Object.getPrototypeOf()或?qū)ο蟮?b>__proto__獲取對象的原型。
var obj = {}; Object.getPrototypeOf(obj) === Object.prototype; // true obj.__proto__ === Object.prototype; // true2.函數(shù)的構(gòu)造調(diào)用
通過函數(shù)的構(gòu)造調(diào)用(注意,我們不把它叫做構(gòu)造函數(shù),因為JavaScript中同樣沒有構(gòu)造函數(shù)的概念,所有的函數(shù)都是平等的,只不過用來創(chuàng)建對象時,函數(shù)的調(diào)用方式不同而已)也是一種常用的創(chuàng)建對象的方式。基于同一個函數(shù)創(chuàng)建出來的對象,理應(yīng)可以共享一些相同的屬性或方法,但這些屬性或方法如果放在Object.prototype里,那么所有的對象都可以使用它們了,作用域太大,顯然不合適。于是,JavaScript在定義一個函數(shù)時,同時為這個函數(shù)定義了一個 默認的prototype屬性,所有共享的屬性或方法,都放到這個屬性所指向的對象中。由此看出,通過一個函數(shù)的構(gòu)造調(diào)用創(chuàng)建的對象,它的原型就是這個函數(shù)的prototype指向的對象。
var f = function(name) { this.name = name }; f.prototype.getName = function() { return this.name; } //在prototype下存放所有對象的共享方法 var obj = new f("JavaScript"); obj.getName(); // JavaScript obj.__proto__ === f.prototype; // true3.Object.create()
第三種常用的創(chuàng)建對象的方式是使用Object.create()。這個方法會以你傳入的對象作為創(chuàng)建出來的對象的原型。
var obj = {}; var obj2 = Object.create(obj); obj2.__proto__ === obj; // true
這種方式還可以模擬對象的“繼承”行為。
function Foo(name) { this.name = name; } Foo.prototype.myName = function() { return this.name; }; function Bar(name,label) { Foo.call( this, name ); // this.label = label; } // temp對象的原型是Foo.prototype var temp = Object.create( Foo.prototype ); // 通過new Bar() 創(chuàng)建的對象,其原型是temp, 而temp的原型是Foo.prototype, // 從而兩個原型對象Bar.prototype和Foo.prototype 有了"繼承"關(guān)系 Bar.prototype = temp; Bar.prototype.myLabel = function() { return this.label; }; var a = new Bar( "a", "obj a" ); a.myName(); // "a" a.myLabel(); // "obj a" a.__proto__.__proto__ === Foo.prototype; //true三. __proto__和prototype
這是容易混淆的兩個屬性。__proto__指向當(dāng)前對象的原型,prototype是函數(shù)才具有的屬性,默認情況下,new 一個函數(shù)創(chuàng)建出的對象,其原型都指向這個函數(shù)的prototype屬性。
四. 三種特殊情況1.對于JavaScript中的內(nèi)置對象,如String、Number、Array、Object、Function等,因為他們是native代碼實現(xiàn)的,他們的原型打印出來都是? () { [native code] }。
2.內(nèi)置對象本質(zhì)上也是函數(shù),所以可以通過他們創(chuàng)建對象,創(chuàng)建出的對象的原型指向?qū)?yīng)內(nèi)置對象的prototype屬性,最頂層的原型對象依然指向Object.prototype。
"abc".__proto__ === String.prototype; // true new String("abc").__proto__ === String.prototype; //true new Number(1).__proto__ ==== Number.prototype; // true [1,2,3].__proto__ === Array.prototype; // true new Array(1,2,3).__proto__ === Array.prototype; // true ({}).__proto__ === Object.prototype; //true new Object({}).__proto__ === Object.prototype; // true var f = function() {}; f.__proto__ === Function.prototype; // true var f = new Function("{}"); f.__proto__ === Function.prototype; // true
3.Object.create(null) 創(chuàng)建出的對象,不存在原型。
var a = Object.create(null); a.__proto__; // undefined
此外,函數(shù)的prototype中還有一個constructor方法,建議大家就當(dāng)它不存在,它的存在讓JavaScript原型的概念變得更加混亂,而且這個方法也幾乎沒有作用。
歡迎關(guān)注我的公眾號:老干部的大前端,領(lǐng)取21本大前端精選書籍!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/84831.html
摘要:我們有了構(gòu)造函數(shù)之后,第二步開始使用它構(gòu)造一個函數(shù)。來個例子這種方式很簡單也很直接,你在構(gòu)造函數(shù)的原型上定義方法,那么用該構(gòu)造函數(shù)實例化出來的對象都可以通過原型繼承鏈訪問到定義在構(gòu)造函數(shù)原型上的方法。 來源: 個人博客 白話解釋 Javascript 原型繼承(prototype inheritance) 什么是繼承? 學(xué)過面向?qū)ο蟮耐瑢W(xué)們是否還記得,老師整天掛在嘴邊的面向?qū)ο笕筇?..
摘要:深入之繼承的多種方式和優(yōu)缺點深入系列第十五篇,講解各種繼承方式和優(yōu)缺點。對于解釋型語言例如來說,通過詞法分析語法分析語法樹,就可以開始解釋執(zhí)行了。 JavaScript深入之繼承的多種方式和優(yōu)缺點 JavaScript深入系列第十五篇,講解JavaScript各種繼承方式和優(yōu)缺點。 寫在前面 本文講解JavaScript各種繼承方式和優(yōu)缺點。 但是注意: 這篇文章更像是筆記,哎,再讓我...
摘要:上面的代碼,運行以后,我們可以看到因為的原型是指向的實例上的,所以可以訪問他的屬性值,那如果我不想讓訪問的構(gòu)造函數(shù)里聲明的屬性值,那怎么辦呢只需要將指向的原型而不是實例就行了。 走在前端的大道上 本篇將自己讀過的相關(guān) javascript原型和原型鏈 文章中,對自己有啟發(fā)的章節(jié)片段總結(jié)在這(會對原文進行刪改),會不斷豐富提煉總結(jié)更新。 文章——深入理解javascript之原型 一般的...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。異步編程入門的全稱是前端經(jīng)典面試題從輸入到頁面加載發(fā)生了什么這是一篇開發(fā)的科普類文章,涉及到優(yōu)化等多個方面。 TypeScript 入門教程 從 JavaScript 程序員的角度總結(jié)思考,循序漸進的理解 TypeScript。 網(wǎng)絡(luò)基礎(chǔ)知識之 HTTP 協(xié)議 詳細介紹 HTT...
摘要:先來說其實構(gòu)造函數(shù)也有,原型對象有,實例有也有,或者更加籠統(tǒng)的說,所有對象都是有的。構(gòu)造函數(shù)的原型對象上的會指向構(gòu)造函數(shù)。由于屬性是可以變更的,所以未必真的指向?qū)ο蟮臉?gòu)造函數(shù),只是一個提示。 續(xù)上一集內(nèi)容,通過構(gòu)造函數(shù)的方式,成功地更新了生產(chǎn)技術(shù),老板笑呵呵,工人少奔波,只是問題總比辦法多,又遇到一個新問題,就是會造成一些資源的重復(fù)和浪費,那么經(jīng)過工程師們的智慧交流,他們產(chǎn)生了一個新技...
閱讀 3110·2021-11-10 11:36
閱讀 3312·2021-10-13 09:40
閱讀 6051·2021-09-26 09:46
閱讀 662·2019-08-30 15:55
閱讀 1409·2019-08-30 15:53
閱讀 1580·2019-08-29 13:55
閱讀 2997·2019-08-29 12:46
閱讀 3204·2019-08-29 12:34