类数组

  1. function test (a, b) {
  2. arguments[2] = 3;
  3. console.log(arguments.length);
  4. console.log(arguments[2]);
  5. }
  6. test(1, 2);
  7. console.log(test.length);

答案:
2 3 2
分析:
首先 arguments 是一个对应于传递给函数的参数的类数组对象。
既然是对象,那么 arguments[2] = 3; 这个操作只是对象属性的赋值,并不会影响 arguments 对象的其它属性。
length 属性是数组的特性,单纯的对象属性操作与其无关。所以第一个输出console.log(arguments.length); 只会输出传入函数实参的个数 2。
而第二个输出 console.log(arguments[2]);arguments[2] 确实是被赋值了,所以输出 3。
第三个输入console.log(test.length);函数的 length 会输出其形参的个数,所以输出 2。


const obj = {
  '2': 3,
  '3': 4,
  push: Array.prototype.push
}

obj.push(1);

console.log(obj.length);
console.log(obj);

答案:
1
{0:1, 2:3, 3:4, length:1, push:Array.prototype.push}
分析:
obj 的 push 方法是来自 Array原型的 push,那么obj.push(1);obj就会当成一个(类)数组来处理。
正常来说,push 方法先找出数组的 length 属性的值为下标赋 push 的值,然后 length++。但是在此时 obj 没有 length 属性,那么 js 引擎只能认为 length 为 0,继续 push 的操作。
所以对 obj 赋值 ‘0’:1,然后 length++,即 length:1。


const obj = {
  '2': 3,
  '3': 4,
  length: 2,
  push: Array.prototype.push
}

obj.push(1);

console.log(obj.length);
console.log(obj);

答案:
3
{2:1, 3:4, length:3, push:Array.prototype.push}
分析:
与上一题解题思路一样,但由于这个类数组已经存在 length 属性,值为2。那么当 obj.push(1); 时,会在下标为2中赋值 ‘2’:1。然后 length++,即 length:3。


隐式转换

({} + {}).length

答案:
30
分析:
本身对象相加是没有任何意义的,但是js引擎为了进行下去,会进行隐式转换,把对象转为字符串,调用了{}.toString(),转为'[object Object]' + '[object Object]'。两个字符串 + 就是字符串连接,得到'[object Object][object Object]',那么此字符串长度就是 30。


([] + []).length

答案:
0
分析:
与上一题解题思路一样,但 [].toString() 是’’一个空字符串。两个’’连接起来也是’’。所以’’的字符串长度就是0。
另外如果数组有元素的 toString() 是输出其元素用逗号相隔的一个字符串,如 [1,2,3].toString() 结果为 ‘1,2,3’。


(function () {}).length

答案:
0
分析:
相当于 function test(){}; test.length 此时 length 表示函数形参列表的长度。此时没有形参,所以结果为0。


(function test () {}).toString().length

答案:
19
分析:
Function.prototype.toString() 方法是返回一个表示当前函数源代的字符串。所以得到这个字符串 ‘function test () {}’,然后这个字符串的 length 长度就是 19。注意不能忽略空格,一个空格也是一个长度单位。


({} + (function () {console.log(123)})()).length

答案:
123
24
分析:
首先 (function () {console.log(123)})() 是一个立即执行函数,那么 console.log(123) 必定会执行,会输出123。
然后此函数除了输出 123 外,并没显式返回。所以 js引擎会隐式返回一个 undefined。变会变为 ({} + undefined).length,对象相加,{} 会转为字符串 ‘[object Object]’。而 undefined 也转为 ‘undefined’字符串。
再用+把两个字符串连接起来,得到 ‘[object Object]undefined’ 这个字符串。最后求其长度 length,得到 24。


this指向

function Test () {}
Test.prototype.a = function () {
  console.log(this);
}

new Test().a.call(null);
new Test().a.call(undefined);

答案:
window
window
分析:
在非严格模式(sloppy mode)下,this指向 null / undefined 时,this都是指向 window。
在严格模式 ‘use strict’ 下,指定的this不再被封装为对象,而且如果没有指定 this 的话它值是 undefined

'use strict';

function Test () {}
Test.prototype.a = function () {
  console.log(this);
}

new Test().a.call(null); // null
new Test().a.call(undefined); // undefined

this 指向 null 就是 null, this 指向 undefined 就是 undefined,不会改至window。
同时注意开启严格模式时,一定要把 ‘use strict’ 放在所有语句之前才能生效。


class Test {
  a () {
    console.log(this);
  }
}

new Test().a.call(null);
new Test().a.call(undefined);

答案:
null
undefined
分析:
此题与上一题相似,但使用 es6 class 作类声明。关键在于类声明和类表达式的主体都执行在严格模式下。


对象

if (a == 1 && a == 2 && a == 3) {
  console.log('you win');
}

答案:

var a = {
    _a: 0,
  toString(){
      return ++ this._a;
  }
}

分析:
一个变量等于3个值,那么这个变量一定是对象。借助对象在关系判断时会隐式转换调用 toString() 转为字符串,那么只需要在对象中重写一个 toString() 方法,用于js引擎作关系判断时劫持其取值即可。


Object.prototype = {
  b: 2
}

var obj = {
  a: 1
}

console.log(obj.b);

答案:
undefined
分析:
Object 原型是不能被替换,如果这样操作会产生大量问题。所以 js 引擎在非严格模式下对象的原型修改于引起静默失败(silently fail),而在严格模式下会报错。
所以 Object.prototype = { b:2 } 是没生效的,那么对 obj.b 访问时只会返回 undefined。
如果要想 obj.b 生效,应该改为 Object.prototype.b = 2;


对象继承的方法有那些?

答案:

  1. 原型链继承
  2. 借用构造函数继承
  3. 组合继承
  4. 寄生组合继承
  5. 圣杯模式

数组

splice 方法有多少个参数,分别为哪些?

答案:
有无数个参数,
第一个是开始的下标位置,
第二个是删除的个数,
第三个打后是插入的值。
对原来数组操作。


slice 方法有多少个参数,分别为哪些?

答案:
有二个参数,
第一个是开始的下标,
第二个是截止的下标,
取值范围是左闭右开。
返回一个新的数组。


push 方法有多少个参数,分别为哪些?

答案:
有无数个参数,
一个或多个元素添加到数组的末尾。


数组的 filter 与 map 方法有何区别,通常会应用到哪些业务中?

答案:
filter 用于数组的过滤,一般是数据的筛选。而 map 是数组映射,通常是对原有数据的加工。


重写数组的filter方法

答案:

Array.prototype.myFilter = function (cb) {
  var _arr = this;
  var _len = _arr.length;
  var _arg2 = arguments[1] || window;
  var _newArr = [];
  var _item;

  for (var i = 0; i < _len; i ++) {
    _item = deepClone(_arr[i]);
    cb.apply(_arg2, [_item, i, _arr]) ? _newArr.push(_item) : '';
  }

  return _newArr;
}

ES5

数组中 some 方法 什么时候返回 true? every 方法什么时候返回true?什么情况下会中断?

答案:
some 是有一个满足条件就返回 true,如果有一个满足条件就停止遍历。
every 是有所有条件都满足就返回 true,如果有一个不满足条件就停止遍历。


if (a === 1 && a === 2 && a === 3) {
  console.log('you win');
}

答案:

var _value = 0;
Object.defineProperty(window, 'a', {
    get(){
        return ++_value;
    }
});

严格模式有什么限制?

答案:
不会意外地创建全局变量;
不会静默失败;
with 不能使用;
eval 不会为包围 eval 代码的范围引入新的变量;
禁止删除声明变量。
对 Arguments 的 callee、caller 不能使用。


ES6

var let const 有什么区别?

答案:
var 的变量名是可以重复声明,并且会变量提升,不受块级作用域的影响。
let 与 const 的变量名不能重复声明,不会变量提升,会生成临时性死区。且只会作用于其块级作用域中。
const 在声明时必需赋值。


简述WeakMap的作用

答案:
WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

在 JavaScript 里,map API 可以通过使其四个 API 方法共用两个数组(一个存放键,一个存放值)来实现。给这种 map 设置值时会同时将键和值添加到这两个数组的末尾。从而使得键和值的索引在两个数组中相对应。当从该 map 取值的时候,需要遍历所有的键,然后使用索引从存储值的数组中检索出相应的值。 但这样的实现会有两个很大的缺点,首先赋值和搜索操作都是 O(n) 的时间复杂度 ( n 是键值对的个数),因为这两个操作都需要遍历全部整个数组来进行匹配。另外一个缺点是可能会导致内存泄漏,因为数组会一直引用着每个键和值。这种引用使得垃圾回收算法不能回收处理他们,即使没有其他任何引用存在了。

相比之下,原生的 WeakMap 持有的是每个键对象的“弱引用”,这意味着在没有其他引用存在时垃圾回收能正确进行。原生 WeakMap 的结构是特殊且有效的,其用于映射的 key 只有在其没有被回收时才是有效的。

正由于这样的弱引用,WeakMap 的 key 是不可枚举的 (没有方法能给出所有的 key)。如果key 是可枚举的话,其列表将会受垃圾回收机制的影响,从而得到不确定的结果。因此,如果你想要这种类型对象的 key 值的列表,你应该使用 [Map](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Map)

基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。

m.set(oBtn, function () { ……… })
oBtn.addEventListener('click', m.get(oBtn), false

箭头函数

const test1 = () => {
  console.log(this);
}

test1();

答案:
window
分析:
箭头函数的 this 指向父级的 this,所以是 window。


const test2 = () => {
  const t = () => {
    console.log(this);
  }
  t();
}

test2();
new test2();

答案:
window
报错
分析:
第一个与上一题一样,箭头函数是不能成为构造函数。因为构造函数的本质是改变其函数的 this 指向,为构造的一个新的对象。然而箭头函数是没有 this。所以不能成为构造函数,会报错。


const test2 = () => {
  test2.t = () => {
    console.log(this);
  }
  test2.t();
}

test2();

答案:
window
分析:
箭头函数的 this 为父级的 this,一层一层,最终指向 window。


const test2 = function () {
  test2.t = () => {
    console.log(this);
  }
  test2.t();
}

new test2();

答案:
{}
分析:
因为 test2() 是被 new 出来,所以是基中调用是以生成出的对象来调用。


const obj = {
  test2: () => {
    console.log(this);
  },
  test3 () {
    console.log(this);
  }
}

obj.test2();
obj.test3();

答案:
window
obj
分析:
obj.test2 是箭头函数,所以 this 为父级的 this,即 obj 的 this。obj 是在 window 下调用,所以 test2 的 this是 window。
obj.test3 是一般的函数,所以谁调用 this 指向谁,所以为 obj。


const test = (...args) => {
  console.log(args);
  console.log(arguments);
}
test();

答案:
[]
报错
分析:
…args 为实参数组,因为 test() 调用没有传入,所以是空数组 []。
在箭头函数中是没有 arguments。因为箭头函数是本质设计是简化 js 中的 this 指向过于复杂的问题。没有自己的[this](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this)[arguments](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/arguments)[super](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/super)[new.target](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new.target),所以会报错。


改为ES6 类声明

;(function () {
  var c = 1;

  function Test () {
    console.log(c);
  }

  Test.prototype.a = function () {
    Test.b();
  }

  Test.b = function () {
    console.log('I am a static function of Test constructor');
  }

  window.Test = Test;
})();

答案:

const Test = (() => {

  let c = 1;

  class Test {
    constructor () {
      console.log(c);
    }

    a () {
      Test.b();
    }

    static b () {
      console.log('I am a static function of Test constructor');
    }
  }

  return Test;

})();

Promise

const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve();
  console.log(2);
})


promise.then(() => {
  console.log(3);
})


console.log(4);

console.log 显示的结果,结果中哪些是同步哪些是异步。

答案:
1 2 4 3
1 2 4 是同步,3 是异步


Promise 中,为什么其执行器是同步,而 then 是异步?

答案:
因为 Promise 本身是为了解决异步的问题同步化。
在执行器中同步是保证运行的顺序,在 then 中异步是为了不阻塞其它同步代码。


let s = new Set();
s.add([1]);
s.add([1]);
s.add(1);
s.add(1);
console.log(s.size);

答案:
3
分析:
数组是引用其地址,所以两个 [1] 是指地址,所以都被添加。
而后面的1是值,最后一个没有被添加上。
所以 set 的 size 为 3。
Promise的存在意义


Iterator

说明for in 与 for of 的区别

答案:
for in 是遍历对象的属性,包括其对象原形上的属性,其中 property 为对象的属性键名。
for of 是对有部署 [Symbol.iterator] 迭代器的对象进行遍历,其 property 是其对象的属性的键值。


使对象的用 for of 代替 for in 的方法,前提不部署[Symbol.iterator]

答案:
先使用 Object.keys() 方法获取对象的 key 的数组,因为数组具有迭代器 iterator,所以可以用 for of 对其访问。