成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

理解 JavaScript(四)

cuieney / 1552人閱讀

摘要:其工作原理我已經(jīng)在第一篇做了大部分的闡述我尚未提及的是在創(chuàng)建新對(duì)象的時(shí)候,會(huì)賦予新對(duì)象一個(gè)屬性指向構(gòu)造器的屬性。

第四篇拖了很久了,真是有點(diǎn)不好意思。實(shí)話實(shí)說(shuō),拖延很久的原因主要是沒(méi)想好怎么寫(xiě),因?yàn)檫@一篇的主題比較有挑戰(zhàn)性:原型和基于原型的繼承——啊~我終于說(shuō)出口了,這下沒(méi)借口拖延了==

原型

我(個(gè)人)不喜歡的,就是講原型時(shí)上來(lái)就拿類(lèi)做比較的,所以我不會(huì)這樣講。不過(guò)我的確講過(guò)構(gòu)造器函數(shù),在這方面和類(lèi)多多少少有共通之處。我的建議是:忘掉類(lèi)。有很多觀點(diǎn)認(rèn)為“類(lèi)”學(xué)的泛濫是面向?qū)ο蟮倪^(guò)度發(fā)展,是一種悲哀,以至于有太多的開(kāi)發(fā)者幾乎把面向?qū)ο蠛皖?lèi)劃上了等號(hào)。在學(xué)習(xí)原型之前,我請(qǐng)你先記住并品味這句話:

面向?qū)ο笤O(shè)計(jì)的精髓在于“抽象”二字,類(lèi)是實(shí)現(xiàn)實(shí)體抽象的一種手段,但不是唯一一種。
prototype__proto__

事先聲明:永遠(yuǎn),永遠(yuǎn)不要在真實(shí)的代碼里使用 __proto__ 屬性,在本文里用它純粹是用于研究!很快我們會(huì)講到它的替代品,抱歉請(qǐng)忍耐。

在 JavaScript 里,函數(shù)是對(duì)象(等學(xué)完了這一篇,不妨研究一下函數(shù)究竟是怎么就成了對(duì)象的?),對(duì)象嘛,毫無(wú)意外的就會(huì)有屬性(方法也是屬性),然后毫無(wú)意外的 prototype 就是函數(shù)的一個(gè)屬性,最后毫無(wú)意外的 prototype 屬性也是一個(gè)對(duì)象。瞧,多么順理成章的事情:

function foo() {}
foo.prototype;    // 里面有啥自己去看

好吧,那 prototype 有啥用?呃,如果你把函數(shù)就當(dāng)做函數(shù)來(lái)用,那它壓根沒(méi)用。不過(guò),若你把函數(shù)當(dāng)作構(gòu)造器來(lái)用的話,新生成的對(duì)象就可以直接訪問(wèn)到 prototype 對(duì)象里的屬性。

// 要充當(dāng)構(gòu)造器了,按慣例把首字母大寫(xiě)
function Foo() {}
var f = new Foo();
f.constructor;    // function Foo() {}

想一下,fconstructor 屬性哪里來(lái)的?如果你想不明白,請(qǐng)用 console.dir(Foo.prototype) 一探究竟。

這說(shuō)明了一個(gè)問(wèn)題:

函數(shù)的原型屬性不是給函數(shù)自己用的,而是給用函數(shù)充當(dāng)構(gòu)造器創(chuàng)建的對(duì)象使用的。

令人疑惑的是,prototype 屬性存在于 Foo 函數(shù)對(duì)象內(nèi),那么由 Foo 創(chuàng)建的實(shí)例對(duì)象 f 是怎么訪問(wèn)到 prototype 的呢?是通過(guò)復(fù)制 prototype 對(duì)象嗎?接著上面的代碼我們繼續(xù)來(lái)看:

f.__proto__;                      // Foo {}
Foo.prototype;                    // Foo {}
f.__proto__ === Foo.prototype;    // true

哦~不是復(fù)制過(guò)來(lái)的,而是一個(gè)叫做 __proto__ 的屬性指向了構(gòu)造器的 prototype 對(duì)象呀。

沒(méi)錯(cuò)!這就是原型機(jī)制的精髓所在,讓我們來(lái)總結(jié)一下所有的細(xì)節(jié)(包括隱含在表象之下的):

函數(shù)擁有 prototype 屬性,但是函數(shù)自己不用它

函數(shù)充當(dāng)構(gòu)造器的時(shí)候可以創(chuàng)建出新的對(duì)象,這需要 new 操作符的配合。其工作原理我已經(jīng)在第一篇做了大部分的闡述

我尚未提及的是:new 在創(chuàng)建新對(duì)象的時(shí)候,會(huì)賦予新對(duì)象一個(gè)屬性指向構(gòu)造器的 prototype 屬性。這個(gè)新的屬性在某些瀏覽器環(huán)境內(nèi)叫做 __proto__

當(dāng)訪問(wèn)一個(gè)對(duì)象的屬性(包括方法)時(shí),首先查找這個(gè)對(duì)象自身有沒(méi)有該屬性,如果沒(méi)有就查找它的原型(也就是 __proto__ 指向的 prototype 對(duì)象),如果還沒(méi)有就查找原型的原型(prototype 也有它自己的 __proto__,指向更上一級(jí)的 prototype 對(duì)象),依此類(lèi)推一直找到 Object 為止

OK,上面的第四點(diǎn)事實(shí)上就是 JavaScript 的對(duì)象屬性查找機(jī)制。由此可見(jiàn):

原型的意義就在于為對(duì)象的屬性查找機(jī)制提供一個(gè)方向,或者說(shuō)一條路線

一個(gè)對(duì)象,它有許多屬性,其中有一個(gè)屬性指向了另外一個(gè)對(duì)象的原型屬性;而后者也有一個(gè)屬性指向了再另外一個(gè)對(duì)象的原型屬性。這就像一條一環(huán)套一環(huán)的鎖鏈一樣,并且從這條鎖鏈的任何一點(diǎn)尋找下去,最后都能找到鏈條的起點(diǎn),即 Object;因此,我們也把這種機(jī)制稱(chēng)作:原型鏈。

現(xiàn)在,我希望統(tǒng)一一下所使用的術(shù)語(yǔ)(至少在本文范圍內(nèi)):

函數(shù)的 prototype 屬性:我們叫它 原型屬性原型對(duì)象

對(duì)象的 __proto__ 屬性:我們叫它 原型

例如:

Foo 的原型屬性(或原型對(duì)象) = Foo.prototype

f 的原型 = f.__proto__

統(tǒng)一術(shù)語(yǔ)的原因在于,盡管 Foo.prototypef.__proto__ 是等價(jià)的,但是 prototype__proto__ 并不一樣。當(dāng)考慮一個(gè)固定的對(duì)象時(shí),它的 prototype 是給原型鏈的下方使用的,而它的 __proto__ 則指向了原型鏈的上方;因此,一旦我們說(shuō)“原型屬性”或者“原型對(duì)象”,那么就暗示著這是給它的子子孫孫們用的,而說(shuō)“原型”則是暗示這是從它的父輩繼承過(guò)來(lái)的。

再換一種說(shuō)法:對(duì)象的原型屬性或原型對(duì)象不是給自己用的,而對(duì)象的原型是可以直接使用的。

__proto__ 的問(wèn)題

既然 __proto__ 可以訪問(wèn)到對(duì)象的原型,那么為什么禁止在實(shí)際中使用呢?

這是一個(gè)設(shè)計(jì)上的失誤,導(dǎo)致 __proto__ 屬性是可以被修改的,同時(shí)意味著 JavaScript 的屬性查找機(jī)制會(huì)因此而“癱瘓”,所以強(qiáng)烈的不建議使用它。

如果你確實(shí)要通過(guò)一個(gè)對(duì)象訪問(wèn)其原型,ES5 提供了一個(gè)新方法:

Object.getPrototypeOf(f)    // Foo {}

這是安全的,盡管放心使用??紤]到低版本瀏覽器的兼容性問(wèn)題,可以使用 es5-shim

自有屬性和原型屬性的區(qū)別

由于對(duì)象的原型是一個(gè)引用而不是賦值,所以更改原型的屬性會(huì)立刻作用于所有的實(shí)例對(duì)象。這一特性非常適用于為對(duì)象定義實(shí)例方法:

function Person(name) {
    this.name = name;
}

Person.prototype.greeting = function () {
    return "你好,我叫" + this.name;
};

var p1 = new Person("張三");
var p2 = new Person("李四");

p1.greeting();    // 你好,我叫張三
p2.greeting();    // 你好,我叫李四

/* 改變實(shí)例方法的行為:*/

Person.prototype.greeting = function () {
    return "你好,我叫" + this.name + ",很高興認(rèn)識(shí)你!";
};

/* 觀察其影響:*/

p1.greeting();    // 你好,我叫張三,很高興認(rèn)識(shí)你!
p2.greeting();    // 你好,我叫李四,很高興認(rèn)識(shí)你!

然而,改變自有屬性則不同,它只會(huì)對(duì)新創(chuàng)建的實(shí)例對(duì)象產(chǎn)生影響,接上例:

function Person(name) {
    this.name = "超人";
}

/* 不影響已存在的實(shí)例對(duì)象 */
p1.greeting();    // 你好,我叫張三,很高興認(rèn)識(shí)你!

/* 只影響新創(chuàng)建的實(shí)例對(duì)象 */
var p3 = new Person("王五");
p3.greeting();    // 你好,我叫超人,很高興認(rèn)識(shí)你!

這個(gè)例子看起來(lái)有點(diǎn)無(wú)厘頭,沒(méi)啥大用,不過(guò)它的精神在于:在現(xiàn)實(shí)世界中,復(fù)雜對(duì)象的行為或許會(huì)根據(jù)情況對(duì)其進(jìn)行重寫(xiě),但是我們不希望改變對(duì)象的內(nèi)部狀態(tài);或者,我們會(huì)實(shí)現(xiàn)繼承,去覆蓋父級(jí)對(duì)象的某些行為而不引向其他相同的部分。在這些情況下,原型會(huì)給予我們最大程度的靈活性。

我們?nèi)绾沃缹傩允亲杂械倪€是來(lái)自于原型的?上代碼~

p1.hasOwnProperty("name");        // true
p1.hasOwnProperty("greeting");    // false

p1.constructor.prototype.hasOwnProperty("greeting");     // true
Object.getPrototypeOf(p1).hasOwnProperty("greeting");    // true

代碼很簡(jiǎn)單,就不用過(guò)度解釋了,注意最后兩句實(shí)際上等價(jià)的寫(xiě)法。

小心 constructor

剛才的這一句代碼:p1.constructor.prototype.hasOwnProperty("greeting");,其實(shí)暗含了一個(gè)有趣的問(wèn)題。

對(duì)象 p1 能夠訪問(wèn)自己的構(gòu)造器,這要謝謝原型為它提供了 constructor 屬性。接著通過(guò) constructor 屬性又可以反過(guò)來(lái)訪問(wèn)到原型對(duì)象,這似乎是一個(gè)圈圈,我們來(lái)試驗(yàn)一下:

p1.constructor === p1.constructor.prototype.constructor;    // true
p1.constructor === p1.constructor.prototype.constructor.prototype.constructor;    // true

還真是!不過(guò)我們不是因?yàn)楹猛娌叛芯窟@個(gè)的。

盡管我們說(shuō):更改原型對(duì)象的屬性會(huì)立即作用于所有的實(shí)例對(duì)象,但是如果你完全覆蓋了原型對(duì)象,事情就變得詭異起來(lái)了:(閱讀接下來(lái)的例子,請(qǐng)一句一句驗(yàn)證自己心中所想)

function Person(name) {
    this.name = name;
}

var p1 = new Person("張三");

Person.prototype.greeting = function () {
    return "你好,我叫" + this.name;
};

p1.name;                      // 張三
p1.greeting();                // 你好,我叫張三
p1.constructor === Person;    // true

/* so far so good, but... */

Person.prototype = {
    say: function () {
        return "你好,我叫" + this.name;
    }
};

p1.say();                    // TypeError: Object # has no method "say"
p1.constructor.prototype;    // Object { say: function }

呃?Person 的原型屬性里明明有 say 方法呀?原型對(duì)象不是即時(shí)生效的嗎?

原型繼承

若是只為了創(chuàng)建一種對(duì)象,原型的作用就無(wú)法全部發(fā)揮出來(lái)。我們會(huì)進(jìn)一步利用原型和原型鏈的特性來(lái)拓展我們的代碼,實(shí)現(xiàn)基于原型的繼承。

原型繼承是一個(gè)非常大的話題范圍,慢慢地你會(huì)發(fā)現(xiàn),盡管原型繼承看起來(lái)沒(méi)有類(lèi)繼承那么的規(guī)整(相對(duì)而言),但是它卻更加靈活。無(wú)論是單繼承還是多繼承,甚至是 Mixin 及其他連名字都說(shuō)不上來(lái)的繼承方式,原型繼承都有辦法實(shí)現(xiàn),并且往往不止一種辦法。

不過(guò)讓我們先從簡(jiǎn)單的開(kāi)始:

function Person() {
    this.klass = "人類(lèi)";
}

Person.prototype.toString = function () {
    return this.klass;
};

Person.prototype.greeting = function () {
    return "大家好,我叫" + this.name + ", 我是一名" + this.toString() + "。";
};

function Programmer(name) {
    this.name = name;
    this.klass = "程序員";
}

Programmer.prototype = new Person();
Programmer.prototype.constructor = Programmer;

這是一個(gè)非常好的例子,它向我們揭示了以下要點(diǎn):

var someone = new Programmer("張三");

someone.name;          // 張三
someone.toString();    // 程序員
someone.greeting();    // ?大家好,我叫張三, 我是一名程序員。

我來(lái)捋一遍:

倒數(shù)第二行,new Person() 創(chuàng)建了對(duì)象,然后賦給了 Programmer.prototype 于是構(gòu)造器原型屬性就變成了 Person 的實(shí)例對(duì)象。

因?yàn)?Person 對(duì)象擁有重寫(xiě)過(guò)的 toString() 方法,并且這個(gè)方法返回的是宿主對(duì)象的 klass 屬性,所以我們可以給 Programmer 定義一個(gè) greeting() 方法,并在其中使用繼承而來(lái)的 toString()

當(dāng) someone 對(duì)象調(diào)用 toString() 方法的時(shí)候,this 指向的是它自己,所以能夠輸出 程序員 而不是 人類(lèi)

還沒(méi)完,繼續(xù)看:

// 因?yàn)?Programmer.prototype.constructor = Programmer; 我們才能得到:
someone.constructor === Programmer;    ?// true

// 這些結(jié)果體現(xiàn)了何謂“鏈?zhǔn)健痹屠^承
??someone instanceof Programmer;         ?// true
??someone instanceof Person;             //? true
??someone instanceof Object;             ?// true
方法重載

上例其實(shí)已經(jīng)實(shí)現(xiàn)了對(duì) toString() 方法的重載(這個(gè)方法的始祖對(duì)象是 Object.prototype),秉承同樣的精神,我們自己寫(xiě)的子構(gòu)造器同樣可以通過(guò)原型屬性來(lái)重載父構(gòu)造器提供的方法:

Programmer.prototype.toString = function () {
    return this.klass + "(碼農(nóng))";
}

var codingFarmer = new Programmer("張三");
codingFarmer.greeting();    // 大家好,我叫張三, 我是一名程序員(碼農(nóng))。
屬性查找與方法重載的矛盾

思維活躍反應(yīng)快的同學(xué)或許已經(jīng)在想了:

為什么一定要把父類(lèi)的實(shí)例賦給子類(lèi)的原型屬性,而不是直接用父類(lèi)的原型屬性呢?

好問(wèn)題!這個(gè)想法非常有道理,而且這么一來(lái)我們還可以減少屬性查找的次數(shù),因?yàn)橄蛏喜檎业臅r(shí)候跳過(guò)了父類(lèi)實(shí)例的 __proto__,直接找到了(如上例)Person.prototype。

然而不這么做的理由也很簡(jiǎn)單,如果你這么做了:

Programmer.prototype = Person.prototype;

由于 Javascript 是引用賦值,因此等號(hào)兩端的兩個(gè)屬性等于指向了同一個(gè)對(duì)象,那么一旦你在子類(lèi)對(duì)方法進(jìn)行重載,連帶著父類(lèi)的方法也一起變化了,這就失去了重載的意義。因此只有在確定不需要重載的時(shí)候才可以這么做。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/87462.html

相關(guān)文章

  • JavaScript系列() - 收藏集 - 掘金

    摘要:函數(shù)式編程前端掘金引言面向?qū)ο缶幊桃恢币詠?lái)都是中的主導(dǎo)范式。函數(shù)式編程是一種強(qiáng)調(diào)減少對(duì)程序外部狀態(tài)產(chǎn)生改變的方式。 JavaScript 函數(shù)式編程 - 前端 - 掘金引言 面向?qū)ο缶幊桃恢币詠?lái)都是JavaScript中的主導(dǎo)范式。JavaScript作為一門(mén)多范式編程語(yǔ)言,然而,近幾年,函數(shù)式編程越來(lái)越多得受到開(kāi)發(fā)者的青睞。函數(shù)式編程是一種強(qiáng)調(diào)減少對(duì)程序外部狀態(tài)產(chǎn)生改變的方式。因此,...

    cfanr 評(píng)論0 收藏0
  • JavaScript標(biāo)準(zhǔn)庫(kù)系列——三大包裝對(duì)象(

    摘要:目錄導(dǎo)語(yǔ)包裝對(duì)象的理解三大包裝對(duì)象的知識(shí)點(diǎn)小結(jié)導(dǎo)語(yǔ)包裝對(duì)象是為了彌補(bǔ)基本數(shù)據(jù)類(lèi)型的非對(duì)象特性而產(chǎn)生的,對(duì)于基本類(lèi)型值而言,本來(lái)是不存在屬性和方法的,但是我們可以在使用字面量創(chuàng)建字符串時(shí),調(diào)用例如的方法,那么其內(nèi)在原理究竟是什么呢閱讀完本篇文 目錄 導(dǎo)語(yǔ) 1. 包裝對(duì)象的理解 2. 三大包裝對(duì)象的知識(shí)點(diǎn) 3. 小結(jié) 導(dǎo)語(yǔ) 包裝對(duì)象是為了彌補(bǔ)基本數(shù)據(jù)類(lèi)型的非對(duì)象特性而產(chǎn)生的,對(duì)于基本類(lèi)型...

    sean 評(píng)論0 收藏0
  • WebAssembly 系列()WebAssembly 工作原理

    摘要:但是它們其實(shí)并不是二選一的關(guān)系并不是只能用或者。正因?yàn)槿绱耍噶钣袝r(shí)也被稱(chēng)為虛擬指令。這是因?yàn)槭遣捎没跅5奶摂M機(jī)的機(jī)制。聲明模塊的全局變量。。下文預(yù)告現(xiàn)在你已經(jīng)了解了模塊的工作原理,下面將會(huì)介紹為什么運(yùn)行的更快。 作者:Lin Clark 編譯:胡子大哈 翻譯原文:http://huziketang.com/blog/posts/detail?postId=58c77641a6d8...

    stormzhang 評(píng)論0 收藏0
  • 前端基礎(chǔ)進(jìn)階():詳細(xì)圖解作用域鏈與閉包

    摘要:之前一篇文章我們?cè)敿?xì)說(shuō)明了變量對(duì)象,而這里,我們將詳細(xì)說(shuō)明作用域鏈。而的作用域鏈,則同時(shí)包含了這三個(gè)變量對(duì)象,所以的執(zhí)行上下文可如下表示。下圖展示了閉包的作用域鏈。其中為當(dāng)前的函數(shù)調(diào)用棧,為當(dāng)前正在被執(zhí)行的函數(shù)的作用域鏈,為當(dāng)前的局部變量。 showImg(https://segmentfault.com/img/remote/1460000008329355);初學(xué)JavaScrip...

    aikin 評(píng)論0 收藏0
  • JavaScript閉包和this綁定

    摘要:首先來(lái)講講阮一峰的文章中的兩道思考題。環(huán)境記錄包含包含了函數(shù)內(nèi)部聲明的局部變量和參數(shù)變量,外部引用指向了外部函數(shù)對(duì)象的上下文執(zhí)行場(chǎng)景。 本文最主要講講JavaScript閉包和this綁定相關(guān)的我的小發(fā)現(xiàn),鑒于這方面的基礎(chǔ)知識(shí)已經(jīng)有很多很好的文章講過(guò)了,所以基本的就不講了,推薦看看酷殼上的理解Javascript的閉包和阮一峰的學(xué)習(xí)Javascript閉包(Closure),寫(xiě)的都非常...

    Airy 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<