Skip to content

JavaScript问题集锦 #2

@creeperyang

Description

@creeperyang

原博客迁移过来(有更改),并将保持更新。


关于JavaScript,工作和学习过程中遇到过许多问题,也解答过许多别人的问题。这篇文章记录了一些有价值的问题。

1. 对象字面值不能正确解析

问题{a:1}.a报错,错误Uncaught SyntaxError: Unexpected token .

解决

({a:1}.a) // 或({a:1}).a

原因

MDN: Object literals

An object literal is a list of zero or more pairs of property names and associated values of an object, enclosed in curly braces ({}). You should not use an object literal at the beginning of a statement. This will lead to an error or not behave as you expect, because the { will be interpreted as the beginning of a block.

简单说,就是声明对象字面值时,语句开头不应该用{,因为js解释器会认为这是语句块(block)的开始。

同理,类似问题{ name: "mc", id: 1 }会报错Uncaught SyntaxError: Unexpected token :也是这个道理。({ name: "mc", id: 1 })即可正确解析。但稍注意下,{name: "mc"}是不会报错的,它等同于name: "mc",并返回一个字符串"mc"

2. 数字的点操作符

问题123.toFixed(2)报错,错误Uncaught SyntaxError: Unexpected token ILLEGAL

解决

(123).toFixed(2) // >> "123.00"
// 以下两种都可以,但完全不推荐
123..toFixed(2)
123 .toFixed(2)

原因

很简单,js解释器会把数字后的.当做小数点而不是点操作符。

3. 连等赋值问题

问题:尝试解释下连等赋值的过程。下面的代码为什么是这样的输出?

var a = {n: 1};  
var b = a; 
a.x = a = {n: 2};  
console.log(a.x);// --> undefined  
console.log(b.x);// --> {n:2}

原因

我们可以先尝试交换下连等赋值顺序(a = a.x = {n: 2};),可以发现输出不变,即顺序不影响结果。

那么现在来解释对象连等赋值的问题:按照es5规范,题中连等赋值等价于
a.x = (a = {n: 2});,按优先获取左引用(lref),然后获取右引用(rref)的顺序,a.xa中的a都指向了{n: 1}。至此,至关重要或者说最迷惑的一步明确。(a = {n: 2})执行完成后,变量a指向{n: 2},并返回{n: 2};接着执行a.x = {n: 2},这里的a就是b(指向{n: 1}),所以b.x就指向了{n: 2}

搜索此题答案时,颜海镜的一篇博客关于此题也有讲述,不过没有讲清楚(或许是我没有领会 :P)。


*以 AST 的角度更清晰地解释此题(2017-11-06)*

esprima 提供解析 JS 到 AST 的功能,我们可以借此看一下这段代码在引擎眼里到底是什么。(其实 node 从 8 开始开始支持编译 JS 到 AST 了 (V8 ignition interpreter),不过 node 好像没有提供接口给我们使用)。

下面是我拿到的上面代码的 AST:

{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "a"
          },
          "init": {
            "type": "ObjectExpression",
            "properties": [
              {
                "type": "Property",
                "key": {
                  "type": "Identifier",
                  "name": "n"
                },
                "computed": false,
                "value": {
                  "type": "Literal",
                  "value": 1,
                  "raw": "1"
                },
                "kind": "init",
                "method": false,
                "shorthand": false
              }
            ]
          }
        }
      ],
      "kind": "var"
    },
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "b"
          },
          "init": {
            "type": "Identifier",
            "name": "a"
          }
        }
      ],
      "kind": "var"
    },
    {
      "type": "ExpressionStatement",
      "expression": {
        "type": "AssignmentExpression",
        "operator": "=",
        "left": {
          "type": "MemberExpression",
          "computed": false,
          "object": {
            "type": "Identifier",
            "name": "a"
          },
          "property": {
            "type": "Identifier",
            "name": "x"
          }
        },
        "right": {
          "type": "AssignmentExpression",
          "operator": "=",
          "left": {
            "type": "Identifier",
            "name": "a"
          },
          "right": {
            "type": "ObjectExpression",
            "properties": [
              {
                "type": "Property",
                "key": {
                  "type": "Identifier",
                  "name": "n"
                },
                "computed": false,
                "value": {
                  "type": "Literal",
                  "value": 2,
                  "raw": "2"
                },
                "kind": "init",
                "method": false,
                "shorthand": false
              }
            ]
          }
        }
      }
    }
  ],
  "sourceType": "script"
}

可以清晰地得到,代码等价于:a.x = (a = {n: 2});。然后核心的知识点是:引用解析发生在实际赋值之前

4. 逗号操作符

问题: 下面的代码返回什么,为什么?

var x = 20;
var temp = {
    x: 40,
    foo: function() {
        var x = 10;
        return this.x;
    }
};
(temp.foo, temp.foo)(); // 20,而不是40

原因

MDN逗号操作符:

The comma operator evaluates each of its operands (from left to right) and returns the value of the last operand.

即逗号操作符会从左到右计算它的操作数,返回最后一个操作数的值。所以(temp.foo, temp.foo)();等价于var fun = temp.foo; fun();fun调用时this指向window,所以返回20。

5. parseInt传入数字

问题: parseInt传入数字时为什么有以下输出?

parseInt(0.000008) // >> 0
parseInt(0.0000008) // >> 8

原因

parseInt(arg)时会调用arg.toString()

(0.000008).toString() // "0.000008"
(0.0000008).toString() // "8e-7"

6. 前端面试题,利用给定接口获得闭包内部对象

问题前端面试题,利用给定接口获得闭包内部对象

var o = (function() {
    var person = {
        name: 'Vincent',
        age: 24,
    };
    return {
        run: function(k) {
            return person[k];
        },
    }
}());

在不改变上面的代码情况下, 怎么得到原有的 person 对象?

解决

Object.defineProperty(Object.prototype, 'self', 
    {
        get: function() {
            return this;
        },
        configurable: true
    });
o.run('self'); // 输出 person

但如果加上person.__proto__ = null,目前还没找到解决方法。

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions