这是一篇尽可能语义化的八股文。因为自己发现每次想要去复习八股文的时候,还需要在脑子里再组织一下语言,如果忘了提前组织,则面试的时候可能会出现茶壶里到饺子的情况,说的可能自己都听不明白。 所以写了这篇文章;相当于直接整理好了面试话术,希望能帮到面试的朋友。
1. 讲一讲var、let、const
var、let和const都是用来声明变量的。他们的区别是:
首先var是ES6之前出现,let和const是ES6之后出现的;
其次在作用域方面,var是函数作用域,let和const是块级作用域。
在变量提升方面,var是有变量提升,表现为可以在声明语句之前访问,返回是undefined,let和const不存在变量提升,存在暂时性死区,表现为声明之前访问会报错。
在重新声明方面,var声明的变量可以重新声明,后声明的值覆盖前面声明的值,而let和const不可以重新声明,会报错
在重新赋值方面,var和let在声明变量后,是可以重新赋值,但是const声明的变量是不能再次赋值。
在创建上下文方面,var声明的变量放在了变量环境,而let和const是放在词法环境中。当访问一个变量的时候,先访问词法环境再访问变量环境。
平时开发中,我们对于一些不修改的常量使用const,对于一些需要重新赋值的使用let,尽量不使用var。
2. 讲一讲JS的数据类型
js的数据类型分为原始类型和引用类型,也可以叫简单类型和复杂类型。
原始类型包括Number,String,Boolean,undefined,null,Symbol
引用类型有Object包函Array和Function。
在数据存储方面,原始类型存放在栈中,引用类型存放在堆中,同时在栈中会存放这个引用类型的地址。
3. 讲一讲 == 和 === 的区别
在js中相等运算符(==)和严格相等运算符(===)都是用来比较两个操作数的。 但是比较的过程是不同的,主要体现在是当比较的时候,是否进行了类型转换。
严格相等运算符(===)在判断两个操作数的时候,先判断两个操作数的类型是否相等,如果类型不同直接返回false。如果类型相同,再去判断值,如果值相等饭回ture,否则饭回false。
相等运算符(==)在比较两个操作数的时候,如果两个操作数的类型相同时:
都是原始类型,则去比较他们两者的值。
都是引用类型,则去比较他们的引用地址。
如果两个操作数的类型不同时,会有转换规则:
undefined 和 null 比较直接返回true,除了彼此,它们不会与其他任何值相等
string和number想比较的时候,会将string 转换成 number在比较
如果其中一项是Boolean值,则会将boolean转换成number在比较
如果其中一项是引用类型,则会先调用引用类型的 valueOf 方法,如果返回还不是简单类型,则会再次调用他的toString方法,进行比较。
NaN和任何数比较都返回false。
4. 讲一讲 === 和 Object.is()的区别
因为严格相等运算符(===)在判断+0和-0的时候(返回了true)和NaN与NaN判断的时候(返回了false)是不符合预期的。使用Object.is()来判断,可以正确判断(+0和-0返回false;NaN和NaN返回true)。
5. 讲一讲原型和原型链
在js中,每个对象都会有一个prototype属性指向原型对象,这个原型对象会有一个constructor的属性指回这个对象。当这个对象创建一个实例对象的时候,会有一个__proto__的属性指向这个原型对象,此时这个原型对象就会被称为实例对象的原型。如果一直访问__proto__属性最终会找到Object.prototype,这条访问的路径就成为了原型链。
当访问对象的属性或方法时,JavaScript 会先查找该对象本身是否有,如果没有就会沿着作用域链一直查找,如果都没找到就会返回undefined。
6. 讲一讲new做了什么事情
首先创建一个新对象
再将这个新对象的__proto__属性指向构造函数的prototype
然后修改构造函数的this,指向新对象
执行构造函数,判断执行结果,如果结果是引用类型,则返回这个结果,否则返回创建的新对象。
(一般情况下,问到直接回答就好,也有部分情况下会让手写:面试又被问到了new做了什么事情?)
7. 讲一讲深拷贝和浅拷贝
如果拷贝的类型是加引用类型,深拷贝会递归复制所有层级,创建一个完全新的对象。浅拷贝只会拷贝对象的一层属性,如果属性值还是引用类型,则会共享一个引用地址,如果修改原对象的引用类型会影响拷贝后的对象。
(可能会考手写深拷贝)
8. 讲一讲typeof和instanceof的区别
这两个都是用来检测数据类型的,typeof只能判断简单类型,但是null会返回object,判断函数会返回function,但是无法判断object和array。
insanceof 是用来判断这个构造函数的prototype属性是否出现在该操作数的原型链上,适用于检测复杂类型,无法判断简单类型。
(可能会手写instanceof)
详解:面试官又来问typeof 与 instanceof 区别了
9. 讲一讲for...of 和 for...in 的区别
for...of是用来遍历可迭代对象的例如Array Map Set,
for...in是用来遍历包括原型链上除了Symbol以外的可枚举属性。
(这里很高概率会被问到如何让for...of遍历object):for...of不能遍历object是因为object不是一个可迭代对象,如果想要将一个对象变成可迭代对象就需要实现[Symbol.Iterator]方法。
(这里也可能让手写)
10. 讲一讲JS的闭包
闭包是表示一个函数能沿着作用域链访问外部函数的变量,具体变现为执行一个函数返回一个函数。V8之所以能实现闭包是因为JS的三个特性,第一是允许函数声明在函数中,第二是函数可以作为参数,第三是允许函数作为返回值。使用闭包可以创建私有化变量,是因为这个变量因为JS的惰性解析和预解析,分析函数内部是否引用了外部变量,如果使用了则会将这个变量放在堆中,使得函数离开调用栈,这个变量也不会销毁,所以如果大量使用闭包,会使内存的使用量变大。
11. 讲一讲箭头函数和普通函数的区别
箭头函数是ES6出的新特性,和普通函数的区别是,首先是写法上的区别,箭头函数更加简洁,没有function声明,如果参数只有一个可以省略括号,如果返回值只有一句可以写一行。其次是箭头函数没有自己的this,如果在箭头函数中访问this返回的是上层函数的this。然后箭头函数也没有arguments对象和prototype属性,所以箭头函数不能被当作构建函数使用,
12. 讲一讲事件循环
事件循环是进行处理异步的一种编程模型。JS是一门单线程语言,运行在浏览器的主线程中,如果使用同步的方式,可能会导致线程阻塞。所以浏览器将异步任务交给其他线程去处理,当异步任务结束后,将这个任务的回调包装成任务放在消息队列的末尾,等待执行。 过去把消息队列分为宏任务和微任务,当浏览器执行完同步任务之后,会先看是否有微任务,执行完之后在去检查微任务队列,如此反复。 常见为宏任务有setTimeout,setInterval,常见的微任务有,Promise.then,new MutaionObserver等。 现在浏览器没有宏任务的说法,而是分为了更细微的任务队列划分,有着不同的优先级,微任务队列仍是最高优先级。
详解:面试必问之 JS 事件循环(Event Loop),看这一篇足够
13. 讲一讲call、apply、bind的区别
这三个都是用来修改this指向的,不同点在于使用方式:
apply传入两个参数,一个this指向的对象,第二个参数是一个数组。
call可以传入n个参数第一个参数为this指向,后续都将作为函数参数。
bind的参数和call一样,不同的是,bind返回的是一个函数,需要调用之后才会执行。
14. 讲一讲Set 和 Map
Set和Map是ES6引进的数据结构,
Set 是可以存储任何类型的集合,特点是存储的值具有唯一性,同时可以保证顺序,我们在平时的开发中利用他的唯一性可以做到去重的目的。
Map 是存储映射关系的对象,和对象的区别是,Map的key可以是任何数据类型。可以保证插入顺序。
15. 讲一讲Promise
Promise是ES6出现的一种异步编程的解决方案,在Promise出现之前,使用回调函数的方式来解决异步,但是循环嵌套过多就会出现回调地狱的情况,代码变得难以维护。Promise的核心就是解决这个问题的。Promise对象有一个状态机,包含了三种状态,padding等待中,表示尚未完成,fulfilled表示完成,有一个结果值,reject表示失败,会返回error。Promise的状态只能从padding状态变为 fulfilled或者reject,一旦变化之后,状态不能再次发生变化。 使用new promise方法创建一个promise对象,然后使用链式调用then方法访问成功后的数据,使用catch方法访问失败返回的error对象,finally方法不管成功失败都会执行的回调函数。
(这里可能会让手写Promise的实现,我没被问到过,但是有同学确实有被问到过)
16. 讲一讲promise.all 和 promise.race的区别
Promise.all传入一个由Promise对象组成的数组,返回一个新的Promise对象。该对象之后在所有项都执行完成之后才会完成。当所有promsie对象都返回成功,则会返回每一个promise对象成功的结果。 如果有一个promise失败,则会返回失败状态的promise,拒绝的原因是第一个失败的promise对象的原因。
Promise.race 也出传入多个Promise对象,但是只会返回第一个完成(无论成功或者失败的)的对象。只会关注第一个完成的。
(这里可能会让手写两者的实现方式)
17. 讲一讲promise.all 和 promise. allSettled的区别
promise.allSettled传入多个promsie对象,返回一个新的promise对象。和promise.all不同的是,allSettled会等待所有promise对象都执行完毕,无论成功还是失败,返回每个promise对象的成功或者失败的结果。
18. 讲一讲防抖和节流
防抖和节流是两种性能优化的技术。用来限制某些操作短时间内频繁操作而造成性能问题。
防抖是指n秒内频繁触发只会触发最后一次,当n秒内频繁触发则会重新计时。例如用户在输入框中打字,我们只需要在打字结束之后触发请求。
节流是指n秒没内频繁触发只会触发一次,n秒内频繁触发不会执行。例如监听滚动条事件,频繁操作n秒内只需要触发一次。
(这里可能会让手写防抖和节流的实现方式)
新手前端,如有问题多多指正。