亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

理解 JavaScript(四)

cuieney / 1583人閱讀

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

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

原型

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

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

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

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

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

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

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

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

這說明了一個問題:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

對象的 __proto__ 屬性:我們叫它 原型

例如:

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

f 的原型 = f.__proto__

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

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

__proto__ 的問題

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

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

如果你確實(shí)要通過一個對象訪問其原型,ES5 提供了一個新方法:

Object.getPrototypeOf(f)    // Foo {}

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

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

由于對象的原型是一個引用而不是賦值,所以更改原型的屬性會立刻作用于所有的實(shí)例對象。這一特性非常適用于為對象定義實(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)識你!";
};

/* 觀察其影響:*/

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

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

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

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

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

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

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

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

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

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

小心 constructor

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

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

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

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

盡管我們說:更改原型對象的屬性會立即作用于所有的實(shí)例對象,但是如果你完全覆蓋了原型對象,事情就變得詭異起來了:(閱讀接下來的例子,請一句一句驗(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 方法呀?原型對象不是即時生效的嗎?

原型繼承

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

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

不過讓我們先從簡單的開始:

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

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;

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

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

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

我來捋一遍:

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

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

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

還沒完,繼續(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)了對 toString() 方法的重載(這個方法的始祖對象是 Object.prototype),秉承同樣的精神,我們自己寫的子構(gòu)造器同樣可以通過原型屬性來重載父構(gòu)造器提供的方法:

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

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

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

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

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

然而不這么做的理由也很簡單,如果你這么做了:

Programmer.prototype = Person.prototype;

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

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

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/87462.html

相關(guān)文章

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

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

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

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

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

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

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

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

    aikin 評論0 收藏0
  • JavaScript閉包和this綁定

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

    Airy 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<