原文引用https://www.dazhuanlan.com/2019/08/26/5d62fe2fe3190/
原文在此,对于 Axel 的文章一直有种虽然短却难以读透的感觉。这篇文章是再读一次的翻译搭配自己的理解说明,如有错误欢迎指教。
注: 下面一些范例当我们在浏览器 console 执行时,回传值与程序执行的顺序在 Chrome 与 Firefox 会有差别。注意一下
箭头
符号就知道哪个是return
了。
例如
12345678910 |
> function () {console.log(‘foo‘);}> foo(); // 在 Chrome 是fooundefined // 在 Firefox 是undefinedfoo |
1. Statements 述句和 Expressions 表达式
一直以来,在读技术文章的时候您一定不陌生这两个词,因为小弟过去对于这种细枝末节并不是很重视,加上计算机背景又不深厚。所以对于一些文章和概念的掌握度一直不是很精确。这次重读一遍 Axel 的文章,希望能够对 javascript 有更深入的理解。
事实上,在 javascript 中能够清楚的分辨 expressions
和 statements
的差异对于撰写程序是有一定的帮助,可以避免掉入一些陷阱。
简单来说一个表达式 expressions
会产生一个值,我们会在撰写它的地方期望得到一个值
。举例来说像是调用 function 中的参数(arguments),或者指定式 =
的右边都属于 expressions 的位置。
参数(parameters),参数(arguments)
下面的每一行都是一个 expression:
123 |
myvar100 + xfn("a", "b") |
而大体来说述句 statements
即执行动作,完成特定任务。赋值,循环和 if 述句
都是 statements
的例子。
在我们实际撰写的程序中到底是怎么区分的呢?让我们看看 MDN 上定义的 if 述句
1234 |
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
- statements
2. statements 与 expressions
让我们看一下这两段类似功能的程序,我们可能会更加清楚它们之间的分别。
if 条件式语句和条件操作符(三元操作符)
123456 |
var x;if (y >= 0) { x = y;} else { x = -y;} |
上面这几句程序无疑都是 statements
,另外 expression
也有个对应的写法和上面这段程序完全等价
1 |
var x = (y >= 0 ? y : -y); |
在等号和分号之间的就是一个 expression
,其中的 ()
不是必须的,但加上去比较容易阅读。
分号; 与 逗号,
在 javascript 中 statement
之间我们可以用 ;
分号来区分和串连。
1 |
foo(); bar() |
而 expression
也有一个鲜为人知的 ,
操作符可以用来串连。
1 |
foo(), bar() |
两个 expression 都会执行,但是返回最后面的。
123456 |
$ ‘a‘, ‘b‘‘b‘ $ var x = (‘x‘, ‘y‘)$ x‘y‘ |
3. 容易产生误会的 expressions (看起来像 statements)
有些 expressions
看起来像是 statements
。下面我们会列出一些容易产生疑义的例子逐一讨论。主要是因为它们会因为不同的位置产生不同的行为。
对象实字(Object Literal)与程序区块(Block)
下面这段范例是一个对象实字,属于 expression
,用来产生一个对象
123 |
{ foo: bar(3, 5)} |
同时它还是一个符合规范的 statement
,因为它具备了:
- block: 一段 statement 放在
{}
中 - label: 我们可以在任何一段 statement 之前放上一个 label,在这边 label 是
foo:
- statement: 一个 expression statement
bar(3, 5)
所以 {}
到底是一个 block 还是对象实字,你可能会说是对象
那让我们来看看下面这个奇怪的例子
123456789101112131415161718192021222324252627282930313233343536373839404142 |
> 1 + ‘string‘‘1string‘ > 1 + undefinedNaN > 1 + null1 > 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
所以执行完之后接 +[]
。
12 |
> +[]0 |
吓尿了吧!除了 if, 循环外 javascript 也具有独立的 block。
下面这段程序说明了 label
和 block
的用法:
12345678 |
function test (printTwo) { printing: { console.log(‘One‘); if (!printTow) break printing; console.log(‘Two‘); } console.log(‘Three‘);} |
执行的结果
12345678 |
> test(false)"One""Three" > test(true)"One""Two""Three" |
从上面验证了 {}
的语法如果遇到 statements
的位置,就会被当成 statements
,而如果在 expressions
的位置就会被当解析成一个值。
1234567 |
> {} + [];// 就是一个最好的例子,{} 被当作 statement 就是一个 block // 如果换成 > var x = {};// 那他就是一个 expression 代表一个值 - 一个对象 |
让我们接着看下一个例子。
Function expression 与 function 声明
下面的程序是一个 function expression
1 |
function () {} |
你也可以给 function expression
一个名称
1 |
function foo () {} |
在当作 function expression
时上面的 function 名称 foo
只存在在 function 内部能使用,举例来说像是一个递归。
你可能困惑了,我们到底在说啥?看看下面的例子,我们要说的是当 function
放在 statements
和 expressions
不同位置时的差异
1234 |
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
我们已经看到有一些 expression
和 statement
语法上是没有区别的。这意味着相同的程序会有不同行为取决于它出现在 expression 的位置
或者是 statement 位置。为了防止产生疑义。javascript 会禁止 expression statement 使用 {}
或 function
开头。
换句话说就是在 javascript 认定为 statement 的位置,使用了 expression
会变成 expression statement
。这并不是 expression
,所以产生一些特殊的状况 {}
会被当作 block 解释,function
开头的语法会被当作函数定义。
所你当你想要使用这两者为开头撰写 expression statement 时,你可以放上 ()
可以确保位于一个 expression 的位置。
这就是 statement 或者 expression 所延伸的问题,也可以说造成我们极度混乱的根源。让我们来看看 eval
和立即调用函数:
eval
eval
会解析他的参数当做一句 statement
。如果你希望 eval 回传一个对象你就需要在对象实字外围放上()
12345 |
> eval("{foo: 123}");123 > eval("({foo: 123})");{foo: 123} |
下次再阅读文档的时候是不是更有感觉了。
立即调用函数
立即调用函数的部分
12 |
(function () { return "abc" }())‘abc‘ |
如果你省略了 ()
如下,你就会得到一个语法错误的消息。function 声明不可以匿名。
12 |
function () { return "abc" }()SyntaxError: function statement requires a name |
就算替 function 加上名称还是喷错
12 |
function foo() { return "abc" }()SyntaxError: syntax error |
因为函数声明不能立即调用(IIFE),不过除了使用 ()
还有些技巧,当我们硬要要把某段程序当做 expression
执行时,可以使用一元操作符 +
或 !
,不过和()
的方式比起来这会影响回传结果,如果你不在意的话这也是一种方式。
123 |
> +function () { console.log("hello") }()NaNhello |
这个 NaN 是 +
遇上 undefined
的结果,喔!对了还有一种是透过 void
的方式
123 |
> void function () { console.log("hello") }()undefinedhello |
连续使用立即调用函数
当你在连续调用 IIFE 的时候必须要注意不要忘记分号 ; 结尾
123 |
(function () {}())(function () {}())// TypeError: undefined is not a function |
上面的程序会产生错误因为 javascript 以为第二行的 ()
是要拿第一行产生的结果当作一个函数来调用。
123 |
(function () {}());(function () {}())// OK |
下面范例因为 javascript 自动补上分号的功能,使用一元操作符的话 ;
可以省略。
123 |
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 对于
expression
和expression statement
解释行为是不一样的。 - 下面这两种语法对于其位置尤其需要注意
function
- statement 位置: 当作函数声明,即建立一个变量它的值是一个 function。,不能立即调用。
- expression 位置: 为 function expression 产生一个为 function 的值,可以被立即调用(IIFE)。
{}
- statement 位置: block 一个程序区块,例如 for, label 的 block。
- expression 位置: 对象实字,建立一个值 - 对象。
原文地址:https://www.cnblogs.com/petewell/p/11410459.html