资讯专栏INFORMATION COLUMN

重讀 Axel 的 Javascript 中的 Expression vs Statement 一文

马龙驹 / 2957人阅读

摘要:主要是因為它們會因為不同的位置產生不同的行為。函數宣告則產生一個行為,即建立一個變數,然後它的值是一個。而且只有可以被立即調用,函數宣告不行。為了防止產生疑義。會禁止使用或開頭。屬於一種,其產生一個值或說回傳一個值,並完成某項任務。

前言

原文在此,對於 Axel 的文章一直有種雖然短卻難以讀透的感覺。這篇文章是再讀一次的筆記與部份翻譯,如有錯誤歡迎指教。

註: 下面一些範例當我們在瀏覽器 console 執行時,回傳值與程式執行的順序在 Chrome 與 Firefox 會有差別。注意一下箭頭符號就知道哪個是 return 了。如下範例

  > function foo () {console.log("foo");}
  > foo();

  // 在 Chrome 是
  foo
  undefined

  // 在 Firefox 是
  undefined
  foo
1. Statements 述句和 Expressions 表達式

一直以來,在讀技術文章的時候您一定不陌生這兩個詞,因為小弟過去對於這種細枝末節並不是很重視,加上計算機背景又不深厚。所以對於一些文章和概念的掌握度一直不是很精確。這次重讀一遍 Axel 的文章,希望能夠對 javascript 有更深入的理解。

事實上,在 javascript 中能夠清楚的分辨 expressionsstatements 的差異對於撰寫程式碼是有一定的幫助,可以避免掉入一些陷阱。

簡單來說一個表達式 expressions 會產生一個值,我們會在撰寫它的地方期望得到一個。舉例來說像是調用 function 中的引數(arguments),或者指定式 = 的右邊都屬於 expressions 的位置。

參數(parameters),引數(arguments)

parameters 即在 function 中定義必須在呼叫程序時傳遞,可以用來取值的符號(變數名稱)

arguments 是實際呼叫時,傳入的值

下面的每一行都是一個 expression:

myvar
100 + x
fn("a", "b")

而大體來說述句 statements 即執行動作,完成特定任務。賦值,迴圈和 if 述句都是 statements 的例子。
在我們實際撰寫的程式碼中到底是怎麼區分的呢?讓我們看看 MDN 上定義的 if 述句

if (condition)
   statement1
[else
   statement2]

要明白這些事情我們得先從 syntax 開始講起,基本上程式是透過一系列規定好的語法組成,稱為 syntax ,它類似於我們人類語言中的文法,不管是中文還是英文。一個程式要遵循著 syntax 並且由一系列 statements 組成的。

javascript 直譯器在解析程式碼時對於語法結構,即這些程式碼出現的位置會有對應的處理方式。

另外,拿上面的例子來說,任何 javascript 預期會有 statements 的地方你都可以使用 expressions,例如在 statement1 的地方呼叫一個 function。
這個 function 就稱作 expression statement 屬於一種特殊的 statement ,這個 function 自然可以 return 一個值,同時也可以在內部產生一些 side effect,不過如果我們重點擺在一些 side effect 部分時,通常就會回傳 undefined。如下圖

通常一個 statement 是獨立的,只會完成某項任務,不過如果它影響了整個程式例如: 異動了機器內部的狀態,或者影響後面的 statement,這些造成的改變我們就稱為 side effect (副作用)

反過來,我們不可以在預期是 expression 的地方換成 statement。例如我們不可以在 function 的引數的地方改成 if 述句

歸納一下關係如下:

syntax

statements

expression statements

expressions

2. statements 與 expressions 實例

讓我們看一下這兩段類似功能的程式碼,我們可能會更加清楚它們之間的分別。

if 條件式語句和條件運算子(三元運算子)
var x;
if (y >= 0) {
  x = y;
} else {
  x = -y;
}

上面這幾句程式碼無疑都是 statements,另外 expression 也有個對應的寫法和上面這段程式碼完全等價

var x = (y >= 0 ? y : -y);

在等號和分號之間的就是一個 expression,其中的 () 不是必須的,但加上去比較容易閱讀。

分號; 與 逗號,

在 javascript 中 statement 之間我們可以用 ; 分號來區分和串連。

foo(); bar()

expression 也有一個鮮為人知的 , 運算子可以用來串連。

foo(), bar()

兩個 expression 都會執行,但是返回最後面的。

> "a", "b"
"b"

> var x = ("x", "y")
> x
"y"
3. 容易產生誤會的 expressions (看起來像 statements)

有些 expressions 看起來像是 statements。下面我們會列出一些容易產生疑義的例子逐一討論。主要是因為它們會因為不同的位置產生不同的行為。

物件實字(Object Literal)與程式碼區塊(Block)

下面這段範例是一個物件實字,屬於 expression ,用來產生一個物件

{
  foo: bar(3, 5)
}

Object Literal 是一個透過 {}, 逗號分隔的鍵值對列表就是 var o = {name:"Object"} 這樣的寫法。

同時它還是一個符合規範的 statement,因為它具備了:

block: 一段 statement 放在 {}

label: 我們可以在任何一段 statement 之前放上一個 label,在這邊 label 是 foo:

statement: 一個 expression statement bar(3, 5)

所以 {} 到底是一個 block 還是物件實字,你可能會說是物件那讓我們來看看下面這個奇怪的例子

// 在看這個奇怪的範例之前讓我們先看看一些 javascript 的行為
// 當我們把非數字相加時
> 1 + "string"
"1string"

> 1 + undefined
NaN

> 1 + null
1

> 1 + [2,3,]
"12,3"

> 1 + {name: "andyyou"}
"1[object Object]"

// 上面的範例我們得知,除了 undefined 和 null,基本上 js 會把物件先 `toString()` 再相加。

> [].toString()
""

> [1, 2, 3].toString()
"1,2,3"

> var o = {};
> o.toString();
"[object Object]"

// 有了上面的基礎知識之後,讓我們來看看這令人嚇尿的行為

> [] + {}
"[object Object]"

// 好!這題如我們所料,[] 產生 "" 加上 {} 產生 "[object Object]"

// 先問你個問題: + 兩邊的運算元能不能互換而結果不變
// 你可能回答: 是!!!
// 但....

> {} + []
0

上面程式碼最後一句的 {} 是一個 block 所以執行完之後接 +[]

> +[]
0

嚇尿了吧!除了 if, 迴圈外 javascript 也具有獨立的 block。
下面這段程式碼說明了 labelblock 的用法:

function test (printTwo) {
  printing: {
    console.log("One");
    if (!printTow) break printing;
    console.log("Two");
  }
  console.log("Three");
}

執行的結果

> test(false)
"One"
"Three"

> test(true)
"One"
"Two"
"Three"

從上面驗證了 {} 的語法如果遇到 statements 的位置,就會被當成 statements,而如果在 expressions 的位置就會被當解析成一個值。

> {} + [];
// 就是一個最好的例子,{} 被當作 statement 就是一個 block

// 如果換成

> var x = {};
// 那他就是一個 expression 代表一個值 - 一個物件

讓我們接著看下一個例子。

Function expression 與 function 宣告

下面的程式碼是一個 function expression

function () {}

你也可以給 function expression 一個名稱

function foo () {}

在當作 function expression 時上面的 function 名稱 foo 只存在在 function 內部能使用,舉例來說像是一個遞迴。
你可能困惑了,我們到底在說啥?看看下面的例子,我們要說的是當 function 放在 statementsexpressions 不同位置時的差異(放在 = 右邊是 expression)

var fn = function me(x) { return x <= 1 ? 1 : x * me(x-1)} // = 等號右邊是一個 expression 的位置
fn(10); // 3628800

console.log(me); // ReferenceError: me is not defined

具名的 function expression 和函數宣告的寫法看起來是沒有區別的。但實際上這兩者的效果截然不同,function expression 產生一個值(一個 function)。函數宣告則產生一個行為,即建立一個變數,然後它的值是一個 function。而且只有 function expression 可以被立即調用,函數宣告不行。

從上面這幾點看來能夠區分 expression 和 statement 挺重要的。

4. 使用物件實字與 function expression 當作 statements

我們已經看到有一些 expressionstatement 語法上是沒有區別的。這意味著相同的程式碼會有不同行為取決於它出現在 expression 的位置
或者是 statement 位置。為了防止產生疑義。javascript 會禁止 expression statement 使用 {}function 開頭。

換句話說就是在 javascript 認定為 statement 的位置,使用了 expression 會變成 expression statement。這並不是 expression,所以產生一些特殊的狀況 {} 會被當作 block 解釋,function 開頭的語法會被當作函數定義。
所你當你想要使用這兩者為開頭撰寫 expression statement 時,你可以放上 () 可以確保位於一個 expression 的位置。

這就是 statement 或者 expression 所延伸的問題,也可以說造成我們極度混亂的根源。讓我們來看看 eval 和立即調用函式:

eval

eval 會解析他的引數當做一句 statement。如果你希望 eval 回傳一個物件你就需要在物件實字外圍放上()

> eval("{foo: 123}");
123

> eval("({foo: 123})");
{foo: 123}

下次再閱讀文件的時候是不是更有感覺了。

立即調用函式

立即調用函式的部分

> (function () { return "abc" }())
"abc"

如果你省略了 () 如下,你就會得到一個語法錯誤的訊息。function 宣告不可以匿名。

> function () { return "abc" }()
SyntaxError: function statement requires a name

就算替 function 加上名稱還是噴錯

> function foo() { return "abc" }()
SyntaxError: syntax error

因為函數宣告不能立即調用(IIFE),不過除了使用 () 還有些技巧,當我們硬要要把某段程式當做 expression 執行時,可以使用一元運算子 +!,不過和()的方式比起來這會影響回傳結果,如果你不在意的話這也是一種方式。

> +function () { console.log("hello") }()
NaN
hello

這個 NaN 是 + 遇上 undefined 的結果,喔!對了還有一種是透過 void 的方式

> void function () { console.log("hello") }()
undefined
hello
連續使用立即調用函式

當你在連續呼叫 IIFE 的時候必須要注意不要忘記分號 ; 結尾

(function () {}())
(function () {}())
// TypeError: undefined is not a function

上面的程式碼會產生錯誤因為 javascript 以為第二行的 () 是要拿第一行產生的結果當作一個函數來呼叫。

(function () {}());
(function () {}())
// OK

下面範例因為 javascript 自動補上分號的功能,使用一元運算子的話 ; 可以省略。

void function () {}()
void function () {}()
// OK

javascript 會自動補上分號是因為接在第一行之後的 void 並不是可以接下去的語句(符合規範能串在一起的寫法)。

另外關於 javascript 自動補上分號有幾項建議如下:

在 return,break,continue,++,-- 五種 statement 中,換行字元可完全等於 ;

var,if,do while,for,continue,break,return,with,switch,throw,try,debugger 關鍵字開頭,以及空的 statement,上一行會自動補上分號。

遇到 expression statement 和 function expression 情況非常複雜,後面請務必要加上分號。

([ 開頭的 statements 前面或上一句不加非常危險。

想要更深入明白 ASI,請參考。

總結

syntax : 語法(文法),該怎麼組織 statements 與 expressions。

expressions : 會產生一個值,其意義就是代表一個值的表式例如 x + y

statements : 完成某項任務的操作。賦值,條件判斷,宣告都算是 statements; if (condiction) { console.log("WoooW!") }

expression statements : 屬於一種 statement,其產生一個值(或說回傳一個值),並完成某項任務。例如:x += 1 或者在 statement 執行一個 side effect 的函數呼叫。

在 statements 位置放入 expressions 要小心(即 expression statement),因為 javascript 對於 expressionexpression statement 解釋行為是不一樣的。

下面這兩種語法對於其位置尤其需要注意

function

statement 位置: 當作函數宣告,即建立一個變數它的值是一個 function。,不能立即調用。

expression 位置: 為 function expression 產生一個為 function 的值,可以被立即調用(IIFE)。

{}

statement 位置: block 一個程式碼區塊,例如 for, label 的 block。

expression 位置: 物件實字,建立一個值 - 物件。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86283.html

相关文章

  • 關於 Javascript {} + {}

    摘要:於是其他的東西相加的時候將會被轉型成數字或者字串。整個過程即先依據轉換為原始型別,這裡要注意並不是最終結果,再來依據需要看是否要再將原生型別轉成數字或字串。這個結果等於只作的算。 這篇文章源自 What is {} + {} in JavaScript? 其實早在 2012 年就問世了。時至 2016 年末純粹是在聊天時重提這個問題,但由於年紀大了記憶力不佳,竟然記錯了,所以才會有這一...

    charles_paul 评论0 收藏0
  • JavaScript 闯关记》之语句

    摘要:会自动调用转换函数将这个表达式的结果转换为一个布尔值。语句语句与语句的关系最为密切,而且也是在其他语言中普遍使用的一种流控制语句。 表达式在 JavaScript 中是短语,那么语句就是整句命令。表达式用来计算出一个值,语句用来执行以使某件事发生。从本质上看,语句定义了 JavaScript 中的主要语法,语句通常使用一或多个关键字来完成给定任务。语句可以很简单,例如通知函数退出;也可...

    OBKoro1 评论0 收藏0
  • JS基础学习04「语句」

    摘要:表达式用来计算出一个值,语句用来执行以使某件事发生。其中,语句会立即退出循环,强制继续执行循环后面的语句。在执行语句之后,结果显示。语句语句的作用是指定函数调用后的返回值。语句语句的作用是把程序运行时产生的错误显式地抛出异常。 表达式在 JavaScript 中是短语,那么语句就是整句命令。表达式用来计算出一个值,语句用来执行以使某件事发生。从本质上看,语句定义了 JavaScript...

    remcarpediem 评论0 收藏0
  • 【知识点】Javascript分号规则

    摘要:花点时间搞清楚中的分号规则吧不管你喜欢结尾带分号或省略分号的模式分号允许的场景分号一般允许出现在大部分语句的末尾,比如等栗子仅有一个分号可以表示空语句在中合法,比如可解析为三个空语句空语句可用于辅助产生语法合法的解析结果,如如果没有末尾的 花点时间搞清楚JS中的分号规则吧~~~不管你喜欢结尾带分号或省略分号的模式 分号允许的场景 分号一般允许出现在大部分语句(statement)的末尾...

    kun_jian 评论0 收藏0
  • 一文快速解读ES6新特性

    摘要:是一项标准,于年月获得批准。静态限制在分配之前阻止使用。使用通用到基于自定义迭代器的迭代。迭代基于这些鸭子类型接口仅使用类型语法进行展示生成器生成器使用和简化迭代器。生成器是迭代器的子类型,包括额外的和。 ECMAScript 2015是一项ECMAScript标准,于2015年6月获得批准。 ES2015是该语言的重要更新,也是自2009年ES5标准化以来该语言的第一次重大更新。现在...

    goji 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<