JavaScript 原型式物件導向

Read From

You Don't Know JS: Prototypes

Prototype

  • [[Prototype]]
  • .__proto__
  • .prototype

[[Prototype]] link

JavaScript 的內部參考特性

代表物件對 "上層" 物件的參考

[[Prototype]] link

  • every object has [[Prototype]]
    • except Object.create(null)
  • Top of [[Prototype]]
    • usualy Object.prototype
  • Usage:
    • for Property lookup
{}

._proto_

  • Just like [[Prototype]]
  • Not for every browser

.prototype

  • every function has one
  • instance's [[Prototype]] will link to .prototype

._proto_ vs .prototype

.prototype .__proto__
For function (constructor) For instances

Prototype Chain

  • think as "Property lookup"
  • use when Key is not on object

Prototype Chain - [[Get]]

  • 查找該物件上是否有同名 key,一找到則:
    • 如果是正常的資料存取器就 return value
    • 如果有設定存取器描述器,就執行 Getter Function
  • 若沒有,往 [[Prototype]] 鏈結 尋找是否有同名 key,一找到則:
    • 如果是正常的資料存取器就 return value
    • 如果有設定存取器描述器,就執行 Getter Function
  • 到達 Prototype Chain 頂端都沒有,則回傳 undefined

Prototype Chain - [[Put]]

  • 查找該物件上是否有同名 key,一找到則檢查:
    • 是否為 setter,是的話執行
    • writable 是否為 false,是的話失敗
    • 都不是的話就正常賦值
  • 若沒有,往 [[Prototype]] 鏈結尋找是否有同名 key,一找到則:
    • 是否為 setter,是的話永遠 都執行那個設值器
    • writable 是否為 false,是的話失敗 ( 嚴格模式 TypeError / 非嚴格模式 無聲失敗 ) 
      • 為了強化 繼承概念 的錯覺才如此設計
        • 導致了奇怪的行為:=不可寫入,Object.defineProperty 可以
    • 都不是的話就創建新屬性來產生 遮蔽(shadow) 屬性 的效果
  • 到達 Prototype Chain 頂端都沒有,就創建新屬性,並賦值到此屬性上

Prototype Class

  • 宣告 類別 & 建構器

  • 設定 類別藍圖

  • 產出類別實體

function Foo() {};
Foo.prototype.prop = 'prototype prop';
var foo = new Foo();
console.log(foo.prop);        // "prototype prop"

constructor call - new

  • Every Function can be constructor

constructor call - new

  • 產生 (建構) 一個新物件
  • 新物件的 [[Prototype]] 指向建構函式的 prototype 屬性
  • 新物件 會被設為 建構函式的 this
  • 預設下,執行完建構函式後就會 return 該新物件
function Foo() {};
Foo.prototype.prop = 'prototype prop';
var foo = new Foo();
console.log(foo.prop);        // "prototype prop"

Prototype Class

Difference

Classic OO

...

...

foo

Foo

.prototype

...

...

foo

(data members & actions)

Prototype Class

Difference

{

    fooAction1: function() {},

    fooAction2: function() {}
}
[[Prototype]]

JavaScript OO

...

...

Change?

Dangerous

foo

Prototype Class

Difference

  • 沒有拷貝動作,只有建立物件連結
    • 改變 [[Prototype]] 內容,所有實體會一起改變
  • 由物件來定義物件的藍圖 (weird!)
  • .constructor 的不安全性

.constructor

  • 可更改
    • 原型物件上可以沒有
    • 可能被遮蔽
    • 可能錯誤鏈結到上層物件
function Foo() {};
Foo.prototype.prop = 'prototype prop';
var foo = new Foo();
foo.constructor === Foo;       // true

function Bar() {};
Bar.prototype = {};
var bar = new Bar();
bar.constructor === Bar;       // false
bar.constructor === Object;    // true

Prototype inherit

  • Child.prototype.__proto__ = Parent.prototype
  • 子類別 建構器先呼叫父類別的建構器 (super())

child

Child

.prototype

Parent

.prototype

(data members & actions)

(data members & actions)

Prototype inherit

  • Child.prototype.__proto__ = Parent.prototype
  • 子類別 建構器先呼叫父類別的建構器 (super())
function Parent() {
    this.name = 'name';
}

Parent.prototype.getName = function() {
    console.log('parent\'s name:' + this.name);
}

function Child() {
    Parent.call(this, arguments);    // call parent exmplicitly, not very well
    this.label = 'label';
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.getLabel= function() {
    console.log('child\'s label:' + this.label);
}

var child = new Child();
child.getName();
child.getLabel();

Object.create()

  • 新建一個物件 
  • 其原型物件設為指定的物件
var childProto = Object.create(Parent.prototype);

Object.create(null)

  • 真正的空物件
var emptyObj = Object.create(null);

Object.create() polyfill

Object.create = function(proto) {
    function F() {};
    F.proto = proto;
    return F.proto;
}

不考慮 enumerable: false 的狀況下

Prototype inherit

Difference

  • 沒有拷貝動作,只有建立物件連結
    • 改變 [[Prototype]] 內容,所有實體會一起改變
  • 由物件來定義物件的藍圖 (weird!)
  • .constructor 的不安全性
  • 藉由 "留著空洞" 讓 父類別補上差異

Prototype Class

Difference

{
    childAction1: function() {},

    childAction2: function() {}

}
{

    parentAction1: function() {},

    parentAction2: function() {}
}
{
    childAction1: function() {},
    parentAction1: function() {},
    childAction2: function() {},
    parentAction2: function() {}
}
[[Prototype]]

Classic OO

JavaScript OO

child

child

introspection

  • obj instanceof Foo; 
  • Foo.prototype.isPrototypeOf(obj); 
  • Object.getPrototypeOf(obj) === Foo.prototype;
  • obj.__proto__ === Foo.prototype; 
  • obj.constructor 

JavaScript Prototype OO

By Chang Henry

JavaScript Prototype OO

  • 16