JS面试题梳理
第 2 题:['1', '2', '3'].map(parseInt) what & why ?
核心1 map函数传参 item index
核心2 parseInt函数传参 item radix_ 可选_ mdn细讲 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/parseInt
第 21 题:有以下 3 个判断数组的方法,请分别介绍它们之间
的区别和优劣
Object.prototype.toString.call() 、 instanceof 以及 Array.isArray() Object.prototype.toString.call()
每一个继承 Object 的对象都有 toString 方法,如果 toString 方法没有重写的
话,会返回 [Object type],其中 type 为对象的类型。但当除了 Object 类型的
对象外,其他类型直接使用 toString 方法时,会直接返回都是内容的字符串,
所以我们需要使用 call 或者 apply 方法来改变 toString 方法的执行上下文。
const an = ['Hello','An'];an.toString();
// "Hello,An"Object.prototype.toString.call(an);
// "[object Array]" 这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。
Object.prototype.toString.call('An')
// "[object String]"Object.prototype.toString.call(1)
// "[object Number]"Object.prototype.toString.call(Symbol(1))
// "[object Symbol]"Object.prototype.toString.call(null)
// "[object Null]"Object.prototype.toString.call(undefined)
// "[object Undefined]"Object.prototype.toString.call(function(){})
// "[object Function]"Object.prototype.toString.call({name: 'An'})
// "[object Object]" Object.prototype.toString.call() 常用于判断浏览器内置对象时。
instanceof
instanceof 的内部机制是通过判断对象的原型链中是不是能找到类型的
prototype。
使用 instanceof 判断一个对象是否为数组,instanceof 会判断这个对象的原型
链上是否会找到对应的 Array 的原型,找到返回 true,否则返回 false。
[] instanceof Array; // true
但 instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型
instanceof Object 都是 true。
[] instanceof Object; // true
Array.isArray()
功能:用来判断对象是否为数组
instanceof 与 isArray
当检测 Array 实例时,Array.isArray 优于 instanceof ,因为 Array.isArray 可以
检测出 iframes
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);xArray =
window.frames[window.frames.length-1].Array;
var arr = new xArray(1,2,3);
// [1,2,3]// Correctly checking for ArrayArray.isArray(arr);
// trueObject.prototype.toString.call(arr);
// true
// Considered harmful, because doesn't work though iframesarr instanceof
Array;
// false
Array.isArray() 与 Object.prototype.toString.call()
Array.isArray()是 ES5 新增的方法,当不存在 Array.isArray() ,可以用
Object.prototype.toString.call() 实现。
if (!Array.isArray) { Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
第 27 题:全局作用域中,用 const 和 let 声明的变量不在
window 上,那到底在哪里?如何去获取?。
在 ES5 中,顶层对象的属性和全局变量是等价的,var 命令和 function 命令声
明的全局变量,自然也是顶层对象。
var a = 12;
function f(){};
console.log(window.a);
// 12console.log(window.f);
// f(){}
但 ES6 规定,var 命令和 function 命令声明的全局变量,依旧是顶层对象的属
性,但 let 命令、const 命令、class 命令声明的全局变量,不属于顶层对象的属
性。
let aa = 1;
const bb = 2;
console.log(window.aa);
// undefinedconsole.log(window.bb);
// undefined
在哪里?怎么获取?通过在设置断点,看看浏览器是怎么处理的:
通过上图也可以看到,在全局作用域中,用 let 和 const 声明的全局变量并没
有在全局对象中,只是一个块级作用域(Script)中
怎么获取?在定义变量的块级作用域中就能获取啊,既然不属于顶层对象,那
就不加 window(global)呗。
let aa = 1;
const bb = 2;
console.log(aa);
// 1console.log(bb);
// 2

第 58 题:箭头函数与普通函数(function)的区别是什么?构
造函数(function)可以使用 new 生成实例,那么箭头函数可
以吗?为什么?
箭头函数是普通函数的简写,可以更优雅的定义一个函数,和普通函数相比,
有以下几点差异:
函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对
象。
不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可
以用 rest 参数代替。
不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
不可以使用 new 命令,因为:
o 1.没有自己的 this,无法调用 call,apply。
o 2.没有 prototype 属性 ,而 new 命令在执行时需要将构造函数
的 prototype 赋值给新的对象的 proto new 过程大致是这样的:
function newFunc(father, ...rest) {
var result = {};
result.proto = father.prototype;
var result2 = father.apply(result, rest);
if (
(typeof result2 === 'object' || typeof result2 === 'function') &&
result2 !== null
) {
return result2;
}
return result;
}
第 65 题: a.b.c.d 和 a['b']['c']['d'],哪个性能更高?
应该是 a.b.c.d 比 a['b']['c']['d'] 性能高点,后者还要考虑 [ ] 中是变量的情况,
再者,从两种形式的结构来看,显然编译器解析前者要比后者容易些,自然也
就快一点。
第 72 题: 为什么普通 for 循环的性能远远高于 forEach 的
性能,请解释其中的原因。
for 循环没有任何额外的函数调用栈和上下文;
forEach 函数签名实际上是
array.forEach(function(currentValue, index, arr), thisValue)
它不是普通的 for 循环的语法糖,还有诸多参数和上下文需要在执行的时候考
虑进来,这里可能拖慢性能;

第 75 题:数组里面有 10 万个数据,取第一个元素和第 10 万个
元素的时间相差多少
数组可以直接根据索引取的对应的元素,所以不管取哪个位置的元素的时间复
杂度都是 O(1)
得出结论:消耗时间几乎一致,差异可以忽略不计
第 76 题:输出以下代码运行结果
// example 1
var a={}, b='123', c=123;
a[b]='b';a[c]='c';
console.log(a[b]);
// example 2var a={}, b=Symbol('123'), c=Symbol('123');
a[b]='b';
a[c]='c';
console.log(a[b]);
// example 3var a={}, b={key:'123'}, c={key:'456'};
a[b]='b';a[c]='c';
console.log(a[b]);
答:
- 对象的键名只能是字符串和 Symbol 类型。
- 其他类型的键名会被转换成字符串类型。
- 对象转字符串默认会调用 toString 方法。
// example 1
var a={}, b='123', c=123;a[b]='b';
// c 的键名会被转换成字符串'123',这里会把 b 覆盖掉。a[c]='c';
// 输出 cconsole.log(a[b]);
// example 2var a={}, b=Symbol('123'), c=Symbol('123');
// b 是 Symbol 类型,不需要转换。a[b]='b';
// c 是 Symbol 类型,不需要转换。任何一个 Symbol 类型的值都是不相等的,
所以不会覆盖掉 b。a[c]='c';
// 输出 bconsole.log(a[b]);
// example 3var a={}, b={key:'123'}, c={key:'456'};
// b 不是字符串也不是 Symbol 类型,需要转换成字符串。
// 对象类型会调用 toString 方法转换成字符串 [object Object]。a[b]='b';
// c 不是字符串也不是 Symbol 类型,需要转换成字符串。
// 对象类型会调用 toString 方法转换成字符串 [object Object]。这里会把
b 覆盖掉。a[c]='c';
// 输出 cconsole.log(a[b]);
第 80 题:介绍下 Promise.all 使用、原理实现及错误处理
const p = Promise.all([p1, p2, p3]);
Promise.all 方法接受一个数组作为参数,p1、p2、p3 都是 Promise 实例,如果
不是,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,
再进一步处理。(Promise.all 方法的参数可以不是数组,但必须具有 Iterator 接
口,且返回的每个成员都是 Promise 实例。)
第 83 题:var、let 和 const 区别的实现原理是什么
三者的区别:
var 和 let 用以声明变量,const 用于声明只读的常量;
var 和 let 用以声明变量,const 用于声明只读的常量;
var 声明的变量,不存在块级作用域,在全局范围内都有效,let 和 const
声明的,只在它所在的代码块内有效;
let 和 const 不存在像 var 那样的 “变量提升” 现象,所以 var 定义变
量可以先使用,后声明,而 let 和 const 只可先声明,后使用;
let 声明的变量存在暂时性死区,即只要块级作用域中存在 let,那么它
所声明的变量就绑定了这个区域,不再受外部的影响。
let 不允许在相同作用域内,重复声明同一个变量;
const 在声明时必须初始化赋值,一旦声明,其声明的值就不允许改变,
更不允许重复声明;如 const 声明了一个复合类型的常量,其存储的是一个引
用地址,不允许改变的是这个地址,而对象本身是可变的。
变量与内存之间的关系,主要由三个部分组成:
变量名
内存地址
内存空间
JS 引擎在读取变量时,先找到变量绑定的内存地址,然后找到地址所指向的内
存空间,最后读取其中的内容。当变量改变时,JS 引擎不会用新值覆盖之前旧
值的内存空间(虽然从写代码的角度来看,确实像是被覆盖掉了),而是重新
分配一个新的内存空间来存储新值,并将新的内存地址与变量进行绑定,JS 引
擎会在合适的时机进行 GC,回收旧的内存空间。
const 定义变量(常量)后,变量名与内存地址之间建立了一种不可变的绑定
关系,阻隔变量地址被改变,当 const 定义的变量进行重新赋值时,根据前面
的论述,JS 引擎会尝试重新分配新的内存空间,所以会被拒绝,便会抛出异常。

浙公网安备 33010602011771号