- 对象字面值不能正确解析问题:
{a:1}.a报错,错误Uncaught SyntaxError: Unexpected token .
解决:
({a:1}.a) // 或({a:1}).a
原因:
就是声明对象字面值时,语句开头不应该用{,因为js解释器会认为这是语句块(block)的开始。
同理,类似问题{ name: “mc”, id: 1 }会报错Uncaught SyntaxError: Unexpected token :也是这个道理。({ name: “mc”, id: 1 })即可正确解析。但稍注意下,{name: “mc”}是不会报错的,它等同于name: “mc”,并返回一个字符串”mc”。
- 数字的点操作符问题:123.toFixed(2)报错,
错误Uncaught SyntaxError: Unexpected token ILLEGAL
解决:
(123).toFixed(2) // >> “123.00” // 以下两种都可以,但完全不推荐 123..toFixed(2) 123 .toFixed(2)
原因:
很简单,js解释器会把数字后的.当做小数点而不是点操作符。
- 连等赋值问题问题:尝试解释下连等赋值的过程。下面的代码为什么是这样的输出?
1
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}。
- 逗号操作符问题: 下面的代码返回什么,为什么?
1
var x = 20; var temp = { x: 40, foo: function() { var x = 10; return this.x; } }; (temp.foo, temp.foo)(); // 20,而不是40
原因:
即逗号操作符会从左到右计算它的操作数,返回最后一个操作数的值。所以(temp.foo, temp.foo)();等价于var fun = temp.foo; fun();,fun调用时this指向window,所以返回20。
- parseInt传入数字问题: parseInt传入数字时为什么有以下输出?
parseInt(0.000008) // >> 0 parseInt(0.0000008) // >> 8
原因:
parseInt(arg)时会调用arg.toString()。
(0.000008).toString() // “0.000008” (0.0000008).toString() // “8e-7”
- 前端面试题,利用给定接口获得闭包内部对象问题: 前端面试题,利用给定接口获得闭包内部对象
1
var o = (function() { var person = { name: 'Vincent', age: 24, }; return { run: function(k) { return person[k]; }, } }());
在不改变上面的代码情况下, 怎么得到原有的 person 对象?
解决:1
Object.defineProperty(Object.prototype, 'self', {
get: function() {
return this;
},
configurable: true
});
o.run('self'); // 输出 person
但如果加上person.proto = null,目前还没找到解决方法。
- 原型链导致的属性更改无效问题: 看下面的代码,为什么obj.x = 100的赋值无效?
1
var proto = Object.create({}, { x: { value: 1, writable: false } }); var obj = Object.create(proto); obj.x = 100; // 无效,strict mode下报错: Uncaught TypeError console.log(obj.x); // 1
原因:
本题中,obj.x实际指向proto.x,writable: false阻止了赋值。
- 位操作符问题: 实现浮点数转整数,或者说取出数字的整数部分。比如-12.921 –> -12,12.921 –> 12等等。
解决:1
function convertToInt(num) {
return num >> 0;
}
convertToInt(-Math.PI); // -3
convertToInt(12.921); // 12
原因:
没有什么高达上,就是神奇的位操作符:有符号右移>>,The Signed Right Shift Operator ( >> ) :
- Let lnum be ToInt32(lval).
本题利用了有符号右移会将左操作数转换为32位整数。
补充:
同理,num | 0也是可以的。
- IEEE-754精度问题问题: 0.1 + 0.2 !== 0.3 // true
原因:
所有使用IEEE-754数字实现的编程语言都有这个问题。
0.1和0.2的二进制浮点数表示并不是精确的,所以相加后不等于0.3。这个相加的结果接近0.30000000000000004。
那么你一定想要比较相加后的结果和预期数字怎么办?宽容比较,即允许一定误差,这个误差值一般是2^-52 (2.220446049250313e-16)。
- Function.prototype.call/apply 的 this 问题问题: 下列情况中this为什么是这样的?
1
function nsm() { console.log(this);} nsm(); // Window{top: xxxx} nsm.call(null/undefined); // Window{top: xxxx} nsm.call(1); // Number {[[PrimitiveValue]]: 1} function sm() {'use strict'; console.log(this);} sm(); // undefined sm.call(null); // null sm.call(undefined); // undefined sm.call(1); // 1
原因:
非严格模式下,this默认指向全局对象,call/apply显式指定this参数时也会强制转换参数为对象(如果不是对象)。其中,null/undefined被替换为全局对象,基础类型被转换为包装对象。
严格模式下,this默认为undefined,且call/apply显式指定this参数时也不会有强制转换。
- 给基础类型添加属性无效问题: 为什么给基础类型添加属性不报错但又无效?
1
var num = 1; num.prop = 2; num.prop // undefined
原因:
num.prop 等同于 Object(num).prop,也就是说我们添加属性到一个新建的对象上,与num没任何关系。
- 数组的展开/扁平问题:
1
[1,2,[2,3,[4,5]]]--->[1,2,2,3,4,5] function flatten(arr) { if(!isArray(arr) || !arr.length) { return []; } else { return Array.prototype.concat.apply([], arr.map(function(val) { return isArray(val) ? flatten(val) : val; })); } function isArray(arr) { return Object.prototype.toString.call(arr).slice(8, -1).toLowerCase() === 'array'; } } flatten([1,2,[2,3,[4,5]]]); // [1, 2, 2, 3, 4, 5]
另外,看到一种方法:利用array.toString()然后重新解析也可以完成,但此时数组元素类型丢失。这种方法利用了ToString(array)会转换成”x,y,z…”。
- jQuery.trigger的extraParameters为undefined的问题问题: 下面slider为什么是undefined?
// self 为 某对象
$slider.trigger(‘slideend’, self);
// 监听
$dom.on(‘slideend’, function(ev, slider) {
// Uncaught TypeError: Cannot read property ‘value’ of undefined
console.log(slider.value);
// slider 为 undefined });1
刚看到错误时感觉莫名其妙:怎么就错了?jQuery用的很 6 好不好? 调试!打了几个断点,找到了原因: data = data != null ? jQuery.makeArray( data ) : []; data.unshift( event ); trigger中的self会被makeArray处理,这本来没什么,坑爹的是 self有个属性: self.length = 100,然后悲剧了,makeArray之后data变成[undefined, undefined, ...]。 好了,debug完毕,问题看起来很简单,但总结出个道理:错误常常出现在你想不到/忽视的地方。 14. delete操作符问题: 试着解释下面代码的结果? ```dash (function(x){ delete x; return x; })(1); // 返回 1
delete x其实是尝试删除局部变量x,局部变量是non-configurable的,所以无法删除,在严格模式下,题中代码将报错。
更正:对局部变量和函数名delete是无效的,delete只能删除属性。delete obj.propName 才是合法的形式。下面以代码详细解释:1
function(x) { 'use strict';
delete x;
})(1);
在严格模式下,删除不合法的标识符x(x是变量)1
(function () { 'use strict';
var obj = {};
Object.defineProperty(obj, 'x', {
configurable: false,
writable: true,
enumerable: true,
value: 'hi' });
delete obj.x;
})() // Uncaught TypeError: Cannot delete property 'x' of #<Object>
合法的删除形式,但属性x是non-configurable的,严格模式下报错
1 | window.x = 100; window.y = 100; (function(x) { console.log(x, window.x, window.y); console.log(delete x, delete y); console.log(x, window.x, window.y); return x; })(1); // 1 100 100 // false true // 1 100 undefined // delete |
x失败,因为x是变量(函数内局部变量x覆盖全局变量x),delete y成功,y是全局对象window的属性。 // 非严格模式下,可以 delete y 的写法,但此时是尝试删除全局对象的同名属性y(y在作用域中不是变量或函数名)
- jQuery.fn.offset不支持获取和设置display:none元素的坐标不支持获取很好理解,display:none元素不在文档流中,获取位置自然是undefined。而$(hiddenElement).offset()会返回{top: 0, left: 0}以提高程序健壮性。
不过问题核心是为什么无法正确设置display:none元素的坐标,具体点,即为什么$(hiddenElement).offset(options)设置的真实值是指定值的两倍?
var offset = {top: 10, left: 10}; $dom.offset(offset); // $dom 是 display none 的 // 实际情况,$dom被设置成 left: 20px; top: 20px;
如上面代码所示,隐藏的元素实际被设置打坐标是给定值的2倍,为什么?来看下jQuery关于offset部分的源码:
1 | curOffset = curElem.offset(); curCSSTop = jQuery.css( elem, "top" ); curCSSLeft = jQuery.css( elem, "left" ); ... if ( options.top != null ) { props.top = ( options.top - curOffset.top ) + curTop; } if ( options.left != null ) { props.left = ( options.left - curOffset.left ) + curLeft; } if ( "using" in options ) { options.using.call( elem, props ); } else { curElem.css( props ); } |
原因就是$el.offset(options)并不简单采用options.left和options.top,而会计算( options.top - curOffset.top ) + curTop作为top(left同理)。隐藏元素的 css属性部分的left和top可以正常取到,但curOffset则是{top: 0, left: 0},所以( options.top - curOffset.top ) + curTop –> options.top + curTop造成一倍误差。
- 找出字符串中出现最多的字母这个问题看起来用到的地方挺多,至少我遇到过不止一次,索性在这里讲一讲。先具体描述下问题:
假设字符串’ababccdeajxac’,请找出出现次数最多的字符?
最先想到的解法是用map纪录每个字符的次数,然后找出最多的即可:
1 | function getMaxNumberOfChar(str) { return (str + '').split('').reduce(function(pre, cur, index, arr) { cur in pre ? pre[cur]++ : (pre[cur] = 1); pre[cur] > pre.value && (pre.char = cur, pre.value = pre[cur]); return pre; }, {value: 0}); } getMaxNumberOfChar('ababccdeajxac') // Object {value: 4, a: 4, char: "a", b: 2, c: 3…} |
此外,可以考虑用正则来辅助处理:1
function getMaxNumberOfChar(str) {
return (str + '').split('').sort().join('').match(/(\w)\1*/g).reduce(function(pre, cur) {
return cur.length > pre.value ? {value: cur.length, char: cur[0]} : pre;
}, {
value: 0
})
}
getMaxNumberOfChar('ababccdeajxac')
// Object {value: 4, char: "a"}
- storage event当你用localStorage或sessionStorage的API去更改Storage时,会触发storage事件:
1
window.addEventListener('storage', function(e) { console.log('storage', e); });
这里没有什么特别的,但基本所有问题的根源,或者说要特别注意的是:本页面更改Storage只能在同域名的其它页面去捕获storage事件。
- 一个函数柯里化问题及更多C君出了这样一个题,要求实现sum函数如下:
sum(1) // 1
sum(1)(2) // 3
sum(1)(2)(3) // 6
第一眼,这不是函数柯里化吗,小case,然后我挥笔写下:1
function sum(item) { var cur = item; var inner = function(next) { return next == null ? cur : (cur += next, inner); }; return item == null ? undefined : inner; }
运行下:
所以最后答案是:1
function sum(item) {
var cur = item;
var inner = function(next) {
if (next != null) cur += next;
return inner;
};
inner.toString = function() {
return cur;
} return inner;
}
- 运算符优先级问题普通的运算符优先级对大家应该不成问题,另外也推荐用括号来明确运算符优先级和代码意图,写出更可读的代码。
但之所以有这个问题,是涉及到typeof运算符,比较新颖。
问题:
var str = ‘why I am ‘ + typeof + ‘’; // so what is str?
str是why I am number,思考一下,上面的代码应该等同于’why I am ‘ + (typeof (+ ‘’))。好,现在问题归结到运算符优先级,查看文档验证:
typeof运算符优先级高于+,并且是right-to-left。
- Prefix Increment Operator(++)的问题关于前自增运算符的一个有意思的问题:
++’52’.split(‘’)[0] //返回的是?
这道题来自Another JavaScript quiz第8题,主要是优先级问题,应该返回6,看完答案应该没什么难理解的。但是,题目的某个注意点:
++’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],对象的属性访问返回的是引用,所以可以正确执行。
var x = ‘5’; ++x // 6 ++’5’[0] // 6
- 你真的了解String.prototype.split吗?
1
var classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/; 'div.btn#submit'.split(classIdSplit); // -> ["", "div", "", ".btn", "", "#submit", ""]
var result = str.split(separator);,
separator是带捕获的正则时,每次命中,都添加到结果数组result中。