iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 29
0
自我挑戰組

你為什麼不問問神奇 JavaScript 呢?系列 第 29

Day29 - 原型

在這之前一直看到 [[Prototype]]

在看過克服 JS 奇怪的地方,就有提到原型鍊
在了解這樣的概念之後,就深深的覺得 JavaScript 在語言界的獨特性。
是用不一樣的思維創造的語言。

Tony 覺得這是最重要的靈魂,一起來了解一下。

[[Prototype]]

[[Prototype]] 是物件的一個內部特性,單純是對另一個物件的參考。在建立物件時,[[Prototype]] 不會是 null。

BUT,空的 [[Prototype]] 還是存在,但不常見。

var myObject = {
    a: 2
};

myObject.a; // 2

經由 RHS 來找 a 時。
會用 [[Get]] 作業。

  1. 確認該物件是否有 a 屬性。
  2. 若不存在,用 [[Prototype]] 方法查找。

到底什麼是[[Prototype]]

var anotherObject = {
    a: 2
};
var myObject = Object.create(anotherObject);

myObject.a; // 2 

你會發現 myObject 其實沒有 a 的屬性,所以 [[Get]] 拿不到。
但是藉由 [[Prototype]] 往上一層尋找,在 anotherObject 上面找到了 a 屬性,就回傳這個值 2。

但是如果沒有這個值呢?
就會往全域找?不一定,因為他是沿著 [[Prototype]] 的串鏈找的,這個鍊稱為原型鍊 ( Prototype Chain )。

Object.create(..) 會在後面解說。
這邊請先當成 myObject 來自於 anotherObject,是目前 [[Prototype]] 會指向的位置。

以下兩個方法,都可以檢查屬性是否在串鏈上。

for ( var k in myObject ){
    console.log( "found: " + k );
}
// found: a
("a" in myObject ); // true

Object.prototype

但是原型鍊的終點是?
每一條正常的就是 Object.prototype,這是所有物件的起源。

設定與遮蔽 ( Shadowing ) 屬性

myObject.foo = "bar";

  1. 若有 foo 在 myObject 上。
    就是單純替 foo 屬性設定 "bar" 的值。
  2. 若沒有,[[Prototype]] 就會巡訪 ( traversed ),直到找到。
  3. 若找不到,就會在 myObject 創造 foo 屬性,並存值。

所以,就算更高更遠的原型鍊的彼方,還是有 foo 屬性會怎樣?
一樣,若有 foo 在 myObject 上。就設定值。
這個現象叫做遮蔽。

這時候出現了三種情況。
若在串鏈上更高的位置,找到了

  1. 一個名為 foo 的資料存取器。
    而且 沒有 被標示為唯讀 ( writable: true )
    結果:新的屬性 foo 會被加到 myObject 上。
  2. 一個名為 foo 的資料存取器。
    而且 被標示為唯讀 ( writable: false )
    2-1. 非嚴格模式
    結果:遮蔽不作用,無聲無息被忽略
    2-2. 在嚴格模式。
    結果:擲出錯誤
  3. 一個名為 foo 的設值器 ( setter )
    結果:不會新增 foo 屬性,不會重新定義 foo 設值器

正常只有第一種,也是一般覺得會發生的。
但第二第三種想要遮蔽呢?
請用 Object.defineProperty(..) 新增 foo 屬性。


遮蔽可能隱含的發生,要小心。如下程式碼。

var anotherObject = {
    a: 2
};
var myObject = Object.create( anotherObject );

anotherObject.a; // 2
myObject.a;      // 2

myObject.hasOwnProperty( "a" ); // false 這邊為止都正常。

myObjcet.a++;    // oops!

anotherObject.a; // 2
myObject.a;      // 3 加到自己本身?!

myObject.hasOwnProperty( "a" ); // true!

因為 myObject.a++ 請看成 myObject.a = myObject.a + 1

  1. [[Get]] 經由 [[Prototype]] 找到 a 屬性,取得anotherObject 上 2 的值。
  2. 將值遞增為 3
  3. [[Put]] 指定值到 myObject 新增的 a 屬性。

若想遞增 anotherObject.a,就 anotherObject.a++ 吧。


「 類別 」

JavaScript 沒有類別。
是非常少數能夠創建一個物件,但完全不用類別的語言之一。

類別函式

先說,這是一個神奇的行為,但因為巧合,就大家一直用。
其實是背後的邏輯恰好碰上。

function foo() {
    // ...
}
Foo.prototype; // { }

這個物件被稱為 Foo 的原型 ( prototype )。
但其實是命名恰好碰上用 prototype。( 那為什麼不設成關鍵字呢? )

那他是什麼?

var a = new Foo();
Object.getPrototypeOf( a ) === Foo.prototype; // true

在 new 得時候。

  1. 會產生空物件
  2. 新物件會帶有 [[Prototype]] 的連結。
  3. 產生 new this 繫結
  4. 若沒有回傳值,就回傳物件以及傳入的值。

[[Prototype]] 連結已產生,是連結到 Foo.prototype。

參考資料

你所不知道的JS


上一篇
Day28 - 用 JS 做多重繼承?
下一篇
Day30 - 原型繼承
系列文
你為什麼不問問神奇 JavaScript 呢?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言