從原型到原型鏈较解,修煉JavaScript內(nèi)功這篇文章真的不能錯過畜疾!

從原型到原型鏈

本文主要從構(gòu)造函數(shù) -> 原型(隱式原型+顯示原型)-> 原型鏈的順序,漸進式講解印衔,希望對你有所做幫助啡捶,共勉~

系列文章:

  1. this指向詳解:思維腦圖與代碼的結(jié)合,讓你一篇搞懂this奸焙、call瞎暑、apply(附面試題)
  2. 本篇內(nèi)容:請看下方目錄

前言

又到了回憶過去的時候了彤敛,知識就是這樣,原型和原型鏈在我之前的實習生涯中用到的很少——幾乎沒有(噗了赌!我菜我攤牌了)墨榄,但是它和this指向問題一樣,是初級揍拆、中級前端開發(fā)在面試時永遠繞不開的話題渠概。是不是大家每次看面經(jīng)的時候都會去搜索原型相關的知識點?

你看這知識嫂拴,總是在考的時候播揪,才能知道它的重要,就好像曾經(jīng)有一段賊拉真摯的面試題擺在我面前...話題拉回來筒狠,今天我們就收了這個孽障猪狈!

一、構(gòu)造函數(shù)

1.1 什么是構(gòu)造函數(shù)辩恼?

構(gòu)造函數(shù)本身就是一個函數(shù)雇庙,與普通函數(shù)沒有任何區(qū)別,不過為了規(guī)范一般將其首字母大寫。構(gòu)造函數(shù)和普通函數(shù)的區(qū)別在于,使用new生成實例的函數(shù)就是構(gòu)造函數(shù)额各,直接調(diào)用的就是普通函數(shù)华弓。

function Person() {
    this.name = 'yuguang';
};
var person = new Person();
console.log(person.name) // yuguang

在這個例子中,Person 就是一個構(gòu)造函數(shù)。

1.2 constructor?

constructor 返回創(chuàng)建實例對象時構(gòu)造函數(shù)的引用。此屬性的值是對函數(shù)本身的引用胸完,而不是一個包含函數(shù)名稱的字符串。

被構(gòu)造出來的person的

可以看到實例對象的constructor指向了它的構(gòu)造函數(shù)翘贮,而它和原型的關系我們在之后會鏈接到一起赊窥。

1.3 都有哪些數(shù)據(jù)類型或者函數(shù)擁有constructor呢?

在JavaScript中狸页,每個具有原型的對象都會自動獲得constructor屬性锨能。除了:argumentsEnumerator芍耘、Error腹侣、GlobalMath齿穗、RegExp等一些特殊對象之外傲隶,其他所有的JavaScript內(nèi)置對象都具備constructor屬性。例如:Array窃页、Boolean跺株、Date复濒、FunctionNumber乒省、Object巧颈、String等。所有主流瀏覽器均支持該屬性袖扛。打開控制臺我們可以驗證一下

// 字符串
console.log('str'.constructor) // ? String() { [native code] }
console.log('str'.constructor === String) // true

// 數(shù)組
console.log([1,2,3].constructor) // ? Array() { [native code] }
console.log([1,2,3].constructor === Array) // true

// 數(shù)字
var num = 1
console.log(num.constructor) // ? Number() { [native code] }
console.log(num.constructor === Number) // true

// Date
console.log(new Date().constructor) // ? Date() { [native code] }
// 注意T曳骸!蛆封!不要混淆哦
console.log(new Date().getTime().constructor) // ? Number() { [native code] }

// Boolean
console.log(true.constructor) // ? Boolean() { [native code] }
console.log(true.constructor === Boolean) // true

// 自定義函數(shù)
function show(){
    console.log('yuguang');
};
console.log(show.constructor) // ? Function() { [native code] }

// 自定義構(gòu)造函數(shù)唇礁,無返回值
function Person(){
    this.name = name;
};
var p = new Person()
console.log(p.constructor) // ? Person()

// 有返回值
function Person(){
    this.name = name;
    return {
        name: 'yuguang'
    }
};
var p = Person()
console.log(p1.constructor) // ? Object() { [native code] }
1.4 模擬實現(xiàn)一個new

既然構(gòu)造函數(shù)與普通函數(shù)的區(qū)別僅僅在于調(diào)用方式上,我們就應該了解new惨篱。

  • 當調(diào)用new運算符時盏筐,該函數(shù)總會返回一個對象;
  • 通常情況下砸讳,構(gòu)造器里的this就指向返回的這個對象琢融;

代碼如下:

通常情況下
var MyClass = function(){
    this.name = 'yuguang';
};
var obj = new MyClass();
obj.name; // yuguang

特殊情況
var MyClass = function(){
    this.name = 'yuguang';
    return {
        name: '老王'
    }
};
var obj = new MyClass();
obj.name // 老王

我們利用 __proto__(隱式原型,下文會提到)屬性來模擬一下new 調(diào)用構(gòu)造函數(shù)的過程:

var objectNew = function(){
    // 從object.prototype上克隆一個空的對象
    var obj = new Object(); 
    // 取得外部傳入的構(gòu)造器簿寂,這里是Person
    var Constructor = [].shift.call( arguments );
    // 更新漾抬,指向正確的原型
    obj.__proto__ = Constructor.prototype; //知識點,要考常遂、要考纳令、要考 
    // 借用外部傳入的構(gòu)造器給obj設置屬性
    var ret = Constructor.apply(obj, arguments);
    // 確保構(gòu)造器總是返回一個對象
    return typeof ref === 'object' ? ret : obj;
}

二、原型

2.1 prototype(顯式原型)

JavaScript 是一種基于原型的語言 (prototype-based language)烈钞,在設計的時候模仿了Java的兩套類型機制:基本類型對象類型泊碑±ぐ矗可見原型很重要毯欣!

每個對象都擁有一個原型對象,類是以函數(shù)的形式來定義的臭脓。prototype表示該函數(shù)的原型酗钞,也表示一個類的成員的集合±蠢郏看下圖:


在這里插入圖片描述

可以發(fā)現(xiàn)Person函數(shù)自己的原型都有什么:

  • constructor (Person.prototype.constructor => Person)
  • __proto__ (我們稱它為隱式原型)

此時我們得到了第一張表示構(gòu)造函數(shù)和實例原型之間的關系圖:

構(gòu)造函數(shù)和實例原型的關系圖

那么我們該怎么表示實例與構(gòu)造函數(shù)原型砚作,也就是 person 和 Person.prototype 之間的關系呢,這時候我們就要講到第二個屬性:

2.2 proto(隱式原型)

這是每一個JavaScript對象(除了 null )都具有的一個屬性嘹锁,叫__proto__葫录,這是一個訪問器屬性(即 getter 函數(shù)和 setter 函數(shù)),通過它可以訪問到對象的內(nèi)部[[Prototype]] (一個對象或 null )领猾。

function Person() {}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true

于是我們更新下關系圖:

實例與實例原型的關系圖

小結(jié): 每個引用類型的隱式原型都指向它的構(gòu)造函數(shù)的顯式原型

2.3 constructor

前文提到了constructor米同,它與原型的關系也可以添加到這張圖里骇扇,更新下關系圖:

實例原型與構(gòu)造函數(shù)的關系圖

根據(jù)上圖的關系,下面這段的結(jié)果面粮,大家就一目了然了:

function Person() {}

var person = new Person();

console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 順便學習一個ES5的方法,可以獲得對象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true

接下來我們要繼續(xù)思考實例和原型的關系:

三少孝、實例與原型

當讀取實例的屬性時,如果找不到熬苍,就會查找與對象關聯(lián)的原型中的屬性稍走,如果還查不到,就去找原型的原型柴底,一直找到最頂層為止婿脸。這樣一個查找過程

舉個例子:

function Person() {}
Person.prototype.name = '老王';

var person = new Person();
person.name = '余光';

console.log(person.name) // 余光

delete person.name;
console.log(person.name) // 老王

在這個例子中,我們給實例對象 person 添加了 name 屬性似枕,當我們打印 person.name 的時候盖淡,結(jié)果自然為 余光(is me)。

描述:

但是當我們刪除了 person 的 name 屬性后凿歼,再次讀取 person.name褪迟,從 person 對象中找不到 name 屬性就會從 person 的原型也就是 person.proto ,也就是 Person.prototype中查找答憔,幸運的是我們找到了 name 屬性味赃,結(jié)果為 老王(這...)

總結(jié):

  • 嘗試遍歷實例a中的所有屬性,但沒有找到目標屬性虐拓;
  • 查找name屬性的這個請求被委托給該實例a的構(gòu)造器(A)的原型心俗,它被a.__proto__ 記錄著并且指向A.prototype;
  • A.prototype存在目標屬性蓉驹,返回他的值城榛;

但是萬一還沒有找到呢?原型的原型又是什么呢态兴?

四狠持、原型的原型

在前面,我們已經(jīng)講了原型也是一個對象瞻润,既然是對象喘垂,我們就可以用最原始的方式創(chuàng)建它,那就是:

var obj = new Object();
obj.name = '余光'
console.log(obj.name) // 余光

其實原型對象就是通過Object構(gòu)造函數(shù)生成的绍撞,結(jié)合之前所講正勒,實例的 __proto__ 指向構(gòu)造函數(shù)的 prototype ,可以理解成傻铣,Object.prototype()是所有對象的根對象章贞,所以我們再次更新下關系圖:

原型的原型關系圖

五、原型鏈

每個對象擁有一個原型對象非洲,通過 __proto__ 指針指向上一個原型 鸭限,并從中繼承方法和屬性就谜,同時原型對象也可能擁有原型,這樣一層一層里覆,最終指向 null丧荐。這種關系被稱為原型鏈 (prototype chain),通過原型鏈一個對象會擁有定義在其他對象中的屬性和方法喧枷。

這個鏈條存在著終點虹统,是因為:Object.prototype 的原型是——null,引用阮一峰老師的 《undefined與null的區(qū)別》 就是:

null 表示“沒有對象”隧甚,即該處不應該有值车荔。這句話也意味著 Object.prototype 沒有原型

我們最后更新一次關系圖,藍色線條就可以表示原型鏈這種關系戚扳。

原型鏈示意圖
補充忧便,易錯點

1.constructor
首先是 constructor 屬性,我們看個例子:

function Person() {}
var person = new Person();
console.log(person.constructor === Person); // true

當獲取 person.constructor 時帽借,其實 person 中并沒有 constructor 屬性,當不能讀取到constructor 屬性時珠增,會從 person 的原型也就是 Person.prototype 中讀取,正好原型中有該屬性砍艾,所以:

person.constructor === Person.prototype.constructor

2.__proto__

其次是 proto 蒂教,絕大部分瀏覽器都支持這個非標準的方法訪問原型,然而它并不存在于 Person.prototype 中脆荷,實際上凝垛,它是來自于 Object.prototype ,與其說是一個屬性蜓谋,不如說是一個 getter/setter梦皮,當使用 obj.proto 時,可以理解成返回了 Object.getPrototypeOf(obj)桃焕。

3.真的是繼承嗎剑肯?

最后是關于繼承,前面我們講到“每一個對象都會從原型‘繼承’屬性”覆旭,實際上退子,繼承是一個十分具有迷惑性的說法岖妄,引用《你不知道的JavaScript》中的話型将,就是:

繼承意味著復制操作,然而 JavaScript 默認并不會復制對象的屬性荐虐,相反七兜,JavaScript 只是在兩個對象之間創(chuàng)建一個關聯(lián),這樣福扬,一個對象就可以通過委托訪問另一個對象的屬性和函數(shù)腕铸,所以與其叫繼承惜犀,委托的說法反而更準確些。

總結(jié)

  • 使用new生成實例的函數(shù)就是構(gòu)造函數(shù)狠裹,直接調(diào)用的就是普通函數(shù)虽界;
  • 每個對象都擁有一個原型對象;
  • 每個引用類型的隱式原型都指向它的構(gòu)造函數(shù)的顯式原型涛菠;
  • Object.prototype是所有對象的根對象莉御;
  • 原型鏈存在終點,不會無限查找下去俗冻;

參考

少俠留步

這是我基礎進階的第二篇文章礁叔,如果對您有幫助,不妨收藏迄薄、點贊琅关、評論一下吧。同時本文章和我的其他文章都記錄在前端進階筆記上讥蔽,如果感興趣涣易,不妨點個star,防走丟哦~

關于我:

希望得到您的建議冶伞,如:

  • 關系圖的方式是否有幫助到你去理解知識點都毒?
  • 文章代碼示例是否有瑕疵?
  • 下一篇文章還要不要表情包了??~碰缔?
  • ...
結(jié)尾
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末账劲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子金抡,更是在濱河造成了極大的恐慌瀑焦,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梗肝,死亡現(xiàn)場離奇詭異榛瓮,居然都是意外死亡,警方通過查閱死者的電腦和手機巫击,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門禀晓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人坝锰,你說我怎么就攤上這事粹懒。” “怎么了顷级?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵凫乖,是天一觀的道長。 經(jīng)常有香客問我,道長帽芽,這世上最難降的妖魔是什么删掀? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮导街,結(jié)果婚禮上披泪,老公的妹妹穿的比我還像新娘。我一直安慰自己搬瑰,他們只是感情好付呕,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著跌捆,像睡著了一般徽职。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上佩厚,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天姆钉,我揣著相機與錄音,去河邊找鬼抄瓦。 笑死潮瓶,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的钙姊。 我是一名探鬼主播毯辅,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼煞额!你這毒婦竟也來了思恐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤膊毁,失蹤者是張志新(化名)和其女友劉穎胀莹,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體婚温,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡描焰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了栅螟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荆秦。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖力图,靈堂內(nèi)的尸體忽然破棺而出步绸,到底是詐尸還是另有隱情,我是刑警寧澤搪哪,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布靡努,位于F島的核電站,受9級特大地震影響晓折,放射性物質(zhì)發(fā)生泄漏惑朦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一漓概、第九天 我趴在偏房一處隱蔽的房頂上張望漾月。 院中可真熱鬧,春花似錦胃珍、人聲如沸梁肿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吩蔑。三九已至,卻和暖如春填抬,著一層夾襖步出監(jiān)牢的瞬間烛芬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工飒责, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赘娄,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓宏蛉,卻偏偏與公主長得像遣臼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拾并,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355