小探js中表达式
由++(自增操作符)引发的思考
最近发现了太多需要记忆的东西,深感自己失去了思考能力,昨夜在聊天发送微博辣鸡++++++++++
后,突发奇想,在控制台里敲下以下代码:
1 | let a = 1; |
Uncaught SyntaxError: Unexpected token '++'
看起来编译器并没有理解我的意图,那么再规范化一下:
1 | let a = 1; |
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 | let x = 1; |
x + 2
是不是个表达式?是的。再来,
1 | delete x; |
是不是个表达式?是的。delete
作用的对象是?1
这个值?还是x
?
引用?
尝试一下:
1 | delete 1; |
true
1 | delete x; |
false
1
1 | let a = { x: 1 }; |
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 | const y = 1; |
Uncaught TypeError: Assignment to constant variable.
在语法层面上即可以看出,左侧的1
是不能赋值的,我们也再次看到了Invalid left-hand side
,与++
导致的如出一辙。
我们先看看规范对于Left-Hand-Side Expressions
的语法描述:
此处不再细究,只需要知道包含了各种表达式(有志之士可以研究下),其中包括1, x
等等等等。那问题出在哪呢?
在赋值表达式的 Early Errors和postfix 表达式的 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]
这便是报语法错误的原因了。
至于IsValidSimpleAssignmentTarget
对二者判断条件的差别与联系,是否能说明,对于标识符(Identifier),也就是俗称的变量名、常量名等,满足其条件,就可以满足Left-Hand-Side Expressions
的条件。我想是正确的,但是未能验证。望补充。
postfix/prefix 操作符是糟粕?
写了这么多,其实是为了解决自增操作符的问题,在其它语言测试的时候,发觉有些语言是没有自增操作符的。如 rust,swift。引用rust FAQ的解释:
虽然这些操作符很方面,但是语义不明确容易造成错误,直接使用x = x + 1
会更明确。