前端面试题 我在面试bat字节时被问的ES6相关问题

做为前端开发,应该都知道,ES6是我们必学的,而之所以强调ES6,是因为在ES6那个版本增加了很多东西,以至于阮一峰大佬还写了本书,也因此,我们经常会在面试里面被问到,你了解ES6吗,知道哪些新特性,而在你说出来之后,就会引来一系列的问题,这篇博客就从我面试bat字节时被问到的一些问题和网上的面经结合做一些总结和问题的回答,同时也会谈到如何从被问的问题里面“引导”面试官去问你其他的问题

新增声明符let const


我们知道,在ES6里面,多了两个声明符,let和const,如果你提到了这个,那么可能会面临下面的问题

var let const三者的区别

作用域的区别

var声明的变量可以在全局作用域,函数作用域和严格模式下的eval作用域里使用,而let和const多了个块级作用域,在if、else、while、for循环的{}里声明的变量,无法在该{}外部使用(这里提到作用域也许会让面试官问到作用域嵌套,当然你也可以自己刻意提起,比如可以说在函数作用域里声明的变量,虽然是被嵌套在另一个函数作用域或者全局作用域里面,但无法使用这个变量,而相对的在函数作用域里面可以使用嵌套它的作用域里声明的变量)

变量提升

var声明的变量有变量提升的特点,在声明一个变量时变量会被提升到文件顶部,所以我们可以使用后面var声明的变量,但是此时变量的值为undefined,而let和const,因为没有变量提升,所以如果在声明前使用的话会引起报错

变量是否可变

这个是关于const声明的变量的特性,是面试中经常被问到的(反正我在面腾讯百度字节都被问到了),const声明的变量不能被重新赋值,如果重新赋值,就会报错,提到这个问题时,就会引出下面的问题

  • const声明的对象,对象的属性可以改变吗:可以,const声明的对象,不能重新赋值,但其对象的属性是可以变化的,我的理解是,const声明的变量不能改变,是指这个变量指向内存中哪一段内容不能变,而对于对象来说,我们的变量存储的是一个地址,表示对象在内存中,在堆中所处的位置,而对象的属性改变,并不会改变这个指向
  • 如何让声明的对象的属性也不能被改变:我自己想到的方法有:proxy代理,Object.defineProperty劫持set操作,Object.freeze,可能还有别的方法,但我当时只回答了这些

箭头函数


箭头函数在ES6时被提出,现在也被广泛运用到开发中,但如果没有好好地去了解它,那么开发时也可能会踩到莫名其妙的坑

箭头函数和一般函数的区别

this指向

箭头函数的this,指向了其执行时所在的作用域,而一般函数的this指向,要考虑到是否用到new构造,是否用到了apply,call,bind来进行this的指向修改

  • 问到了这个问题,一般就会问到了apply,call,bind的区别了,包括this的四种指向的情况,这个可以在《你不知道的JavaScript》里面看,我觉得挺详细的,当然网上也有很多介绍详细的文章了,这里不做赘述

不能使用apply,call,bind

这个也是上面提到的了,因为箭头函数的this是其执行时所在的作用域,所以也无法同个apply,call,bind来改变this指向

  • 这里说一下,你可以直接在这里顺便说一下apply,call,bind三者的区别,让面试官知道你这方面也是了解的,而此外,你也可以提提这三者哪个的性能比较好,这个我也做过了测试,所以在面试的时候我也会说出来,自己测试过这三个的性能,哪个比较好,感觉会留下比较好的印象,至少你有去多做考虑,而不是只会用,详见我之前写的博客JavaScript辨别apply,call,bind 用法与性能

无法使用arguments对象

一般的函数如果我们传入的参数个数是不定的话,那么可以通过arguments对象来列出所有传入的参数,而箭头函数却不能使用这个对象,但是,我们可以使用扩展运算符来代替arguments对象

不能使用yield命令

箭头函数不能使用yield命令,因此箭头函数不能使用generator函数(这个回答的话可以考虑是否回答,如果你对generator函数并不是很了解,那就可以不说,不然面试官可能会问你相关的知识,别问我为什么知道的。。。)

没有prototype属性,无法通过new来构建实例

箭头函数没有prototype属性,而我们知道,new构建实例时除了需要对this的指向进行指定,还要让实例对象继承该方法的原型,所以箭头函数也就无法使用new来构建实例

箭头函数的优点

要说箭头函数的优点,我也没有准确的答案,我就发表一下我个人的观点

  • 写的时候比较比较便利,虽说function这个单词也不难写,但是显然,直接用()=>{}的写法会更快
  • 相对代码体积比较小,在很小的程度上有一点性能的提高
    (实际上这点提高可能在写的函数特别多的时候会有感觉,但一般我其实觉得还是没什么大影响,而且虽说现在浏览器大多支持箭头函数语法,但是我们在写的时候一般还是会通过babel进行一个编译,所以实际上这个优点有点牵强,但是只说一点又好像不太好,总之这种问题就是言之有理即可,尽量让面试官觉得你有好好地思考,逻辑合理就行)

什么时候适合使用箭头函数

这个也是一种挺开放的问题了,主要把适合用的原因说清楚就行

  • 首先,肯定是在不需要去实例化,改变this指向时使用,只要不是使用箭头函数会影响到实际功能的,就可以出于编码便利的理由去使用箭头函数
  • 还有就是,在我们要用到的this,确实就是指向所在作用域的时候,当然这种情况比较少见,而且会对代码的语义有一点影响,最好不要在多人参与的项目里面这么用

Map与Set


这两个倒是比较少问到,虽然我每次面试被问到ES6了解哪些时都有提到,但只有面试百度的时候被问到了,姑且写一下
(关于Map和Set,可以直接看阮一峰的ES6入门,也可以看看我看了这本书做的笔记ES6学习笔记(五)Set结构和Map结构

Map和Set怎么使用,特点是什么

当时我回答的时候,就回答了Map和Set里面的结构是什么,比如Map传入的数组参数,是一个二元数组,每个数组成员里面有两个成员,第一个值为键名,第二个值为键值

new Map([[k1,1],[k2,2]]);

包括用到的一些API,其实也就get,set那些,忘了的话也没太大关系(emmmmm反正那次百度面试我是过了)
然后Set的话也差不多,但是Set我倒是在做算法题时有时会直接用来做去重处理,面试的时候也就顺便提了一下,结果面试官可能觉得我Set比较熟,就继续问下去了。。。

Set传入的值如果在这个Set中已经有该值了,那会发生什么

如果Set中已经有该值了,那么继续添加的话会静默失败

  • 如果添加进去的,是一个属性相同的对象呢:如果这个对象是存储在这个变量的话,如下(当时是视频面试,所以可以写代码给面试官看)
var obj = {foo:1}
var s = new Set([obj])
s.add(obj)

那还是会静默失败,但是如果添加的是一个没有存储在变量里的对象,也就是说,每次都是在堆内存里新开辟一部分内存,然后将指向那部分内容的地址存入Set里面,那么因为每次地址都不同,所以每次都会存入成功,就像下面的代码

var s = new Set([{foo:1}])
s.add({foo:1})

所以这里其实还是需要对对象的存储方式有所了解,这也是比较基础的内容,要好好打牢(顺便一提关于基本类型和引用类型的存储方式在几个大厂除了bat字节外包括京东网易也都被问过了)

Promise


promise异步相信也是经常在面试题里见到了,包括笔试题也是,那些打印顺序什么的,这里就说说我被问到的一些问题

Promise.then

首先要知道,promise.then是一个微任务,想到了微任务,你可能就会想到面试笔试里出现的这类题目
(下题为我面试字节时的真题)

console.log('begin');
setTimeout(() => {
    console.log('setTimeout 1');
    Promise.resolve().then(() => {
        console.log('promise 1');
        setTimeout(() => {
            console.log('setTimeout2 between promise1&2');
        })
    }).then(() => {
        console.log('promise 2');
    });
}, 0); console.log('end');

提问:上面的打印顺序是什么,其实这主要是微任务和宏任务的问题,除了单方面回答这道问题的答案外,还可以顺便说一下除了setTimeout和Promise.then外,还有什么宏任务,微任务(关于这个,我之前也有相关的文章:详解JavaScript异步执行顺序

Promise.all是怎么实现的

这个可能就是考考你对promise一些API的了解,其实也就是,传入一个数组,数组里面所有的成员,如果不是promise对象的话,就先使用Promise.resolve处理,接着,如果里面所有的Promise状态都变成resolved的话,那么这个方法返回的Promise的状态就变成resolved,而只要里面一个状态变成rejected,返回的Promise的状态就为rejected
接着,我就手写了Promise.all。。。,还好之前自己写过

Promise._all = function(promises){
    return new Promise((resolve,reject)=>{
        // 将可遍历的类型转为数组形式
        promises = Array.from(promises)
        // 若数组长度为0,则直接resolve([])
        if(promises.length === 0){
            resolve([])
        }else{
            // 使用一个变量count来计算当前已完成的promise
            let count = 0
            for(let i=0;i<promises.length;i++){
                Promise.resolve(promises[i]).then(val=>{
                    promises[i] = val
                    // 若完成的promise数量和数组长度一致,说明全部完成,则返回处理好的数组
                    if(++count === promises.length){
                        resolve([promises])
                    }
                }).catch(err=>{
                    reject(err)
                })
            }
        }
    })
}

顺手贴上我自己写的Promise.race的实现代码

Promise._race = function(promises){
    promises = Array.from(promises)
    return new Promise((resolve,reject)=>{
        promises.forEach(p=>{
            Promise.resolve(p).then(val=>{
                resolve(val)
            }).catch(err=>{
                reject(err)
            })
        })
    })
}

比起使用回调,使用Promise的好处

  • 首先,使用回调可能会造成回调地狱,而使用Promise,以then的方式,不仅避免了回调地狱,还使代码的可阅读性更高
  • 其次使用Promise可以更好地实现异步,说到底,Promise就是一个异步方法
  • 还有,使用Promise实现异步的时候,可以通过将状态变更方法赋值给Promise外的变量,让我们能在Promise外部进行异步的处理,这点在axios的源码里面就有运用到(因为我自己看过axios的源码,本来想引导看面试官会不会问到axios的源码上,结果没问)

我在面试时被问到的暂时就是这些,还有其他的内容我会在之后慢慢补充进这篇博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值