-
Notifications
You must be signed in to change notification settings - Fork 207
Description
从原博客迁移过来(有更改),并将保持更新。
关于JavaScript,工作和学习过程中遇到过许多问题,也解答过许多别人的问题。这篇文章记录了一些有价值的问题。
1. 对象字面值不能正确解析
问题:{a:1}.a
报错,错误Uncaught SyntaxError: Unexpected token .
。
解决:
({a:1}.a) // 或({a:1}).a
原因:
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.x
和a
中的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
原因:
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
,目前还没找到解决方法。