《你不知道的JavaScript》 (中) 讀書筆記

SHERlocked93發表於2019-01-06

本書屬於基礎類書籍,會有比較多的基礎知識,所以這裡僅記錄平常不怎麼容易注意到的知識點,不會全記,供大家和自己翻閱;

上中下三本的讀書筆記:

  1. 《你不知道的JavaScript》 (上) 讀書筆記
  2. 《你不知道的JavaScript》 (中) 讀書筆記
  3. 《你不知道的JavaScript》 (下) 讀書筆記

第一部分 型別和語法

第二章 值

43.toFixed(3)
// 報錯: Invalid or unexpected token
43..toFixed(3)
// "43.000"
複製程式碼

這是因為42.toFixed(3)這裡因為.被視為常量42的一部分,所以沒有.屬性訪問運算子來呼叫toFixed方法。而42..toFixed則沒有問題。

第四章 強制型別轉換

JSON.stringify在物件中遇到undefinedfunctionsymbol時會自動將其忽略,在陣列中則會返回null,比如:

JSON.stringify([1, 23, 4, null, undefined, function(){ return 123 }])
// "[1,23,4,null,null,null]"
複製程式碼

JS中的假值 undefinednullfalse+0-0NaN""

  1. 除了空字串外的所有字串都是真值
  2. 所有物件都是真值

關於真假值的判斷:

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
複製程式碼

第五章 語法

結果值

語句都有個結果值:

  1. 賦值表示式 b = a 的結果值是a的值
  2. 規範定義 var 的結果值是 undefined
  3. 程式碼塊 { ... } 的結果值是其最後一個語句\表示式的結果

標籤語句

{ 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預設的執行順序是從左到右,但是有時候不是,比如:

  1. ? : 三元運算子是右關聯,比如? : ? : ,其實是? : (? :) 這樣的順序
  2. = = 連等是右關聯,比如 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
複製程式碼
  1. 如果 finally 中丟擲異常,函式會終值,如果之前 try 中已經 return 了返回值,則返回值會被丟棄;
  2. finally 中的 return 會覆蓋 trycatch 中 return 的返回值;
  3. 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 的非同步化造成的。

如果遇到這種情況:

  1. 使用JS偵錯程式中的斷點,而不要依賴控制檯輸出;
  2. 把物件序列化到一個字串中,以強制執行一次快照,比如通過 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:歡迎大家關注我的公眾號【前端下午茶】,一起加油吧~

《你不知道的JavaScript》 (中) 讀書筆記

相關文章