腦圖學習 JavaScript 之犀牛書【四 · 二】表示式、計算順序

ZxBing0066發表於2019-10-31

介紹

本篇講解第四章講到的關於 表示式 的定義、複雜表示式的計算過程

自我提問

  • new 操作符做了什麼?
    function Class() {
        this.a = 1;
        return {a: 2};
    }
    new Class();
    複製程式碼
  • 複雜表示式的計算順序是怎麼斷定的?
    var a = {i: 1};
    var b = a;
    a.j = a = {k: 2};
    複製程式碼

腦圖

腦圖學習 JavaScript 之犀牛書【四 · 二】表示式、計算順序

關鍵知識點

表示式的定義

犀牛書中對 表示式 的定義為 可以計算出結果的短語,簡單來說就是 有返回值的程式碼

MDN 上對錶達式的定義是:

An expression is any valid unit of code that resolves to a value.

表示式是一組程式碼的集合,它返回一個值。

按照這些定義理解,只要有返回值的程式碼都可以認為是表示式。

然而不確定理解是否有誤,畢竟就好像函式宣告,也有返回值,為啥就和函式表示式區分開,查了很久也沒找到確切的定義。猜測定義應該是有操作符參與計算的才算表示式,畢竟函式表示式和宣告寫法上差的就是賦值,而且表示式和運算子一般都是一起出現的。

腦圖學習 JavaScript 之犀牛書【四 · 二】表示式、計算順序

表示式型別

看下常見的表示式型別

原始表示式

原始表示式是表示式的 最小單位,常量、直接量、關鍵字、變數

null
undefined
true
false
this
someVar
1.2
'string'
複製程式碼

犀牛書這裡有註明,null 是關鍵字,但是 undefined 是全域性變數。確實是這樣,可以試試給 null 和 undefined 賦值,但為啥這樣設計呢,不懂。

腦圖學習 JavaScript 之犀牛書【四 · 二】表示式、計算順序

物件、陣列初始化表示式

[]
[1, 2, 1+3]
{a: 1, b: 2}
複製程式碼

函式定義表示式

var func = function(x) {
    return x * x;
}
複製程式碼

屬性訪問表示式

a.x
a[x]
複製程式碼

這裡犀牛書有說了很重要的一點:不管使用哪種形式的屬性訪問表示式,在 . 和 [ 之前的表示式總是會首先計算。本文最下方有關於這個的驗證。

呼叫表示式

fun(x)
Math.max(1, 2)
複製程式碼

呼叫表示式前的表示式是一個屬性訪問表示式時這個呼叫叫做 方法呼叫,方法呼叫會將方法中的 this 指向呼叫的物件。

非方法呼叫表示式非嚴格模式會使用全域性物件作為 this 關鍵字的值,嚴格模式下為 undefined。

物件建立表示式

new Object()
new String
複製程式碼

物件建立表示式的建立步驟:

  1. 建立一個新的空物件
  2. 將建構函式 prototype 掛到空物件的原型鏈上(這個犀牛書上沒說,因為這裡書上說的比較粗,具體的會在第 9 章講解)
  3. 將這個新物件作為建構函式中 this 關鍵字的值
  4. 通過執行建構函式初始化新物件的屬性
  5. 如果建構函式返回了一個值(僅限物件值,原始值會被無視),新物件會被廢棄,返回值會作為表示式的值,否則會將這個新物件作為表示式的值

算數表示式

1 + 2
's' + 'a'
1 - null
++a
12 | 1
12 ^ 4
~12
12 << 1
12 >> 1
-12 >>> 1
複製程式碼

關係表示式

null == undefined
a = 1
a === b
a != 1
a !== 1
'a' < 'b'
'a' > 'b'
'a' <= 'b'
'a' >= 'b'
'x' in y
a instanceof Date
複製程式碼

邏輯表示式

x == 0 && y == 0
x == 0 || y == 0
!x
複製程式碼

賦值表示式

x = 1
a.b = 1
a++
b--
--a
++b
a += b
b *= a
複製程式碼

eval 表示式

eval('1 + 2')
複製程式碼

其它表示式

a ? 1 : 2
typeof true
delete a.b
void a++
a++, b++
複製程式碼

複雜表示式

將簡單表示式連線在一起就可以構成複雜表示式。

var a = b = c + 1 / (1 + 2) ? 1 : 2 * 100 - typeof '123'
複製程式碼

運算順序圖解

這裡結合上一篇的運算子的相關知識對一些複雜表示式進行解析,看看複雜表示式到底是如何計算的。主要關係運算子的優先順序、結合性、左值等概念,不太瞭解的可以看下上一篇

先來個網紅題目簡單解析一下

var a = {i: 1};
var b = a;
a.j = a = {k: 2};
複製程式碼

腦圖學習 JavaScript 之犀牛書【四 · 二】表示式、計算順序

  1. 取出 a.j 的左值
  2. 取出 a 的左值
  3. 將 {k: 2} 賦值給 a
  4. 將 a = {k: 2} 的返回值賦值給 a.j

為什麼要先取出 a.j 的記憶體地址:

  1. 上方屬性訪問表示式有說過,不管使用哪種形式的屬性訪問表示式,在 . 和 [ 之前的表示式總是會首先計算
  2. . 的運算子優先順序高於 =,所以會優先計算,在賦值語句執行前就會執行 . 運算子取出 a.j 的左值

驗證一下第一個說法是否正確:

var b = {i: 1};
var a = {i: 1, a: b};
a.a = 2, a.a.i = 3;
複製程式碼

按照犀牛書的說法 a.a.i 中的 a.a 會優先計算為 b,然後 b 的 i 被賦值為 3,不過經驗證這個說法並不正確。 所以其實並沒有這個額外的規則,只是單純的運算子優先順序和左值的問題。

再來個複雜一點的例子,看下錶達式計算時如何推斷優先順序。

var k = {
    get a() {
        console.log('a');
        return 'a';
    },
    get b() {
        console.log('b');
        return 'b';
    },
    get c() {
        console.log('c');
        return 'c';
    }
};
var b = { b: 1 };
var a = { a: 1, b: b, c: 2 };
var c = { c: 1 };
var v = 2;
a[k.a] = b[k.b] = (v * 10 + c[k.c] / 10 + v++ - ++v) | 1;
// a
// b
// c
// { a: 19, b: { b: 19 }, c: 2 } { b: 19 } 4
console.log(a, b, v);
複製程式碼

腦圖學習 JavaScript 之犀牛書【四 · 二】表示式、計算順序

按照運算子的 優先順序從低到高 分解成最基礎的表示式和運算子,按照 結合性結合相同優先順序 的運算子,然後按照 從左往右 的順序計運算元表示式,深度優先 計算表示式。

所以順序是

a[k.a]
b[k.b]
v * 10
c[k.c]
(c[k.c]) / 10
v++
++v
(v * 10) + ((c[k.c]) / 10) + (v++) - (++v)
((v * 10) + ((c[k.c]) / 10) + (v++) - (++v)) | 1
b[k.b] = (((v * 10) + ((c[k.c]) / 10) + (v++) - (++v)) | 1)
a[k.a] = (b[k.b] = (((v * 10) + ((c[k.c]) / 10) + (v++) - (++v)) | 1))
複製程式碼

按照上述過程將複雜表示式拆解後,計算過程就一目瞭然了。

系列文章目錄

  1. 腦圖學習 JavaScript 之犀牛書【一】
  2. 腦圖學習 JavaScript 之犀牛書【二】詞法結構
  3. 腦圖學習 JavaScript 之犀牛書【三 · 一】資料型別
  4. 腦圖學習 JavaScript 之犀牛書【三 · 二】型別轉換、變數
  5. 腦圖學習 JavaScript 之犀牛書【四 · 一】運算子、型別轉換
  6. 腦圖學習 JavaScript 之犀牛書【四 · 二】表示式
  7. 腦圖學習 JavaScript 之犀牛書【五】語句

相關文章