如果需要markdown文件,可在https://github.com/csdd-21/FE...下载,源仓欢迎star、交流、指正..
个人感觉这篇文章的面试题覆盖率应该达到了90%以上,所有的文字不是东拼西凑出来的,而是自己花时间去掌握了之后加上自己理解总结出来的。如果你看到了它,那我建议你只是看看,然后去实践,敲一敲、测一测就会发现不过如此
如果有哪里不对的地方,非常感谢能够留言告诉我~
掌握原理
原型对象、原型链
- 原型对象
- 原型对象即
prototype
,函数才有原型对象,实例不会有原型对象,原型对象prototype
可以理解为存放所有实例公用的属性或方法
- 原型对象即
- 原型链
- JavaScript中每个对象都有私有属性
__proto__
指向它的构造函数的原型对象prototype
,该原型对象也有自己的原型对象(也是通过__proto__
访问),层层向上直到null
,null
没有原型并作为这个原型链中的最后一个环节。 JavaScript中所有对象都是位于原型链顶端Object的实例
instance.__proto__ === instance.constructor.prototype
Function.prototype.__proto__ === Object.prototype
- 对象中都有私有属性
__proto__
指向它的构造函数的原型对象prototype
,函数对象除了有__proto__
属性之外,还有prototype
属性,当函数对象作为构造函数创建实例时,该prototype
属性将被作为实例对象的__proto__
指向的值 - 当一个对象调用属性或方法但在自身不存在时,会通过私有属性
__proto__
指向原型对象prototype
上去找,如果没找到,就去原型的原型找,依次类推,直到找到该属性或方法,找不到返回undefined
,从而形成原型链
- JavaScript中每个对象都有私有属性
new
关键字会进行如下的操作:
- 创建一个空对象
{}
- 为该对象添加
__proto__
属性并链接至它的构造函数的原型对象prototype
- 将
this
指向该新对象并执行构造函数 - 隐式
return this
(如果显式返回非基本数据类型会覆盖掉)
注:new、Object.create
区别:new
生成的对象会自动进行原型链链接,Object.create
接收第一个参数作为新对象__proto__
指向的原型对象
- 创建一个空对象
- 原型链继承 — 重写prototype
- 实现:
- 重写Son.prototype为一个Father实例
Son.prototype = new Father()
Son.prototype.constructor = Son
- 重写Son.prototype为一个Father实例
- 特性:
- Son原型链上的属性值修改都会在Son实例之间互相影响
- Son的prototype被重写,因此还需要重新为Son指定构造函数constructor
- 在Son.prototype重写之前的原型链上的属性和方法都会丢失
- 实现:
- 使用es6的Object.setPrototypeOf()来为Son设置原型对象
Object.setPrototypeOf(Son.prototype,new Father())
- 使用es6的Object.setPrototypeOf()来为Son设置原型对象
- 特性:
- Son原型链上的属性值修改都会在Son实例之间互相影响
- 与之前重写Son.prototype不同,Son原本原型链上的constructor、属性、方法都不会丢失
- 实现:
- 构造函数继承
- 实现:
- 在Son的构造函数内部通过call、apply来执行Father的构造函数,这样后Father实例属性就变成了Son实例自身的属性,但是只拿到了Father的实例属性,拿不到Father原型链
Father.call(this,arg1,arg2,..)
或Father.apply(this,[argsArray])
- 在Son的构造函数内部通过call、apply来执行Father的构造函数,这样后Father实例属性就变成了Son实例自身的属性,但是只拿到了Father的实例属性,拿不到Father原型链
- 特性:
- 解决了原型链继承的"Son原型链上的属性值修改都会在Son实例之间互相影响"问题
- 拿不到Father原型上的属性或方法
- 实现:
- 组合继承
- 实现:
- 原型链+构造函数的组合继承
Son.prototype = new Father()
Father.call(this,arg1,arg2,..)
或Father.apply(this,[argsArray])
- 原型链+构造函数的组合继承
- 特性:
- Son的实例和原型链上都有Father的属性
- 实现:
- 寄生组合继承 — 重写prototype
- 实现:
- 原型链+构造函数的组合继承
Father.call(this)
Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son
- 原型链+构造函数的组合继承
- 特性:
- 直接拿Father的原型对象,而不是Father实例
- Son原本原型链上的constructor、属性、方法都丢失
- 实现:
- 原型链+构造函数的组合继承
Father.call(this)
?Object.setPrototypeOf(Son.prototype, Father.prototype)
- 特性:
- 直接拿Father的原型对象,而不是Father实例
- Son原本原型链上的constructor、属性、方法都不会丢失
- 实现:
- 类继承
- 实现:
- 寄生组合继承的语法糖写法,使用extends关键字,且在Son类的构造函数里调用super()
class Son extends Father
super()
- 寄生组合继承的语法糖写法,使用extends关键字,且在Son类的构造函数里调用super()
- 特性:
- extends相当于原型链连接
- super相当于通过call、apply执行父类构造函数,super()必须在使用this前调用
- 实现:
- 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
绑定父作用域的上下文- 如果有
new
关键字,this
指向new
出来的实例 - 可通过
call、apply、bind
改变this
指向(无法改变箭头函数的this
绑定指向,只能改变它的父函数的this
)
- 优先级:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
- new绑定:
this
指向new
出来的实例 - 显式绑定:
call、apply、bind
- 隐式绑定:作为对象的方法调用时,
this
指向该对象
? ( 作为事件监听器addEventListener
时,this
指向触发事件的元素)
- 默认绑定: 作为函数独立调用,严格模式下
this
指向undefined
,非严格模式下指向window
- new绑定:
- 隐式丢失:
- 函数丢失隐式绑定的对象,从而应用默认绑定(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
- 定义:
Promise
构造函数,可以用new Promise()
实例化一个promise对象用来处理异步操作,Promise
接受一个函数参数,该函数参数里又有两个参数分别是resolve
和reject
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:
初始状态,非fulfilled
或rejected
fulfilled:
操作成功完成rejected:
操作失败
- 抛出错误方式:
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()
,无论fulfilled
或rejected
都会被调用,也不接收参数
- 构造函数方法:
.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
可用break
、continue
和return
来跳出循环,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()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性
- 定义:
- 同步、异步任务:在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
,依次循环
- 执行script中的所有同步代码/同步任务,直接进入主执行栈中立即执行,若遇到微任务放到微任务队列,若遇到宏任务放到宏任务队列(若是
- 函数式编程:
- 每个函数只做一件事情、只接受一个参数、只返回一个结果,不会污染其它、不会有其它副作用
- 好处是:逻辑清晰易于调试,坏处:复杂逻辑的话会导致函数嵌套过深反而不好了
解决嵌套过深,可以用链式调用,需要函数内部都返回this
- 响应式编程:待补充
基本结构 对象
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 |
pop 、shift |
删除的元素 |
push 、unshift |
新长度(可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里的值可以是数值、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 -> 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..in
或Object.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
WeakSet
的成员只能是对象,而不能基本数据类型WeakSet
中的对象都是弱引用,即垃圾回收机制不考虑WeakSet
对该对象的引用(弱引用随时会消失,ES6规定WeakSet
不可遍历)- 只有
add、delete、has
方法,不可使用迭代方法 - WeakSet和Set:
- 与
Set
相比,WeakSet
只能是对象的集合,而不能是任何类型的任意值 WeakSet
持弱引用:集合中对象的引用为弱引用。 如果没有其他的对WeakSet
中对象的引用,那么这些对象会被当成垃圾回收掉。 这也意味着WeakSet
中没有存储当前对象的列表。 正因为这样,WeakSet
是不可枚举的
- 与
- WeakMap
- 对象是一组键值对的集合,其中的键是弱引用的。其键必须是对象(不能是原始数据类型),而值可以是任意的
- 相比之下,原生的
WeakMap
持有的是每个键对象的“弱引用”,这意味着在没有其他引用存在时垃圾回收能正确进行。原生WeakMap
的结构是特殊且有效的,其用于映射的key只有在其没有被回收时才是有效的 WeakMap
是弱引用,因此很适合用来跟踪对象引用,如vue3就是使用WeakMap来确定Effect的
Object.defineProperty
- 定义:
- 数据劫持,直接在一个对象上定义一个新属性,或修改一个现有属性,并返回此对象,利用
getter、setter
函数来控制对象的访问
- 数据劫持,直接在一个对象上定义一个新属性,或修改一个现有属性,并返回此对象,利用
- 特点:
- 无法检测数组元素变化,需要对数组方法进行重写,无法检测数组长度修改
- 无法检测到对象属性的添加或删除
- 必须遍历对象的每个属性,为每个属性都进行
get、set
拦截 - 必须深层遍历嵌套的对象
- 定义:
Proxy
- 定义:
- 用于创建一个对象的代理,不在局限某个属性,而是直接对整个对象进行代理,目标对象可以是数组、对象、函数、代理..等任意类型,Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写
- 特点:
- 不需要对数组的方法进行重载,支持13种拦截操作
- 可以检测到对象属性的添加或删除
- 针对整个对象,而不是对象的某个属性(浅层,仍需递归)
- 仍需要将嵌套对象进行遍历为响应式
- 参数:
target
:源对象handler
:代理操作/拦截器/拦截函数,对proxy
对象进行拦截和操作
- handle:(Proxy支持的拦截操作一览,一共13种)
- get(target, propKey, receiver)
拦截对象属性的读取,比如proxy.foo
和proxy['foo']
。
- set(target, propKey, value, receiver)
拦截对象属性的设置,比如proxy.foo = v
或proxy['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)
。
- get(target, propKey, receiver)
- 定义:
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(){})
,匹配成功返回首次匹配位置,失败返回-1str.match(reg, function(){})
,匹配成功返回匹配结果数组,失败返回nullstr.replace(reg, function(){})
,匹配后的新字符串str.split()
- reg方法:
- 基础
- 匹配位置:
^
表示匹配字符串的开始位置(例外:用在中括号中[]
时,表示为取反,即不匹配括号中字符串)$
表示匹配字符串的结束位置
- 匹配次数:
?
表示匹配零次或一次+
表示匹配一次到多次 (至少有一次)*
表示匹配零次到多次
- 括号:
()
小括号表示匹配括号中全部字符[]
中括号表示匹配括号中任意一个字符,描述范围,如[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
promise
、async await
for..of
、迭代器和生成器...
展开运算符- 解构赋值(数组解构时有序、对象无序)
- 默认参数、剩余参数、
arguments
参数 - 箭头函数
- sessionStorage、localStorage
attribute
是html
标签自定义的属性property
就是dom元素在js中作为对象拥有的属性
// 创建
createDocumentFragment()//创建DOM片段
createElement()//创建具体的元素
createTextNode()//创建文本节点// 查找
getElementsByTagName()
getElementsByName()
getElementById()appendChild()//添加
removeChild()//移除
replaceChild()//替换
insertBefore()//插入
事件代理、事件机制
- 事件代理:
- 也称事件委托,利用
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()
- 箭头函数
- 函数声明法,
- 作用域链
- 预编译阶段
- 手写一个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进制
推荐阅读
- 操作系统|[译]从内部了解现代浏览器(1)
- web网页模板|如此优秀的JS轮播图,写完老师都沉默了
- JavaScript|vue 基于axios封装request接口请求——request.js文件
- vue.js|vue中使用axios封装成request使用
- JavaScript|JavaScript: BOM对象 和 DOM 对象的增删改查
- JavaScript|JavaScript — 初识数组、数组字面量和方法、forEach、数组的遍历
- JavaScript|JavaScript — call()和apply()、Date对象、Math、包装类、字符串的方法
- JavaScript|JavaScript之DOM增删改查(重点)
- javascript|vue使用js-xlsx导出excel,可修改格子样式,例如背景颜色、字体大小、列宽等
- javascript|javascript中的数据类型转换