总结一下常见面试题目,跳槽也好,不跳也罢,忙也好,不忙也没关系,每周一期,自我驱动,巩固知识,卷死他们!
问题1:JS 异步解决⽅案的发展历程以及优缺点。
答案:回调函数(callback)优点:解决了同步的问题(只要有⼀个任务耗时很⻓,后⾯的任务都必须排队 等着,会拖延整个程序的执⾏。) 缺点:回调地狱,不能⽤ try catch 捕获错误,不能 return
Promise 优点:解决了回调地狱的问题 缺点:⽆法取消 Promise ,错误需要通过回调函数来捕获
Generator 特点:可以控制函数的执⾏,可以配合 co 函数库使⽤
Async/await 优点:代码清晰,不⽤像 Promise 写⼀⼤堆 then 链,处理了回调地狱的问题 缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性⽽使⽤ await 会导致性能上的降低。
问题2:有以下 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]"
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 Array
Array.isArray(arr);
// true
Object.prototype.toString.call(arr);
// true
// Considered harmful, because doesn't work though iframes
arr 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]';
};}
问题3:ES6数组去重的⽅法
答案:
• from && set
利⽤Set集合的唯⼀性,再利⽤Array.from将类数组变回数组
let arr = [1,1,2,3]
const uniarr = Array.from(new Set(arr))
console.log(uniarr)
可以简写为
[...new Set(arr)]
• filter && indexOf
过滤数组,indexOf判断元素在数组⾸次出现的下标与当前下标是否⼀致,⼀致为唯⼀值,不⼀致为重
复元素
let arr = [1,1,2,3]
const uniarr = arr.filter((item, index) => {
return arr.indexOf(item) === index
})
console.log(uniarr)
• forEach && includes
遍历数组,如果元素不在已选列表,则将元素放进已选数组
let arr = [1,1,2,3]
let uniarr = []
arr.forEach((item) => {
if(!uniarr.includes(item)) uniarr.push(item)
})
console.log(uniarr)
• filter && Map
过滤数组,创建⼀个空的键值对集合,判断没有在集合中就返回,就保存,然后返回true,后⾯再有 重复元素,就直接返回false
Map的键可以是任意类型,Ojbect的键只能是字符串
let arr = [1,1,2,3]
let myMap = new Map()
const uniarr = arr.filter(item => {
return !myMap.has(item) && myMap.set(item, 1)
})
console.log(uniarr)
问题4:如何不使⽤loop循环,创建⼀个⻓度为100的数组,并且每个元素的值等于它的下标
答案:
Array.from(Array(100).keys())
Array(100)只是⼀个稀疏数组,⽆法⽤map去遍历。
Array(100).keys()返回的是⼀个类数组的迭代器,内容是下标
Array.from再将迭代器转成真正的数组
问题5:requestAnimationFrame为什么能优化动画性能
答案:requestAnimationFrame ⽐起 setTimeout、setInterval的优势主要有两点:
1、requestAnimationFrame 会把每⼀帧中的所有DOM操作集中起来,在⼀次重绘或回流中就完 成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,⼀般来说,这个频率为每秒60帧。
2、在隐藏或不可⻅的元素中,requestAnimationFrame将不会进⾏重绘或回流,这当然就意味着更 少的的cpu,gpu和内存使⽤量。
「CPU节能」:使⽤setTimeout实现的动画,当⻚⾯被隐藏或最⼩化时,setTimeout 仍然在后台执 ⾏动画任务,由于此时⻚⾯处于不可⻅或不可⽤状态,刷新动画是没有意义的,完全是浪费CPU资 源。⽽requestAnimationFrame则完全不同,当⻚⾯处理未激活的状态下,该⻚⾯的屏幕刷新任务 也会被系统暂停,因此跟着系统步伐⾛的requestAnimationFrame也会停⽌渲染,当⻚⾯被激活 时,动画就从上次停留的地⽅继续执⾏,有效节省了CPU开销。
「函数节流」:在⾼频率事件(resize,scroll等)中,为了防⽌在⼀个刷新间隔内发⽣多次函数执⾏,
使⽤requestAnimationFrame可保证每个刷新间隔内,函数只被执⾏⼀次,这样既能保证流畅性,
也能更好的节省函数执⾏的开销。⼀个刷新间隔内函数执⾏多次时没有意义的,因为显⽰器每
16.7ms刷新⼀次,多次绘制并不会在屏幕上体现出来。
问题6:解决JavaScript数字精度丢失问题的⽅法
答案:本质上在处理这类问题的时候,基本的思路就是通过将浮点数转换成整数进⾏计算,然后再将
整数的⼩数点位调整,转回正确的浮点数结果。
例如:
// 0.1 + 0.2
(0.110 + 0.210) / 10 == 0.3 // true
问题7:document.onload和document.ready两个事件的区别
答案:
document.onload ⻚⾯加载完成(图⽚,⽂件之类的)
document.ready ⻚⾯只加载DOM层,但未加载媒体⽂件
问题8:函数防抖与节流
答案:先看个例⼦:
<input type="text" id="unDebounce">(没有防抖的input)
<script>
function ajax (content) {
console.log('ajax request' + content)
}
let inputs = document.getElementById('unDebounce')
inputs.addEventListener('keyup', function (e) {
ajax(e.target.value)
})
</script>
只要⽤⼾按下键盘,就会不断发送请求,这样⼦会浪费资源
防抖优化
<input type="text" id="unDebounce">(有防抖的input)
<script>
function ajax (content) {
console.log('ajax request' + content)
}
function debounce (fun, delay) {
return function (args) {
let that = this
let _args = args
// 清除时间
clearTimeout(fun.id)
fun.id = setTimeout (function () {
fun.call(that, _args)
}, delay)
}
}
let input_content = document.getElementById('unDebounce')
let debounceAjax = debounce(ajax, 500)
input_content.addEventListener('keyup', function (e) {
debounceAjax(e.target.value)
})
</script>
防抖优化之后,⽤⼾频繁输⼊不会触发请求, 只有⽤⼾输⼊间隔之后才会执⾏函数, 如果停⽌输⼊,但⼜重新输⼊,⼜会重新触发函数
函数节流
<input type="text" id="entry">(有节流的input)<script>
function ajax (content) {
console.log('ajax request ' + content + new Date())
}
function happen(fun, delay) {
let last, deferTimer
return function (args) {
let that = this
let _args = arguments
let now = +new Date()
if (last && now < last + delay) {
clearTimeout(deferTimer)
deferTimer = setTimeout(function () {
last = now
fun.apply(that, _args)
}, delay)
}else {
last = now
fun.apply(that,_args)
}
}
}
let input_content = document.getElementById('entry')
let throttleAjax = happen(ajax, 1000)
input_content.addEventListener('keyup', function (e) {
throttleAjax(e.target.value)
})
</script>
⽆论怎么输⼊,ajax 会按照设定好的时间,在每⼀秒发送⼀次请求。
问题9:下⾯代码的输出是什么?
(() => {
let x = (y = 10);
})();
console.log(typeof x);
console.log(typeof y);
答案:
解析:letx=y=10; 是下⾯这个表达式的缩写:
y = 10;
let x = y;
我们设定 y等于 10时,我们实际上增加了⼀个属性 y给全局对象。在浏览器中, window.y等于 10. 然 后我们声明了变量 x等于 y,也是 10.但变量是使⽤ let声明的,它只作⽤于 块级作⽤域, 仅在声明它的 块中有效;就是案例中的⽴即调⽤表达式(IIFE)。使⽤ typeof操作符时, 操作值 x没有被定义:因为我 们在 x声明块的外部,⽆法调⽤它。这就意味着 x未定义。未分配或是未声明的变量类型为 "undefined". console.log(typeofx)返回 "undefined". ⽽我们创建了全局变量 y,并且设定 y等于 10.这个值在我们的代码各处都访问的到。y已经被定义了,⽽且有⼀个 "number"类型的值。 console.log(typeofy)返回 "number".
问题10:下⾯2段代码四个数分别打印是多少?
var num1 = 2;
var num2 = 20;
var num3 = ++num1 + num2;
var num4 = num1 + num2;
console.log(num1 +'-' + num2 +'-'+ num3 +'-' + num4)
var num1 = 2;
var num2 = 20;
var num3 = num1-- + num2;
var num4 = num1 + num2;
console.log(num1 +'-' + num2 +'-'+ num3 +'-' + num4)
答案:第⼀段代码分别会打印:3-20-23-23,第⼆段代码会打印:1-20-22-21; 考察的是js⼀元操作符:递增( ++ )和递减( -- ),这两个操作符可以放在变量的前⾯,也可以放在变量 的后⾯。但是前后是有区别的。
1、变量a中,a++ 与 ++a都是对a进⾏⾃加1,结果完全相同;递减操作符都是⾃减1。
2、在赋值中使⽤前置型是先⾃加(⾃减)1,再赋值;后置型是先赋值,再⾃加(⾃减)1。
3、在计算并赋值中使⽤前置型会先进⾏⾃加或⾃减,再进⾏计算,最后赋值;后置型会先进⾏计算,再赋值,最后进⾏⾃加或⾃减。