[day 03] Function & Object: 關於Prototype Chain繼承


※ 本文同步發表於隨性筆記

本系列文章討論JS 物件導向設計相關的特性。 不含CSS,不含HTML
建議先有些JS基礎再繼續閱讀。
你也可以看看從零開始遲來的Web開發筆記
雖然是「7天寫作松」挑戰,但同樣可以視為系列後續文章

No CSS! No HTML! No Browser!
Just need programming language


Prototype

有人說:「在寫JVM語言前,你必須先是Java程式開發人員」。
在寫TypeScript前,你還是得先會JavaScript。
[在進階一點(誤)] 在寫任何程式語言前,比必須先會組合語言

class之前,必須了解的prototype chain

好拉,上面引言最後一個是來亂的,但是在正式開始寫JS的之前,你還是比需要有 prototype chain 的概念。

JavaScript 是個沒有實做 class 關鍵字的動態語言,所以會對那些基於類別(class-based)語言(如 Java 或 C++)背景出身的開發者來說會有點困惑。(在 ES2015 有提供 class 關鍵字,但那只是個語法糖,JavaScript 仍然是基於原型(prototype-based)的語言)。1

本節不會立馬就進到原形鏈,在那之前,會先來看看Python、Ruby這類OOP裡同樣有的東西。如果你不是從那來的,可以直接到原形鏈去看。但建議多少還是看一點,有些在JS還蠻常用的。

TypeOf

typeof可以回傳物件的類型,類似於Python的type、Ruby的v.class,如果你來自Java、C++,很抱歉在那裡沒有類似概念,或者你可以找找看Java的Reflection(反射),但通常,編譯器已經先處裡掉類型問題,故而無大量相關需求。(我不確定Java getClass2是否可用,請君自行嘗試)

他的輸出值可能有:

  • "undefined"
  • "object"
  • "boolean"
  • "number"
  • "bigint"
  • "string"
  • "function"
  • "symbol" (ECMAScript 2015 新增)

JS常被人詬病的是typeof null會得到"object"。不過我從Python來的,type(None) #=> <class NoneType> ,覺得還挺正常的。
既然null不像undefined是未定義,那其型態就不能是null,用undefined的話更容易出現邏輯矛盾(null是空,是未定義類別?那到底是啥?)。將null想程式空物件的話,是物件類別就好接受了。(注意!此處空物件並非指{}。但如果你來自Lisp,那麼'()nil確實等價)

InstanceOf

A instanceof B用於檢查A是否為B的子實例。Python的話會是isinstance(1,int) # => True,Ruby的話,有&is_a?&kind_of?可以用。這同樣適用於繼承關係,他會去檢查原形鏈。至於原形鏈等等會說道。

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。3

不過還是有些和你想的不同

'string' 和 String不同

var o1 = 'Hello World';
var o2 = new String('Hello World');
console.log("o1 == o2: ", o1 == o2) // true
console.log("o1 === o2: ", o1 === o2) // false
console.log("typeof o1: ", typeof o1) // 'string'
console.log("typeof o2: ", typeof o2) // 'object
console.log("o2 instanceof String: ", o2 instanceof String) // true
console.log("o1 instanceof String: ", o1 instanceof String) // false

相似的還有很多,我...晚一點會說到。

原形設計模式 & 原形鏈(Prototype Chain)

所有物件都會有__proto__屬性,可能是null,可能指向其參考、繼承的目標。

※ Note: 在之前__proto__並非標準,但因為大多數瀏覽器都在用,Node.js也可以用,於是乎後來就成了標準的一部分。但這種寫法不太符合多數JS規範的設計,所以你也可以用Object.getPrototypeOf4

還記得昨天的例子嗎?

obj0 = {
    name: "World",
    hello(){console.log(`Hello, ${this.name}`)},
}
function FactoryNew(name){
  var new_obj = Object.create(obj0)
  new_obj.name = name
  return new_obj;
}

var obj1 = FactoryNew("Daniel");
obj1.hello() // => Hello, Daniel
obj0.hello() // => Hello, World

實際上Object.create並沒有真正複製物件到新物件,他只是將一個空物件的__proto__指向了參考物件。

Object.is(obj1.__proto__, obj0); // => true
Object.is(Object.getPrototypeOf(obj1), 
          obj0); // => true

obj1空空如也,只有後來新增的name

Object.getOwnPropertyNames(obj1) // => [ 'name' ]
Object.getOwnPropertySymbols(obj1) // => []
console.dir(obj1) // => { name: 'Daniel' }
console.dir(obj0) // => { name: 'World', hello: [Function: hello] }

當然obj0也是有__proto__的,他指向個空物件:

console.dir(obj0.__proto__) // => {}

然後這個空物件的__proto__指向null,也就中止。是不是有點「鏈」的感覺了?

console.log(obj0.__proto__.__proto__) // => null

阿喔!不是這個「鍊」。 http://fav.me/d887c0i

instanceof

再回頭來看看instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。[^3]

因為後者必須是callable,所以現在得換昨天的另一個例子:

function FooConstructor(name){
  this.name = name;
  this.hello = function(){
     console.log(`Hello, ${this.name}`);
  }
}

var obj2 = new FooConstructor("Foo");
obj2.hello() // Hello, Foo

obj2 instanceof FooConstructor // => true

函式在JS有些特別,他會自帶prototye屬性,實際上obj2.__proto__就是因為指向這個物件,所以instanceof會判斷為true

Object.is(obj2.__proto__, FooConstructor.prototype); // => true

這同樣也用在原形鏈上

obj2 instanceof Object // => true
Object.is(obj2.__proto__.__proto__,
         Object.prototype) // => true

因為obj2的原形鏈上,存在Object.prototype,所以同時也是Object的實例。

Function & Object

幾乎所有東西都是Object,就算是Function也是一樣。

Function instanceof Object // => true
FooConstructor instanceof Object // => true
Object instanceof Object // => true

不過Object也是Function

Object instanceof Function // => true

原形鏈上唯一的意外只有Object.prototype

Object.prototype instanceof Object // => false

我覺得這篇還不錯--js中先有Function,还是先有Object,可以去閱讀看看。在JS的世界裡,先有了Object.prototype,再有的Function.prototype,最後才出現FunctionObject

※ 儘管出現順序並不宜定要如此,不過這樣蠻符合我的思考邏輯的。

總有例外

除了Object.prototype例外以外,原形鏈外的 基本型別 基本也不是Object還記得我們string的例子嗎?

"Hello, World" instanceof String; // => false
"Hello, World" instanceof Object; // => false

不只基本字串型別,幾乎所有基本型別都不在原形鏈規則上(不過null是在原形鏈上的(中止)):

1 instanceof Number; // => false
1 instanceof Object; // => false

true instanceof Boolean; // => false
true instanceof Object; // => false
false instanceof Boolean; // => false
false instanceof Object; // => false

undefined instanceof Object; // => false

null instanceof Object; // => false

typeof 18014398509481984n // => bigint, 注意最後有'n'
18014398509481984n instanceof BigInt; // => false
18014398509481984n instanceof Object; // => false
#js #javascript #EMCAScript
寫了這麼久的JS,你還在物件之前的時代嗎?只有資料、函式可以用,破破的抽象化,不會難以維護?儘管JS起初並不以物件導向設計,但透過原形鏈設計,其仍然可以具有好維護的物件導向特色。本系列從最基礎的this,深入ES6之後的class。






Related Posts

1 個 div 和 4 行 CSS 就能更了解瀏覽器渲染引擎

1 個 div 和 4 行 CSS 就能更了解瀏覽器渲染引擎

用 Python 自學資料科學與機器學習入門實戰:Pandas 基礎入門

用 Python 自學資料科學與機器學習入門實戰:Pandas 基礎入門

用 Python 自學資料科學與機器學習入門實戰:Numpy 基礎入門

用 Python 自學資料科學與機器學習入門實戰:Numpy 基礎入門



Comments