[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







你可能感興趣的文章

LeetCode JS Easy 2704. To Be Or Not To Be

LeetCode JS Easy 2704. To Be Or Not To Be

用PHP實作一個懷舊風留言板

用PHP實作一個懷舊風留言板

使用Pagy製作Blog分頁(Ruby on Rails)

使用Pagy製作Blog分頁(Ruby on Rails)






留言討論