17.谈谈对promise理解。
1)promise是为解决异步处理回调金字塔问题而产生的
2)两个特点:Promise状态不受外界影响,(pending,fulfilled,rejuected);Promise状态一旦改变,就不会再变
3)三个缺点:
1.无法取消Promise,一旦新建它就会立即执行,无法中途取消;
2.如果不设置回调函数,Promise内部抛出的错误,不会反映到外部
3.当处于pending状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成
4)Promise在哪存放成功回调序列和失败回调序列?
1.onResolvedCallbacks 成功后要执行的回调序列 是一个数组
2.onRejectedCallbacks 失败后要执行的回调序列 是一个数组
5)手写promise
class Promise{
constructor(executor){
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
// 成功回调函数数组和失败回调函数数组
this.onResolveCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value => {
if(this.state === 'pending'){
this.state = 'fulfilled';
this.value = value;
// 成功的话遍历成功回调函数数组然后执行这些函数
this.onResolvedCallbacks.forEach(fn=>fn());
}
};
let reject = reason => {
if(this.state === 'pending'){
this.state = 'rejected';
this.reason = reason;
// 失败的话遍历失败回调函数数组然后执行这些函数
this.onRejectedCallbacks.forEach(fn=>fn());
}
};
try{
executor(resolve,reject)
}catch(err){
reject(err);
}
}
}
Promise.prototype.then=function(onFulfilled, onRejected){
if(this.state === 'fulfilled'){
onFulfilled(this.value);
}
if(this.state === 'rejected'){
onRejected(this.reason);
}
// 当状态为等待态时,我们要将成功/失败的回调函数加入到对应的数组中
if(this.state === 'pending'){
// onFulfilled传入到成功数组
this.onResolvedCallbacks.push(()=>{
onFulfilled(this.value);
})
// onRejected传入到失败数组
this.onRejectedCallbacks.push(()=>{
onRejeced(this.reason);
})
}
}
6)手写Promise.all方法(返回一个promise对象)
Promise2.all = function(arrP) {
let list = []
let len = 0
return new Promise2((resolve, reject) => {
for(let i = 0; i < arrP.length; i++) {
arrP[i].then( data=> {
list[i] = data
len++
if(len === arrP.length) resolve(list)
}, error => {
reject(error)
})
}
})
}
7)手写PromiseRace
// Promise.race
Promise.race=function(promiseArr){
function isPromise(val){
if(!val && typeof val==='object'|| typeof val==='function'){
if(typeof val.then==='function'){
return true
}
} else{
return false
}
}
return new Promise((resolve,reject)=>{
for(let i=0;i<promiseArr.length;i++){
let value=promiseArr[i];
if(value&&isPromise(value)){
value.then(v=>resolve(v)).catch(e=>reject(e))
}else{
resolve(value)
}
}
})
}
18.Typescript中type和interface的区别是什么?
1)接口是通过继承的方式来扩展,类型别名是通过 & 来扩展。
2)接口可以自动合并,而类型别名不行
19.柯里化是什么?有什么用?怎么实现?
1)是什么:把一个多参数的函数转化为单参数函数的方法。
2)作用:
1.惰性求值 (Lazy Evaluation):柯里化收的函数是分步执行的,第一次调用返回的是一个函数,第二次调用的时候才会进行计算。起到延时计算的作用,通过延时计算求值,称之为惰性求值。
function sum(a){
return function(b){
return a+b
}
}
2.动态生成函数
function power(n){
return function (number){
let result = 1;
for(let i = 0; i < n; ++i){
result *= number;
}
return result;
}
}
20.CmmonJS和ESM区别?
1)CommonJs: module.exports= {} 导出; require导入
1.CommonJs可以动态加载语句,代码发生在运行时
2.CommonJs混合导出如果使用exports导出单个值之后,就不能在导出一个对象值,3.CommonJs导出值是拷贝,可以修改导出的值,这在代码出错时,不好排查引起变量污染
2)Es Module: export default导出,import导入
1.Es Module是静态的,不可以动态加载语句,只能声明在该文件的最顶部,代码发生在编译时
2.Es Module混合导出,单个导出,默认导出,完全互不影响
3.Es Module导出和引用值之前都存在映射关系,并且值都是可读的,不能修改
20.js垃圾回收机制。
js内存分为栈内存和堆内存,栈JavaScript用于存储静态数据,静态数据是引擎在编译时知道大小的数据。这包括指向对象和函数的引用和原始值(strings, numbers, booleans, undefined 和 null);由于JS引擎知道大小不会变,所以它们将为每个值分配固定大小的内存。堆是用于存储JavaScript对象和函数。与栈不同,JS引擎不会为这些对象分配固定大小的内存空间。栈包含对堆中对象数据的引用。
栈垃圾回收的方式非常简便,当一个函数执行结束之后,JavaScript 引擎销毁该函数保存在栈中的执行上下文。当函数直接结束,栈空间处理完成了,但是堆空间的数据还是存储在堆空间中,需要垃圾回收器将堆空间中的垃圾数据回收。在 V8 中会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。 新生区中使用Scavenge算法,老生区中使用标记-清除算法和标记-整理算法。
Scavenge算法: 1. 标记:对对象区域中的垃圾进行标记 2. 清除垃圾数据和整理碎片化内存:副垃圾回收器会把这些存活的对象复制到空闲区域中,并且有序的排列起来,复制后空闲区域就没有内存碎片了 3. 角色翻转:完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域
标记-清除算法: 1. 标记:标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。 2. 清除:将垃圾数据进行清除。 3. 产生内存碎片:对一块内存多次执行标记 - 清除算法后,会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存。
标记-整理算法 1. 标记:和标记 - 清除的标记过程一样,从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素标记为活动对象。 2. 整理:让所有存活的对象都向内存的一端移动 3. 清除:清理掉端边界以外的
21.实现一个发布订阅。
// 定义事件中心类
class MyEvent {
handlers = {} // 存放事件 map,发布者,存放订阅者
// 绑定事件
$on(type, fn) {
if (!Reflect.has(this.handlers, type)) { // 如果没有定义过该事件,初始化该订阅者列表,Reflect.has检查一个对象是否含有某个属性
this.handlers[type] = []
}
this.handlers[type].push(fn) // 存放订阅的消息
}
// 触发事件
$emit(type, ...params) {
if (!Reflect.has(this.handlers, type)) { // 如果没有该事件,抛出错误
throw new Error(`未注册该事件${type}`)
}
this.handlers[type].forEach((fn) => { // 循环事件列表,执行每一个事件,相当于向订阅者发送消息
fn(...params)
})
}
// 取消订阅
$remove(type, fn) {
if (!Reflect.has(this.handlers, type)) {
throw new Error(`无效事件${type}`)
}
if (!fn) { // 如果没有传入方法,表示需要将该该类型的所有消息取消订阅
return Reflect.deleteProperty(this.handlers, type)
} else {
const inx = this.handlers[type].findIndex((handler) => handler === fn)
if (inx === -1) { // 如果该事件不在事件列表中,则抛出错误
throw new Error('无效事件')
}
this.handlers[type].splice(inx, 1) // 从事件列表中删除该事件
if (!this.handlers[type].length) { // 如果该类事件列表中没有事件了,则删除该类事件
return Reflect.deleteProperty(this.handlers, type)
}
}
}
}
22. EventLoop 事件循环
JS是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。
宏任务:setTimeout setInterval I/O UI渲染 setimmediate
微任务:process.nextick promise MutaionObserver
process.nextick会再下次事件循环的开始前被调用;而setTimeout 和 setInterval是在下一次事件循环队列中
23.浏览器进程
GUI线程就是渲染页面的,他解析HTML和CSS,然后将他们构建成DOM树和渲染树就是这个线程负责的。
JS引擎线程就是负责执行JS的主线程,前面说的"JS是单线程的"就是指的这个线程。的Chrome V8引擎就是在这个线程运行的。需要注意的是,这个线程跟GUI线程是互斥的。
23.如何判断元素是否在视口
1) offsetTop, scrollTop
offsetTop当前元素顶端距离父元素顶端距离,、scrollTop当前元素顶端距离窗口顶端距离 el.offsetTop - e.scrollTop <= viewPortHeight
2)getBoundingClientRect()
bottom: height: left: right: top: width:
top 大于等于 0 left 大于等于 0
bottom 小于等于视窗高度 right 小于等于视窗宽度
3)IntersectionObserver
是以new的形式声明一个对象,接收两个参数callback和options
callback是添加监听后,当监听目标发生滚动变化时触发的回调函数。接收一个参数entries,即IntersectionObserverEntry实例
options是一个对象:配置 root rootMargin threshold
24.for 0f 和for in
1)for..of适用遍历数/数组对象/字符串/map/set等拥有迭代器对象的集合.但是不能遍历对象,因为没有迭代器对象.与forEach()不同的是,它可以正确响应break、continue和return语句 ,for-of循环不支持普通对象
2)for-in可以迭代一个对象的属性,使用for in会遍历数组所有的可枚举属性,包括原型,
25.map,weakmap,set,weakset
介绍下 Set、Map、WeakSet 和 WeakMap - 掘金 (juejin.cn)
map,weakmap,set,weakset ,ES6 新增的一种新的数据结构,Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构
1)Set类似于数组,但成员是唯一且无序的,没有重复的值。常用于数组去重
let set1 = new Set([1, 2, 3])
let set2 = new Set([4, 3, 2])
交集(Union):let intersect = new Set([...set1].filter(value => set2.has(value)))
并集(Union):let union = new Set([...set1, ...set2])
差集(Difference):let difference = new Set([...set1].filter(value => !set2.has(value)))
操作方法
add(value):新增,相当于 array里的push
delete(value):存在即删除集合中value
has(value):判断集合中是否存在 value
clear():清空集合
遍历方法(遍历顺序为插入顺序)
keys():返回一个包含集合中所有键的迭代器
values():返回一个包含集合中所有值得迭代器
entries():返回一个包含Set对象中所有元素得键值对迭代器
forEach(callbackFn, thisArg):用于对集合成员执行callbackFn操作,如果提供了 thisArg 参数,回调中的this会是这个参数,没有返回值
2)WeakSet 结构与 Set 类似,也是不重复的值的集合。 WeakSet 只能储存对象引用,不能存放值。值都是被弱引用的,即垃圾回收机制不考虑 WeakSet 对该对象的应用,如果没有其他的变量或属性引用这个对象值,则这个对象将会被垃圾回收掉(不考虑该对象还存在于 WeakSet 中),所以,WeakSet 对象里有多少个成员元素,取决于垃圾回收机制有没有运行,运行前后成员个数可能不一致,ES6规定 WeakSet 对象是无法被遍历的
3)JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。ES6 提供了 Map 数据结构,类似于对象,也是键值对的集合,但是 “键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 的键实际上是跟内存地址绑定的
4)WeakMap结构与Map结构类似,也是用于生成键值对的集合,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名,WeakMap的键名所指向的对象,不计入垃圾回收机制。,一是没有遍历操作(即没有keys()、values()和entries()方法),也没有size属性。WeakMap只有四个方法可用:get()、set()、has()、delete()。
26.Gnerators
ES6 新增了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,从而可以改变执行流程,或者可以暂停或者延迟执行。有人说他是懒惰的迭代器。使用 Generator 函数和一般函数一样,是在函数名后面加上 (),但是 Generator 函数不会像普通函数那样马上执行,而是会返回一个指针,这个指针指向对象的内部状态,所以要调用遍历器对象 Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。为不具备 Iterator 接口的对象提供遍历方法。
27.async和await
Promise,用then链来解决多层回调问题,但是来连续调用then,会使得代码很冗长,代码的可读性不好,所以在ES7中就提出了async和await来解决这一问题,他其实就是promise的语法糖。async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其结果返回出来。
28.事件冒泡
DOM事件流(event flow )存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
事件捕获(event capturing):当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播。即点击了子元素,如果父元素对应的事件的话,会先触发父元素绑定的事件。
事件冒泡(dubbed bubbling):鼠标点击或者触发dom事件时,浏览器会从当前节点是由内到外进行事件传播,直到根节点。
29.事件委托
事件委托就是把原本需要绑定在子元素上的事件(onclick、onkeydown 等)委托给它的父元素,让父元素来监听子元素的冒泡事件,并在子元素发生事件冒泡时找到这个子元素。
30.JS类型转换
显示转换
Number()pasreInt() pasreFloat()
String() String() toString()
Boolean()
隐式转换
31.01.+02为什么不等于0.3
因为浮点数运算的精度问题。在计算机运行过程中,需要将数据转化成二进制,然后再进行计算。小数转化为IEEE754的过程:先转化为二进制的形式,然后再用科学计数法表示,接着把通过科学计数法表示的二进制数用IEEE754标准表示。js中的Number类型遵循IEEE754标准,在IEEE754标准的64位浮点数相加,因为浮点数自身小数位数的限制而截断的二进制在转化为十进制,就变成0.30000000000000004,所以在计算时会产生误差。
32.{},new object ,object.creat的区别
字面量和new关键字创建的对象是Object的实例,原型指向Object.prototype,继承内置对象Object
Object.create(arg, pro)创建的对象的原型取决于arg,arg为null,新对象是空对象,没有原型,不继承任何对象;arg为指定对象,新对象的原型指向指定对象,继承指定对象