小探js中表达式

由++(自增操作符)引发的思考

最近发现了太多需要记忆的东西,深感自己失去了思考能力,昨夜在聊天发送微博辣鸡++++++++++后,突发奇想,在控制台里敲下以下代码:

1
2
let a = 1;
a++++;

Uncaught SyntaxError: Unexpected token '++'

看起来编译器并没有理解我的意图,那么再规范化一下:

1
2
let a = 1;
(a++)++;

Uncaught SyntaxError: Invalid left-hand side expression in postfix operation

什么鬼?我们不妨自行用通俗的语言解释一下这个错误,(a++),在自增前返回 a 的值,也就是 1,然后再对 1 做自增,然后报错。

不妨来验证一下:

1
1++;

Uncaught SyntaxError: Invalid left-hand side expression in postfix operation

一模一样的错误,但是,这句话究竟在讲什么?

postfix++操作符

对于后置的自增操作符(前置、自减类似),mdn 有如下的解释[1]

The increment operator increments (adds one to) its operand and returns a value.

  • If used postfix, with operator after operand (for example, x++), then it increments and returns the value before incrementing.

说明我们此操作符的理解大致正确,

然后看语法,es6 标准中语法规定如下[2]

PostfixExpression[Yield] :

  • LeftHandSideExpression[?Yield]
  • LeftHandSideExpression[?Yield][no lineterminator here] ++
  • LeftHandSideExpression[?Yield][no lineterminator here] –

说明后置的自增表达式可以由LeftHandSideExpression和自增运算符组成。那么,什么是 LeftHandSideExpression?什么是表达式(Expression)?

表达式(Expression)

An expression is any valid unit of code that resolves to a value.[3 from mdn, Expressions]

表达式是任何能被解析成值的代码单元。当然 mdn 也介绍了下表达式的分类等等,此处不加赘述。

我们看个例子:1 + 2是不是表达式?是的。那么,

1
2
let x = 1;
x + 2;

x + 2是不是个表达式?是的。再来,

1
delete x;

是不是个表达式?是的。delete作用的对象是?1这个值?还是x

引用?

尝试一下:

1
delete 1;

true

1
2
delete x;
x;

false
1

1
2
3
let a = { x: 1 };
delete a.x;
a.x;

true
undefined

很明显,我们并不是删除了a.x的值,我们只是删除了a.x的引用,当然,想了解更多为什么delete 1返回true可以看下官方文档 or 周爱民老师的极客时间专栏

对于一个“变量”而言,我们可以使用它的值,也可以使用它的引用,比如:

1
x = x;

对这个表达式的理解,涉及两个概念,LHS 和 RHS,RHS 查询与简单地查找某个变量的值别无二致,而 LHS 查询则是试图 找到变量的容器本身,从而可以对其赋值。[from 你不知道的 JavaScript 上卷]

我们对=后面的x做 RHS,对=前面的x做 LHS,把 RHS 的结果赋给 LHS 的结果,也就是“把x的值赋给x”。

那么,这里的“变量”指的是什么?“赋值”表达式又有何特殊之处呢?

LeftHandSideExpression

我们不妨再测试一下:

1
1 = 2;

Uncaught SyntaxError: Invalid left-hand side in assignment

很明显报错了,我们不可能给 1 赋值。而且这里的错误不同于我们给一个常量赋值:

1
2
3
4
5
const y = 1;
y = 2;

function fn() {}
fn() = 1;

Uncaught TypeError: Assignment to constant variable.

在语法层面上即可以看出,左侧的1是不能赋值的,我们也再次看到了Invalid left-hand side,与++导致的如出一辙。

我们先看看规范对于Left-Hand-Side Expressions语法描述
此处不再细究,只需要知道包含了各种表达式(有志之士可以研究下),其中包括1, x等等等等。那问题出在哪呢?

赋值表达式的 Early Errorspostfix 表达式的 Early Errors里可以找到一条

It is an early Reference Error if IsValidSimpleAssignmentTarget of LeftHandSideExpression is false.

那这个IsValidSimpleAssignmentTarget又在检查什么呢?

字面上看,在检查是否是可以被赋值的对象,在标准中,有以下规则:

对于一个标识符(Identifier),除了

If this IdentifierReference is contained in strict mode code and StringValue of Identifier is “eval” or “arguments”, return false.

都是返回 true。[ref]

对于Left-Hand-Side Expressions,应用以下规则[ref]

Left-Hand-Side Expressions IsValidSimpleAssignmentTarget

这便是报语法错误的原因了。

至于IsValidSimpleAssignmentTarget对二者判断条件的差别与联系,是否能说明,对于标识符(Identifier),也就是俗称的变量名、常量名等,满足其条件,就可以满足Left-Hand-Side Expressions的条件。我想是正确的,但是未能验证。望补充。

postfix/prefix 操作符是糟粕?

写了这么多,其实是为了解决自增操作符的问题,在其它语言测试的时候,发觉有些语言是没有自增操作符的。如 rust,swift。引用rust FAQ的解释:
虽然这些操作符很方面,但是语义不明确容易造成错误,直接使用x = x + 1会更明确。