【聚沙】
对象字面量
问题:
1
| { a: 9 }.a // Uncaught SyntaxError: Unexpected token .
|
解决:
1
2
| ( { a: 1 }.a ) // 方式1
( { a: 1 } ).a // 方式2
|
原因:MDN 对象字面值(Object literals))
对象字面值是封闭在花括号对({})中的一个对象的零个或多个”属性名-值”对的(元素)列表。你不能在一条语句的开头就使用对象字面值,这将导致错误或非你所预想的行为,因为此时左花括号({)会被认为是一个语句块的起始符号。
位运算
位运算会自动将数字转换成整数
实现一个类似 parseInt()
的函数:
1
2
3
4
5
6
7
| function convertInt(num) {
// return num >> 0;
// return num << 0;
// return num | 0;
// return num ^ 0;
return ~~ num;
}
|
Prefix Increment Operator(++)的问题
关于前自增运算符的一个有意思的问题:
1
| ++'52'.split('')[0] // 返回的是?
|
这道题来自 Another JavaScript quiz 第8题,主要是优先级问题,应该返回6,看完答案应该没什么难理解的。但是,题目的某个注意点:
1
2
| ++'5'
// Uncaught ReferenceError: Invalid left-hand side expression in prefix operation
|
却非常有意思。所以问题是为什么 ++'5'
报错而 ++'52'.split('')[0]
可以正确执行
阅读 http://es5.github.io/#x11.4.4,可以看到 Prefix Increment Operator 操作的第5步 PutValue(expr, newValue)
要求 expr
是引用。
而在这里,
'5'
是值,不是引用,所以报错。
'52'.split('')[0]
返回的是 ['5','2'][0]
,对象的属性访问返回的是引用,所以可以正确执行。
正确方式:
1
2
3
4
| var x = '5';
++x // 6
++'5'[0] // 6
|
数组展平
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| function flatten(arr) {
if ( !isArray(arr) ) {
return arr;
}
return [].concat.apply([], arr.map(function(item) {
return flatten(item);
}));
}
function isArray(arr) {
return toString.call(arr).slice(8, -1) === 'Array';
}
|
调用方式:
1
2
3
| flatten( [3, 4, [9, 5], [2, 1, 7], 4, 8] )
// [3, 4, 9, 5, 2, 1, 7, 4, 8]
|
获取闭包对象
一道题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| var obj = (function() {
var person = {
name: 'vincent',
age: 24
};
return {
run: function(key) {
return person[key];
}
};
})();
Object.defineProperty(Object.prototype, 'get', {
get: function() {
return this;
}
});
console.log( obj.run('age') )
console.log( obj.run('get') )
|
为什么不要使用 eval
eval 函数会在当前作用域中执行一段 JavaScript 代码字符串。
1
2
3
4
5
6
7
8
| var foo = 1;
function test() {
var foo = 2;
eval('foo = 3');
return foo;
}
test(); // 3
foo; // 1
|
但是 eval 只在被直接调用并且调用函数就是 eval 本身时,才在当前作用域中执行。
1
2
3
4
5
6
7
8
9
| var foo = 1;
function test() {
var foo = 2;
var bar = eval;
bar('foo = 3');
return foo;
}
test(); // 2
foo; // 3
|
译者注:上面的代码等价于在全局作用域中调用 eval,和下面两种写法效果一样:
1
2
3
4
5
6
7
8
9
| // 写法一:直接调用全局作用域下的 foo 变量
var foo = 1;
function test() {
var foo = 2;
window.foo = 3;
return foo;
}
test(); // 2
foo; // 3
|
1
2
3
4
5
6
7
8
9
| // 写法二:使用 call 函数修改 eval 执行的上下文为全局作用域
var foo = 1;
function test() {
var foo = 2;
eval.call(window, 'foo = 3');
return foo;
}
test(); // 2
foo; // 3
|
在任何情况下我们都应该避免使用 eval 函数。99.9% 使用 eval 的场景都有不使用 eval 的解决方案。
伪装的 eval
定时函数 setTimeout 和 setInterval 都可以接受字符串作为它们的第一个参数。 这个字符串总是在全局作用域中执行,因此 eval 在这种情况下没有被直接调用。
安全问题
eval 也存在安全问题,因为它会执行任意传给它的代码, 在代码字符串未知或者是来自一个不信任的源时,绝对不要使用 eval 函数。
结论
绝对不要使用 eval,任何使用它的代码都会在它的工作方式,性能和安全性方面受到质疑。 如果一些情况必须使用到 eval 才能正常工作,首先它的设计会受到质疑,这不应该是首选的解决方案, 一个更好的不使用 eval 的解决方案应该得到充分考虑并优先采用。
对象比较
虽然 == 和 === 操作符都是等于操作符,但是当其中有一个操作数为对象时,行为就不同了。
1
2
3
4
5
| {} === {}; // false
new String('foo') === 'foo'; // false
new Number(10) === 10; // false
var foo = {};
foo === foo; // true
|
这里等于操作符比较的不是值是否相等,而是是否属于同一个身份;也就是说,只有对象的同一个实例才被认为是相等的。 这有点像 Python 中的 is 和 C 中的指针比较。
阅读
浅谈API安全设计
JavaScript问题集锦
一种面试思路
Interviewing a front-end developer
站点
JavaScript Garden