精品伊人久久大香线蕉,开心久久婷婷综合中文字幕,杏田冲梨,人妻无码aⅴ不卡中文字幕

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
對象原型

通過原型這種機制,JavaScript 中的對象從其他對象繼承功能特性;這種繼承機制與經典的面向對象編程語言的繼承機制不同。本文將探討這些差別,解釋原型鏈如何工作,并了解如何通過 prototype 屬性向已有的構造器添加方法

預備知識: 基本的計算機素養,對 HTML 和 CSS 有基本的理解,熟悉 JavaScript 基礎(參見 First stepsBuilding blocks)以及面向對象的JavaScript (OOJS) 基礎(參見 Introduction to objects)。
目標: 理解 JavaScript 對象原型、原型鏈如何工作、如何向 prototype 屬性添加新的方法。

基于原型的語言?

JavaScript 常被描述為一種基于原型的語言 (prototype-based language)——每個對象擁有一個原型對象,對象以其原型為模板、從原型繼承方法和屬性。原型對象也可能擁有原型,并從中繼承方法和屬性,一層一層、以此類推。這種關系常被稱為原型鏈 (prototype chain),它解釋了為何一個對象會擁有定義在其他對象中的屬性和方法。

準確地說,這些屬性和方法定義在 Object 的構造器函數之上,而非對象實例本身。

在傳統的 OOP 中,首先定義“類”,此后創建對象實例時,類中定義的所有屬性和方法都被復制到實例中。在 JavaScript 中并不如此復制——而是在對象實例和它的構造器之間建立一個連接(作為原型鏈中的一節),以后通過上溯原型鏈,在構造器中找到這些屬性和方法。

以上描述很抽象;我們先看一個例子。

理解原型對象

讓我們回到 Person() 構造器的例子。請把這個例子載入瀏覽器。如果你還沒有看完上一篇文章并寫好這個例子,也可以使用 oojs-class-further-exercises.html 中的例子(亦可參考源代碼)。

本例中我們將定義一個構造器函數:

function Person(first, last, age, gender, interests) { // 屬性與方法定義 };

然后創建一個對象實例:

var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);

在 JavaScript 控制臺輸入 'person1.',你會看到,瀏覽器將根據這個對象的可用的成員名稱進行自動補全:

在這個列表中,你可以看到定義在 person1 的原型對象、即 Person() 構造器中的成員—— name、age、genderinterestsbiogreeting。同時也有一些其他成員—— watch、valueOf 等等——這些成員定義在 Person() 構造器的原型對象、即 Object 之上。下圖展示了原型鏈的運作機制。

那么,調用 person1 的“實際定義在 Object 上”的方法時,會發生什么?比如:

person1.valueOf()

這個方法僅僅返回了被調用對象的值。在這個例子中發生了如下過程:

  • 瀏覽器首先檢查,person1 對象是否具有可用的 valueOf() 方法。
  • 如果沒有,則瀏覽器檢查 person1 對象的原型對象(即 Person)是否具有可用的 valueof() 方法。
  • 如果也沒有,則瀏覽器檢查 Person() 構造器的原型對象(即 Object)是否具有可用的 valueOf() 方法。Object 具有這個方法,于是該方法被調用,

注意:必須重申,原型鏈中的方法和屬性沒有被復制到其他對象——它們被訪問需要通過前面所說的“原型鏈”的方式。

注意:沒有官方的方法用于直接訪問一個對象的原型對象——原型鏈中的“連接”被定義在一個內部屬性中,在 JavaScript 語言標準中用 [[prototype]] 表示(參見 ECMAScript)。然而,大多數現代瀏覽器還是提供了一個名為 __proto__ (前后各有2個下劃線)的屬性,其包含了對象的原型。你可以嘗試輸入 person1.__proto__person1.__proto__.__proto__,看看代碼中的原型鏈是什么樣的!

prototype 屬性:繼承成員被定義的地方

那么,那些繼承的屬性和方法在哪兒定義呢?如果你查看 Object 參考頁,會發現左側列出許多屬性和方法——大大超過我們在 person1 對象中看到的繼承成員的數量。某些屬性或方法被繼承了,而另一些沒有——為什么呢?

原因在于,繼承的屬性和方法是定義在 prototype 屬性之上的(你可以稱之為子命名空間 (sub namespace) )——那些以 Object.prototype. 開頭的屬性,而非僅僅以 Object. 開頭的屬性。prototype 屬性的值是一個對象,我們希望被原型鏈下游的對象繼承的屬性和方法,都被儲存在其中。

于是 Object.prototype.watch()、Object.prototype.valueOf() 等等成員,適用于任何繼承自 Object() 的對象類型,包括使用構造器創建的新的對象實例。

Object.is()、Object.keys(),以及其他不在 prototype 對象內的成員,不會被“對象實例”或“繼承自 Object() 的對象類型”所繼承。這些方法/屬性僅能被 Object() 構造器自身使用。

注意:這看起來很奇怪——構造器本身就是函數,你怎么可能在構造器這個函數中定義一個方法呢?其實函數也是一個對象類型,你可以查閱 Function() 構造器的參考文檔以確認這一點。

  1. 你可以檢查已有的 prototype 屬性。回到先前的例子,在 JavaScript 控制臺輸入:
    Person.prototype
  2. 輸出并不多,畢竟我們沒有為自定義構造器的原型定義任何成員。缺省狀態下,構造器的 prototype 屬性初始為空白?,F在嘗試:
    Object.prototype

你會看到 Objectprototype 屬性上定義了大量的方法;如前所示,繼承自 Object 的對象都可以使用這些方法。

JavaScript 中到處都是通過原型鏈繼承的例子。比如,你可以嘗試從 String、Date、NumberArray 全局對象的原型中尋找方法和屬性。它們都在原型上定義了一些方法,因此當你創建一個字符串時:

var myString = 'This is my string.';

myString 立即具有了一些有用的方法,如 split()、indexOf()、replace() 等。

重要prototype 屬性大概是 JavaScript 中最容易混淆的名稱之一。你可能會認為,這個屬性指向當前對象的原型對象,其實不是(還記得么?原型對象是一個內部對象,應當使用 __proto__ 訪問)。prototype 屬性包含(指向)一個對象,你在這個對象中定義需要被繼承的成員。

create()

我們曾經講過如何用 Object.create() 方法創建新的對象實例。

  1. 例如,在上個例子的 JavaScript 控制臺中輸入:
    var person2 = Object.create(person1);
  2. create() 實際做的是從指定原型對象創建一個新的對象。這里以 person1 為原型對象創建了 person2 對象。在控制臺輸入:
    person2.__proto__

結果返回 person1 對象。

constructor 屬性

每個對象實例都具有 constructor 屬性,它指向創建該實例的構造器函數。

  1. 例如,在控制臺中嘗試下面的指令:
    person1.constructorperson2.constructor

    都將返回 Person() 構造器,因為該構造器包含這些實例的原始定義。

    一個小技巧是,你可以在 constructor 屬性的末尾添加一對圓括號(括號中包含所需的參數),從而用這個構造器創建另一個對象實例。畢竟構造器是一個函數,故可以通過圓括號調用;只需在前面添加 new 關鍵字,便能將此函數作為構造器使用。

  2. 在控制臺中輸入:
    var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);
  3. 現在嘗試訪問新建對象的屬性,例如:
    person3.name.firstperson3.ageperson3.bio()

正常工作。通常你不會去用這種方法創建新的實例;但如果你剛好因為某些原因沒有原始構造器的引用,那么這種方法就很有用了。

此外,constructor 屬性還有其他用途。比如,想要獲得某個對象實例的構造器的名字,可以這么用:

instanceName.constructor.name

具體地,像這樣:

person1.constructor.name

修改原型

從我們從下面這個例子來看一下如何修改構造器的 prototype 屬性。

  1. 回到 oojs-class-further-exercises.html 的例子,在本地為源代碼創建一個副本。在已有的 JavaScript 的末尾添加如下代碼,這段代碼將為構造器的 prototype 屬性添加一個新的方法:
    Person.prototype.farewell = function() { alert(this.name.first + ' has left the building. Bye for now!');}
  2. 保存代碼,在瀏覽器中加載頁面,然后在控制臺輸入:
    person1.farewell();

你會看到一條警告信息,其中還顯示了構造器中定義的人名;這很有用。但更關鍵的是,整條繼承鏈動態地更新了,任何由此構造器創建的對象實例都自動獲得了這個方法。

再想一想這個過程。我們的代碼中定義了構造器,然后用這個構造器創建了一個對象實例,此后向構造器的 prototype 添加了一個新的方法:

function Person(first, last, age, gender, interests) { // 屬性與方法定義};var person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']);Person.prototype.farewell = function() { alert(this.name.first + ' has left the building. Bye for now!');}

但是 farewell() 方法仍然可用于 person1 對象實例——舊有對象實例的可用功能被自動更新了。這證明了先前描述的原型鏈模型。這種繼承模型下,上游對象的方法不會復制到下游的對象實例中;下游對象本身雖然沒有定義這些方法,但瀏覽器會通過上溯原型鏈、從上游對象中找到它們。這種繼承模型提供了一個強大而可擴展的功能系統。

注意:如果運行樣例時遇到問題,請參閱 oojs-class-prototype.html 樣例(也可查看即時運行)。

你很少看到屬性定義在 prototype 屬性中,因為如此定義不夠靈活。比如,你可以添加一個屬性:

Person.prototype.fullName = 'Bob Smith';

但這不夠靈活,因為人們可能不叫這個名字。用 name.firstname.last 組成 fullName 會好很多:

Person.prototype.fullName = this.name.first + ' ' + this.name.last;

然而,這么做是無效的,因為本例中 this 引用全局范圍,而非函數范圍。訪問這個屬性只會得到 undefined undefined。但這個語句若放在先前定義的 prototype 的方法中則有效,因為此時語句位于函數范圍內,從而能夠成功地轉換為對象實例范圍。你可能會在 prototype 上定義常屬性 (constant property) (指那些你永遠無需改變的屬性),但一般來說,在構造器內定義屬性更好。

譯者注:關于 this 關鍵字指代(引用)什么范圍/哪個對象,這個問題超出了本文討論范圍。事實上,這個問題有點復雜,如果現在你沒能理解,也不用擔心。

事實上,一種極其常見的對象定義模式是,在構造器(函數體)中定義屬性、在 prototype 屬性上定義方法。如此,構造器只包含屬性定義,而方法則分裝在不同的代碼塊,代碼更具可讀性。例如:

// 構造器及其屬性定義function Test(a,b,c,d) { // 屬性定義};// 定義第一個方法Test.prototype.x = function () { ... }// 定義第二個方法Test.prototype.y = function () { ... }// 等等……

在 Piotr Zalewa 的 school plan app 樣例中可以看到這種模式。

總結

本文介紹了 JavaScript 對象原型,包括原型鏈如何允許對象之間繼承特性、prototype 屬性、如何通過它來向構造器添加方法,以及其他有關主題。

下一篇文章中,我們將了解如何在兩個自定義的對象間實現功能的繼承。

本站僅提供存儲服務,所有內容均由用戶發布,如發現有害或侵權內容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Javascript輕松理解prototype
JavaScript中定義對象的四種方式
JavaScript學習筆記【4】類和模塊、子集和擴展
類和模塊
js構造函數的方法與原型prototype
JavaScript中的原型和原型鏈
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯系客服!

聯系客服

主站蜘蛛池模板: 高雄市| 宁乡县| 常熟市| 从化市| 即墨市| 洪湖市| 彰化市| 桐柏县| 弥勒县| 花莲市| 潮州市| 皮山县| 太湖县| 涟源市| 加查县| 密云县| 昭通市| 额敏县| 页游| 遂川县| 荥经县| 宜川县| 布尔津县| 大兴区| 白水县| 白河县| 城口县| 什邡市| 濮阳县| 阜南县| 霞浦县| 永安市| 明水县| 鄂尔多斯市| 沅江市| 光山县| 明光市| 青龙| 青川县| 新泰市| 甘南县|