进阶前端【每日一题】(100题)

3,017 阅读43分钟

Hello,大家好,我是disguiseFish,每日一题学习有一段时间啦~收获良多,每天会做些题来储备自己的知识量,在这里把它们分享记录下来,我们一起学习进步吧! 这是一个日更帖哟!(工作日更新)

2022.03.03

100.大文件上传“秒传”怎么实现的?

所谓的"秒传"其实是同一个文件防止重复上传,核心逻辑是服务端会对文件做md5校验。判断hash是否上传过,有且完整,秒传,有但不完整,断点,没有切片上传

大文件上传实现“秒传”实际上是对文件内容生成hash,在切片并发上传之前将这个文件hash请求到服务端,服务端判断这个hash已经存在,则说明这个文件已经被上传到服务侧了,即使文件名不同,根据其内容生成的hash是准确的,这时候告诉前端无须上传(服务端已经有这个文件了),即实现了秒传。

过程是:

1)前端计算好文件md5,并将md5赋值给文件唯一标识。

2)前端向后端发送一个get请求,携带md5值,询问后端是否该秒传文件。

3)验证md5,其实就是查数据库,如果有,则返回秒传标识,如果没有,则按正常上传流程继续上传文件。

4)上传完成后,将该文件md5值记录到数据表中,以便下次验证md5。

99.vue组件继承怎么实现的?

vue.extend , extends

98.提升首屏加载速度的方案有哪些?从前端资源、ssr两个角度分别说说

静态资源、第三方包可采用cdn,prefetch preload,tree sharking ,DllPugin,SplitChunksPlugins, polyfill,gzip,骨架屏, 减少dom重绘回流, CDN, minimize, preload 路由图片懒加载,组件抽离,服务端渲染所有数据请求和 html内容已在服务端处理完成,浏览器收到的是完整的 html 内容,首页加载更快。

SPA模式下的SSR的核心是同构:路由同构,预取同构,渲染同构。

像动态页面缺点其实就是你首屏还是需要去做一次异步请求拉一下数据再去渲染就会产生一个页面抖动

ssr处理其实就是在我们建立完TCP连接发起http请求之后网关层面做一些转发的时候我们可以在这时候由服务器去拉取数据,返回已经有数据的html页面。由于服务器之间的请求更加稳定高效所以效果其实是很好的,但是有个问题就是服务器去拉数据的话还是有可能失败的超时的,所以前端还是需要做一个补偿,例如onload后再去异步请求数据。还有就是这个服务端的拉取数据的方式利用redis缓存的话平均耗时要比http请求快10倍左右

2022.03.02

97. 垃圾回收的方式

标记清除(涵盖标记整理)引用计数 并行回收 增量标记(三色标记法 写屏障法)➕惰性清理

实际上是老生代主要在使用的标记清除, 引用计数是很少使用的,标记清除原理就是比如所有的对象标记0,
然后去查哪些对象被引用了就标记1,一轮下来谁没标记1谁就是没有被引用的直接垃圾回收,
但是这样会有个问题就是回收完了之后内存空间是零零散散的,所以有了标记整理,
就是把没被清除的对象往内存空间的一侧挪,相当于排列起来,这样另一侧就有大块的内存空间 方便后续存放,

新生代和老生代的区别在于垃圾回收的频率,老生代放的就是大、旧的对象 新生代就是 新的、小的对象,
有个地方要注意就是 
如果新的对象进入内存空间发现新生代的内存空间不够放(记得好像是对象的大小超过剩余空间的百分之20吧不太记得了) 是直接进入老生代的,
然后标记清除呢实际上就是 停止代码运行 然后标记+清除 可能需要花500ms假设 
那这个时间js逻辑代码是停止的,这个行为叫做全停顿;
所以新生代高频率回收假设用了标记清除的话 就会一直全停顿,所以就有了增量标记,增量标记很好理解 其实就是 标记一下跑一下js代码继续标记跑一下代码再标记,标记到没有了之后就回收,各大浏览器回收机制应该不太一样,

标记清除可能需要500ms,那实际上用了增量标记一定是大于等于500ms的,花多一丢丢的标记时间换一个防止全停顿 不亏,
然后增量标记他标记一会跑一会代码再回来标记就会有个问题就是 光靠01是不够的,
因为你没办法知道上次标记到哪个位置,对象引用需要递归去查,所以就有了三色标记法;


白色标识没标记的对象(最后还是白色就被回收)
灰色就是自己被标记了,但是引用我的对象还没标记(这样跑完js代码就从灰色的继续跑就行)
黑色的标识自己和成员变量都被标记了,然后还有个问题就是js代码有可能修改了对象的引用,会导致你标记不准确,所以就有了写屏障,
就是黑色标记的引用了白色标记的对象的时候把白色改成灰色,这样下次跑标记的时候就从灰色这个开始跑,不会漏掉

96. 什么情况会导致内存泄露

未被清除的定时器,不合理的闭包使用,多个全局变量,申明dom的引用但dom已经被制空或被删除

95. 如何减少垃圾回收

引用类型的申明,不使用及时制空,对象为null,数组.length=0 定时器不使用及时清除

2022.03.01

94. 说一下ES6模块与CommonJS模块的异同点。

 区别:
        1. CommonJS是对模块的浅拷贝,ES6 module是对模块的引用。即ES6 module只存只读,不能改变其值,也就是指针指向不能改变,类似const2. 可以对CommonJS重新赋值(改变指针指向)。但是对ES6 module赋值会编译报错。import的接口是read-only(只读状态),不能修改其变量值(不能修改指针指向),但可以改变变量内部指针指向。
        3. CommonJS模块是运行时加载,ES6模块是编译时输出接口。
        4. CommonJS模块的require()是同步加载模块,ES6模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
        5. CommonJS模块顶层的this指向当前模块,而ES6模块顶层this指向undefined。
        
共同点:
        1. 都可以对引入的对象进行赋值,即对对象内部属性的值进行改变。
        

93. 什么是类数组对象?遍历类数组对象有哪些方法?

什么是类数组对象?

一个拥有length属性和若干个索引属性的对象就可以被称为类数组对象。常见的类数组有arguments和DOM方法的返回结果。函数的参数也可以被看作是类数组对象,因为有length属性,代表可接受的参数个数。

遍历类数组的方法有哪些?

 1. 通过call和apply调用数组的forEach方法来遍历。
 2. 也可以通过把类数组转化成数组来遍历。
            a. Array.from(arrayLike)方法。
            b. ES6的扩展运算符(展开运算符)。
            c. 通过call调用数组的slice方法转换。Array.prototype.slice.call(arrayLike);
            d. 通过call调用数组的splice方法转换。Array.prototype.splice.call(arrayLike,0);
            e. 通过apply调用数组的concat方法来转换。Array.prototype.cancat.apply([],arrayLike);

92. 输出题:

function a(xx) {
    this.x = xx;
    return this;
}
var x = a(5);
var y = a(6);
console.log('x:', x);
console.log('y:', y);

正确输出: x: 6, y: Window

解析:

var x = a(5) => 此时x指向window;

var y = a(6) => 此时x等于6,y指向window

2022.02.28

91.对slot的理解和原理,slot的场景有哪些?

slot插槽,是Vue的内容分发机制。在模板中编写显示在子组件的内容。 原理是子组件实例化时,渲染模板时如遇到slot标签。使用父组件传递的$slot的插槽属性进行替换渲染。 一般用于自定义子组件内容的场景。

slot又名插槽,是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。slot又分三类,默认插槽,具名插槽和作用域插槽。

  • 普通插槽:默认插槽,在父组件下渲染;在编译的时候就把组件包括slot的都渲染好了,等我们要渲染的时候,把自身的节点直接赋值在插槽上,普通插槽是个替换的过程,相当于在父组件上渲染的;

  • 具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。

  • 作用域插槽:可以是匿名插槽,也可以是具名插槽,编译的时候并不会渲染slot,会把它解析成函数,当函数渲染的时候才会调用这个函数在子组件下进行渲染,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。

90.大型项目中 vue项目怎么划分结构和划分组件比较合理呢?

我对组件的理解是通过components来使用的,在模板中使用自定义标签的组件。

  • 按照组件分类划分为业务组件和公共组件,业务组件负责处理页面的某一块逻辑。切割页面的代码和逻辑,方便维护管理。
  • 公共组件类似于通用的头部/底部信息,通用的xx选择弹窗,基于UI组件库的二次封装等不常变动多处使用的组件。

2022.02.25

89.Vue组件之间的传值方式有哪些?

props,$emit,$on

$parent,$children

$attrs 和$listeners

provide,inject

$refs

eventBUS

VUEX

88.为什么v-if和v-for不建议用在同一标签?

v-for 优先级高于v-if;解析时先解析 v-for 再解析 v-if。

如果连用的话,会先循环,每次循环往里面加条件判断,如果有一百项,每一项都会判断一下,性能会很差。如果遇到需要同时使用时可以考虑写成计算属性的方式,或者在外层加一个div或者template去判断

87.不需要响应式的数据应该怎么处理?

不需要响应的数据可以不设置劫持,使用object.freeze,让defineReactive的时候不能修改get set进行依赖收集

2023.02.24

86.谈谈requestAnimationFrame和requestIdleCallback

requestAnimationFrame自带函数节流功能,在60帧每秒的浏览器中基本可以保证在 16.6 毫秒内只执行一次(不掉帧的情况下),并且该函数的延时效果是精确的,没有其他定时器时间不准的问题,requestAnimation属于高优先级任务,所以每一帧渲染的都会执行回调

当关注用户体验,不希望因为一些不重要的任务(如统计上报)导致用户感觉到卡顿的话,就应该考虑使用requestIdleCallback。因为requestIdleCallback回调的执行的前提条件是当前浏览器处于空闲状态。requestIdleCallback属于低优先级任务,只有在浏览器处于空间时才会去执行

85.谈谈web worker的使用场景

通常用于计算量大的场景,比如大文件上传计算哈希,视频解码。webWorker里面不可以访问dom,加载的文件只能是来源于网络的文件,和主线程通信要通过postmessage

84.jsonp的原理以及实现

利用script、img等标签能够跨域请求资源的特点实现。实现:前端通过动态创建script标签并在src请求的服务端资源查询字符串中带入与服务端约定好的key和callback函数名,请求回来的js会直接执行传入的callback函数,并在函数参数中携带内容

2022.02.23

83. https的连接过程

juejin.cn/post/703190…

82. 描述一下组件的渲染和更新过程(Vue)

juejin.cn/column/6961…

81. new Vue做了什么事

juejin.cn/column/6961…

2022.02.22

80. 说一说Typescript中Omit,Partial,泛型

  • Partial 将泛型 T 中的所有属性转化为可选属性
  • Omit 从泛型 T 中提取出不在泛型 K 中的属性类型,并组成一个新的对象类型

79. 写过哪些自定义hook(开放题)

78. 说一说react的时间切片

2022.02.21

77. 事件循环题

image.png

1.setImmediate在同一个异步队列里优先级永远比setTimeout高

2.nextTick在同一个任务队列里优先级永远比promise高

76. 事件循环执行async await和promise怎么转换

上边红色框async await 等价于 下边红框new Promise

image.png

75.promise.all和async...await的区别

promise.all 是同时执行多个promise,当所有的promise resolve后返回一个包含所有结果的数组; Async ..await 是es7提供的用来处理异步的语法,它的特点就是用形似同步的写法书写异步,使代码更加简洁

74.SSR的原理

传统的SPA是服务端返回客户端(浏览器)空的html,由js动态生成页面,而SSR是提前在服务端将html生成好,直接返回给客户端,好处就是降低白屏时间。 SSR原理: SSR会将页面通过服务器生成html字符串,然后发送到浏览器,服务器渲染能够更有利于SEO和首屏渲染时间,但是会给服务器带来压力

2022.02.18

73.虚拟dom为什么可以保证性能下限?

框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作(分层设计),它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限。

虚拟dom的最大优势就不是性能,是不用手动操作dom提升开发效率和跨平台操作,比如服务器渲染,weex开发等等

因为没有频繁操作DOM。虚拟DOM将真实DOM抽象为JS对象,页面每次变化,生产新的DOM树,用diff比对,来避免频繁操作dom带来的性能损耗,然后渲染。

如果只操作一个两个dom,那肯定是原生快

缺点:

  1. 无法进行极致优化:虽然虚拟DOM + 合理的优化, 足以应对大部分应用的性能需要,但在一些性能要求极高的应用中虚拟DOM无法进行针对性的极致优化。

  2. 首次渲染大量DOM时,由于多了一层DOM计算,会比innerHTML插入慢。

72.子类定位absolute如果父类没有relative,那么它会怎样?

absolute是绝对定位,不占据空间会脱离文档流;

父类没有relative的话,会相对于 static 定位以外的父元素进行定位,找不到就相对根节点定位

当父级使用transform后,fixed和absolute会相对具有transform的父元素定位;而且具有transform的父元素占位置,相对于fixed和absolute会具有类似relative的特性,不过top/left等属性无效;

71.样式前缀-webkit 只能适配部分浏览器,如果想适配全部的呢?

webpack插件:可以通过postcss-loader的autoprefixer自动加前缀

70.如何用webpack将样式全局引入

文件引入全局样式或者使用webpack的插件:sass-resources-loader或者style-resources-loader等等

2022.02.17

69.Object.freeze和object.seal的异同

Object.freezeobject.seal
冻结一个对象。封闭一个对象
不能添加新的属性,不能删除已有属性,不能添加新属性,不能删除已有属性
不能修改该对象已有属性的可枚举性、可配置性、可写性不能修改该对象已有属性的可枚举性、可配置性、可写性
不能修改已有属性的值已有属性的值以然可以修改
冻结一个对象后该对象的原型也不能被修改

68.eval()的作用

是一个全局函数,能把对应的字符串解析成js代码并运行。

例如:

eval("var x = 1")  
//相当于var x = 1;

缺点:1.声明不会被变量提升;2.耗性能,会执行两次,一次解析成js语句,一次执行。

67.发布/订阅模式和观察者模式的区别

在设计模式中,他俩都被称为发布订阅模式,但两者有一些细微的差别:

观察者模式:两者关联比较强,发布者能直接和订阅者进行交互,比如vue中响应式原理的实现依赖的是Watcher和Dep的相互记忆,两者紧密且有直接联系,是典型的观察者模式;

发布订阅模式:发布者和订阅者都独立性较强,两者互不直接干扰,由第三方来完成实际的通信操作。比如eventBus则是发布订阅的标准示例,onon和emit都是依赖事件总线去实现的,两者互不直接干扰

2022.02.16

66.Vue3.0有什么更新

  • 响应式原理的改变 Vue3.x 使用 Proxy 取代 Vue2.x 版本的 Object.defineProperty
  • 支持Composition API:minx的一些缺陷都解决了,代码不够有条理性也解决了,组件选项声明方式 Vue3.x 使用 Composition API setup 是 Vue3.x 新增的一个选项, 他是组件内使用 Composition API 的入口。
  • 模板语法变化: slot 具名插槽语法 自定义指令 v-model 升级,template 中可以放多个div
  • 其它方面的更改 Suspense 支持 Fragment(多个根节点)和 Protal(在 dom 其他部分渲染组建内容)组件,针对一些特殊的场景做了处理。 基于 treeshaking 优化,提供了更多的内置功能。Composition API本质上是将 Options API 背后的机制暴露给用户直接使用,这样用户就拥有了更多的灵活性,也使得 Vue3 更适合于 TypeScript 结合。
  • VUE3用TS编写
  • vdom的对比算法更新,只更新了vdom的绑定动态数据部分
  • 生命周期改了

image.png

2022.02.15

65.介绍一下浏览器缓存策略,项目中如何使用

浏览器缓存策略链接:juejin.cn/post/694793…

64.lerna的作用

多个package js项目 包管理, 解决packages之间的依赖关系, 项目中只用来发布

63.webpack代码分割,页面滑动加载js,优化首屏速度

webpack优化首屏速度:blog.csdn.net/qq_42975998…

滑动加载: www.cnblogs.com/Darren_code…

2022.02.14

62.js实现lru cache

class LRUCache {
  constructor(limit) {
    this.limit = limit;
    this.cache = new Map();
  }

  get(key) {
    if (!this.cache.has(key)) return undefined;
    const value = this.cache.get(key);
    this.cache.delete(key);
    this.cache.set(key, value);
    return value;
  }

  put(key, value) {
    if (this.cache.has(key)) this.cache.delete(key);
    else if (this.cache.size >= this.limit) {
      this.cache.delete(this.cache.keys().next().value);
    }
    this.cache.set(key, value);
  }
}

// ["LRUCache","put","put","get","put","get","put","get","get","get"]
// [[2],[1,1],[2,2],[1],[3,3],[2],[4,4],[1],[3],[4]]
const lruCache = new LRUCache(2);
lruCache.put(1, 1);
lruCache.put(2, 2);
const res1 = lruCache.get(1);
lruCache.put(3, 3);
const res2 = lruCache.get(2);
lruCache.put(4, 4);
const res3 = lruCache.get(1);
const res4 = lruCache.get(3);
const res5 = lruCache.get(4);

console.log(res1, res2, res3, res4, res5);
// 1 undefined undefined 3 4

61.什么是类数组?类数组怎么转化为数组?

简单定义: 一个对象有length属性,就是类数组(如document.getElementsByTagName,document.querySelectorAll返回的结果,function中的arguments等等)

转化方式:

(1)Array.from

(2)扩展运算符,但只能作用于iterable对象(有Symbol.iterator)属性值,该方法严格意义上是有问题的 如 [...document.querySelectorAll("div")] 没问题,但 [...{ length: 3 }]会抛出异常

(3)通过 call/apply 改变 this 指向 或者 arguments 来完成转化 如 Array.prototype.slice.call(arrayLike),

Array.prototype.concat.apply([], arrayLike)等等

60.怎么找到并删除项目中没有使用到的package

使用depcheck查找没有使用过的package并从package.json中移除

2022.02.11

59.实现三栏布局

要求:左边宽度50px,中间占1/3,右边占2/3

参考: image.png

58.介绍一下定时构建方面的内容

  1. 基于Jenkins,在项目配置里的构建触发器中配置日程表,日程表使用的是cron语法,Jenkins即可定时化执行构建任务;Jenkins配置定时任务:www.cnblogs.com/zhongyehai/…

  2. 基于后端的定时任务,比如在前端的场景配置中加一个定时构建开关和cron表达式配置,后端在数据库中增加对应的字段,动态执行定时任务;Egg的定时任务:eggjs.org/zh-cn/basic…

  3. gitlab也可以配置定时任务,在CI/CD - Schedules可以给任务设置定时。基本上CI/CD的平台都可以配置定时任务,后端的服务也可以配置定时任务,本质上就是起一个进程来执行定时脚本;

可以做成配置,具体看场景需求,一般来说还是Jenkins比较好用

57.站在前端工程化的角度,对待如单双引号这些规范问题,应该怎么去做

配置eslint文件,和husky:

首先确定好团队使用的是单引号还是双引号还是反引号风格,然后在.eslintrc.js文件中配置对应的规则,同时配置husky命令,限制提交;

如果是旧项目,可以写一个watch脚本,只监听修改文件,以达到增量修改的目的,不用整个项目都修改(工作量过大);

至于用单引号还是双引号,制定规范时可能就会约定这些统一的规则,但是每一点规则至少都要有制定人的理由和思考,要有说服能力

56.npm包如何去管理

可以把package.json,node还有其他你认为的方式说一下 ci/cd吧 只要代码上传到线上分支 就 打包发布 丢链接:www.conardli.top/blog/articl…

2022.02.10

55.如何处理白屏错误页的监控的

  • react:有个ErrorBoundary 错误边界这个东西,封装个 错误边界容器组件 去包裹其他的组件,componentDidCatch API 可以获取error并且处理上报问题。
  • hook就effect去上报
  • window.onerror拉取异常信息;利用埋点进行上报;同时对错误页的pvuv进行监控;还可以利用Sentry+sourcemap直接定位到问题代码

54.讲述一下 setTimeout 及 setInterval 的最小执行时间问题

setInterval 如果设置了固定时间执行,实际在浏览器执行的时候并不一定会按照这个固定的时间执行。假设浏览器当前任务耗时较多 就可能导致setInterval的任务堆积 导致执行时间不准

53.什么是受控组件

受控组件,简单来讲,就是受我们控制的组件,组件的状态全程响应外部数据,因此,受控组件我们一般需要初始状态和一个状态更新事件函数

2022.02.09

52.巩固for...of的代码题

image.png

答案:

image.png

51.组件传递方法的几种方式

注意:这里问的不是传递值的方法~是问传递方法的方法,其实用有的传递值的方法也能拿到要传递的方法

父组件 调用子组件的方法,子组件调用的父组件的方法,兄弟组件调用方法 onon emit

50.手写快排

image.png

49.禁止软键盘弹出的几种方式

document.activeElement.blur()或者用div标签模拟input

2022.02.08

48.谈一下 es6 class 继承,并说明和es5继承有什么不同

es6继承实质上是es5继承的语法糖,并不是类似Java等语言的类继承,本质依旧是原型继承。

不同点:语法上的不同 es6通过extends关键字实现继承 es5通过改变子类的prototype为父类的实例实现

47.实现数组sort方法

Array.prototype.sort方法默认以数组的Unicode编码进行排序,如果指定了compareFunction则按照函数返回结果排序。

数组sort方法可以通过传入的比较函数对数组内元素进行排序

丢链接:blog.csdn.net/sinat_32546…

46.介绍下 webpack 热更新原理,是如何做到在不刷新浏览器的前提下更新页面

丢链接:www.jianshu.com/p/db9c035ea…

2022.02.07

45.说说几种js判断数据类型的常用方法,以及他们是否有缺陷

看看第七题答案

补充缺陷:

  • typeof能判断简单数据类型,且null object array判断出来的结果都是object
  • instanceof无法判断简单数据类型,且object array的原型链上都有Object,所以这两个instanceof Object 都是true;
  • constructor会有构造函数被改变的风险,不一定准确
  • Object.prototype.toString.call准确是准确,但需要对结果进行截取,用起来代码比较长Object.prototype.toString.call.slice(8, -1)

44.说说for...in和for...of的区别

for...infor...of
遍历数组或者对象的属性遍历数组或者具有Symbol.iterator属性的对象
同步遍历异步遍历
会遍历原型链上可读性属性不可遍历对象上的原型链上的属性

2022.01.28

43.说一下设计模式,并举例子说明实际运用,顺便说一下它的实现方式和优缺点

推荐看看修言老师的掘金小册

image.png

2022.01.27

42.介绍一下Webpack的Tapable的事件流机制

丢一个连接:juejin.cn/post/684490…

41.实际项目中写过哪些Loader和Plugin?

丢一个连接:juejin.cn/post/684490…

40.Webpack如何分包和提取公共代码?

丢一个连接:juejin.cn/post/684490… 丢一个连接:www.cnblogs.com/xiaonian8/p…

2022.01.26

39.bind绑定过一次,之后多次bind会改变this指向吗?为什么?

bind多次绑定是无效,只有第一次有效果, 多次绑定bind只是一直在改变this的指向,最终还是变回第一次绑定的this

38.描述下前端模块化机制?

模块化是根据功能或业务将一个大程序拆分成互相依赖的小文件,再用简单的方式拼装起来;前端模块化规范有 AMD、CMD、CommonJS、ES2015 规范,如果没有模块化,则所有script标签必须保证顺序正确,否则会依赖报错,全局变量存在命名冲突,占用内存无法被回收,IIFE/namespace会导致代码可读性低等诸多问题。

37.手写实现下 promise.all 和 promise.race

class Mypromise {
  constructor(fn) {
    // 表示状态
    this.state = "pending";
    // 表示then注册的成功函数
    this.successFun = [];
    // 表示then注册的失败函数
    this.failFun = [];

    let resolve = (val) => {
      // 保持状态改变不可变(resolve和reject只准触发一种)
      if (this.state !== "pending") return;

      // 成功触发时机  改变状态 同时执行在then注册的回调事件
      this.state = "success";
      // 为了保证then事件先注册(主要是考虑在promise里面写同步代码) promise规范 这里为模拟异步
      setTimeout(() => {
        // 执行当前事件里面所有的注册函数
        this.successFun.forEach((item) => item.call(this, val));
      });
    };

    let reject = (err) => {
      if (this.state !== "pending") return;
      // 失败触发时机  改变状态 同时执行在then注册的回调事件
      this.state = "fail";
      // 为了保证then事件先注册(主要是考虑在promise里面写同步代码) promise规范 这里模拟异步
      setTimeout(() => {
        this.failFun.forEach((item) => item.call(this, err));
      });
    };
    // 调用函数
    try {
      fn(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  // 实例方法 then

  then(resolveCallback, rejectCallback) {
    // 判断回调是否是函数
    resolveCallback =
      typeof resolveCallback !== "function" ? (v) => v : resolveCallback;
    rejectCallback =
      typeof rejectCallback !== "function"
        ? (err) => {
            throw err;
          }
        : rejectCallback;
    // 为了保持链式调用  继续返回promise
    return new Mypromise((resolve, reject) => {
      // 将回调注册到successFun事件集合里面去
      this.successFun.push((val) => {
        try {
          //    执行回调函数
          let x = resolveCallback(val);
          //(最难的一点)
          // 如果回调函数结果是普通值 那么就resolve出去给下一个then链式调用  如果是一个promise对象(代表又是一个异步) 那么调用x的then方法 将resolve和reject传进去 等到x内部的异步 执行完毕的时候(状态完成)就会自动执行传入的resolve 这样就控制了链式调用的顺序
          x instanceof Mypromise ? x.then(resolve, reject) : resolve(x);
        } catch (error) {
          reject(error);
        }
      });

      this.failFun.push((val) => {
        try {
          //    执行回调函数
          let x = rejectCallback(val);
          x instanceof Mypromise ? x.then(resolve, reject) : reject(x);
        } catch (error) {
          reject(error);
        }
      });
    });
  }
  //静态方法
  static all(promiseArr) {
    let result = [];
    //声明一个计数器 每一个promise返回就加一
    let count = 0;
    return new Mypromise((resolve, reject) => {
      for (let i = 0; i < promiseArr.length; i++) {
      //这里用 Promise.resolve包装一下 防止不是Promise类型传进来
        Promise.resolve(promiseArr[i]).then(
          (res) => {
            //这里不能直接push数组  因为要控制顺序一一对应(感谢评论区指正)
            result[i] = res;
            count++;
            //只有全部的promise执行成功之后才resolve出去
            if (count === promiseArr.length) {
              resolve(result);
            }
          },
          (err) => {
            reject(err);
          }
        );
      }
    });
  }
  //静态方法
  static race(promiseArr) {
    return new Mypromise((resolve, reject) => {
      for (let i = 0; i < promiseArr.length; i++) {
        Promise.resolve(promiseArr[i]).then(
          (res) => {
            //promise数组只要有任何一个promise 状态变更  就可以返回
            resolve(res);
          },
          (err) => {
            reject(err);
          }
        );
      }
    });
  }
}

// 使用
// let promise1 = new Mypromise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(123);
//   }, 2000);
// });
// let promise2 = new Mypromise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(1234);
//   }, 1000);
// });

// Mypromise.all([promise1,promise2]).then(res=>{
//   console.log(res);
// })

// Mypromise.race([promise1, promise2]).then(res => {
//   console.log(res);
// });

// promise1
//   .then(
//     res => {
//       console.log(res); //过两秒输出123
//       return new Mypromise((resolve, reject) => {
//         setTimeout(() => {
//           resolve("success");
//         }, 1000);
//       });
//     },
//     err => {
//       console.log(err);
//     }
//   )
//   .then(
//     res => {
//       console.log(res); //再过一秒输出success
//     },
//     err => {
//       console.log(err);
//     }
//   );


2022.01.25

36.说说Vue中nextTick的降级策略

采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法

let callbacks = [];
let pending = false;
function flushCallbacks() {
  pending = false; //把标志还原为false
  // 依次执行回调
  for (let i = 0; i < callbacks.length; i++) {
    callbacks[i]();
  }
}
let timerFunc; //定义异步方法  采用优雅降级
if (typeof Promise !== "undefined") {
  // 如果支持promise
  const p = Promise.resolve();
  timerFunc = () => {
    p.then(flushCallbacks);
  };
} else if (typeof MutationObserver !== "undefined") {
  // MutationObserver 主要是监听dom变化 也是一个异步方法
  let counter = 1;
  const observer = new MutationObserver(flushCallbacks);
  const textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {
    characterData: true,
  });
  timerFunc = () => {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
} else if (typeof setImmediate !== "undefined") {
  // 如果前面都不支持 判断setImmediate
  timerFunc = () => {
    setImmediate(flushCallbacks);
  };
} else {
  // 最后降级采用setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0);
  };
}

export function nextTick(cb) {
  // 除了渲染watcher  还有用户自己手动调用的nextTick 一起被收集到数组
  callbacks.push(cb);
  if (!pending) {
    // 如果多次调用nextTick  只会执行一次异步 等异步队列清空之后再把标志变为false
    pending = true;
    timerFunc();
  }
}

所以顺序:Promise,MutationObserver,setImmediate,setTimeout

35.数组拍平有哪些实现方式,能不能控制拍平的层数?

1.递归 2.扩展运算符3.递归加扩展运算符4.递归加reduce 5. toString +split 6.原生的flat(参数为层数) 7.while + some +apply 8.生成器

function flatter(arr) {
  if (!arr.length) return;
  return arr.reduce(
    (pre, cur) =>
      Array.isArray(cur) ? [...pre, ...flatter(cur)] : [...pre, cur], []
  );
}

image.png

34.骨架屏的实现方式

骨架屏的实现方式 1. png 2. 手写代码+样式 3. 利用ui库实现 4. 利用puppeteer拉取页面结构生成 类似饿了么page-skeleton-webpack-plugin

丢一个学习连接:juejin.cn/post/699467…

2022.01.24

33.简单介绍一下requestAnimationFrame

requestAnimationFrame是html5的一种API-请求动画帧,主要是重绘之前调用的回调函数,每个刷新间隔只会执行一次,有效节省CPU开销;优化动画的时候使用,保证浏览器每一帧执行一次我们传入的回调,比较流畅,人眼效果看着就会很好,也不会额外浪费性能;

32.defineProperty有什么缺点?vue3为什么用Proxy,对Proxy了解多少?

defineProperty数据劫持的时候,若是对对象劫持的话需要一个个属性进行劫持,对于深的数据要进行递归劫持,复杂数据的话性能相对较差,defineProperty是可以监听数组元素的变化的,因为数组可能有很多项 如果一个个加上get set的话 性能会很差,所以没用defineProperty对数组进行监听,数组的响应式是通过数组重写的方法实现的

vue3采用proxy直接劫持对象(包括数组),避免了递归带来的性能问题,同时提供了多达13种劫持方法;而Proxy可以直接使用就监听到所有方式的数据变化,性能较好,不用一层层递归

13种劫持方法: 1.get; 2.set image.png

31.什么是bfc?bfc解决了什么问题?

BFC是块格式化上下文,主要是一种独立渲染的区域,既不会影响外部,也不会被外部影响;

可以解决margin重叠问题,浮动问题。

2022.01.21

30.如何优化选择器,提高性能?

选择器层级不能太长,尽量简短

尽量不使用通配符规则,和标签规则

尽量使用继承的属性,而不用重复指定

image.png

29.如何重置元素的属性值到初始值?

场景:给出任何HTML元素,该元素是另一个元素的子元素,并且会自动继承一系列CSS属性:如何将这些属性中的一个(或全部)设置为默认值?

级规范确实引入了initial关键字,但将属性设置为 初始值 会将其重置 为CSS定义的 默认值, 而不是浏览器 定义的默认值

all: initial在元素上指定,它将阻止所有继承并重置所有属性

All:unset
* {属性 值}

这对于不希望继承外部页面样式的页面中包含的“窗口小部件”的根元素很有用。但是请注意,应用于该元素的任何“默认”样式

image.png

28.“DOM 为什么这么慢”以及“如何使 DOM 变快”

dom操作是非常耗费性能的,因为可能会触发重排重绘

减少dom频繁操作的方法:document.createFragment 而virtual dom不一定会变快

2022.01.20

27.现在有多个spa的项目,有angular的,有vue的和react的,如何将他们合并成一个大统一的spa项目

微前端:可以使各个子模块或者子系统隔离,更新一个子模块/子系统的时候不会影响到其他的模块。然后又能实现一个数据共享

用微前端实现:把多个项目整个到一起,共享用户信息和状态,比如这里可以使用iframe

微前端->web component->自定义标签

mp.weixin.qq.com/s/DTnChuMYu…

26.什么时候使用“git rebase”代替“git merge”?

git rebasegit merge
将commit合并到目标分支后以时间线合并分支,按时间排序
好处 合并 不会产生新的记录

25.实现一个 sleep 函数,比如 sleep(1000) 意味着等待1000毫秒,可从 Promise、Generator、Async/Await 等角度实现

//Promise
const sleep = time => {
  return new Promise(resolve => setTimeout(resolve,time))
}
sleep(1000).then(()=>{
  console.log(1)
})



//Generator
function* sleepGenerator(time) {
  yield new Promise(function(resolve,reject){
    setTimeout(resolve,time);
  })
}
sleepGenerator(1000).next().value.then(()=>{console.log(1)})



//async
function sleep(time) {
  return new Promise(resolve => setTimeout(resolve,time))
}
async function output() {
  let out = await sleep(1000);
  console.log(1);
  return out;
}
output();



//ES5
function sleep(callback,time) {
  if(typeof callback === 'function')
    setTimeout(callback,time)
}

function output(){
  console.log(1);
}
sleep(output,1000);

2022.01.19

24.babel的工作原理 preset和plugin的执行顺序;

原理: babel是一种源码到源码的转译器,之所以要转译是因为有些新的API低版本的浏览器不认识。babel的转译分为parse,traverse,generator三步。

  1. parse:首先要将我们写的代码转为机器能够识别的数据结构,也就是AST,抽象语法树;
  2. traverse:转为AST后,就可以通过调用不同的visitor函数来对节点进行增删改等处理;(调用visitor的模式被称为访问者模式)
  3. generator:最后再将处理完成的AST转为源码,同时生成sourcemap。

preset和plugin的执行顺序:

  • 先执行完所有Plugin,再执行Preset。(先plugin后preset)
  • 插件顺序从前往后排序(从前往后)
  • perset顺序是颠倒的(从后往前)

23.keep-alive实现原理

Vue 内置的一个组件,可以实现组件缓存,当组件切换时不会对当前组件进行卸载;

有三个注意的地方:

  1. 有两个用于判断是否缓存的属性:include/exclude

  2. 有两个生命周期判断是否处于活跃状态:activated/deactivated

  3. LRU算法,选择最久未使用的组件予以淘汰

原理:

export default {
  name: "keep-alive",
  abstract: true, //抽象组件

  props: {
    include: patternTypes, //要缓存的组件
    exclude: patternTypes, //要排除的组件
    max: [String, Number], //最大缓存数
  },

  created() {
    this.cache = Object.create(null); //缓存对象  {a:vNode,b:vNode}
    this.keys = []; //缓存组件的key集合 [a,b]
  },

  destroyed() {//销毁的时候做移除操作
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys);
    }
  },

  mounted() {
    //动态监听include  exclude  动态添加删除缓存组件处理
    this.$watch("include", (val) => {
      pruneCache(this, (name) => matches(val, name));
    });
    this.$watch("exclude", (val) => {
      pruneCache(this, (name) => !matches(val, name));
    });
  },

  render() {
    const slot = this.$slots.default; //获取包裹的插槽默认值
    const vnode: VNode = getFirstComponentChild(slot); //获取第一个子组件,只缓存第一个
    const componentOptions: ?VNodeComponentOptions =
      vnode && vnode.componentOptions;
    if (componentOptions) {
      // check pattern
      const name: ?string = getComponentName(componentOptions);
      const { include, exclude } = this;
      // 不走缓存
      if (
        // not included  不包含
        (include && (!name || !matches(include, name))) ||
        // excluded  排除里面
        (exclude && name && matches(exclude, name))
      ) {
        //不包含在缓存的列表里的话就直接返回虚拟节点
        return vnode;
      }

      const { cache, keys } = this;
      // 如果组件没key,会创建一个key(组件的标签+key+cid)
      const key: ?string =
        vnode.key == null
          ? // same constructor may get registered as different local components
            // so cid alone is not enough (#3269)
            componentOptions.Ctor.cid +
            (componentOptions.tag ? `::${componentOptions.tag}` : "")
          : vnode.key;
      if (cache[key]) {
        //通过key 找到缓存 获取实例
        vnode.componentInstance = cache[key].componentInstance;
        // make current key freshest
        remove(keys, key); //通过LRU算法把数组里面的key删掉,LRU算法:最近最久未使用法
        keys.push(key); //刚使用的把它放在数组末尾,
      } else {
        cache[key] = vnode; //没找到就换存下来
        keys.push(key); //把它放在数组末尾
        // prune oldest entry  //如果超过最大值就把数组第0项删掉
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
      }

      vnode.data.keepAlive = true; //标记虚拟节点已经被缓存
    }
    // 返回虚拟节点
    return vnode || (slot && slot[0]);
  },
};


2022.01.18

22.用es5的方式实现promise

resolvePromise(newPromise, x, resolve, reject) {
    // 如果 newPromise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 newPromise
    // 这是为了防止死循环
    if (newPromise === x) {
      return reject(new TypeError('The promise and the return value are the same'));
    }

    if (x instanceof MyPromise) {
      // 如果 x 为 Promise ,则使 newPromise 接受 x 的状态
      // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
      x.then((y) => {
        this.resolvePromise(newPromise, y, resolve, reject);
      }, reject);
    } else if (typeof x === 'object' || this.isFunction(x)) {
      // 如果 x 为对象或者函数
      if (x === null) {
        // null也会被判断为对象
        return resolve(x);
      }

      let then = null;

      try {
        // 把 x.then 赋值给 then 
        then = x.then;
      } catch (error) {
        // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
        return reject(error);
      }

      // 如果 then 是函数
      if (this.isFunction(then)) {
        let called = false;
        // 将 x 作为函数的作用域 this 调用
        // 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
        try {
          then.call(
            x,
            // 如果 resolvePromise 以值 y 为参数被调用,则运行 resolvePromise
            (y) => {
              // 需要有一个变量called来保证只调用一次.
              if (called) return;
              called = true;
              this.resolvePromise(newPromise, y, resolve, reject);
            },
            // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
            (r) => {
              if (called) return;
              called = true;
              reject(r);
            });
        } catch (error) {
          // 如果调用 then 方法抛出了异常 e:
          if (called) return;

          // 否则以 e 为据因拒绝 promise
          reject(error);
        }
      } else {
        // 如果 then 不是函数,以 x 为参数执行 promise
        resolve(x);
      }
    } else {
      // 如果 x 不为对象或者函数,以 x 为参数执行 promise
      resolve(x);
    }
  }
  

手写 Promise 以及 Promise.all Promise.race 的实现:

class Mypromise {
  constructor(fn) {
    // 表示状态
    this.state = "pending";
    // 表示then注册的成功函数
    this.successFun = [];
    // 表示then注册的失败函数
    this.failFun = [];

    let resolve = (val) => {
      // 保持状态改变不可变(resolve和reject只准触发一种)
      if (this.state !== "pending") return;

      // 成功触发时机  改变状态 同时执行在then注册的回调事件
      this.state = "success";
      // 为了保证then事件先注册(主要是考虑在promise里面写同步代码) promise规范 这里为模拟异步
      setTimeout(() => {
        // 执行当前事件里面所有的注册函数
        this.successFun.forEach((item) => item.call(this, val));
      });
    };

    let reject = (err) => {
      if (this.state !== "pending") return;
      // 失败触发时机  改变状态 同时执行在then注册的回调事件
      this.state = "fail";
      // 为了保证then事件先注册(主要是考虑在promise里面写同步代码) promise规范 这里模拟异步
      setTimeout(() => {
        this.failFun.forEach((item) => item.call(this, err));
      });
    };
    // 调用函数
    try {
      fn(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  // 实例方法 then

  then(resolveCallback, rejectCallback) {
    // 判断回调是否是函数
    resolveCallback =
      typeof resolveCallback !== "function" ? (v) => v : resolveCallback;
    rejectCallback =
      typeof rejectCallback !== "function"
        ? (err) => {
            throw err;
          }
        : rejectCallback;
    // 为了保持链式调用  继续返回promise
    return new Mypromise((resolve, reject) => {
      // 将回调注册到successFun事件集合里面去
      this.successFun.push((val) => {
        try {
          //    执行回调函数
          let x = resolveCallback(val);
          //(最难的一点)
          // 如果回调函数结果是普通值 那么就resolve出去给下一个then链式调用  如果是一个promise对象(代表又是一个异步) 那么调用x的then方法 将resolve和reject传进去 等到x内部的异步 执行完毕的时候(状态完成)就会自动执行传入的resolve 这样就控制了链式调用的顺序
          x instanceof Mypromise ? x.then(resolve, reject) : resolve(x);
        } catch (error) {
          reject(error);
        }
      });

      this.failFun.push((val) => {
        try {
          //    执行回调函数
          let x = rejectCallback(val);
          x instanceof Mypromise ? x.then(resolve, reject) : reject(x);
        } catch (error) {
          reject(error);
        }
      });
    });
  }
  //静态方法
  static all(promiseArr) {
    let result = [];
    //声明一个计数器 每一个promise返回就加一
    let count = 0;
    return new Mypromise((resolve, reject) => {
      for (let i = 0; i < promiseArr.length; i++) {
      //这里用 Promise.resolve包装一下 防止不是Promise类型传进来
        Promise.resolve(promiseArr[i]).then(
          (res) => {
            //这里不能直接push数组  因为要控制顺序一一对应(感谢评论区指正)
            result[i] = res;
            count++;
            //只有全部的promise执行成功之后才resolve出去
            if (count === promiseArr.length) {
              resolve(result);
            }
          },
          (err) => {
            reject(err);
          }
        );
      }
    });
  }
  //静态方法
  static race(promiseArr) {
    return new Mypromise((resolve, reject) => {
      for (let i = 0; i < promiseArr.length; i++) {
        Promise.resolve(promiseArr[i]).then(
          (res) => {
            //promise数组只要有任何一个promise 状态变更  就可以返回
            resolve(res);
          },
          (err) => {
            reject(err);
          }
        );
      }
    });
  }
}

// 使用
// let promise1 = new Mypromise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(123);
//   }, 2000);
// });
// let promise2 = new Mypromise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(1234);
//   }, 1000);
// });

// Mypromise.all([promise1,promise2]).then(res=>{
//   console.log(res);
// })

// Mypromise.race([promise1, promise2]).then(res => {
//   console.log(res);
// });

// promise1
//   .then(
//     res => {
//       console.log(res); //过两秒输出123
//       return new Mypromise((resolve, reject) => {
//         setTimeout(() => {
//           resolve("success");
//         }, 1000);
//       });
//     },
//     err => {
//       console.log(err);
//     }
//   )
//   .then(
//     res => {
//       console.log(res); //再过一秒输出success
//     },
//     err => {
//       console.log(err);
//     }
//   );

2022.01.17

21.观察者和订阅-发布的区别,各自用在哪里

发布订阅模式观察者模式
把事情都放在一个盒子里,等发布的时候你把盒子里的事拿出来,发布和订阅没有关系就叫发布订阅;他们都可以叫做发布订阅模式vue的响应式用的是观察者模式,因为内部每个属性都会记录它所有的观察者,当属性变化了会通知所有的观察者去更新,观察者模式的发布订阅是有关系的;观察者模式内部有个收集的关系,被观察者要收集观察者
有个事件中心,不耦合,发布订阅可以指定执行被触发回所有watcher都执行
大多数时候是异步的(使用消息队列)同步的,比如当事件触发
例子:vue的生命周期,消息队列例子:vue的响应式

20.对象数组如何去重

可以通过for循环以及reduce方法;通过循环使用hashmap进行去重

举个例子:

var arr = []
var data = [
    {id:1,time:'1'},
    {id:2,time:'2'},
    {id:2,time:'3'},
]
for(let val of data){
    arr.push(val.id)
}
var newArr = [];
var newArr2 = [];
for(var i =0;i<arr.length-1;i++){
    if(newArr.indexOf(arr[i]) == -1){
        newArr.push(arr[i]);
        newArr2.push(data[i]);
    }
}
data= newArr2;

19.怎样选择合适的缓存策略

缓存策略主要还是强缓存和协商缓存的相关概念,两者区别就是在于协商缓存会像服务器发送一次请求,强缓存不向服务器请求数据。通常先走强缓存判断,如果没有命中强缓存会去判断是否满足协商缓存,若都不满足则重新请求数据;

如果是不怎么替换的lib文件 使用强缓存;js png css使用协商缓存

通过更改js的hash值来更新缓存,文件名字不同

还有一种 小于4kb的图片可以使用base64放到内存中

2022.01.14

18.promise怎么实现链式调用跟返回不同的状态

promise有三种状态,分别是pending,fulfilled,rejected,主要是依赖于resolve和reject方法来改变它的状态,then之后会继续返回一个新的对象,再次调用所获取的参数就是上一个then方法的return返回值

promise返回promise实例就能实现链式调用和jq一样,主要通过resolve和reject方法修改当前状态pending--》fullfilled pending--》rejected

17.不应该使用箭头函数一些情况

  • 不能用箭头函数来作为构造函数,不能new,不能yield,
  • 避免在定义对象方法时使用
  • 避免在需要 prototype 上使用
  • 避免在需要 arguments 上使用
  • 避免在动态上下文中的回调函数里使用
  • 避免在需要 caller 的时候使用
  • 箭头函数不能改变this指向,如果需要改变this指向的时候不能用箭头函数,this指向上下文
  • 无法使用call,apply,bind来改变它的this指向;虽然不会报错

16.vuex实现原理

vuex本质就是一个插件,利用Vue.use全局api注入,实现原理就是提供一个状态管理容器state,通过getters可以读取state对象,当需要改变state的时候可以通过commit/mutations进行状态改变操作,利用dispatch来触发actions,actions包含了同异步操作,会按照注册顺序依次触发。

源码角度来说的话:vue.use(vuex)调用后通过install在Vue的mixins上注入了beforeCreate方法。初始化vue实例的时候会调用这个方法,这个方法里面就讲$store注入到了Vue上,借助Vue响应式原理使state能够响应数据更新视图

image.png

2022.01.13

15. Vue2.0 框架怎么实现对象和数组的监听?

vue框架通过Object.defineProperty实现对对象的监听,通过重写数组的七个方法(pop,unshift,shift,push,sort,reverse,splice)来实现对数组的监听;同时都会用到递归来遍历

14. Vue 中的 key 有什么作用?

给元素打上唯一标识,主要用于diff算法;

(解答思路:先说一下主要目的是什么,然后没有key的话是怎么样的,有key的话是如何变动数据的,然后结合diff算法说一下)

image.png

13. vue组件中 data 为什么是一个函数?

为了组件中的数据不相互影响;data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。

2022.01.12

12. Map和Set区别 常见用法

MapSet
对象,键可以是任何数据类型类似数组,Set里面的元素自动去重,不具有顺序
一般用Map来存储经常变动的数据Set常见于去重
常见用法:set;get;delete;size;has;clear常见用法add;delete;size;has;clear
1.在内存固定时, map可比object 节约50%;2.添加,删除, 大量查找,速度优于object去重很方便,记得用...转成数组
包括普通map ,weakmap()

weakmap:

  1. 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。
  2. 它的存在解决 map 无引用自动垃圾回收这一情况;弱引用,而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。在用delete方法删除后,key会被垃圾回收。
  3. 正因为key是弱引用,weakmap的key不能枚举。

11. TS中type和interface的区别

type(类型别名)interface(接口)
都支持描述一个对象或者函数都支持描述一个对象或者函数
不能使用extends,要进行扩展的话需要使用交叉类型& 的形式可以使用extends直接进行扩展
可以使用自动推断不可使用自动推断
描述类型描述数据结构
不能能够声明合并
不可继承可以继承
type 语句中可以使用 typeof 获取实例的类型进行赋值:type T = typeof 变量不可以
可以声明基本类型别名,联合类型,元组等类型只能声明对象的形状,不能重命名原始的类型(string、number等)

tips:

// 当type需要继承interface的时候可以利用合并的方式实现:
interface Name { 
  name: string; 
}
type User = Name & { 
  age: number; 
}

什么时候用type什么时候用interface主要根据你的实际场景来~ image.png

10. CDN如何实现加速

CDN全名是内容分发网络,当用户发送请求到达服务器时,服务器会根据用户的区域信息,为用户分配最近的CDN服务器,而CDN就近的网络节点,不仅能提高用户的访问速度,也能减少服务器的带宽消耗,降低负载。

详细说就是:

在用户和服务器间加入中间层CDN,利用DNS的重定向技术,DNS服务器会返回一个跟用户最接近的点的IP地址给用户,CDN节点的服务器负责响应用户的请求,提供所需的内容。 CDN关键技术

  • 缓存算法决定命中率、源服务器压力、POP节点存储能力
  • 分发能力取决于IDC能力和IDC策略性分布
  • 负载均衡(智能调度)决定最佳路由、响应时间、可用性、服务质量
  • 基于DNS的负载均衡以CNAME实现[to cluster],智取最优节点服务
  • 缓存点有客户端浏览器缓存、本地DNS服务器缓存
  • 缓存内容有DNS地址缓存、客户请求内容缓存、动态内容缓存
  • 支持协议如静动态加速(图片加速、https带证书加速)、下载加速、流媒体加速、企业应用加速、手机应用加速
  • 当 cdn 缓存服务器中没有符合客户端要求的资源的时候,缓存服务器会请求上一级缓存服务器,以此类推,直到获取到。最后如果还是没有,就会回到我们自己的服务器去获取资源。
  • 没有资源,资源过期,访问的资源是不缓存资源等都会导致回源。

2022.01.10

9. 说一下css选择器优先级

image.png

8. @import,href,src 三者区别场景

image.png

7. 你所知道的数据类型的检测方式有哪些?说一下instanceof的原理

数据类型检测方式:typeof,instanceof,constructor(有问题),Object.prototype.toString.call

instanceof的原理:遍历左侧实例的原型链判断是否有属性和右侧的构造函数的prototype属性一样,一样则返回true,否则false

6. 说一下三次握手和四次挥手

image.png

5 vue-router的实现原理

路由映射表里存储了url和组件的映射关系,利用栈记录url的访问记录(history),访问url的时候根据匹配起去查映射表对应的组件返回并渲染,记录到history中,通过前进/后退访问url的时候就是优先从history去找,就可以实现不同url映射到不同的组件;

vue-router有两种模式:

  1. hash模式:#后面的都是hash,hash值会跟着请求传递参数但不会对服务端造成影响,可以实现路径改变但不刷新的功能;缺点是不太好看;可以通过window.addEventListener("hashchange", funcRef, false)监听hash执行回调
  2. history模式:利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础;缺点是虽然美观,但是刷新会出现 404,需要和后端配合解决这个问题

2022.01.07

4.vue的v-if和v-for为什么不建议用在同一个标签上

由于vue在解析指令时会将指令转换成render函数,而v-for的优先级高于v-if,所以会先进行循环,而后对每个循环的进行v-if判断,这样会造成性能的消耗,如果一起写的话会警告;最好做的做法就是在v-for的外层套一层template来绑定v-if,先进行v-if的判断,这样就能够避免性能的损耗

3.浏览器的渲染过程是怎样的

html会转成dom树,css 转成 cssom树,dom树会和cssom树构建成渲染树,接着来了会根据渲染树进行重排计算各个节点的位置和大小,布局完后会会把它们绘制在显示屏上~其中遇到js的话会优先执行js,停止渲染

image.png

2.什么是回流,什么情况下会触发回流

回流,就是重排,当移动元素位置,增删改元素的时候,第一次渲染的时候,改变浏览器窗口大小的时候会引发重排,频繁重排会降低性能,因为它浏览器重新计算元素大小位置重新绘制页面了

image.png

1.cookie如何实现跨域

setHeader("Access-Control-Allow-Origin", "*");

access-control-allow-creadential: true ;

还要axios加withCreadential

image.png