※ 本文同步發表於隨性筆記
本系列文章討論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 getClass
2是否可用,請君自行嘗試)
他的輸出值可能有:
- "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.getPrototypeOf
4
還記得昨天的例子嗎?
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
,最後才出現Function
和Object
。
※ 儘管出現順序並不宜定要如此,不過這樣蠻符合我的思考邏輯的。
總有例外
除了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