本書屬於基礎類書籍,會有比較多的基礎知識,所以這裡僅記錄平常不怎麼容易注意到的知識點,不會全記,供大家和自己翻閱;
上中下三本的讀書筆記:
第一部分 型別和語法
第二章 值
43.toFixed(3)
// 報錯: Invalid or unexpected token
43..toFixed(3)
// "43.000"
複製程式碼
這是因為42.toFixed(3)
這裡因為.
被視為常量42
的一部分,所以沒有.
屬性訪問運算子來呼叫toFixed
方法。而42..toFixed
則沒有問題。
第四章 強制型別轉換
JSON.stringify
在物件中遇到undefined
、function
、symbol
時會自動將其忽略,在陣列中則會返回null
,比如:
JSON.stringify([1, 23, 4, null, undefined, function(){ return 123 }])
// "[1,23,4,null,null,null]"
複製程式碼
JS中的假值 undefined
、null
、false
、+0
、-0
、NaN
、""
- 除了空字串外的所有字串都是真值
- 所有物件都是真值
關於真假值的判斷:
new Boolean(false) // true
new Number(0) // true
new String("") // true
Boolean("false") // true
Boolean("0") // true
Boolean("''") // true
Boolean([]) // true
Boolean({}) // true
Boolean(function() {}) // true
複製程式碼
第五章 語法
結果值
語句都有個結果值:
- 賦值表示式
b = a
的結果值是a
的值 - 規範定義
var
的結果值是undefined
- 程式碼塊
{ ... }
的結果值是其最後一個語句\表示式的結果
標籤語句
{ foo: bar() }
這裡的 foo
是標籤語句,帶標籤的迴圈跳轉可以使用 continue\break
來實現執行標籤所在迴圈的下一輪迴圈或跳出標籤所在迴圈;
foo: for (var i = 0; i < 4; i++){
for (var j = 0; j < 4 ; j++){
if ((i * j) === 3){
console.log('stoping', i, j)
break foo;
}
console.log(i, j)
}
}
// 0 0
// 0 1
// 0 2
// 0 3
// 1 0
// 1 1
// 1 2
// stoping 1 3
複製程式碼
這裡的 break foo
不是指跳轉到標籤 foo
所在位置繼續執行,而是跳出標籤 foo
所在的迴圈/程式碼塊,繼續執行後面的程式碼。因此這裡的標籤語句並非傳統意義上的 goto
;
關聯
運算子有優先順序,那麼如果多個相同優先順序的運算子同時出現,執行的順序就和關聯順序有關了,JS預設的執行順序是從左到右,但是有時候不是,比如:
? :
三元運算子是右關聯,比如? : ? :
,其實是? : (? :)
這樣的順序= =
連等是右關聯,比如a=b=c=2
,其實是(a=(b=(c=2)))
函式引數
像函式傳遞引數時,arguments
陣列中對應單元會和命名引數建立關聯(linkage)以得到相同的值;相反,不傳遞引數就不會建立關聯:
function foo(a){
a=42
console.log(arguments[0])
}
foo(2) // 42
foo() // undefined
複製程式碼
注意:嚴格模式沒有建立關聯一說;
try...finally
finally
中的程式碼總是會在 try
之後執行,即使 try
中已經 return 了,如果有 catch
的話則在 catch
之後執行;
function foo(){
try{
return('returned')
} finally {
console.log('finally')
}
}
console.log(foo())
// finally
// returned
複製程式碼
- 如果
finally
中丟擲異常,函式會終值,如果之前try
中已經 return 了返回值,則返回值會被丟棄; finally
中的 return 會覆蓋try
和catch
中 return 的返回值;finally
中如果沒有 return,則會返回前面 return 的返回值;
switch
switch
中的 case
執行的匹配是 ===
嚴格相等的,也就是說如果不是 true,是真值也是不通過的:
switch(true) {
case ('hello' || 10):
console.log('world') // 不會執行
break;
default:
console.log('emmm')
}
// emmm
複製程式碼
所以這裡的字串即使是真值,也是不被匹配,所以可以通過強制表示式返回 Boolean 值,比如 !!('hell0' || 10)
default
是可選的,無需放在最後一個,且並非必不可少:
switch(10){
case 1:
case 2:
default:
console.log('hello')
case 3:
console.log(3)
break;
case 4:
console.log(4)
}
// hello
// 3
複製程式碼
上面這個例子的邏輯是:首先找匹配的 case,沒找到則執行 default
,因為其中沒有 break,所以繼續執行 case 3
中的程式碼,然後 break;
附錄
全域性 DOM 變數
由於瀏覽器歷史遺留問題,在建立帶有 id
屬性的 DOM 元素的時候也會建立同名的全域性變數:
<div id='foo'><div>
<scripts>
console.log(foo) // 列印出DOM元素
</scripts>
複製程式碼
所以說 HTML 中儘量少用 id 屬性...
第二部分 非同步和效能
第一章 非同步:現在和將來
非同步控制檯
某些瀏覽器的 console.log
並不會把傳入的內容立即輸出,原因是在許多程式(不只是JS)中,I/O 是非常低速的阻塞部分,所以,從頁面\UI的角度來說,瀏覽器在後臺非同步處理控制檯 I/O 能夠提高效能,這時使用者可能根本意識不到其發生。
var a = { b: 1 }
console.log(a)
a.b++
複製程式碼
這時候控制檯看到的是 a 物件的快照 {b:1}
,然而點開看詳情的話是 {b:2}
;這段程式碼在執行的時候,瀏覽器可能會認為需要把控制檯 I/O 延遲到後臺,這種情況下,等到瀏覽器控制檯輸出物件內容時,a.b++
可能已經執行,因此會在點開的時候顯示 {b:2}
,這是 I/O 的非同步化造成的。
如果遇到這種情況:
- 使用JS偵錯程式中的斷點,而不要依賴控制檯輸出;
- 把物件序列化到一個字串中,以強制執行一次快照,比如通過
JSON.stringify
;
第三章 Promise
回撥未呼叫
如果 Promise 狀態一直未改變,怎麼得到通知呢,這裡可以使用 Promise.race
競態,如果在設定時間內還未返回,那麼 Promise 將會被 reject
;
function timeoutPromise(delay) {
return new Promise((resolve, reject) => {
setTimeout(() => { reject('Timeout!') }, delay)
})
}
Promise.race([foo(), timeoutPromise(3000)])
.then(() => console.log('Promise 及時完成'))
.catch(() => console.log('Promise 超時了'))
複製程式碼
第四章 生成器
輸入和輸出
function* foo(x) {
return x * (yield 'hello')
}
const it = foo(6)
let res = it.next()
res.value // hello
res = it.next(7)
res.value // 42
複製程式碼
可以看到第一個 next
並沒有傳參,因為只有暫停的 yield
才能接受這樣一個通過 next
傳遞的參,而在生成器剛生成還沒有 next()
這時候還沒有暫停的 yield
來接受這樣一個值,所以會默默丟棄傳遞給第一個 next
的任何引數。
生成器中的 Promise 併發
function* foo() {
const r1 = yield request('http://some.url.1')
const r2 = yield request('http://some.url.2')
}
複製程式碼
這種方式的兩個請求是序列的,yield
只是程式碼中一個單獨的暫停點,不能同時在兩個點上暫停,如果希望並行的傳送,那麼考慮:
function* foo() {
const p1 = request('http://some.url.1')
const p2 = request('http://some.url.2')
const r1 = yield p1
const r2 = yield p2
}
複製程式碼
PS:歡迎大家關注我的公眾號【前端下午茶】,一起加油吧~