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

原文引用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 中能够清楚的分辨 expressionsstatements 的差异对于撰写程序是有一定的帮助,可以避免掉入一些陷阱。

简单来说一个表达式 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

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。
下面这段程序说明了 labelblock 的用法:

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 放在 statementsexpressions 不同位置时的差异

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

我们已经看到有一些 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 回传一个对象你就需要在对象实字外围放上()

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 自动补上分号有几项建议如下:

  1. 在 return,break,continue,++,– 五种 statement 中,换行符可完全等于 ;
  2. var,if,do while,for,continue,break,return,with,switch,throw,try,debugger 关键字开头,以及空的 statement,上一行会自动补上分号。
  3. 遇到 expression statement 和 function expression 情况非常复杂,后面请务必要加上分号。
  4. ([ 开头的 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.cnblogs.com/petewell/p/11410459.html

时间: 2024-10-18 18:36:56

重读 Axel 的 Javascript 中的 Expression vs Statement 一文的相关文章

JavaScript中的正则表达式(终结篇)

JavaScript中的正则表达式(终结篇) 在之前的几篇文章中,我们了解了正则表达式的基本语法,但那些语法不是针对于某一个特定语言的.这篇博文我们将通过下面几个部分来了解正则表达式在JavaScript中的使用: JavaScript对正则表达式的支持程度 支持正则表达式的RegExp类型 RegExp的实例属性 RegExp的实例方法 RegExp的构造函数属性 简单的应用 第一部分:JavaScript对正则表达式的支持程度 之前我介绍了正则表达式的基本语法,如果大家不是很了解可以先看下面

JavaScript中的函数表达式

在JavaScript中,函数是个非常重要的对象,函数通常有三种表现形式:函数声明,函数表达式和函数构造器创建的函数. 本文中主要看看函数表达式及其相关的知识点. 函数表达式 首先,看看函数表达式的表现形式,函数表达式(Function Expression, FE)有下面四个特点: 在代码中须出现在表达式的位置 有可选的函数名称 不会影响变量对象(VO) 在代码执行阶段创建 下面就通过一些例子来看看函数表达式的这四个特点. FE特点分析 例子一:在下面代码中,"add"是一个函数对象

JavaScript大杂烩6 - 理解JavaScript中的this

在JavaScript开发中,this是很常用的一个关键字,但同时也是一个很容易引入bug的一个关键字,在这里我们就专门总结一下页面中可能出现的this关键字(包括几种在其他页面文件中出现的this). JavaScript中的this关键字通常只使用在函数中,它指向当前函数的调用者,这是this关键字的本质,所有的使用方式都是围绕这个展开的,让我们来看一下在各种性质的函数中this的用法.1. 在对象的函数中使用this var person = { name: 'Frank', say: f

JavaScript中“javascript:void(0) ”是什么意思

来源: <a href="javascript:test();void(0);">here</a> 此处:Javascript中void是一个操作符,该操作符指定要计算一个表达式但是不返回值. void 操作符用法格式如下:1. javascript:void (expression)2. javascript:void expression expression 是一个要计算的 Javascript 标准的表达式.表达式外侧的圆括号是可选的,鉴于规范化,以及养

C++、Java、JavaScript中的正则表达式

编程思想之正则表达式 什么是正则表达式? 正则表达式(Regular Expression)就是用某种模式去匹配一类字符串的公式.如你要在一篇文章中查找第一个字是"罗"最后一个字是"浩"的三个字的姓名,即"罗*浩":那么"罗*浩"就是公式,也称作模式(Pattern),这篇文章就是要匹配的串(或叫文本text).再如,你要检查输入的一个字符串是否是126邮箱的格式,你得制定一个规则去查检,这种规则就是正则表达式. 从入门开始

Javascript 中的严格模式

原文:http://www.ruanyifeng.com/blog/2013/01/javascript_strict_mode.html 一.概述 除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict mode).顾名思义,这种模式使得Javascript在更严格的条件下运行. 设立"严格模式"的目的,主要有以下几个: - 消除Javascript语法的一些不合理.不严谨之处,减少一些怪异行为; - 消除代码运行的一些不安全之

JavaScript中‘this’关键词的优雅解释

本文转载自:众成翻译 译者:MinweiShen 链接:http://www.zcfy.cc/article/901 原文:https://rainsoft.io/gentle-explanation-of-this-in-javascript/ 1. this之谜 许多时候,this关键词对我以及许多刚起步的JavaScript程序员来说,都是一个谜.它是一种很强大的特性,但是理解它需要花不少功夫. 对有Java, PHP或者其他常见的编程语言背景的人来说,this仅仅被看成是类方法中当前对象

JavaScript中的函数表达式及递归

在JavaScript中,函数是个非常重要的对象,函数通常有三种表现形式:函数声明,函数表达式和函数构造器创建的函数. 本文中主要看看函数表达式及其相关的知识点. 函数表达式 首先,看看函数表达式的表现形式,函数表达式(Function Expression, FE)有下面四个特点: 在代码中须出现在表达式的位置 有可选的函数名称 不会影响变量对象(VO) 在代码执行阶段创建 下面就通过一些例子来看看函数表达式的这四个特点. 特点分析 例子一:在下面代码中,"add"是一个函数对象,&

图解JavaScript中的原型链

转自:http://www.jianshu.com/p/a81692ad5b5d typeof obj 和 obj instanceof Type 在JavaScript中,我们经常用typeof obj和obj instanceof Type来识别类型,那么两者的区别在哪?先来看两段代码 <!--typeof obj的方式判断--> <script>    var str = "toby";    console.log(typeof str);// stri