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

1,926 阅读27分钟

Hello,大家好,我是disguiseFish,坚持早起每天每日一题有一段时间啦,收获良多,每天会做些题来储备自己的知识量,在这里把它们分享记录下来,我们一起学习进步吧! 这是一个日更帖哟!每日做的题都贴出了对应的理解,或许你还有其他理解~欢迎来骚扰哟!

2022.04.24

100.单页面应用部署时怎么解决跨域问题

其实就是问跨域,有很多种方式,比如用nodejs中间件,nginx,cors等等不详细说了

99. 怎么配置负载均衡,CDN,及缓存。

  • 负载均衡:blog.csdn.net/weixin_3602…

  • 配置CDN:就是后端那边配好了后给你个域名,,你根据需要加呗,Nginx 主要的负载均衡策略有以下四种:轮询策略,最少连接数负载均衡策略,ip-hash 负载均衡策略,权重负载均衡策略;

  • 看是要配置什么缓存,如果是浏览器缓存加在请求头,http缓存,文件图片缓存,proxy_cache等等,我就不一一说了,老生常谈的问题

2022.04.22

98. 只适配了pc的管理系统后期突然要求适配ipad等移动端的解决方案?

image.png

也就是监听resize 然后改变scale,也可以往viewepoint方向去思考,但监听resize 对pc的页面也会产生影响的,本身就是针对移动端进行适配,viewport只会在移动端生效,不会增加pc页面的负担

97. 像基于umi,Next这种集成式开箱即用的框架,项目做了很久,文件,包越来越多,导致启动服务,热加载甚至是产线上页面加载过慢,像这种情况可以从哪几个方面着手去优化,能否落实到某个点?

拆分成多个项目 打包就快了,其实就是性能优化,这方面收割机里有

96. new Date("2022-4-22")和 new Date("04/22/2022")的区别?

new Date("2022-4-22"),new Date("04/22/2022")在兼容上有区别,safari不支持new Date("2022-4-22")

image.png

95. 在做一个外企项目,可能客户是美国那边的,你们对时区以及前后端传值是怎么处理的?

时区问题,美国一般是-5时区,我们是+8时区,后端存的更多的是零时区,我们这边像那种设计到时区的需要配合传零时区给后端

2022.04.21

94. 图片懒加载的实现方式?

  • 手动监听,手动替换

    进入页面假设我们有一个图片列表,但我们并不需要一次性完全加载这些图片,我们只要保证可视区域图片可见即可,所以我们利用window.innerHeight 结合可配置属性preload(vue-lazyload默认是1.3)来当成一个可视化区域+预加载区域,判断当前图片的节点是否在区域内(利用getBoundingClientRect获取图片节点top属性),这里的window.innerHeight实际上会随着scroll事件触发而变化,对于每一张图片我们采用new Image的形式,这样可以监听到这个image是否加载成功,可以结合loading使用。lazyload内部实现疯狂计算节点属性会引发回流,能优化的点还挺多的,图片宽高预设,节点缓存

  • 用插件

  • 调api:IntersectionObserve

93. 如何去实现一个 CSRF 攻击?

  • 欺骗用户点击某链接然后获取他的cookie去发送请求

    CSRF攻击即跨站点请求伪造,原理就是诱导用户登陆访问“目标网站”,“目标网站”验证用户信息成功后此时浏览器内cookie已经有了“目标网站”鉴权token,这时候一旦用户访问“攻击网站”,“攻击网站”就会向“目标网站”发出请求,因为浏览器内已经有了这个受信token,所以“目标网站”仍会以为是用户主动触发,从而实现CSRF攻击。常见的防御手段一个是 验证Referer,自定义http头属性,请求参数携带token

92. 场景:一个使用webpack多页面打包的应用,打包后的产物有文件夹A,B,C;如何将 A,B,C 的js静态文件打包成一个js文件?减少JS请求,想把多页面打包后的JS产物合并成一个

这个场景可能发生在电商网站,这个网页每一个页面都是多页面打包生产的产物,图片请求很多;每切换一个页面都需要请求对应页面的几kb的JS文件,所以想合并一下JS文件

2022.04.20

91. 你的优缺点是什么

90. 工作发生了冲突如何解决

沟通分析冲突原因,合理解决

89. 如果让你带新人,你会怎么做

多向新人请教问题,刺激其主观能动性,自己探索解决方案,挖掘其潜力?

2022.04.19

88. 为什么微任务优先于宏任务,分别是由谁控制的

js的主线程本身也算一个宏任务,执行异步任务时,优先执行微任务,在执行下一个宏任务,微任务是es出的, 宏任务是浏览器出的准则,

87. 闭包原理

老生常谈的问题了~

86. 所有的闭包都不会被垃圾回收机制回收吗

闭包和垃圾回收没有直接联系,不是说闭包就会有垃圾回收的

function foo() { 
 const obj = {}
 const obj2 = { a: 1, b: 2 }
 return {
  get(key) {
   return obj[key]
  },
  set(key, value) {
   obj[key] = value
   return true
  }
 }
}
const { set, get } = foo()

比如这个例子 foo中obj被引用了所以不会被回收,obj2才会被回收

2022.04.18

85. vite热更新的大致原理

84. vite预编译都做了什么操作或者优化

2022.04.15

83. http 2.0 与 1.1 的区别

  • 2.0 二进制分帧、
  • 多路复用 不必再按照之前的版本按请求-应答的模式进行通信,
  • 首部压缩 减小包体积,
  • 服务器推送:服务器可以主动推送资源

82. TLS/SSL 协议的工作原理

http和https之间的差别就隔了个TLS/SSL加密,详情可见:juejin.cn/post/684490…

81. 状态码 301 和 302 的区别及应用场景

  • 301 永久重定向,比如网页换了个新地址,让搜索引擎收录新地址,

  • 302 临时重定向,是临时换的地址,搜索引擎还是收录旧地址

2022.04.14

80. 常见的内置错误有哪些?

有四个,类型错误 引用错误,语法错误 还有堆栈溢出

79. 正则匹配后台返回数据中的a标签?

var str =  <A href="www.baidu.com/"></A><br/> <a href="http://www.baidu1.com/"></a> <a href='https://www.baidu2.com/'></a>;

用正则去识别出href里面的东西,这个插件也能保存vueX里面的数据

78. 如何让vueX里面的数据刷新不丢失?

数据持久化可以用localStorage和session存,vuex-persistedstate插件

2022.04.13

77. 权限控制怎么做

权限控制: RBAC

juejin.cn/post/704880…

76. 单点登录原理,具体的流程是怎样的

SSO

juejin.cn/post/684490…

75. 公共组件需要考虑哪些因素

API 好用,文档友好,可迭代

image.png juejin.cn/post/684490…

2022.04.12

74. 简述什么是Tree-Shaking?(webpack)

tree-shaking的前提是在编译时对加载的模块进行分析,tree shaking 对于定义但没有引用的代码会移除,全局api tree-shaking方便打包器可以检测没有用到的代码并删除

73. vue2&3 - 简述diff 算法的原理(vue)

v2 是双端diff,双端对比之后,会发现有很多边界没有被处理

v3是快速diff,快速diff 的话,主要是参考了文本的处理方案,每次会先对节点做预处理,从前往后查找到不同的节点和从后往前找不同的节点,如果找到的节点 大于旧节点的长度并且小于新节点的长度,说明是新增的,如果小于旧节点的长度,说明是删除的,还有一种情况是 前后相同,中间不同,对于这种情况 vue 内部做了个最长递增子序列来处理,找到连续上升的节点,这些节点只用移动处理,其余节点进行删除,这样的话,整个diff 就基本完成了,不过这是针对有key的情况下;没有key的情况下,是选新旧节点最短的那个,跑完之后,就把多的给删掉,对于没有key导致删除节点错误的情况是因为,vue在没有diff之前,就已经 错了,所以diff完当然是错的,react的事件处理机制 不了解,只知道是所有事件都冒泡到根节点进行处理

v3 最长递增子序列的好处 是比 v2 要快的

image.png

72 说说对React事件机制的理解?(react)

image.png

2022.04.11

71. 常见的图片格式及使用场景 (CSS)

image.png

苹果图片大多是heic

70. 浏览器是如何对 HTML5 的离线储存资源进行管理和加载?(HTML)

service worker

69. 手写实现将虚拟 Dom 转化为真实 Dom (JS)

参考:juejin.cn/post/684490…

输入代码:
const el = require('./element.js');
const ul = el('ul', {id: 'list'}, [
  el('li', {class: 'item'}, ['Item 1']),
  el('li', {class: 'item'}, ['Item 2']),
  el('li', {class: 'item'}, ['Item 3'])
])
const ulRoot = ul.render();
document.body.appendChild(ulRoot);

代码输出:
<ul id='list'>
  <li class='item'>Item 1</li>
  <li class='item'>Item 2</li>
  <li class='item'>Item 3</li>
</ul>

编码实现element.js?

<!-- html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>repl.it</title>
    <link href="style.css" rel="stylesheet" type="text/css" />
  </head>
  <body>
    <script src="script.js"></script>
  </body>
</html>

//script.js
var el = require("./element.js")
window.onload = function(){
const ul = el('ul', {id: 'list'}, [
  el('li', {class: 'item'}, ['Item 1']),
  el('li', {class: 'item'}, ['Item 2']),
  el('li', {class: 'item'}, ['Item 3'])
])
const ulRoot = ul.render();
document.body.appendChild(ulRoot);
}

//element.js
function Element(tagName,props,children){
  this.tagName = tagName
  this.props = props
  this.children = children
}

Element.prototype.render = function(){
  var el = document.createElement(this.tagName) //创建DOM元素
  var props = this.props

  for(propName in props){
    var propVal = props.propName
    el.setAttribute(propName,propVal) //给DOM元素添加属性
  }

  var children = this.children || []
  children.forEach(child=>{
    el.appendChild((child instanceof Element)? child.render():document.createTextNode(child))
  })
  return el
}

module.exports = function(tagName,props,children){
  return new Element(tagName,props,children)
}

思路:观察script.js中的el是element.js导出的一个函数,通过这个函数可以生成的实例可以调用自己的render方法生成新的dom元素,所以可知element.js导出的函数是一个对象(实例)生成器,通过传入相应的属性可以获得实例,因此在element.js中应该还有一个构造函数,他接收这些属性并且拥有render方法,利用这些属性生成dom元素。到这里,其实重点来到了如何去写Element.prototype.render方法。首先使用document.createElement(this.tagName)创建DOM元素el,再使用el.setAttribute(propName,propVal) 循环的为此元素增加属性,之后就是 el.appendChild((child instanceof Element)? child.render():document.createTextNode(child))为el.children增加子元素,在这里需要判断child是否是element或者只是普通的text,如果是element还要递归调用render函数。多次调用dom API需要一定的熟练度。

2022.04.08

68.vue和react的异同和各自优缺点(技术选型时如何考虑)

vuereact
支持模板语法
vue 有官方全家桶
支持响应式
因为有模板,对于一些自定义要求比较高的,反而需要通过渲染函数实现,会比较麻烦,类型提示不够友好
采用 jsx
完美支持 ts,可以支持各种复杂的自定义功能
第三方轮子太多

技术选型: 看团队成员的熟练程度,或者负责人对这个很熟

67. 类组件和函数组件的异同

hooks 写起来更方便,不过需要注意闭包陷阱,没有官方的异常捕获hook,class 有

类组件函数组件
创建组件继承自 react 的 componet 基类,需要使用 this 进行获取值、方法,属于原型的知识。不需要获取 this,可以直接从作用域中直接获取变量和方法。
表现区别类组件的 props、state 是不可变的、this 是可变的。函数组件具有值捕获的特性,真正将数据和渲染紧紧的绑定到一起了(overreacted.io/zh-hans/how…
生命周期有对应的生命周期函数组件则没有,唯一有的则是 hooks 这种 自变量与因变量的关系。
渲染方面类组件使用 render 方法函数组件则是直接返回
性能优化类组件采用 PureComponent 或者 生命周期 shouldComponentUpdate 进行判断 prop 变化函数组件则使用React.memo、useMomo、useCallback 等
测试方面函数组件方便测试
复用性类组件需要结合 hoc 复用函数组件可以自定义 hooks

2022.04.07

66.浏览器在解析HTML的时候,做了哪些兼容策略。

浏览器解析不对的话,会给你做兼容处理:

  • 浏览器自身会做未闭合标签的处理,自动补充闭合标签的功能,单 标签的闭合;像vue解析的时候就给你报错了

  • 还有浏览器可以直接写div这些,浏览器会给你加上body这些,不需要自己加,这也是一个优化,

栈也只是维护一个状态机而已,本身语法的解析也就只是去靠状态机去做的,感兴趣可以去了解一下AOT 和JIT 的区别

65.babel原理

babel的主要编译流程是 parse 是把源码转成 AST,transform 是对 AST 做增删改,generate 是打印 AST 成目标代码并生成 sourcemap。

image.png

image.png

64.对ts的理解

juejin.cn/post/687211…

2022.04.06

63. 对象的遍历方法有哪些?他们都可以遍历对象的那些属性

答案见git收割机- js模块

62. js中使用到了哪些设计模式

答案见30题

2022.04.02

61. SPA应用的优缺点

优点:

1.有良好的交互体验,不会重新加载整个网页,只是局部更新

2.减轻服务器压力,只处理数据不用处理界面

3.共用一套后端程序代码

缺点:

1.SEO难度高,只有一个网页无法针对不同的内容编写不同的SEO信息

2.初次加载耗时多--导致白屏过长,为了实现单页应用供能及展示效果,需要在加载页面的时候将所有的JavaScript,css同一加载,在VUE中可以使用按需先加载解决

60. 定时器的清除

image.png

最近有小伙伴提出,有时候能看到以上两种清除定时器的方式,他们都可以清除吗?

其实clearTimeout是官方提供的关闭定时器的方式,而赋值null只是给这个变量换了个地址,”凭啥你换个地址我就要停止“ 所以~ 赋值null没用!!再给大家实践一下

image.png 一般最好先clearTimeout 再给这个变量赋值null释放内存

59. 如何实现动态更换主题色

css 变量

 null >= 0  // true
 null == undefined // true
 null == "" // false

58. 跨域解决方案,CORS如何携带cookie

1).服务端需要设置

Access-Control-Allow-Credentials: true

Access-Control-Allow-Origin: [特定域名] // 不可以是*

2).客户端

XMLHttpRequest发请求需要设置withCredentials=true

fetch 发请求需要设置 credentials = include

SameSite(用来防止 CSRF 攻击和用户追踪)不能为none

2022.04.01

57. Vue2中普通插槽和作用域插槽实现的区别

  1. 普通插槽是在父组件编译和渲染阶段生成 vnodes,所以数据的作用域是父组件实例,子组件渲染的时候直接拿到这些渲染好的 vnodes。

  2. 作用域插槽,父组件在编译和渲染阶段并不会直接生成 vnodes,而是在父节点 vnode 的 data 中保留一个 scopedSlots 对象,存储着不同名称的插槽以及它们对应的渲染函数,只有在编译和渲染子组件阶段才会执行这个渲染函数生成 vnodes,由于是在子组件环境执行的,所以对应的数据作用域是子组件实例。

简单地说,两种插槽的目的都是让子组件 slot 占位符生成的内容由父组件来决定,但数据的作用域会根据它们 vnodes 渲染时机不同而不同。

56. 请实现如下的函数,可以批量请求数据,所有的 URL地址在 urls 参数中,同时可以通过 max 参数控制请求的并发度,当所有请求结束之后,需要执行 callback 回调函数。发请求的函数可以直接使用 fetch 即可

function sendRequest ( urls: string[], max: number, callback: () => void ) {

}

在限制的max范围内拿到最先执行完的任务 然后从数组中删除 接着添加新的 以此类推

答案:

function fetch(url) {
    // 模拟接口请求
    return new Promise(resolve => {
     setTimeout(() => {
      resolve(url)
     }, 1000 * Math.random())
    })
   }

   function sendRequest(urls, max, callback) {
    if (!urls || !max) return

    if (urls.length === 0) {
     callback && callback()
     return
    }

    // 存储并发max的promise数组
    let arr = [],
     i = 0

    function handleFetch() {
     // 所有请求都处理完后,返回一个resolve
     if (i === urls.length) return Promise.resolve()
     // 取出第i个url,放入fetch里面,每取一次 i++
     let one = fetch(urls[i++])
     // 将当前的promise存入并发数组中
     arr.push(one)
     // 当promise执行完后,从数组中删除
     one.then(res => {
      console.log(res)
      arr.splice(arr.indexOf(one), 1)
     })
     let p = Promise.resolve()
     // 当并行数量达到最大后,用race比较第一个完成的,然后再调用一下函数自身
     if (arr.length >= max) p = Promise.race(arr)
     return p.then(() => handleFetch())
    }

    // urls循环完后,现在arr里面剩下的promise对象,使用all等待所有的都完成之后 执行callback
    handleFetch()
     .then(() => Promise.all(arr))
     .then(() => callback())
   }

   sendRequest(
    ['url1', 'url2', 'url3', 'url4', 'url5', 'url6', 'url7', 'url8'],
    3,
    () => {
     console.log('fetch end')
    }
   )

2022.03.31

55. 乾坤

乾坤具体实现代码是用proxy,沙箱具体原理创建一个唯一的类 window 对象,手动执行子应用的 js 脚本,将类 window 对象作为全局变量,对全局变量的读写都作用在类 window 对象上,html entry 阶段解析出来的所有 js 脚本字符串 在执行时会先使用一个 IIFE - 立即执行函数包裹,然后通过 eval 方法手动触发。

54.浏览器包含哪些进程,浏览器渲染进程里包含哪些主要的线程?

  1. 浏览器进程包括插件进程,渲染进程,网络进程,浏览器主进程,gpu进程等

  2. 渲染进程包括 定时器线程,浏览器渲染,异步请求线程,事件触发等;

进程间 数据交互要通过管道或者ipc,线程间数据交互是共享当前进程的

53.哪些操作会造成内存泄漏?

  1. 使用了未声明的变量,从而意外的创建了一个全局变量,而使得这个变量在内存中一直无法回收
  2. 设置了setInterval 忘记取消,如果在循环函数中对外部变量有引用时,那么这个变量会被留在内存中
  3. 获取了一个DOM元素的引用,而后面这个元素被删除,但是因为我们的引用 导致一直无法被回收
  4. 不合理的使用闭包,导致其中的变量一直留在内存中
// 不合理的使用闭包
function outer() {
  var largeObject = {longStr: Date.now() + Array(10000000000).join('*')};
  function help() {
    largeObject;
  }
  return function () {};
}
var inner = outer();
inner();
闭包里内存泄漏的场景:
1. 嵌套的函数中是否有使用该变量。
2. 嵌套的函数中是否有直接调用eval3. 是否使用了with表达式。


其中
- eval用来执行js代码,在严格模式下不允许使用,可以用new Function取代,
缺点就是内部声明的变量都是全局变量,而且耗费性能,因为你输入进去的是字符串,他需要解析成js语句再执行

- with用来引用对象的属性,比如vue源码ast语法编译用的那个 vue-tempalate-compiler 解析一个 template 模板
你能看到他其实就是 with(this) { _c...},还有你日常写一些比如公用的对象 比如 Math.max(Math.pow(), Math.sqrt()),
类似这种你就可以简写成 with(Math) { max(pow(), sqrt()) }
  1. 尾递归: 尾递归就是function f() { return f() },如果你return f() + f() 这种就不算

52.你能说清楚到底宏任务和微任务是什么?是谁发起的?为什么微任务的执行要先于宏任务呢?

同步代码算宏任务(最大那层script),执行宏任务的同步任务,然后再去跑宏任务下的微任务,然后渲染,继续下一轮任务

2022.03.30

51.什么是回流,什么是重绘,如何针对回流和重绘进行性能优化

回流:Render Tree中部分或全部元素的尺寸、结构、或某些属性,位置发生改变时,浏览器重新渲染部分或全部文档的过程

重绘:页面中元素样式(颜色) 的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它的过程

回流一定会导致重绘,重绘不会导致重排。

50.如何实现一个可以取消的promise

promise本身是不支持取消的,想实现promise在3s后不返回就把这个promise取消,可以通过promise.race方法进行模拟,给race中传入两个promise,一个是目标promise,一个是自建的promise.reject,在3s后,目标promise没有返回,则自建promise会先返回reject,从而达到取消的效果。

49.为什么vue中data是一个函数,而methods、computed等其他options可以不是一个函数?

为了保证各个实例之间的data不相互污染,一个页面中可能会多次用到同一个组件。如果是对象 同一个组件数据就互相污染了啊;其他几个都是函数的属性的值;

因为vue是将methods中的内容代理到vm实例上的,所以修改vm实例上的函数,是不会影响到原本methods中的原函数的,但是data因为存在引用的关系是会影响到的,watch监听的也是自己实例对象上的data,data本身就已经是function了,所以不会影响其他组件

2022.03.29

48.nodejs中间件怎么实现权限设计?

node 怎么转发请求:维护一个转发映射关系表,然后路由接收到url,判断在不在映射表里然后在的话请求映射值,拿到结果后从原路由送回去

node并发不行,请求一多,CPU繁忙,内存飙升,Node直接阻塞,然后就挂了,Java是多线程

rbac+中间件:rbac是基于角色的权限控制。通过角色关联用户,角色关联权限的方式间接赋予用户权限。所谓权限是资源的集合,常见的有页面权限、操作权限和数据权限。

中间件的实现是利用compose去实现,compose 是一个闭包,它接收一个中间件函数数组,返回一个函数。这个函数接收 context, next 两个参数。所有使用到这个中间件的路由,会先进入到compose执行中间件的代码。

中间件的权限设计,先在路由通过权限中间件进行拦截,拿到用户的token,校验对应的权限,如果符合则next,否则返回403。

47.TDD、BDD、ATDD、DDD分别是什么?

TDD(Test-driven development)测试驱动开发,先写测试用例再进行功能开发;有的人谈起TDD有的时候指UTDD有的指ATDD 其中UT和AT区别:

  • AT和代码不相关,宏观一些 缺点:只能手工统计比例,没工具,但可以很快告诉你什么地方有问题

  • UT:和代码相关,更细一些,通常指函数,组件这些

  • UTDD:在代码层次,在编码之前写测试脚本,可以称为单元测试驱动开发(Unit Test Driven Development,UTDD)

  • ATDD:在业务层次,在需求分析时就确定需求(如用户故事)的验收标准,即验收测试驱动开发(Acceptance Test Driven Development,ATDD)。

DDD(Domain-drive Design)领域驱动开发,着重于业务的实现,主要解决团队中不同岗位和角色之间,交流语言不统一的问题。详情请看mp.weixin.qq.com/s/UaJ56G_Vd…

BDD(Behavior-driven development):即行为驱动开发

2022.03.28

46. http/1.1的分块传输编码与http/2的数据流的区别?

http1.1分块传输编码指的是:可以不使用 Content-Length字段(使用这个字段服务端响应前必须知道响应数据长度,影响传输效率),只要请求或响应头有 Transfer-Encoding 字段就表明是分块传输编码,分块传输编码以16进制的方式标识数据块长度,当最后一次数据块为0的时候表示响应数据发送完毕。

http2数据流最大的特点就是不按顺序发送,每个数据包都有自己的标记,一个请求/回应包含的所有的数据包称作数据流,数据流也有自己的编号。数据包发送标记的就是这个数据流的编号id。 两者的区别在于:是否按顺序传输,数据包的标记方式,取消传输数据的方法(http1.1只能关闭tcp链接,http2可以发送 RST_STREAM 帧信号来取消数据流传输)

45. http/2有了多工,是不是很多合并资源,减少请求的操作就不必要了?为什么

首先http2多路复用可以让一个同一链接实现多个http请求(谷歌默认6个)。那我们在什么情况下需要减少请求呢?可能是一些接口的合并,资源文件的合并(雪碧图),换句话说当我们使用http2一定程度上通过合并请求来减少请求的操作可以减少,但不排除某些页面请求数量超过6个,比如首屏页面多个图片,我们可以让浏览器对多个域名建立连接从而增加请求并行数。当我们在一个链接里堆积太多请求的时候,一旦发生丢包,整个连接的所有请求都会出现阻塞情况,所以在实际应用中我们会看到很多都是图片一个域名,静态文件一个域名,尽量的减少同一域名连接发生队头阻塞的情况。所以我们还是应该权衡实际情况,比如想要加快首屏,我们按需加载路由(资源更小了,后续切换页面还是要请求其他页面资源),图片懒加载(首屏减少了请求次数)等等。

2022.03.25

44.使用react-hooks 实现倒计时

连接:stackblitz.com/edit/vitejs…

43.reactive 和 ref 区别

  • 一般reactive是用来定义响应式(对象)类型的数据,reactive 是引用类型的,

  • ref是定义响应式普通类型的数据,ref是基本类型的,ref 修改值时,需要在变量名后加 .vlaue 才能修改值,内部是使用get set 来操作,reactive 则是 Proxy实现

ref源码:

image.png

2022.03.24

42.白屏时间和首屏时间的计算方式;

白屏就是FP,页面开始加载到浏览器中检测到渲染时触发,如果白屏时间过长会让用户认为这个页面不能用,或者性能很差

performance.timing.responseStart-performce.timing.navigationStart

performance.getEntriesByType('paint')[0].startTime

首屏就是FCP,代表的是页面绘制完第一个dom内容的点,我们打开Lighthouse能直接得出,也可以通过下面的api得出

performance.getEntriesByType('paint')[1].startTime

41.TCP和UDP的区别;

http3用的是UDP,http3之前用的TCP TCP:面向连接,三次握手,可靠。一对一,面向字节流,传输过程:三次握手,传输数据,四次挥手,通常用于文件传输,

UDP: http3用的UDP,无连接,不可靠,可以多对多,开销小,传输过程就得从传输方的应用层说到接受方的应用层了,想了解具体可以去查一下,通常用于视频,直播。

40简述Service Worker和应用场景;

是一个独立于浏览器背后的线程,可以实现一个缓存功能,详情收割机git里都有

2022.03.23

39.https加密和解密的详细过程和用到的什么算法及这些算法的简单解释

这道题涵盖的东西蛮多的,可以先回答一下,它和http的区别,主要做了哪些事情,以及加密过程,然后hash的算法,如何算的,用的什么

这些之前此类题目里分享的连接里有提及

2022.03.22

38.对于一个给定的字符串 如何判断是不是ip地址

参考:

/^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$/

0~255,两位以上的第一位不能是0

答案可以在网上搜搜

2022.03.21

37.简述一下ast

ast就是一个描述语法结构的一个js对象,比如在vue中用ast来描述html结构,可以方便用ast来生成虚拟dom,使用虚拟dom来进行diff,使元素尽量复用优化渲染效率。

36.fiber和虚拟dom的关系

fiber是react对虚拟dom的改造,它不是一个树形结构,而是一个优化过的链表结构,里面主要有children,sibling,return(parent)

35.tcp断开链接四次握手

当客户端认为数据已经发送完毕,向服务端发送连接释放请求,服务端收到请求后,断开与客户端的连接,由于tcp是客户端与服务端双向连接,此时服务端还是可以向客户端发送消息,当服务端的数据发送完毕后,向客服端发送连接释放请求,此时会等待2MSL(请求报文在网络存存活的最大时间),如果在2MSL时间内,服务端没有重新发送请求,则认为服务端已经发送完毕,此时才会断开双方连接

2022.03.18

34.用一个函数来实现模板字符串中变量的替换

image.png

参考: image.png

33. 如何检测到对象中有循环引用

try catch包裹JSON.stringify(obj), 有报错就存在对象引用,刷了下面那个算法就知道了 image.png

拓展:找出循环的那个节点

其中可以从JSON.stringfiy还有第二第三个参数去尝试一下~

image.png

image.png

32. class A {}; class B extends A{},问:new B() instanceof B, new B() instanceof A 的结果

都是 true

31. ES6 extends语法糖其实是什么继承方式

寄生组合继承

2022.03.17

30.说说有什么常用的设计模式?发布订阅和观察者模式有什么不同?

常用的设计模式有22种,其中有单例模式,工厂模式,观察者模式,发布订阅模式,策略模式,装饰器模式,代理模式,适配器模式,原型模式等等;

发布订阅模式和观察者模式最大的差别: 观察者模式只需要2个角色,即观察者和被观察者,被观察者至少有三个方法:添加观察者、移除观察者、通知观察者,观察者模式模块之间有一定的依赖性,依赖性稳定;而发布订阅需要至少3个角色来组成,包括发布者、订阅者和发布订阅中心,发布者和订阅者不直接进行通信,靠发布订阅中心管理,发布订阅模式模块之间的独立性较强

29.在vue框架/react框架的发展中,为了实现组件的复用,开始使用mixin,到高阶组件到hook,这中间有什么优化?

  • mixin解决的是代码复用问题,将多个对象的属性拷贝到目标对象上去,但mixin可能会相互依赖,相互耦合,不利于代码维护
  • 高阶组件是装饰模式的一种实现,它的出现是可以解决mixin耦合的问题,不会互相依赖耦合。缺点是如果大量使用 HOC,将会产生非常多的嵌套。
  • Hook可以抽象状态,复用逻辑,避免地狱式嵌套,也更易于理解。

28. 设计组件时,会怎么细分组件的粒度,公共组件和业务组件抽离时需要注意什么?

设计组件遵循视图和逻辑分离的原则,然后根据复用程度去细分组件的粒度。 公共组件抽离时需要注意通用性,需要更多考虑应用的场景,逻辑不能与业务产生耦合;业务组件抽离时需要注意业务数据依赖问题;

2022.03.16

27.前端的sandbox是什么原理

sandbox就是沙箱隔离,就是个沙箱环境,比如浏览器就是个沙箱环境,内部不会影响外部,或者iframe 就是沙箱 。一般 微前端方案就是把 子应用 放在 独立的沙箱环境中,互相隔绝影响,也就是留下 受控的 通信手段, 隔绝其他不受控的影响,最简单的, 子应用崩了,页面不能崩

其中:

整个qiankun框架中我们知道了什么东西:

1. qiankun是如何完善single-spa中留下的巨大缺口-————加载函数。

2. qiankun通过什么策略去加载子应用资源————window.fetch。

3. qiankun如何隔离子应用的js的全局环境————通过沙箱。

4. 沙箱的隔离原理是什么————在支持proxy中有一个代理对象,子应用优先访问到了代理对象,如果代理对象没有的值再从window中获取。如果不支持proxy,那么通过快照,缓存,复原的形式解决污染问题。

5. qiankun如何隔离css环境————shadowDOM隔离;加上选择器隔离。

6. qiankun如何获得子应用生命周期函数————export 存储在对象中,然后解构出来。

7. qiankun如何该改变子应用的window环境————通过立即执行函数,传入window.proxy为参数,改变window环境。

26.vuex的实现原理

juejin.cn/post/705032…

25.实现mergePromise函数

//实现mergePromise函数,把传进去的数组顺序先后执行,
//并且把返回的数据先后放到数组data中
const timeout = ms => new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve();
    }, ms);
});
 
const ajax1 = () => timeout(2000).then(() => {
    console.log('1');
    return 1;
});
 
const ajax2 = () => timeout(1000).then(() => {
    console.log('2');
    return 2;
});
 
const ajax3 = () => timeout(2000).then(() => {
    console.log('3');
    return 3;
});
 
function mergePromise(ajaxArray) {
    //todo 补全函数
}
 
mergePromise([ajax1, ajax2, ajax3]).then(data => {
    console.log('done');
    console.log(data); // data 为 [1, 2, 3]
});
 
// 分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]

答案:

function mergePromise(ajaxArray) {
            let result = []
            return new Promise((resolve, reject) => {
                if (ajaxArray && ajaxArray.length != 0) {
                    let ajaxTaskList = [];
                    ajaxArray.map((item, index) => {
                        //创建下载任务
                        let ajaxTask = {
                            fn: item,
                            index: index + 1,
                            task: (downloadTask) => {
                                //下载
                                downloadTask.fn().then((data) => {
                                    if (data) {
                                        result.push(data)
                                        if (downloadTask.nextTask) {
                                            downloadTask.nextTask.task(downloadTask.nextTask);
                                        } else {
                                            resolve(result)
                                        }
                                    }
                                })
                            }
                        }
                        ajaxTaskList.push(ajaxTask);
                        //将上下任务关联
                        if (index > 0) {
                            ajaxTaskList[index - 1].nextTask = ajaxTaskList[index];
                        }
                    });
                    let startTask = ajaxTaskList[0];
                    startTask.task(startTask);
                }
            })
        }

image.png

2022.03.15

24.JSON.parse(JSON.stringify(obj) ) 深拷贝对象 会出现什么问题

如果有date和函数,undefined,symbol,会变成null

23. 说说看你知道的JS的作用域和执行上下文(不限于某个方向)

作用域是指程序源代码中定义变量的区域。作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。当 JavaScript 执行一段可执行代码时,会创建对应的执行上下文。对于每个执行上下文,都有三个重要属性:

  • 变量对象:存储了在上下文中定义的变量和函数声明
  • 作用域链:当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链
  • this

作用域:juejin.cn/post/697803…

执行上下文:juejin.cn/post/697767…

22. 说说看 Promise 和 async…await 的区别和联系,如果只写async 没有await结果会怎么样?

只写async 没有await:只写 async 会被包装成 promise,只写 await 会报错

Promise 和 async…await 的区别和联系: async/await 是建立在 Promises上的,async/await相对于promise来讲,写法更加优雅。async await就是generator语法糖,可以用generator来模拟async await。

21. Object.defineProperty 是否可以监测数组的变动,如果可以的话,Vue为什么会对数组进行特殊处理

可以,对数组进行defineProperty以为着数组的长度有多长,他的每一个下标都会有自己__ob__,也就是说每一个下标都是响应式的,虽然你arr[index]可以变成响应式,但是性能浪费是非常明显的

2022.03.14

20.涉及css的性能优化有哪些?webpack拆分css和合并css的plugin分别是什么?

涉及css性能优化的操作:

(1)减少css嵌套最好别超过3层

(2)提取公共样式类

(3)减少通配符*或者类似[checked="true"]这类选择器的使用

(4)不要嵌套id选择器,id选择器是唯一的,某类选择器里再去嵌套id选择器会降低性能

(5)webpack minimize

(6)gzip压缩(或新出的brotil压缩)css文件

(7)精灵图合成icon

利用mini-css-extract-plugin抽离css把所有样式文件打包到一个类似common.css文件里然后一个link引入页面

19.fetch有哪些缺点?

  • fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。

  • fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: 'include'})

  • fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费

  • fetch没有办法原生监测请求的进度,而XHR可以

18.什么是声明式渲染?什么是命令式渲染?

  • 声明式渲染比如vue的template,你只需要写@click=xxx就可以实现对函数的监听,不需要了解底层的运行方式,只取结果就行
  • 命令式渲染就是必须要指明操作步骤,易于理解。比如 先querySelector节点,然后设置attributes和textContent,最后insertSibling或者append等操作

2022.03.11

17.谈谈闭包与即时函数的应用

立即执行函数 IIFE Immediately-Invoked Function Expressions

即时函数就是立即执行函数,文件加载后就会立即执行,可以用来避免产生全局变量或者命名冲突,执行后会销毁,内部变量也会被回收,结合闭包主要是形成一个新的作用域

应用一 - 创建临时独立作用域

var n= 0

setInterval(() => console.log(++n), 1000)复制代码

setInterval((function() {

  var n = 0

  return function() {

    console.log(++n)

  }

})(), 1000)

应用二 - 解决变量名冲突

<script src="https://lib.baomitu.com/jquery/3.6.0/jquery.min.js"></script>

<script>

  // 假设其他库占用的$

  const $ = () => console.log("Not jQuery");

  (function ($) {

    // 通过闭包还是可以限制作用域的名称

    $(document).ready(function () {

      console.log("Hello jQuery");

    });

  })(jQuery);

  $()

</script>

应用三 - 使用简洁变量名

var data {
  abc : {
    efg : 0
  } 
}
​
(function() {
  console.log(v)
})(data.abc.efg)

应用四 - 循环陷阱

const ary = [];
  for (var i = 0; i < 5; i++) {
    ary.push(function () {
      return console.log(i);
    });
  }
  ary[0]();
  ary[1]();

// 由于即时函数的参数为实参复制关系,相当于复制的现场快照
const ary = [];
for (var i = 0; i < 5; i++) {
  (function(n) {·
    ary.push(function () {
      return console.log(n);
    });
  })(i)
}
ary[0]();
ary[1]();

16.Virtual DOM 真的比操作原生 DOM 快吗?谈谈你的想法

可以从以下几点来说

1. 原生 DOM 操作 VS 通过框架封装操作。

这是一个性能 vs. 可维护性的取舍。框架的意义在于为你掩盖底层的 DOM 操 

作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。针对任何一个 benchmark,我都可以写出比任何框架更快的手动优化,但是那有什么意义呢?在构建一个实际应用的时候,你难道为每一个地方都去做手动优化吗?出于可维护性的考虑,这显然不可能。框架给你的保证是,你在不需要手动优化的情况下,我依然可以给你提供过得去的性能。 

2. React Virtual DOM (react技术栈的同学可做参考)

React 从来没有说过 “React 比原生操作 DOM 快”。React 的基本思维模式是每次有变动就整个重新渲染整个应用。如果没有 Virtual DOM,简单来想就是直接重置 innerHTML。很多人都没有意识到,在一个大型列表所有数据都变了的情况下,重置 innerHTML 其实是一个还算合理的操作... 真正的问题是在 “全部重新渲染” 的思维模式下,即使只有一行数据变了,它也需要重置整个innerHTML,这时候显然就有大量的浪费。  我们可以比较一下 innerHTML vs. Virtual DOM 的重绘性能消耗: 

ï innerHTML: render html string O(template size) + 重新创建所有 DOM 元 

素 O(DOM size) 

ï Virtual DOM: render Virtual DOM + diff O(template size) + 必要的 DOM 

更新 O(DOM change) 

Virtual DOM render + diff 显然比渲染 html 字符串要慢,但是!它依然是纯 js 层面的计算,比起后面的 DOM 操作来说,依然便宜了太多。可以看到, innerHTML 的总计算量不管是 js 计算还是 DOM 操作都是和整个界面的大小相关,但 Virtual DOM 的计算量里面,只有 js 计算和界面大小相关,DOM 操作是和数据的变动量相关的。前面说了,和 DOM 操作比起来,js 计算是极其便宜的。这才是为什么要有 Virtual DOM:它保证了 1)不管你的数据变化多少,每次重绘的性能都可以接受;2) 你依然可以用类似 innerHTML 的思路去写你的应用。 

3. MVVM VS Virtual DOM

相比起 React,其他 MVVM 系框架比如 Angular, Knockout 以及 Vue、Avalon 采用的都是数据绑定:通过 Directive/Binding 对象,观察数据变化并保留对实际 DOM 元素的引用,当有数据变化时进行对应的操作。MVVM 的变化检查是数据层面的,而 React 的检查是 DOM 结构层面的。 MVVM 的性能也根据变动检测的实现原理有所不同:Angular 的脏检查使得任何变动都有固定的 O(watcher count) 的代价;Knockout/Vue/Avalon 都采用了依赖收集,在 js 和 DOM 层面都是 O(change): 

ï 脏检查:scope digest O(watcher count) + 必要 DOM 更新 O(DOM change)

ï 依赖收集:重新收集依赖 O(data change) + 必要 DOM 更新 O(DOM change)

可以看到,Angular 最不效率的地方在于任何小变动都有的和 watcher 数量相关的性能代价。但是!当所有数据都变了的时候,Angular 其实并不吃亏。依赖收集在初始化和数据变化的时候都需要重新收集依赖,这个代价在小更新的时候几乎可以忽略,但在数据量庞大的时候也会产生一定的消耗。MVVM 渲染列表的时候,由于每一行都有自己的数据作用域,所以通常都是每一行有一个对应的 ViewModel 实例,或者是一个稍微轻量一些的利用原型继承的 "scope" 对象,但也有一定的代价。所以,MVVM 列表渲染的初始化几乎一定比 React 慢,因为创建 ViewModel / scope 实例比起 Virtual DOM 来说要昂贵很多。这里所有 MVVM 实现的一个共同问题就是在列表渲染的数据源变动时,尤其是当数据是全新的对象时,如何有效地复用已经创建的 ViewModel 实例和  DOM 元素。假如没有任何复用方面的优化,由于数据是 “全新” 的,MVVM 实际上需要销毁之前的所有实例,重新创建所有实例,最后再进行一次渲染!这就是为什么题目里链接的 angular/knockout 实现都相对比较慢。相比之下,React 的变动检查由于是 DOM 结构层面的,即使是全新的数据,只要最后渲染结果没变,  那么就不需要做无用功。 

Angular 和 Vue 都提供了列表重绘的优化机制,也就是 “提示” 框架如何有效地复用实例和 DOM 元素。比如数据库里的同一个对象,在两次前端 API 调用里面会成为不同的对象,但是它们依然有一样的 uid。这时候你就可以提示track by uid 来让 Angular 知道,这两个对象其实是同一份数据。那么原来这份数据对应的实例和 DOM 元素都可以复用,只需要更新变动了的部分。或者,你也可以直接 track by index来进行“原地复用”:直接根据在数组里的位置进行复用。在题目给出的例子里,如果angular实现加上trackbyindex 来进行 “原地复用”:直接根据在数组里的位置进行复用。在题目给出的例子里,如果 angular 实现加上 track by index 的话,后续重绘是不会比 React 慢多少的。甚至在 dbmonster 测试中,Angular 和Vue 用了 track by $index 以后都比 React 快: dbmon (注意 Angular 默认版本无优化,优化过的在下面) 

顺道说一句,React 渲染列表的时候也需要提供 key 这个特殊 prop,本质上和 track-by 是一回事。 

4. 性能比较也要看场合

在比较性能的时候,要分清楚初始渲染、小量数据更新、大量数据更新这些不同的场合。Virtual DOM、脏检查 MVVM、数据收集 MVVM 在不同场合各有不同的表现和不同的优化需求。Virtual DOM 为了提升小量数据更新时的性能,也需要针对性的优化,比如 shouldComponentUpdate 或是 immutable data。 

ï 初始渲染:Virtual DOM > 脏检查 >= 依赖收集 

ï 小量数据更新:依赖收集 >> Virtual DOM + 优化 > 脏检查(无法优化) > Virtual DOM 无优化ï 

大量数据更新:脏检查 + 优化 >= 依赖收集 + 优化 > Virtual DOM(无法/无需优化)>> MVVM 无优化 

不要天真地以为 Virtual DOM 就是快,diff 不是免费的,batching 么 MVVM 也能做,而且最终 patch 的时候还不是要用原生 API。在我看来 Virtual DOM 真 正的价值从来都不是性能,而是它

  1. 为函数式的 UI 编程方式打开了大门; 2) 可以渲染到 DOM 以外的 backend,比如 ReactNative。

5. 总结

以上这些比较,更多的是对于框架开发研究者提供一些参考。

主流的框架 + 合理的优化,足以应对绝大部分应用的性能需求。如果是对性能有极致需求的特殊情况,其实应该牺牲一些可维护性采取手动优化:比如 Atom 编辑器在文件渲染的实现上放弃了 React 而采用了自己实现的 tile-based rendering;又比如在移动端需要 DOM-pooling 的虚拟滚动,不需要考虑顺序变化,可以绕过框架 的内置实现自己搞一个

15.Vue3.0 是如何变得更快的?

从以下几点来说

  1. diff 方法优化 :Vue2.x 中的虚拟 dom 是进行全量的对比。

  2. Vue3.0 中新增了静态标记(PatchFlag):在与上次虚拟结点进行对比的时候,值对比带有 patch flag 的节点,并且可以通过 flag 的信息 得知当前节点要对比的具体内容化。hoistStatic 静态提升 

Vue2.x : 无论元素是否参与更新,每次都会重新创建。 

Vue3.0 : 对不参与更新的元素,只会被创建一次,之后会在每次渲染时候被不停的复用。 

  1. cacheHandlers 事件侦听器缓存  默认情况下 onClick 会被视为动态绑定,所以每次都会去追踪它的变化但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可

14.请简述你对 react 的理解

React 起源于 facebook,react 是一个用于构建用户界面的 js 库 

特点: 声明式设计:react 采用范式声明,开发者只需要声明显示内容,react 就会自动完成 

高效:react 通过对 dom 的模拟(也就是虚拟 dom),最大限度的减少与 dom 的交互 

灵活:react 可以和已知的库或者框架很好配合 

组件:通过 react 构建组件,让代码更容易复用,能够很好应用在大型项目开发中,把页面功能拆分成小模块 每个小模块就是组件 

单向数据流:react 是单向数据流,数据通过 props 从父节点传递到子节点,如果父级的某个 props 改变了,react 会重新渲染所有的子节点

2022.03.10

13. 当position跟display、overflow、float这些特性相互叠加后会出现什么情况?

这个要考虑一个优先级~

-- 收割机笔记里有写到的,要打的字太多了这里就不多叙述了

12. 怎么取消一个ajax请求?

使用vue等框架的话,就会使用axios发送ajax请求。 在axios中是通过axios.CancelToken.source() 方法取消请求 cancelToken

11. Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题 ?

set方法首先判断进来的是数组还是对象,数组的话就用我们aop切片改造后的splice来触发更新视图,对象的话还是将新的属性利用defineReactive做一个数据劫持,对象的话还需要判断对象本身是不是响应式对象 如果不是直接赋值;最后利用对象自身的dep触发渲染watcher实现视图更新

什么是 aop切片: aop切片编程就是将原来的方法又包了一层,先保存原方法的执行结果,对执行结果进行加工/或者做一些其他的处理逻辑,这样调用原方法的时候会走到这个逻辑处理

2022.03.09

10. 说下 fiber

juejin.cn/post/694389…

9. 简述 vue2 和 vue3 diff 区别,并且这样做的好处是什么,v3 为什么切换成了最长递增子序列

image.png

8. 说下 infer作用

infer 主要是用于类型推断,可以通过 infer 去推断 类型的部分值,推断后的结果,会被保存到 右边的变量中,从而在后续判断进行使用

image.png

7. 说下 逆变和斜变,如何判断?

juejin.cn/post/685551…

2022.03.08

6. 描述一下 computed和watch的区别,为什么computed是lazy的。

  • computed表示计算属性,依赖其他值,有缓存(只要不触发就一直保留上次的计算结果),可以设置getter和setter,主要用于模板渲染,某个值依赖其他响应式对象或其他计算属性可以使用。在缓存的变化时才会去获取最新的值,在响应式依赖没有发生改变时不会重新求值

  • watch就是监听,监听到值变化后执行回调,可以在回调中做一些逻辑操作,适用于观测某个值变化后去完成一些业务逻辑。

computed lazy主要是在initComputed的时候创新Watcher实例定义的一个option,这个lazy设置为true,可以在Watcher构造函数中标识一个dirty属性,后续根据和这个dirty属性判断computed依赖的值是否改变来决定是否需要重新计算;还有就是根据这个lazy判断是否是computed的实例来决定初始值需不需要调用get方法。

5. 描述一下bind的实现原理,以及特点

实现:

Function.prototype.mybind = function(fn, ...args){
    if(typeof this !== 'function') return// 类型判断
    let content= this// 把this存起来
    return fanction Fn(){
        return content.apply(this instanceof Fn? this: content,[...args,...arguments])
    }
}

特点:

  1. 不会立即执行,返回的是一个函数;
  2. 如果是通过new调用的话,那么this并不会指向bind的第一个参数,而是指向新实例
  3. 一旦确定了this值,就不会改

4. 作用域插槽的实现原理

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

image.png

2022.03.07

3. nextTick的原理

采用微任务优先的方法调用异步方法去执行被nextTick包装的方法,用了优雅降级,

优先级: (微任务优先)Promise -> MutationObserver -> (到这里已经变成了宏任务了)setImmediate -> setTimeout。

然后多次的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();
  }
}


2. 代码输出题

// eg1
console.log(foo)
{
 console.log(foo)
 function foo () { }
 foo = 1
 console.log(foo)
}
console.log(foo)
undefined
[Function: foo]
1
[Function: foo]
// eg2
console.log(foo)
{
 console.log(foo)
 function foo () { 1 }
 console.log(foo)
 foo = 1
 console.log(foo)
 function foo () { 2 }
 console.log(foo)
}
console.log(foo)
undefined
[Function: foo]
[Function: foo]
1
1
1
// eg3
var a = 1
function func (a, b = function anonymous1 () { a = 2 }) {
 a = 3
 b()
 console.log(a)
}
func(5)
console.log(a)
2
1


// eg4
var a = 1
function func (a, b = function anonymous1 () { a = 2 }) {
 var a = 3
 b()
 console.log(a)
}
func(5)
console.log(a)
3
1

// eg5
var a = 1
function func (a, b = function anonymous1 () { a = 2 }) {
 var a = 3
 b = function anonymous2 () { a = 4 }
 b()
 console.log(a)
}
func(5)
console.log(a)
4
1

2022.03.04

1.说说你理解的模块化

1.模块化的概念

首先是为了实现让变量方法更容易维护,有自己的一个作用域,不会污染全局。

2.模块化规范发展史

  • 最简单的模块化就是IIFE立即执行函数,也是一个闭包,有自己的私有变量;

  • 然后就是模块化规范的制定,比如commonjs,同步加载,值拷贝,requrie引入,module.export =value / module.xx = value导出,只能作用在nodejs环境;

  • amd规范,异步加载,require引入,defind定义,前置依赖浏览器环境;

  • cmd规范在amd基础上实现,变成了就近依赖,也是浏览器环境;

  • 然后就是umd规范,最大的特点就是支持nodejs环境和浏览器环境,实现原理就是判断环境然后使用amd/commonjs规范,如果都不行就挂到window上;

  • 最后是我们常见的esmodule,import导入,export/export default导出,动态加载,异步,值引用,支持两个环境。

聊着聊着 treeShaking 然后 webpack - 。-

  1. amd前置依赖和cmd后置依赖怎么区分,你能解释下他们的依赖关系吗?
// CMD
define(function(require, exports, module) {   
    var a = require('./a')   
    a.doSomething()    
    var b = require('./b')   
    b.doSomething()    
    ... 
})
// AMD 
define(['./a', './b'], function(a, b) {  // 依赖必须一开始就写好
    a.doSomething()
    ...
    b.doSomething()
    ...
})

4.umd支持node环境和浏览器了还要弄一个esmodule呢?

umd难配置 但是体积小 esm有更强大的功能比如tree-shaking,浏览器可以直接运行~