Dec 18

关于js中Block与Object的识别的问题

Lrdcq , 2017/12/18 10:32 , 程序 , 閱讀(6413) , Via 本站原創
这篇博客只是一个关于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),其实可以直接投进去看看就清晰了:
{
    "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}报错太反直觉了,而针对这个场景做了优化,只是优化的方式可能...有点粗暴而已。
关键词:javascript
logo