做前端,JS基础永远是最重要的,好的JS基础,不仅能让你日常开发更具有解决问题的能力,也能让你的代码更加的准确减少bug量,当然,最重要的是,好的JS基础是面试的第一敲门砖,尤其是越优秀的团队,越注重JS基础的考察,越是面试的时候,没问什么基础,反而问“有没有做过某某某”、“这个东西会做不”、或者偶尔问了几道JS基础,也没有顺着你的答案深挖下去,这样的团队,大概率,面试官都不怎么行,他怕自己翻车,并且进去之后,这样的团队一般以搬砖为主,并且 ,不注重JS基础的团队,一般会出现很多的恶心代码,甚至出现后端随意改前端的情况~恐怖吧?所以,我希望你,一定是通过自己的强大的JS能力敲开的门而不是侥幸的经历。这算一点面经吧,如果你面试遇到了我上面说到的情况,那么大概率是那样的,好了,废话不多说了,来看一看大厂最常问的几道JS面试题吧。这些题一般都是出现在一面中。我来总结一下。
关于EventLoop的考察
关于事件循环(EventLoop)的考察,不管大厂小厂几乎是百分之百的出现!一定要掌握,如果你还对这些不理解,赶紧补知识!下面是我写的两篇关于事件循环的讲解,大家可以选择性看一下。
关于EventLoop的面试题,大概有这么两类
- “说说什么是事件循坏”,这是最低级的一种问问题,然后你就可以白话来描述了,例如,“JS是单线程的语言,存在异步的情况,犹豫浏览器或者NodeJS环境的机制,导致JS在运行的时候,分为同步任务和异步任务,同步任务先执行,异步任务后执行,异步任务又分为微任务和宏任务,微任务优先级高于宏任务,微任务是一队一队的执行,宏任务是一个一个的执行的,一直到微任务和宏任务的队列清空为止...”等等等一系列文字描述,我个人任务,但凡是看过两篇关于EventLoop文章的,都能有上面的白话描述。
- 出题实践,这些题往往也是难度不一,一种是直接套定义就可以套出结果来的,一些就需要你很了解其细节才能答出来的了,下面是一道某O2O公司的面试真题,看一下
console.log("task start");
task1();
setTimeout(() => {
console.log("setTimeout");
});
new Promise(resolve => {
console.log("promise");
resolve();
}).then(() => {
console.log("promise 1");
});
async function task1() {
await task2();
console.log("task1");
}
async function task2() {
console.log("task2");
}
console.log("task end");
面试也就是一个踩坑的过程,上面这道题可以说是事件循环里面比较难的一道题了,下面我来说一下坑点
- 函数调用是一个同步的过程,直接进入函数逻辑即可,不要到这不知道该怎么走
- Promise本身是同步的!
.then
才是异步的,不要见了Promise就是微任务! -
async/await
本身是返回一个Promise的。只不过会把逻辑绕一下
总结了上面的坑,那么我们可以套原理来求结果了
a. 先执行同步任务,输出'task start'、‘task2’,这里执行完task2后,不要去走end,这时还是会返回到task1中,并且执行完task2后会挂起来一个Promise,这时候应该是接着往下task1()后面走的,所以会打印‘promise’、最后打印task end,这是第一轮的同步任务,先执行完了。'task start'、‘task2’、‘promise’、‘task end’。
b. 接下来执行微任务,我们看一下在第一轮执行的过程中,遇到了哪些微任务,我们需要一次性将他执行完,那就是task1返回的这个Promise,所以先走‘task1’,再就是‘promise 1’。这时候所有微任务执行完了, 执行宏任务‘setTimeout’
OK,这道题就是这样一个过程,中间绕是绕在了async
函数里,只要你记住,他本省返回的是一个Promise就好了,上面说了Promise本身是同步的,then才是异步的,自己在捋一捋哈,因为我也感觉我说的有点乱。
关于词法分析的问题
词法分析,是JS面试题中比较多的一类面试题了也,除了正常的词法分析,还会有一些绕的欺骗词法,这类题一般以题的形式出现。关于词法分析,大家还是多看看基础知识,这里不做详细概念讲解,大概意思就是,JS在运行过程中,会根据你写代码的顺序,来分析各个变量的作用域,下面看一下某短视频公司的面试真题
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
这里就是,foo中的value在词法分析阶段就已经确定了。所以打印1。详细知识点,这里需要自己补一下。因内容比较多,就不赘述了。
关于this指向问题
this指向问题可以说也是百分之百的面试内容存在。不管是大公司小公司,都会问,这里也是有两种形式
- 说一下this指向吧,这是最低级的一种,考概念的话,大概就是this是调用执行栈,总是执行调用它的那个对象。对于箭头函数,它没有自己的this,只是继承至他词法最近的一层普通函数的this指向,明白了这个概念,大部分问概念的都没问题
- 大部分公司都会以面试题的形式出现,面试题也是各种各样的坑,但只要你记住我上面说的白话概念,那么大部分都会做,这里主要的坑是在箭头函数的this执行, 同样也需要好好参悟我上面说的概念。下面看两道题,都是一线大厂真题
var o = {
a: () => {
const obj = {
fn: () => {
console.log(this);
}
}
function fn() {
console.log(this)
var a = obj.fn
a();
}
fn.call({b:1})
}
}
o.a();
这里结果为{b:1},window,可以套一下我上面的概念,应该不能理解为啥箭头函数按个会执行window,为了验证再来一道
var name = "JavaScript"
function language() {
let name = "CSS"
let other = {
name: "HTML",
fn: () => {
var name = "Vue"
console.log(this.name)
}
}
return other
}
language().fn()
这里是打印的JavaScript,this指向的是window,可以按照我上线的分析,分析一下,这里的箭头函数的this是继承了language的this,所以其指向window。
几大手写
各种手写,几乎是所有大厂必考的题,小厂考的可能不太大。下面总结一下常见的手写
实现一个new
遇到这样的问题,一般会先问一下JS的原型链,继承,最后问JS中new一个对象的时候,发生了什么事?,如果你答出来了,那么会接着问下面的问题,“你能实现一个new吗”,如果答不上来发生了什么事,那么就没有然后了。好,我们先来看一下,new一个对象的时候,发生了什么事。
- 以构造器的prototype属性为原型,创建新对象;
- 将this(也就是上一句中的新对象)和调用参数传给构造器,执行;
- 如果构造器没有手动返回对象,则返回第一步创建的新对象,如果有,则舍弃掉第一步创建的新对象,返回手动return的对象。
明白了new的时候发生了什么事以后,那么我们就可以做对应的代码实现了
// 构造器函数
let Parent = function (name, age) {
this.name = name;
this.age = age;
};
Parent.prototype.sayName = function () {
console.log(this.name);
};
//自己定义的new方法
let newMethod = function (Parent, ...rest) {
// 1.以构造器的prototype属性为原型,创建新对象;
let child = Object.create(Parent.prototype);
// 2.将this和调用参数传给构造器执行
let result = Parent.apply(child, rest);
// 3.如果构造器没有手动返回对象,则返回第一步的对象
return typeof result === 'object' ? result : child;
};
//创建实例,将构造函数Parent与形参作为参数传入
const child = newMethod(Parent, 'echo', 26);
child.sayName() //'echo';
上面是相对比较乞丐一点的,如果想要更加完整的实现,还需要自己再去看一下,或者,面试的时候,能写出一个乞丐版,把里面的东西讲清楚也好。
实现一个Promise.all
同样,关于Promise.all的手动实现也是一个很大概率出现的面试题,一般会出现在大厂的一面中,比较Promise.all的效果大家还是很熟悉的,面试官就会问了,“你说一下Promise.all是干啥的”,概念性的东西,还是比较简单的,你可能只需要回答:“接受一个Promise的队列,只有当所有的Promise都成功后才返回结果”,于是,就有个下面的追问, 那你知道Promise.all是怎么实现的吗?OK,我直接写出代码。
首先Promise.all的效果是下面这样的
let promise1 = new Promise(function(resolve) {
resolve(1);
});
let promise2 = new Promise(function(resolve) {
resolve(2);
});
let promise3 = new Promise(function(resolve) {
resolve(3);
});
let promiseAll = Promise.all([promise1, promise2, promise3]);
promiseAll.then(function(res) {
console.log(res); // [1,2,3]
});
那么我们只要实现上面的结果就好了
const myPromiseAll = (arr)=>{
let result = [];
return new Promise((resolve,reject)=>{
for(let i = 0;i < arr.length;i++){
arr[i].then((data)=>{
result[i] = data;
if(result.length === arr.length){
resolve(result)
}
},reject)
}
})
}
大概的思路是,因为传入的是一个Promise的数组,那么每一项都具有then
属性,这样我们把结果都放入一个数组中,如果,结果的长度和传入的数组长度相同,就说明,所有的Promise都执行完毕了,我们把结果resolve就可以了。当然了,上面的代码也是一个乞丐版的,我个人觉得面试的过程中,写出这样的乞丐版把思路说清就够了,当然,我还为大家准备了一个比较完整版的,比如我们会判断传入的数组的每一项到底是不是Promise
function isPromise(obj) {
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}
const myPromiseAll = (arr)=>{
let result = [];
return new Promise((resolve,reject)=>{
for(let i = 0;i < arr.length;i++){
if(isPromise(arr[i])){
arr[i].then((data)=>{
result[i] = data;
if(result.length === arr.length){
resolve(result)
}
},reject)
}else{
result[i] = arr[i];
}
}
})
}
实现一个bind
实现一个bind也是出现率很高的一个手写面试题,面试官一般会先问call
、apply
、bind
三者的区别,当你说完后,会让你实现一个bind~。关于bind的实现,我们只要知道bind是干了哪些事,“改变this
指向,并返回一个函数”我们去实现这个功能就好了,注意参数的收集,下面我总结了三种实现bind的方法,由初级到高级,当然了,你写的越高级,边界情况越多,那么你的评分会更高
初级版(使用const
和...
,代码简洁,不兼容IE)
// 初级:ES6 新语法 const/...
function bind_1(asThis, ...args) {
// 这里的 this 就是调用 bind 的函数 func
const fn = this;
return function (...args2) {
return fn.call(asThis, ...args, ...args2);
};
}
中级版本(兼容IE,但不支持new )
// 中级:兼容 ES5
function bind_2(asThis) {
var slice = Array.prototype.slice;
var args = slice.call(arguments, 1);
var fn = this;
if (typeof fn !== "function") {
throw new Error("cannot bind non_function");
}
return function () {
var args2 = slice.call(arguments, 0);
return fn.apply(asThis, args.concat(args2));
};
}
实际面试中,一般写中级版本就够了,机油思想,也够复杂,我觉得
高级版(复杂但支持new)
// 高级:支持 new,例如 new (funcA.bind(thisArg, args))
function bind_3(asThis) {
var slice = Array.prototype.slice;
var args1 = slice.call(arguments, 1);
var fn = this;
if (typeof fn !== "function") {
throw new Error("Must accept function");
}
function resultFn() {
var args2 = slice.call(arguments, 0);
return fn.apply(
resultFn.prototype.isPrototypeOf(this) ? this : asThis, // 用来绑定 this
args1.concat(args2)
);
}
resultFn.prototype = fn.prototype;
return resultFn;
}
写在后面
本文,根据朋友们分享的经验,做了一篇总结和部分讲解,希望对正在面试的你,有一定的帮助