總結下這段時間吸收的許多小知識,以備忘記後翻閱。
關於面向對象
面向對象特徵:
- 具有唯一標識性
- 具有狀態
- 具有行爲
JS的面向對象和JAVA的實現思路不一樣,JS是基於原型並非基於類。但是JS爲了看起來更像JAVA,爲此添加了一些特性。
拋開爲了看起來像JAVA引入的特性,JS面向對象總結起來很簡單:每個對象都有原型,對象上找不到屬性就去自身原型上找,直到Object.prototype。
在ES3時代爲了操作原型我們沒有辦法只能使用 new
但是ES5之後引入了專門操作原型的方法:
- Object.create()
- Object.setPrototypeOf()
- Object.getPrototypeOf()
這樣我們就可以拋開類的概念直接面對原型來實現面向對象。
JS關於模擬JAVA的面向對象部分
在我的理解中有以下部分屬於模擬JAVA的行爲:
- function 可以被new調用
- 引入this機制
- instanceof 操作符
通過 new 調用函數主要做了三件事:
- 使用被new 調用的函數的prototype屬性構造一個新對象
- 將新對象作爲函數的this,調用該函數
- 如沒有引用類型返回則返回這個新建的對象
上面就是在模擬JAVA中的類,在ES5中拋開new我們可以做到同樣的事情,並且不會有讓人以爲這是一個類。
this更是複雜,一大批文章去解釋JS中的this,由此也可以看出有些複雜。
JS中的數據類型
JS一共有7中數據類型:
- Null
- Undefined
- Number
- String
- Boolean
- Symbol (ES6新加)
- Object (引用類型)
7種數據類型又被分爲兩類:基本類型和引用類型。
怎麼判斷數據對應的是哪種數據類型呢?
總結有三種:typeof
、instanceof
和 Object.prototype.toString.call()
,這三種都有自己的特點。
typeof
基本上可以直接返回數據對應的類型,有兩個例外,一個是 typeof null
返回 "object"
,另一個是 typeof function() {}
返回 "function"
instanceof
左邊是一個對象右邊是一個構造函數。
instanceof會遍歷對象的原型,查找是否有函數的prototype屬性。
我認爲,檢查數據是否是數組就很方便了 [] instanceof Array
,當然這個是有問題的,所以後面又提供了Array.isArray()
方法判斷是否是數組。
Object.prototype.toString.call()
該方法提供的返回值十分詳細,不僅會返回上面其中類型,還包括很多JS內置對象,例如:Array
,Date
,RegExp
等。
但是這個對象有個問題,會強制裝箱。也就是說判斷不出是基本類型還是引用類型,如果在意的話需要配合上typeof
。
Object.prototype.toString.call(1); // [object Number]
Object.prototype.toString.call(new Number(1)); // [object Number]
返回值都一樣。
關於裝箱和拆箱
說道裝箱和拆箱就要提及一下它的表象 – 隱式類型轉換。
JS中隱式類型轉換應用廣泛,最臭名昭著的就是 ==
。還有當我們直接調用 str.indexOf
方法的時候,str明明是基本值還不報錯就是裝箱在起作用。
裝箱
每個基本值都有一個對應的對象類,裝箱就是將基本值轉換爲對應的對象類型。但是Symbol是不能通過new 調用的,所以我們要另想辦法。
上面提到的Object.prototype.toString.call
會強制裝箱:
function a() {
return this;
}
a.call(Symbol()) // 查看打印
拆箱(toPrimitive)
拆箱會先調用對象的valueOf
方法,如果返回值是非基本值再調用toString
方法,返回值非基本值則報錯。
還記得偏門的面試題麼?
if (a == 2 && a == 3) {
console.log(true);
}
雖然是語言不好的一面,但也可以瞭解下。這裏就用到了拆箱知識。
如果 a 是對象和基本類型做 ==
比較首先會對 a 拆箱,拆箱步驟如上所述。
let b = 1;
let a = {
valueOf: () => ++b
}
if (a == 2 && a == 3) {
console.log(true);
}
這樣就會打印出 true
。
自定義拆箱
關於拆箱最後想說的是現在可以自定義這個行爲了。
let o = {
[Symbol.toPrimitive]: () => "hello world"
};
console.log('' + o);
Number
關於Number一直有一個疑問就是NaN到底是不是數字。typeof NaN
其實返回的是 number 。所以Not a Number 是一個 Number。
我們都知道JS中數字有精度問題:0.1 + 0.2 === 0.3
返回的是false。這個時候們就要藉助 Number.EPSILON來幫我們判斷,Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON
。
JS中是區分 +0 和 -0 的,但是 +0 === -0
返回true。那麼怎麼區分+0 和 -0 呢,這也是polyfill Object.is() 需要考慮的問題。
我們可以通過用 +0 和 -0 做分母來區分:
1 / 0 === Infinity // true
1 / -0 === -Infinity // true
1 / 0 === 1 / -0 // false
函數
通過實際開發中我們可以知道,有的函數可以直接調用和使用new
調用,有的函數指能通過new
調用有的函數只能直接調用。
這背後是有機制在控制的。JS中的數據類型大體分爲兩種,一個是基本類型,一個是引用類型。那麼按理說function不是基本類型那就是引用類型,引用類型就是隻有對象,函數也是一個對象,爲什麼函數這個對象可以調用而其他對象不行呢?
是因爲函數這個對象具有兩個私有屬性決定了上面的特性,我們並沒有手段去定義和查看這兩個私有屬性。一個是 [[constructor]]
,另一個是 [[call]]
。分別決定了是否可以通過new調用和直接調用。
注:不能通過new調用函數例如 () => {}
箭頭函數,不能直接調用的函數例如Image
這樣的環境提供的函數。
總結於即刻時間重學前端欄目,winter說知識是免費的,教育是收費的,這邊總結出來的應該算知識,教育還留在了欄目裏。