JavaScript 问题集锦
关于 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}
。
以 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
,目前还没找到解决方法。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
13.
jQuery.trigger
的extraParameters
为undefined
的问题问题: 下面
slider
为什么是undefined
?刚看到错误时感觉莫名其妙:怎么就错了?jQuery用的很 6 好不好?
10. Function.prototype.call/apply 的 this 问题
问题: 下列情况中
this
为什么是这样的?原因:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
非严格模式下,
this
默认指向全局对象,call/apply
显式指定this
参数时也会强制转换参数为对象(如果不是对象)。其中,null/undefined
被替换为全局对象,基础类型被转换为包装对象。严格模式下,
this
默认为undefined
,且call/apply
显式指定this
参数时也不会有强制转换。11. 给基础类型添加属性无效
问题: 为什么给基础类型添加属性不报错但又无效?
原因:
Property Accessors规范 https://es5.github.io/:
GetValue规范 https://es5.github.io/:
最后可知,
num.prop
等同于Object(num).prop
,也就是说我们添加属性到一个新建的对象上,与num
没任何关系。12. 数组的展开/扁平
问题:
[1,2,[2,3,[4,5]]]
--->[1,2,2,3,4,5]
另外,看到一种方法:利用
array.toString()
然后重新解析也可以完成,但此时数组元素类型丢失。这种方法利用了ToString(array)
会转换成"x,y,z..."
。7. 原型链导致的属性更改无效
问题: 看下面的代码,为什么
obj.x = 100
的赋值无效?原因:
es5 assignment
本题中,
obj.x
实际指向proto.x
,writable: false
阻止了赋值。8. 位操作符
问题: 实现浮点数转整数,或者说取出数字的整数部分。比如
-12.921 --> -12
,12.921 --> 12
等等。解决:
原因:
没有什么高达上,就是神奇的位操作符:有符号右移
>>
,The Signed Right Shift Operator ( >> ) :本题利用了有符号右移会将左操作数转换为32位整数。
补充:
同理,
num | 0
也是可以的。9. IEEE-754精度问题
问题:
0.1 + 0.2 !== 0.3 // true
原因:
所有使用IEEE-754数字实现的编程语言都有这个问题。
0.1
和0.2
的二进制浮点数表示并不是精确的,所以相加后不等于0.3
。这个相加的结果接近0.30000000000000004
。那么你一定想要比较相加后的结果和预期数字怎么办?宽容比较,即允许一定误差,这个误差值一般是
2^-52
(2.220446049250313e-16
)。update 2017-02-13:
ES2015
标准化了这个值,可通过Number.EPSILON
访问