Javascript、ES6面试题总结

如果需要markdown文件,可在https://github.com/csdd-21/FE...下载,源仓欢迎star、交流、指正..
个人感觉这篇文章的面试题覆盖率应该达到了90%以上,所有的文字不是东拼西凑出来的,而是自己花时间去掌握了之后加上自己理解总结出来的。如果你看到了它,那我建议你只是看看,然后去实践,敲一敲、测一测就会发现不过如此
如果有哪里不对的地方,非常感谢能够留言告诉我~
掌握原理 原型对象、原型链

  • 原型对象
    • 原型对象即prototype,函数才有原型对象,实例不会有原型对象,原型对象prototype可以理解为存放所有实例公用的属性或方法
  • 原型链
    • JavaScript中每个对象都有私有属性__proto__指向它的构造函数的原型对象prototype,该原型对象也有自己的原型对象(也是通过__proto__访问),层层向上直到nullnull没有原型并作为这个原型链中的最后一个环节。 JavaScript中所有对象都是位于原型链顶端Object的实例
      instance.__proto__ === instance.constructor.prototype
      Function.prototype.__proto__ === Object.prototype
    • 对象中都有私有属性__proto__指向它的构造函数的原型对象prototype,函数对象除了有__proto__属性之外,还有prototype属性,当函数对象作为构造函数创建实例时,该prototype属性将被作为实例对象的__proto__指向的值
    • 当一个对象调用属性或方法但在自身不存在时,会通过私有属性__proto__指向原型对象prototype上去找,如果没找到,就去原型的原型找,依次类推,直到找到该属性或方法,找不到返回undefined,从而形成原型链
new
  • new 关键字会进行如下的操作:
    1. 创建一个空对象{}
    2. 为该对象添加__proto__属性并链接至它的构造函数的原型对象prototype
    3. this指向该新对象并执行构造函数
    4. 隐式return this(如果显式返回非基本数据类型会覆盖掉)
      注:new、Object.create区别:new生成的对象会自动进行原型链链接,Object.create接收第一个参数作为新对象__proto__指向的原型对象
继承
  1. 原型链继承 — 重写prototype
    • 实现:
      • 重写Son.prototype为一个Father实例
        Son.prototype = new Father()
        Son.prototype.constructor = Son
    • 特性:
      • Son原型链上的属性值修改都会在Son实例之间互相影响
      • Son的prototype被重写,因此还需要重新为Son指定构造函数constructor
      • 在Son.prototype重写之前的原型链上的属性和方法都会丢失
    原型链继承 — Object.setPrototypeOf()
    • 实现:
      • 使用es6的Object.setPrototypeOf()来为Son设置原型对象
        Object.setPrototypeOf(Son.prototype,new Father())
    • 特性:
      • Son原型链上的属性值修改都会在Son实例之间互相影响
      • 与之前重写Son.prototype不同,Son原本原型链上的constructor、属性、方法都不会丢失
  2. 构造函数继承
    • 实现:
      • 在Son的构造函数内部通过call、apply来执行Father的构造函数,这样后Father实例属性就变成了Son实例自身的属性,但是只拿到了Father的实例属性,拿不到Father原型链
        Father.call(this,arg1,arg2,..)Father.apply(this,[argsArray])
    • 特性:
      • 解决了原型链继承的"Son原型链上的属性值修改都会在Son实例之间互相影响"问题
      • 拿不到Father原型上的属性或方法
  3. 组合继承
    • 实现:
      • 原型链+构造函数的组合继承
        Son.prototype = new Father()
        Father.call(this,arg1,arg2,..)Father.apply(this,[argsArray])
    • 特性:
      • Son的实例和原型链上都有Father的属性
  4. 寄生组合继承 — 重写prototype
    • 实现:
      • 原型链+构造函数的组合继承
        Father.call(this)
        Son.prototype = Object.create(Father.prototype)
        Son.prototype.constructor = Son
    • 特性:
      • 直接拿Father的原型对象,而不是Father实例
      • Son原本原型链上的constructor、属性、方法都丢失
    寄生组合继承 — Object.setPrototypeOf()
    • 实现:
      • 原型链+构造函数的组合继承
      ? Father.call(this)
      ? Object.setPrototypeOf(Son.prototype, Father.prototype)
    • 特性:
      • 直接拿Father的原型对象,而不是Father实例
      • Son原本原型链上的constructor、属性、方法都不会丢失
  5. 类继承
    • 实现:
      • 寄生组合继承的语法糖写法,使用extends关键字,且在Son类的构造函数里调用super()
        class Son extends Father
        super()
    • 特性:
      • extends相当于原型链连接
      • super相当于通过call、apply执行父类构造函数,super()必须在使用this前调用
class
  • class
    • class可以看作为语法糖/构造函数的另一种写法,class不可变量提升(构造函数可)
    • class里定义的属性为实例上的属性,class里定义的方法为原型对象prototype的方法,要在类的原型对象上添加属性只能通过类名.prototype.xx=''定义
    • class里可设置setter函数和getter函数(vue3的ref实现原理)
  • constructor
    • 实例属性除了可定义在constructor()里的this上,也可以定义在类的最顶层(且不需要写this),但这只适用实例赋值不需要传参的情况
    • 一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。通过new命令生成对象实例时,自动调用该方法
    • 继承时子类必须在constructor方法中调用super方法,否则新建实例时会报错
  • static
    • 静态属性/方法:通过static关键字定义,且只能通过类名.xx调用,实例无法调用
    • 静态方法里面的this指向这个类而不是类的实例,静态方法里面的super指向父类
this
  • 定义:
    • this总是指向函数直接调用者(只有在调用的时候才能确认this),而箭头函数this绑定父作用域的上下文
    • 如果有new关键字,this指向new出来的实例
    • 可通过call、apply、bind改变this指向(无法改变箭头函数的this绑定指向,只能改变它的父函数的this
  • 优先级:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
    • new绑定:this指向new出来的实例
    • 显式绑定:call、apply、bind
    • 隐式绑定:作为对象的方法调用时,this指向该对象
      ? ( 作为事件监听器addEventListener时, this指向触发事件的元素)
    • 默认绑定: 作为函数独立调用,严格模式下this指向undefined,非严格模式下指向window
  • 隐式丢失:
    • 函数丢失隐式绑定的对象,从而应用默认绑定(window或undefined)
  • call、apply、bind区别:
    • call:B.call(A, args1,args2,..)
    • apply:B.apply(A, argsArray)
    • bind:B.bind(A, args1,args2,..)
      注:call、apply的唯一区别只有传参方法,call接受多个参数,apply只接受一个参数数组。call、apply都是改变this指向且立即执行函数本身,而bind是返回原函数的拷贝、且拥有指定的this和初始参数
箭头函数、构造函数
  • 箭头函数:
    • 箭头函数的this绑定了父作用域的上下文,箭头函数没有自己的this,通过call()、apply()、bind()方法调用时,会忽略掉第一个参数,因此只能传递参数,不能改变this指向
    • 不可以当作构造函数、没有new、prototype、arguments,但有__proto__、剩余参数、默认参数
    • 没有自己的arguments对象,但可访问父上下文的arguments
      注:vue2中帮我们把methods里面的this绑定到实例上(源码:遍历所有methods并分别调用bind),所以vue2不允许用箭头函数定义this
  • 构造函数:
    • 构造函数一般用大写,普通函数也可以使用new关键字来当做构造函数使用
    • 构造函数使用new关键字调用时会进行原型链链接和将this指向new出来的实例,而普通函数调用时this指向调用者,如果没有调用者那就默认指向window
    • 构造函数默认返回新对象,而普通函数如何没有显示return出来就默认return undefined
闭包、垃圾回收机制、内存泄漏/溢出
  • 闭包:
    • 定义:
      • 一个函数和对其父作用域们局部变量的引用的组合叫做闭包,且注意闭包取的是父作用域中对应变量最终的值(不止是引用与其相邻的父函数里的变量才算,父的父的父都算,全局也算)
      • 闭包就是当一个函数即使是在它的词法作用域之外被调用时,也可以记住并访问它的词法作用域
        (一般函数的词法环境在函数调用结束后就被销毁,但是闭包会保存对创建时所在词法环境的引用(销毁前在堆里存放一个该变量),即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的)
    • 好处:
      • 闭包中的变量可被缓存、不被垃圾回收机制清除,也不会与其它环境变量发生命名冲突
    • 坏处:
      • 滥用闭包会增大内存消耗甚至内存泄漏,解决方法:使用完后及时将其赋值为null来释放内存
    • 使用场景:
      • 防抖、节流、函数柯里化、vuex中通过方法访问getters、setTimeout引用外部变量/clearTimeout
  • 垃圾回收机制:根据不同浏览器,垃圾回收机制有两种:引用计数(chrome)、标记清除
    • 引用次数:引用+1、不再引用-1、值为0时 => 清除
    • 标记清除:标记符、进入环境、离开环境 => 清除
      注: weakMap、weakSet是弱引用,不需要被垃圾回收机制回收
  • 内存泄漏和内存溢出的区别
    • 内存泄漏:占用的内存没有及时释放,内存泄漏多了就容易导致内存溢出(大对象)
    • 内存溢出:函数运行时会需要的内存超出了计算机为其分配的内存,常见如死循环(循环递归调用但是没有判断让其return出去的语句)
Promise、async await、异步
  • Promise
    • 定义:
      • Promise构造函数,可以用new Promise()实例化一个promise对象用来处理异步操作,Promise接受一个函数参数,该函数参数里又有两个参数分别是resolvereject
        • resolve函数作用是将promise对象的状态从pending变为fulfilled,以及保存resolve('xx')里的内容作为返回结果,结果可在.then()中获取
        • reject函数作用是将promise对象的状态从pending变为rejected,以及保存reject('xx')里的内容作为错误信息返回,抛出的错误可在.then()的第二个函数参数里或在.catch()获取
      • promise对象用于表示一个异步操作的最终状态(完成/失败)及其结果值
    • 特性:
      • 当处于pending状态时,无法得知目前进展到哪一个阶段
      • promise只会执行一个resolve或者reject,其他的忽略掉
      • 如果不设置错误处理,promise内部抛出的错误,不会反应到外部(不会阻塞运行,但会报错uncought in promise)
      • 对于Promise.all()..等一些方法而言,promsie的结果可能会提前返回,但即使已经返回,如果还有未执行完的promise任务的话,仍然继续执行,这也是promise的缺点,无法取消Promise,一旦创建就会执行,无法中途取消
    • 状态:(共3种)
      • pending: 初始状态,非fulfilledrejected
      • fulfilled: 操作成功完成
      • rejected: 操作失败
      注:fulfilled与 rejected一起合称settled
    • 抛出错误方式:
      • return Promise.reject('xx')
      • throw new Error('xx')
    • 链式调用:
      • .then()返回新的Promsie实例,这也是能够链式调用原因,在.then()里若返回任意一个非promise值,都会被包装为一个promise对象,即return xx => Promise.resolve(xx)
      • .catch(),无论链式调用多少层级,.catch()都能捕获到上层错误(错误有类似“冒泡”的性质,直到被捕获为止)
    • 实例方法:
      • .then(),接收两个函数参数,第一个函数用来处理resolve返回的结果,第二个函数用来处理reject抛出的错误,then方法返回的是一个新的Promise实例,也就是promise能链式调用原因
      • .catch(),捕获reject抛出的错误,可以看作是.then()的第二个函数参数的语法糖(若错误在.then()的第二个函数参数里被捕获,那这里就不会再被捕获)
      • .finally(),无论fulfilledrejected都会被调用,也不接收参数
    • 构造函数方法:
      • .resolve() //Promise.resolve()相当于new Promise( ( resolve,reject)=>{ resolve('xx') } )的语法糖
      • .reject() //Promise.reject()也是个语法糖,同上
      • .all() //所有都成功或任意一个失败返回
      • .allSettled() //所有的promise有结果后才返回
      • .any() //一个成功或全部失败返回
      • .race() //任意一个成功或失败就返回
        注:promise的结果即使已经返回,但如果还有未执行完的promise任务的话,仍然会执行只是结果被忽略,这也是promise的缺点
  • async await
    • async,用来声明一个异步方法,返回一个promise对象
    • await,等待promise异步方法执行并返回结果(并不是把异步变为同步),await后面的代码为微任务,相当于promsie.then()(只有async里面的、await后面的代码会被阻塞,async外的代码是不会被阻塞的)
    • 当异步结果为失败,需要用try..catch处理异常
      注:promise.then解决回调地狱,async await解决.then链式调用过长问题
  • 实现异步方法
    • promise、async await、setTimeout、setInterval、生成器函数
迭代器对象、生成器函数
  • 迭代器:
    • 特点:
      • 迭代器是一个对象,迭代器是通过重复调用next()方法按顺序返回可迭代对象中的元素,返回值为具有value、done属性的一个对象,其中value为当前迭代的返回值,done为是否已经迭代完的布尔值,未终止迭代done为false,终止迭代后done为true
  • 生成器:
    • 特点:
      • 生成器函数使用function*编写,生成器函数提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态
      • 首次调用时并不会马上执行它里面的语句,而是返回一个生成器(Generator)的迭代器对象(Iterator)且生成器状态为。当这个迭代器的next() 方法第一次被调用时,其内的语句会执行到第一个yield位置为止,并返回yield后紧跟的值。生成器函数在执行时能暂停,后面又能从暂停处继续执行。如果遇到yield*,则表示将执行权移交给下一个生成器函数或可迭代对象(即当前生成器暂停执行,后面的如果还有yield语句也不生效了)
        注:yield*1 => 会报错,因为1是非迭代对象,无法移交执行权
      • next()方法返回一个对象,这个对象包含两个属性:value、done,value属性表示本次yield表达式的返回值,done属性为布尔类型,表示生成器后续是否还有yield语句,即生成器函数是否已经执行完毕并返回
    • 代码:
      // 调用`next()`方法时,如果传入了参数,那么这个参数会传给上一条执行的yield的左边变量 function* gen_fn(){ yield 10; x=yield 'foo'; yield x; } var gen = gen_fn(); //首次调用生成器函数,返回一个生成器的迭代器对象 gen.next()// {value:10, done:false} gen.next()// {value:'foo', done:false} gen.next(100)// {value:100, done:false},参数传给上一条yield语句的左边变量 gen.next()// {value:undefined, done:true}

      function* gen_fn() { yield* [1,2]; yield* "34"; yield* arguments; } var iterator = gen_fn(5,6); iterator.next()// { value: 1, done: false } iterator.next()// { value: 2, done: false } iterator.next()// { value: "3", done: false },字符串也可迭代 iterator.next()// { value: "4", done: false } iterator.next()// { value: 5, done: false } iterator.next()// { value: 6, done: false } iterator.next()// { value: undefined, done: true }

  • async await
    • 特点:async函数就是将生成器函数(Generator )的星号(*)替换成async,将yield替换成await,仅此而已
可迭代、可枚举
  • 可迭代对象:
    • 定义:
      • 一种数据结构只要部署了Iterator接口(有Symbol.iterator属性),我们就称这种数据结构是“可迭代的”(iterable),对象中的成员便可由for..of按次序遍历,为可迭代对象的有:Array、Map、Set、String、 Arguments、NodeList对象(Object、WeakSet、WeakMap不是可迭代的)
      • 对象Object之所以没有默认部署Iterator接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定(即对象里的键值对是无序的)
    • for..of
      • 遍历可迭代对象,包括Array、Map、Set、String、Arguments、NodeList对象
      • 遍历计算生成的数据结构:Array、Set、Map调用keys()、values()、entries()后返回对应的遍历器对象ArrayIterator、SetIterator、MapIterator,这些返回的Iterator对象都可以用for..of遍历
      • for..of可用breakcontinuereturn来跳出循环,forEach不可
      • object没有内置迭代器,因此无法使用keys()、values()、entries()这些方法,但可通过Object内置对象来使用,即Object.keys()、Object.values()、Object.entries()
    • 代码:
      //for..of遍历可迭代对象 //for..of遍历Map let map = new Map().set('a', 1) for(let value of map )=> ['a', 1] for(let [value,key] of map)=>a , 1//for..of遍历对象 for (let key of Object.keys(obj)) //for..of遍历Array、Set、Map调用keys()、values()、entries()后都的返回值 for (let item of [1,2,3].entries())=> [0,1]、[1, 2]、[2, 3]

  • 可枚举属性:
    • 定义:
      • 如果使用字面量创建对象时,对象的属性默认是可枚举的,如果使用Object.defineProperty()新增对象属性时,属性默认是不可枚举的,需要设置{enumerable:true}才是可枚举属性
    • 重要的对象api比较:
      • for..in:遍历对象自身的、和原型链上的除Symbol外的可枚举属性
      • Object.getOwnPropertySymbols():遍历对象自身的所有Symbol属性
      • Object.keys():遍历自身的可枚举属性
      • Object.getOwnPropertyNames():遍历对象所有的可枚举或不可枚举的属性
      • Object.assign(): 忽略enumerablefalse的属性,只拷贝对象自身的可枚举的属性
事件循环、宏任务、微任务
  • 同步、异步任务:在JavaScript中,所有的任务都可以分为:
    • 同步任务:立即执行的任务,进入主执行栈中立即执行
    • 异步任务:异步执行的任务,进入任务队列等待执行,异步任务又分为宏任务、微任务(若是setTimeout,会等到计时结束后再加到宏任务队列里等待执行,若是promise.then会等到promise有结果后推到微任务等待执行)
  • 宏任务、微任务:
    • 宏任务:script、setTimeout/setInterval、UI render、setImmediate、I/O(Node.js)
    • 微任务:Promise.then、process.nextTick(Node.js)、MutaionObserver、Object.observe(已废弃;Proxy对象替代)
  • 一次完整的Event loop:
    • 执行script中的所有同步代码/同步任务,直接进入主执行栈中立即执行,若遇到微任务放到微任务队列,若遇到宏任务放到宏任务队列(若是setTimeout会等到计时结束在放到宏任务队列里)
    • 执行完所有同步任务后主执行栈为空,查询是否有可执行的微任务,若有则执行微任务
    • 若需要则渲染 UI
    • 然后开始下一个宏任务Event loop,依次循环
函数式编程、响应式编程
  • 函数式编程:
    • 每个函数只做一件事情、只接受一个参数、只返回一个结果,不会污染其它、不会有其它副作用
    • 好处是:逻辑清晰易于调试,坏处:复杂逻辑的话会导致函数嵌套过深反而不好了
    函数柯里化、promise都可看做是函数式编程的一种体现
    解决嵌套过深,可以用链式调用,需要函数内部都返回this
  • 响应式编程:待补充
import、require、export 待补充
基本结构 对象 Object.is()
方法判断两个值是否为同一个值。
如,NaN === NaN //false Object.is(NaN) === Object.is(NaN ) //true
Object.assign()
通过复制一个或多个对象来创建一个新的对象。— 对象浅拷贝(忽略对象的不可枚举属性,只拷贝可枚举属性)
Object.create()
创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
如,Object.create( {'a':11}, {'b':{value:22} } )
Object.defineProperty()
给对象添加/修改一个属性并指定该属性的配置。— 且可使用get/set控制对象的访问、vue2响应式原理
Object.getPrototypeOf()
返回指定对象的原型对象。— 查爹
Object.setPrototypeOf()
设置对象的原型(即内部 [[Prototype]] / __proto__属性)。— 换爹
Object.entries()
返回给定对象自身可枚举属性的 [key, value] 数组。— 可以用来迭代
Object.values()
返回给定对象自身可枚举值的数组。
Object.keys()
返回一个包含所有自身可枚举属性名称的数组。 — 只遍历自身可枚举属性,for..in遍历自身和原型链上的
for..in
以任意顺序遍历一个对象的除Symbol以外的可枚举属性。— 遍历自身,也遍历原型链上的
Object.getOwnPropertyNames()
返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性名。
Object.getOwnPropertySymbols()
返回一个数组,它包含了指定对象自身所有的符号属性。
内置对象 略
函数对象 略
数组 Array.from()
从类数组对象或者可迭代对象中创建一个新的数组实例。— 数组浅拷贝
Array.isArray()
用来判断某个变量是否是一个数组对象。
以下是数组常用方法的形参、返回值的比较:
  • 记住数组方法的返回值、以及方法是否改变原数组,特别是splice、slice、sort、forEach、map、reduce
  • 记住数组方法的形参,特别是push、unshift、splice、concat、indexOf
  • 数组的第一个形参为函数参数,且该箭头函数里有3个参数item、index、array的所有数组方法有:
    every、some、filter、forEach、map、find、findindex
  • reduce有2个参数,第1个为函数参数,第2个为初始值
    arr.reduce( (preArr,curVal,curIndex,array) => {..}, initialValue )
    其中preArr就是上一轮迭代返回值,若无返回值,那就是undefined
  • keys()、values()、entries()
    ES6提供三个新的方法keys()、values()、entries()用于遍历数组,它们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历
返回值
find 找到返回元素 没有undefined
findindex 找到返回位置 没有-1 (注:比较indexOf())
includes Boolean
popshift 删除的元素
pushunshift 新长度(可push、unshift多个值,不止一个也可以)
splice() 包含开始下标,由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组( 若未删除元素,新的元素会插入在之前(第一个下标的之前)
slice() 包含开始下标,从数组中删除的元素; 如果无元素为返回空数组
concat() 合并多个数组后返回一个新数组(不限个数、不改变原数组)
sort() 排序后的原数组(在原数组上排序)
indexOf index,不存在返回-1(接受2个参数,要查找的元素和开始查找的位置)
lastIndexOf 同上
every Boolean
some Boolean
filter 满足条件的所有元素
forEach undefined
map 计算后新的Array
reduce 函数累计处理的结果(共接收4个参数分别是accu累计器、curVal当前值、curIndex当前索引、array数组)
reduceRight 函数累计处理的结果(与reduce的区别就是顺序:从右往左)
flat 扁平数组,接受一个数字参数表示扁平的层级,第一层为0
Set
  • Set是值的集合、永远唯一、不重复,Set里的值可以是数值、Symbol、函数等任意类型
    • 原型链:set实例 -> Set -> Object
    • 属性:.size获取长度
    • new:接收一个可迭代对象初始化
    • 基本操作:has()、add()、delete()、clear()
    • 迭代操作:keys、values、entries、forEach、for..of
      数组去重 [...new Set([1,1,2,2,3])]字符串去重 [...new Set('ababbc')].join('')Set实现数组的交集、并集、差集 let a = new Set([1, 2, 3]); let b = new Set([4, 3, 2]); // 并集 let union = new Set([...a, ...b]); // Set {1, 2, 3, 4} // 交集 let intersect = new Set([...a].filter(x => b.has(x))); // set {2, 3} // (a 相对于 b 的)差集 let difference = new Set([...a].filter(x => !b.has(x))); // Set {1}

Map
  • Map用于保存键值对,每一个键值对唯一不重复
    • 原型链:map实例 -> Map -> Object
    • 属性:.size获取长度
    • new:接收二维数组作参数
    • 基本操作:has、get、set、delete、clear
    • 迭代操作:keys、values、entries、forEach、for..of
      注:Set、Map的方法其它一致,只有一点不一样,Set的添加是add、Map的添加是set
  • Map与Object对比:
    Map Object
    key可为任意值 key只能是字符串或Symbol
    有序,Map遍历顺序就是插入顺序 无序(一般是数字排前)
    .size获取map对象里的键值对个数 手动计算Object.keys(obj).length
    可迭代,即可用for..of遍历 不可迭代,不能用for..of,若要遍历应该用for..inObject.keys(obj).forEach
    不支持JSON(结果永远为{} 支持JSON
    • 少量的数据而言使用Object比Map占用更少的内存,而对于大量的数据且需对大量的数据进行增删改查应用Map更高效
let mySet = new Set([1,2,3]) let obj = {'a':1,'b':2} JSON.parse(JSON.stringify(mySet))// {},map使用JSON方法永远等于空对象{} JSON.parse(JSON.stringify(obj))// {'a':1,'b':2}

  • 注意,只有对同一个对象的引用,Map结构才将其视为同一个键
    const map = new Map(); map.set(['a'], 555); map.get(['a']) //undefinedlet b = ['a'] map.set ( b, 555); map.get ( b ) //555,只有对同一个对象的引用,Map结构才将其视为同一个键

    //(二维)数组传Map new Map([['a',1],['b',2]]) //Map转数组 [...new Map([['a',1],['b',2]])]//对象转Map let obj = {'a':1,'b':2} let map = new Map(Object.entries(obj))

WeakSet、WeakMap
  • WeakSet
    • WeakSet的成员只能是对象,而不能基本数据类型
    • WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用(弱引用随时会消失,ES6规定WeakSet不可遍历)
    • 只有add、delete、has方法,不可使用迭代方法
    • WeakSet和Set:
      • Set相比,WeakSet只能是对象的集合,而不能是任何类型的任意值
      • WeakSet持弱引用:集合中对象的引用为弱引用。 如果没有其他的对WeakSet中对象的引用,那么这些对象会被当成垃圾回收掉。 这也意味着WeakSet中没有存储当前对象的列表。 正因为这样,WeakSet是不可枚举的
  • WeakMap
    • 对象是一组键值对的集合,其中的键是弱引用的。其键必须是对象(不能是原始数据类型),而值可以是任意的
    • 相比之下,原生的WeakMap持有的是每个键对象的“弱引用”,这意味着在没有其他引用存在时垃圾回收能正确进行。原生WeakMap的结构是特殊且有效的,其用于映射的key只有在其没有被回收时才是有效的
    • WeakMap是弱引用,因此很适合用来跟踪对象引用,如vue3就是使用WeakMap来确定Effect的
Proxy
  • Object.defineProperty
    • 定义:
      • 数据劫持,直接在一个对象上定义一个新属性,或修改一个现有属性,并返回此对象,利用getter、setter函数来控制对象的访问
    • 特点:
      • 无法检测数组元素变化,需要对数组方法进行重写,无法检测数组长度修改
      • 无法检测到对象属性的添加或删除
      • 必须遍历对象的每个属性,为每个属性都进行get、set拦截
      • 必须深层遍历嵌套的对象
  • Proxy
    • 定义:
      • 用于创建一个对象的代理,不在局限某个属性,而是直接对整个对象进行代理,目标对象可以是数组、对象、函数、代理..等任意类型,Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写
    • 特点:
      • 不需要对数组的方法进行重载,支持13种拦截操作
      • 可以检测到对象属性的添加或删除
      • 针对整个对象,而不是对象的某个属性(浅层,仍需递归)
      • 仍需要将嵌套对象进行遍历为响应式
    • 参数:
      • target:源对象
      • handler:代理操作/拦截器/拦截函数,对proxy对象进行拦截和操作
    • handle:(Proxy支持的拦截操作一览,一共13种)
      • get(target, propKey, receiver)
        拦截对象属性的读取,比如proxy.fooproxy['foo']
      • set(target, propKey, value, receiver)
        拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
      • has(target, propKey)
        拦截propKey in proxy的操作,返回一个布尔值。
      • deleteProperty(target, propKey)
        拦截delete proxy[propKey]的操作,返回一个布尔值。
      • ownKeys(target)
        拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
      • getOwnPropertyDescriptor(target, propKey)
        拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
      • defineProperty(target, propKey, propDesc)
        拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
      • preventExtensions(target)
        拦截Object.preventExtensions(proxy),返回一个布尔值。
      • getPrototypeOf(target)
        拦截Object.getPrototypeOf(proxy),返回一个对象。
      • isExtensible(target)
        拦截Object.isExtensible(proxy),返回一个布尔值。
      • setPrototypeOf(target, proto)
        拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
      • apply(target, object, args)
        拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
      • construct(target, args)
        拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)
Reflect
  • Reflect
    • 定义:
      • Reflect是一个内置的对象,它提供拦截 JavaScript 操作的方法
    • 特点:
      • 一句话概括就是Reflect就是Object原生对象的一个副本,保留并优化原有的默认行为和方法
      • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为
手写代码 防抖、节流
  • 防抖
    • 在规定时间里,事件处理函数只执行一次,若规定时间内多次触发,则重新计时
    function debounce(fn,delay){ let timer = null return function() {//借助闭包 if(timer){ clearTimeout(timer) }//规定时间内再次触发则重新计时 timer = setTimeout(fn,delay) } }function showTop() { var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; console.log('滚动条位置:' + scrollTop); } window.onscroll = debounce(showTop,200)

  • 节流
    • 在规定时间里,事件处理函数只执行一次,若在规定时间内多次触发函数,也只执行第一次,直到过了规定的时间,再次触发函数,函数才会再次执行
    function throttle(fn,delay){ let timer = null return function() { if(timer){ return false }//规定时间内再次触发忽略 timer = setTimeout(() => { fn() timer = null; //执行结束后赋值为null }, delay) } }function showTop() { var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; console.log('滚动条位置:' + scrollTop); } window.onscroll = throttle(showTop,200)

  • 防抖和节流的比较
    防抖和节流,都用于高频事件、短时间内连续触发的事件,以滚动事件为例,不同之处如下,
    • 防抖为规定时间内多次触发就重新计时,因此如果一直拖滚动条进行滚动,那就永远无法得到scrollTop值
    • 节流为规定时间内多次触发,只有第一次有效,其余忽略,因此如果一直拖着滚动条进行滚动,还是会以规定的时间间隔持续输出scrollTop的值
  • 防抖和节流其它使用场景
    • 搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求
    • 页面resize事件,常见于需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况)
对象、数组深/浅拷贝 本节需要掌握对象、数组的所有浅拷贝、深拷贝方法,其实也等同于问为如何拼接、合并多个对象或数组,答案是一样的
  • 对象拷贝
    • 对象浅拷贝
      • ... 展开运算符
      • Object.assign(忽略对象的不可枚举属性,只拷贝对象的可枚举属性)
    • 对象深拷贝
      • JSON.parse(JSON.stringify())(缺点:无法拷贝函数、Symbol、undefined的值,因此需要自己封装深拷贝函数)
    • 递归
  • 数组拷贝
    • 数组浅拷贝
      • ...展开运算符
      • concat
      • slice
      • Array.from()
    • 数组深拷贝
      • JSON.parse(JSON.stringify())
      • 递归
// 递归函数实现数组或对象的深拷贝 function recursive(obj) { if (typeof obj !== 'object' || obj == null) return objlet res = obj instanceof Array ? [] : {}for (let key in obj) {//for..in遍历自身和原型链上的属性 if (obj.hasOwnProperty(key)) {//排除原型链上的属性,只遍历出自身的 res[key] = typeof key === 'object' ? recursive[key] : obj[key] } } return res }// 拼接、合并数组 let arr_1 = [1,2,3] let arr_2 = [4,5,6] console.log('(1)', [...arr_1,...arr_2]) console.log('(2)', arr_1.push(...arr_2)) console.log('(3)', arr_1.unshift(...arr_2)) console.log('(4)', arr_1.splice(0,0,...arr_2)) console.log('(5)', arr_1.concat(arr_2)) console.log('(6)', arr_1.push.apply(arr_1,arr_2))

递归和扁平
// 递归 function trans2Tree(arr) { let res = [], parentArr = [] for (let i = 0; i < arr.length; i++) { if (!arr[i].parent) { parentArr.push(...arr.splice(i, 1)) i-- } } if (parentArr.length !== 0) { res = tree(parentArr, arr) } else { res = parentArr } function tree(parentArr, arr) { if (arr.length == 0) return parentArr parentArr.forEach(parent => { let childArr = [] for (let i = 0; i < arr.length; i++) { if (arr[i].parent == parent.id) { childArr.push(...arr.splice(i, 1)) i-- } } parent.children = childArr if (childArr.length > 0) { tree(childArr, arr) } }) return parentArr } return res }// 扁平,可用es6的flat(),或自己手写代码把嵌套的一层层递归出来

去重
  • 对象里重复属性去重
    • Map
    • reduce
let obj_1 = {'a':1,'b':2} let obj_2 = {'b':222,'c':3} let res= {}res = new Map([...Object.entries(obj_1),...Object.entries(obj_2)])//不过注意这时结果是Map

  • 数组去重
    以下是数组常用的去重方法汇总:
    • Set、Map、object
    • 通过下标:indexOf、lastIndexOf
    • 通过数组迭代方法们:includes、reduce+includes/indexOf、filter
let arr = [1,2,2,2,3,3] let res = []// Set(最快速的方法,不过只适用于数组里的元素都是基本数据类型) res = Array.from(new Set(arr))// new Set(arr) => Set(3) {1, 2, 3}// Map(这个方法其实跟Object非常像) let temp = new Map() for (let i=0; i//pre是累计器结果,为一个数组 // pre.indexOf(cur) !== -1 ? pre: [...pre,cur] pre.includes(cur) ? pre: [...pre,cur]//[...pre,cur]从而得到新的数组 ,[])// sort排序后比较相邻值 let arr = [3,3,2,2,2,1] let res = [] arr.sort((a,b)=>a-b) res.push(arr[0])//先push一个进去 for (let i=1; i arr.indexOf(item) === index)// 递归loop// 双层for循环

排序
let arr = [3, 2, 10, 0, 5, 4, 9, 1, 5, 7, 6, 8, 1]; // 冒泡排序 function bubbleSort(arr) { for (let i = 0; i < arr.length; i++) { for (let j = 0; j < arr.length - 1; j++) { if (arr[j] > arr[j + 1]) {//依次比较相邻,就像在不断冒泡 [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]] } } } return arr }// 选择排序 function selectSort(arr) { for (let i = 0; i < arr.length; i++) { let index = i//index表示最小数的索引 for (let j = i; j < arr.length; j++) {//从[i,arr.length-1]里选择出一个最小值 if (arr[index] > arr[j]) { index = j } } [arr[index], arr[i]] = [arr[i], arr[index]] } return arr }// 插入排序 function insertSort(arr) { for (let i = 1; i < arr.length; i++) {//从下标为1的位置开始遍历 let j = i//此时已排序数组为[0,i],未排序数组[j=i,arr.length-1] while (j > 0 && arr[j] < arr[j - 1]) {// 抽出第j张卡,插入到[0,j]数组里的正确位置 [arr[j], arr[j - 1]] = [arr[j - 1], arr[j]] j-- } } return arr } console.log('插入', insertSort(arr_3)); // 快速排序 function quickSort(arr) { if (arr.length < 2) return arr//无这个if判断会导致栈溢出 let pivot = arr[0] let bigArr = [], smallArr = [] for (let i = 1; i < arr.length; i++) { arr[i] < pivot ? smallArr.push(arr[i]) : bigArr.push(arr[i])//arr[i]为基准数 } return [...quickSort(smallArr), pivot, ...quickSort(bigArr)] }// 归并排序 function merge(leftArr, rightArr) { let res = [] while (leftArr.length > 0 && rightArr.length > 0) { if (leftArr[0] < rightArr[0]) { res.push(leftArr.shift()) } else { res.push(rightArr.shift()) } } return res.concat(leftArr, rightArr) } function mergeSort(arr) { if (arr.length < 2) return arr let mid = Math.floor(arr.length / 2) let leftArr = arr.slice(0, mid) let rightArr = arr.slice(mid, arr.length) return merge(mergeSort(leftArr), mergeSort(rightArr))//每个数字分为一组后再顺序合并merge }

正则
  • 表达式(下面的reg为自定义的匹配规则,str为自定义字符串)
    • reg方法:
      • reg.exec(str)
      • reg.test(str),匹配成功返回true,失败返回false
    • string匹配:
      • str.search(reg, function(){}),匹配成功返回首次匹配位置,失败返回-1
      • str.match(reg, function(){}),匹配成功返回匹配结果数组,失败返回null
      • str.replace(reg, function(){}),匹配后的新字符串
      • str.split()
  • 基础
    • 匹配位置:
      • ^ 表示匹配字符串的开始位置(例外:用在中括号中[]时,表示为取反,即不匹配括号中字符串)
      • $ 表示匹配字符串的结束位置
    • 匹配次数:
      • ? 表示匹配零次或一次
      • + 表示匹配一次到多次 (至少有一次)
      • * 表示匹配零次到多次
    • 括号:
      • () 小括号表示匹配括号中全部字符
      • [] 中括号表示匹配括号中任意一个字符,描述范围,如[0-9 a-z A-Z]
      • {} 大括号用于限定匹配次数,如{n}表示匹配n个字符,{n,}表示至少匹配n个字符,{n,m}表示至少n,最多m
    • 通过$\获取小括号匹配内容:
      • $n$加一个数字的形式表示第几个小括号里匹配的内容,在函数里(function(){})使用
      • \n\加一个数字的形式表示第几个小括号里匹配的内容,在正则规则定义里(//g) 使用
    • 范围、以\开头:
      • \w 表示英文字母和数字,\W 非字母和数字
      • \d 表示数字,\D 非数字
      • \s 空格,\S 非空格
    • 其它:
      • \ 转义字符,如\*表示匹配*号
      • | 表示为,两项中取一项
      • . 表示匹配单个字符
      • g 全局匹配,i 不区分大小写
常见的正则匹配:(代码见js文件) 去掉首尾、中间的所有空格 变量名转换为驼峰 验证手机号码是否为11位、加密手机号码为'124****7890'的形式 身份证号 车牌号码 校验账号、密码,规则必须是字母、数字、_组成,且长度为5-20 邮箱(如2433378@qq.com) url

基础 值分类
  • 基本数据类型
    • null、undefined、boolean、number、string、Symbol、BigInt
      注:Symbol是一个内置对象,Symbol值作为对象属性名时,访问时不能用点运算符
      ? BigInt是一个内置对象,可表示任意大整数,以数字+n结尾表示,且可以保证大整数的精度问题
  • 引用数据类型
    • object、array、map、set、weakmap、weakset、function..
null、undefined区别
  • null:表示空值
    • 作为对象原型链终点
    • 当使用完闭包、或使用完一个较大对象时,可将其设为null释放内存
  • undefined:表示"缺少值",即此处应有值但还未定义
    • 变量已声明但未赋予初始值,let a; console.log(a) => undefined
    • 调用对象并不存在的属性,let obj = {}; console.log(obj.a) => undefined
    • 调用函数时,未给形参提供实参,function fn(a){ console.log(a) }; fn(); => undefined
    • 函数没有返回值时,默认返回undefined
  • null、undefined 相同点:
    • 都为基本数据类型/原始数据类型
    • 转换为布尔值都是false
      • !!null //false
      • !!undefined //false
  • null、undefined 不同点:
    • 类型不一样:
      • typeof null //object,注:null intanceof Object -> false
      • typeof undefined //undefined
    • 转化为数值时不一样:
      • Number(10+null); //10,null转为0
      • Number(10+undefined); //NaN,undefined转为NaN
typeof、instanceof区别
  • typeof
    • 判断基础数据类型返回对应的基础数据类型
    • 判断函数返回'function'(判断String、Number..内置对象也返回'function')
      typeof Array->'function' typeof [] -> object
    • 判断除函数外的引用数据类型都是返回'object'
    • 特殊的返回值:typeof null => object
  • instanceof
    • 构造函数的prototype属性是否出现在某个实例对象的原型链上
      注:[] instanceof Array === [] instanceof Object => true(所有引用类型都符合该表达式)
let、var、const区别
  • var
    • 函数作用域,有变量提升(注:除了var、函数也有函数声明提升)
  • let、const
    • 块级作用域、无变量提升、存在“暂存性死区”(在块级作用域开始到let、const声明变量的区域就叫“暂存性死区”)
    注:“暂存性死区”也可以理解为只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
trusy、falsy
  • falsy的所有情况:null | undefined | NaN | 0 | -0 | 0n | "" | '' | ``(共9个)
默认参数、剩余参数、arguments参数
  • 默认参数:
    • 没有值或传入undefined时使用默认形参
  • 剩余参数:
    • 将一个不定数量的参数表示为一个数组,必须写在函数形参的最后一个(否则报错)
  • arguments
    • 是一个对应于传递给函数(非箭头函数)的参数的类数组对象,arguments是类数组,只能使用index访问元素和length获取长度,其它数组的方法都不可使用
    • 除了有length属性、还有callee属性
    注:剩余参数、arguments区别:
    • 剩余参数只包含那些没有对应形参的实参,而arguments对象包含了传给函数的所有实参
    • 剩余参数是数组,能够使用数组上的所有方法,arguments是类数组,只能使用index访问元素和length获取长度,其它数组的方法都不可使用
    • arguments对象还有一些附加的属性(如callee属性)
柯里化函数
  • 是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数的新函数,bind的实现机制就是柯里化
ES6新增
  • 新增:
    • let、const
    • Map、Set、WeakSet、WeakMap
    • Proxy、Reflect
    • promiseasync await
    • for..of、迭代器和生成器
    • ...展开运算符
    • 解构赋值(数组解构时有序、对象无序)
    • 默认参数、剩余参数、arguments参数
    • 箭头函数
    • sessionStorage、localStorage
attribute、property区别
  • attributehtml标签自定义的属性
  • property就是dom元素在js中作为对象拥有的属性
增、删、改、查节点
// 创建 createDocumentFragment()//创建DOM片段 createElement()//创建具体的元素 createTextNode()//创建文本节点// 查找 getElementsByTagName() getElementsByName() getElementById()appendChild()//添加 removeChild()//移除 replaceChild()//替换 insertBefore()//插入

事件代理、事件机制
  • 事件代理:
    • 也称事件委托,利用dom元素的事件冒泡,把子元素事件委托给父元素进行监听和触发,从而减少事件注册、节省内存占用、提高性能
  • 事件机制:
    • 捕获 -> 目标阶段 -> 冒泡
    补:dom事件流
创建对象、判断数组、创建函数
  • 创建对象方法:
    • 字面量创建 let obj = {a:1,..}
    • Object构造函数 let obj = new Object({a:1,..})
    • create函数 let obj = Object.create(proto,[propertiesObject])
    • 自定义函数 let obj = new Fn()
  • 判断是否数组:
    • Array.isArray([])
    • [] instanceof Array
    • [].constructor == Array
    注:不能用typeof [] -> object,因为typeof是用来判断基本数据类型的,判断引用数据类型都只会返回object
  • 创建函数:
    • 函数声明法,function fn(){} 会函数提升
    • 函数表达式法,var fn = function Fn(){} // 这里只是fn变量的var提升,fn变量还未赋值,因此无法在之前调用fn/Fn
    • 构造函数法,var fn = new Function()
    • 箭头函数
else
  • 作用域链
  • 预编译阶段
  • 手写一个promise
  • 构造函数、普通函数、回调函数、自调用函数、递归函数分别是什么、之间有和区别
  • 说出数组[]的原型链,[] - Array.prototype - Object.prototype - null
  • 验证用户月份输入是否正确,不要简单粗暴的if(month<=12),用正则匹配去做
  • 为什么说js是单线程的
  • array/set的互转、array/map互转、object/set的互转、object/map的互转
  • promise网络请求超时时优化处理:(采用promise.race())
    const promise = new Promise( (res,rej)=>{ setTimeout(res, 5000, "hello world"); }); const abort = new Promise( (res,rej)=> { rej({ name: "abort", message: "the promise is aborted", aborted: true }); }); Promise.race([promise, abort]) .then(console.log) .catch(e => { console.log(e); });

  • 进制缩写:
    【Javascript、ES6面试题总结】0b 二进制,0x 16进制

    推荐阅读