Dec
18
这篇博客只是一个关于js中BlockStatement与ObjectExpression的识别的有趣问题的小记,并没有任何工程实践意义,纯属娱乐与探讨。
现象
在偶然分析一个问题的过程中,发现chrome中有一个奇特的问题:
这4组表达式都是直接打出的对象,而随着表达式末尾有无分号的区别,结果居然不一样。吐血了。
乍看起来,肯定是chrome有bug,几乎周围所有的人都是觉得 有分号的表达式 出错了吧。
然而,在ff等其他jscore的现代浏览器中尝试过一番之后,验证了:
1. chrome确实有bug,或者说是v8的bug(node表现也一样),其他jscore中两种表达式的结果就是一致的。
2. 正确的结果是 有分号的表达式。
懵逼了吧。在回去仔细看看上面的几个表达式,并且做了更多的测试:
1. 明显在表达式开始的{}出现了问题,只要不在表达式开始,都是对象。
2. 在表达式开始时,它没有参与后面的运算。
3. 是否是对象,真的和分号无关。
同时,把上面这些表达式放到chrome做分号对比测试时,顺便发现,chrome这个bug发生在表达式开始和末尾都是{}的时候,比如那一句{a:1}+1;,chrome中就没有出现bug,都得到了1。
那么亲们,猜到了到底怎么回事儿了么?
答案
答案很明确了,正确的情况下,出现在表达式开始的{},是个代码块。而里面的a:,是一个label。有一个在线分析js语法树的网站(http://esprima.org/demo/parse.html),其实可以直接投进去看看就清晰了:
如果还看没懂上面说的东西,想象一下下面这一段代码:
用到了几个罕见的特性:
1. label,这种在js中少见并不推荐使用的特性,主要用于break和continue定向跳跃。
2. 独立代码块,代码块会返回其中最后一个表达式的值。
因此{a:1}会得到1。
疑问
因此,我们就有疑问了,既然在js中{a:1}这样的语句应该看作BlockStatement,也可以看做ObjectExpression,除去chrome中的bug,jscore应该是怎么识别它到底是哪一个呢?
大体翻阅es标准,并没有找到这样细节上的问题的答案。不过通过各种测试,我们还是可以得到推断的结论:
1. 非常粗暴的判断:在句首的{}一定是代码块。比如下面的几句,明显是一个对象,或者尝试表现得像对象,也因为被识别为代码块而报错了。
当然反之,不在句首的{}一定是对象。像下面两句不在句首尝试使用block并取出返回值,出现语法错误。
2. 代码块后面自带分号,代码块返回值当然也无法参与运算。比如这两句:
其实可以看做{a:1}后面是有分号的,即{a:1};+1;后面是+1所以得到的1。当然第二句后面是*1,自然就出错了。语法树分析工具看一下第一句也能说明问题,可以看到这里+是UnaryExpression即一元表达式,而不是BinaryExpression即二元表达式:
起因与结果
js这种语法比较容易混淆的情况,这样清晰的了解定义,才能确实的写明白js代码。当然,这次这个case确实没有任何实用意义,权且当了解。
当然,一开始我是希望它有实用意义的。这件事的起因是我想写类似于如下代码:
这种写法在大部分c系语言中都可以正常使用,在js中却不能如愿。因此才在调查js到底是怎么判断{}到底什么情况是BlockStatement什么情况是ObjectExpression。结果得到的是如此冷冰冰的结果,看来是真凉了,用不起来了。
同时我们还发现了一个疑似v8的bug。当然也不排除是一个feature,有人觉得{a:1}得1,{'a':1}报错太反直觉了,而针对这个场景做了优化,只是优化的方式可能...有点粗暴而已。
现象
在偶然分析一个问题的过程中,发现chrome中有一个奇特的问题:
这4组表达式都是直接打出的对象,而随着表达式末尾有无分号的区别,结果居然不一样。吐血了。
乍看起来,肯定是chrome有bug,几乎周围所有的人都是觉得 有分号的表达式 出错了吧。
然而,在ff等其他jscore的现代浏览器中尝试过一番之后,验证了:
1. chrome确实有bug,或者说是v8的bug(node表现也一样),其他jscore中两种表达式的结果就是一致的。
2. 正确的结果是 有分号的表达式。
懵逼了吧。在回去仔细看看上面的几个表达式,并且做了更多的测试:
1. 明显在表达式开始的{}出现了问题,只要不在表达式开始,都是对象。
2. 在表达式开始时,它没有参与后面的运算。
3. 是否是对象,真的和分号无关。
同时,把上面这些表达式放到chrome做分号对比测试时,顺便发现,chrome这个bug发生在表达式开始和末尾都是{}的时候,比如那一句{a:1}+1;,chrome中就没有出现bug,都得到了1。
那么亲们,猜到了到底怎么回事儿了么?
答案
答案很明确了,正确的情况下,出现在表达式开始的{},是个代码块。而里面的a:,是一个label。有一个在线分析js语法树的网站(http://esprima.org/demo/parse.html),其实可以直接投进去看看就清晰了:
{
"type": "Program",
"body": [
{
"type": "BlockStatement",
"body": [
{
"type": "LabeledStatement",
"label": {
"type": "Identifier",
"name": "a"
},
"body": {
"type": "ExpressionStatement",
"expression": {
"type": "Literal",
"value": 1,
"raw": "1"
}
}
}
]
}
],
"sourceType": "script"
}
如果还看没懂上面说的东西,想象一下下面这一段代码:
//原代码块
{
var x = 1;
x;
}
//简化为
{
1;
}
//原语句加上label
{
lable: var x = 1;
x;
}
//label名字为a再简化的话
{
a: 1;
}
//简化为
{a:1}
//
用到了几个罕见的特性:
1. label,这种在js中少见并不推荐使用的特性,主要用于break和continue定向跳跃。
2. 独立代码块,代码块会返回其中最后一个表达式的值。
因此{a:1}会得到1。
疑问
因此,我们就有疑问了,既然在js中{a:1}这样的语句应该看作BlockStatement,也可以看做ObjectExpression,除去chrome中的bug,jscore应该是怎么识别它到底是哪一个呢?
大体翻阅es标准,并没有找到这样细节上的问题的答案。不过通过各种测试,我们还是可以得到推断的结论:
1. 非常粗暴的判断:在句首的{}一定是代码块。比如下面的几句,明显是一个对象,或者尝试表现得像对象,也因为被识别为代码块而报错了。
当然反之,不在句首的{}一定是对象。像下面两句不在句首尝试使用block并取出返回值,出现语法错误。
2. 代码块后面自带分号,代码块返回值当然也无法参与运算。比如这两句:
其实可以看做{a:1}后面是有分号的,即{a:1};+1;后面是+1所以得到的1。当然第二句后面是*1,自然就出错了。语法树分析工具看一下第一句也能说明问题,可以看到这里+是UnaryExpression即一元表达式,而不是BinaryExpression即二元表达式:
{
"type": "Program",
"body": [
{
"type": "BlockStatement",
"body": [
{
"type": "LabeledStatement",
"label": {
"type": "Identifier",
"name": "a"
},
"body": {
"type": "ExpressionStatement",
"expression": {
"type": "Literal",
"value": 1,
"raw": "1"
}
}
}
]
},
{
"type": "ExpressionStatement",
"expression": {
"type": "UnaryExpression",
"operator": "+",
"argument": {
"type": "Literal",
"value": 1,
"raw": "1"
},
"prefix": true
}
}
],
"sourceType": "script"
}
起因与结果
js这种语法比较容易混淆的情况,这样清晰的了解定义,才能确实的写明白js代码。当然,这次这个case确实没有任何实用意义,权且当了解。
当然,一开始我是希望它有实用意义的。这件事的起因是我想写类似于如下代码:
document.body.appendChild({
var img = document.createElement("img");
img.src = "";//todo
img.onload = function (e) {
//todo
}
img;
});
这种写法在大部分c系语言中都可以正常使用,在js中却不能如愿。因此才在调查js到底是怎么判断{}到底什么情况是BlockStatement什么情况是ObjectExpression。结果得到的是如此冷冰冰的结果,看来是真凉了,用不起来了。
同时我们还发现了一个疑似v8的bug。当然也不排除是一个feature,有人觉得{a:1}得1,{'a':1}报错太反直觉了,而针对这个场景做了优化,只是优化的方式可能...有点粗暴而已。