类数组
function test (a, b) {
arguments[2] = 3;
console.log(arguments.length);
console.log(arguments[2]);
}
test(1, 2);
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;
对象继承的方法有那些?
答案:
- 原型链继承
- 借用构造函数继承
- 组合继承
- 寄生组合继承
- 圣杯模式
数组
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 对其访问。