关于对象

  1. 对象字面值不能正确解析问题:
    {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”。

  1. 数字的点操作符问题:123.toFixed(2)报错,
    错误Uncaught SyntaxError: Unexpected token ILLEGAL

解决:
(123).toFixed(2) // >> “123.00” // 以下两种都可以,但完全不推荐 123..toFixed(2) 123 .toFixed(2)

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

  1. 连等赋值问题问题:尝试解释下连等赋值的过程。下面的代码为什么是这样的输出?
    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. 逗号操作符问题: 下面的代码返回什么,为什么?
    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。

  1. 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. 前端面试题,利用给定接口获得闭包内部对象问题: 前端面试题,利用给定接口获得闭包内部对象
    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,目前还没找到解决方法。

  1. 原型链导致的属性更改无效问题: 看下面的代码,为什么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阻止了赋值。

  1. 位操作符问题: 实现浮点数转整数,或者说取出数字的整数部分。比如-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 ( >> ) :

  1. Let lnum be ToInt32(lval).
    本题利用了有符号右移会将左操作数转换为32位整数。
    补充:
    同理,num | 0也是可以的。
  1. IEEE-754精度问题问题: 0.1 + 0.2 !== 0.3 // true
    原因:
    所有使用IEEE-754数字实现的编程语言都有这个问题。
    0.1和0.2的二进制浮点数表示并不是精确的,所以相加后不等于0.3。这个相加的结果接近0.30000000000000004。

那么你一定想要比较相加后的结果和预期数字怎么办?宽容比较,即允许一定误差,这个误差值一般是2^-52 (2.220446049250313e-16)。

  1. 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. 给基础类型添加属性无效问题: 为什么给基础类型添加属性不报错但又无效?
    1
    var num = 1; num.prop = 2; num.prop // undefined

原因:
num.prop 等同于 Object(num).prop,也就是说我们添加属性到一个新建的对象上,与num没任何关系。

  1. 数组的展开/扁平问题:
    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…”。

  1. 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在作用域中不是变量或函数名)

  1. 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造成一倍误差。

  1. 找出字符串中出现最多的字母这个问题看起来用到的地方挺多,至少我遇到过不止一次,索性在这里讲一讲。先具体描述下问题:
    假设字符串’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"}

  1. storage event当你用localStorage或sessionStorage的API去更改Storage时,会触发storage事件:
    1
    window.addEventListener('storage', function(e) { 
        console.log('storage', e);
    });

这里没有什么特别的,但基本所有问题的根源,或者说要特别注意的是:本页面更改Storage只能在同域名的其它页面去捕获storage事件。

  1. 一个函数柯里化问题及更多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;
}

  1. 运算符优先级问题普通的运算符优先级对大家应该不成问题,另外也推荐用括号来明确运算符优先级和代码意图,写出更可读的代码。
    但之所以有这个问题,是涉及到typeof运算符,比较新颖。
    问题:
    var str = ‘why I am ‘ + typeof + ‘’; // so what is str?
    str是why I am number,思考一下,上面的代码应该等同于’why I am ‘ + (typeof (+ ‘’))。好,现在问题归结到运算符优先级,查看文档验证:

typeof运算符优先级高于+,并且是right-to-left。

  1. 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

  1. 你真的了解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中。