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

介紹

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

自我提問

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

腦圖

關鍵知識點

表達式的定義

犀牛書中對 表達式 的定義爲 能夠計算出結果的短語,簡單來講就是 有返回值的代碼數組

MDN 上對錶達式的定義是:bash

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

表達式是一組代碼的集合,它返回一個值。post

按照這些定義理解,只要有返回值的代碼均可以認爲是表達式。學習

然而不肯定理解是否有誤,畢竟就好像函數聲明,也有返回值,爲啥就和函數表達式區分開,查了好久也沒找到確切的定義。猜想定義應該是有操做符參與計算的纔算表達式,畢竟函數表達式和聲明寫法上差的就是賦值,並且表達式和運算符通常都是一塊兒出現的。ui

表達式類型

看下常見的表達式類型this

原始表達式

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

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

犀牛書這裏有註明,null 是關鍵字,可是 undefined 是全局變量。確實是這樣,能夠試試給 null 和 undefined 賦值,但爲啥這樣設計呢,不懂。 prototype

對象、數組初始化表達式

[]
[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};
複製代碼

  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);
複製代碼

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

因此順序是

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 之犀牛書【四 · 二】表達式
相關文章
相關標籤/搜索