Hello,大家好,我是disguiseFish,坚持早起每天每日一题有一段时间啦,收获良多,每天会做些题来储备自己的知识量,在这里把它们分享记录下来,我们一起学习进步吧! 这是一个日更帖哟!每日做的题都贴出了对应的理解,或许你还有其他理解~欢迎来骚扰哟!
2022.04.24
100.单页面应用部署时怎么解决跨域问题
其实就是问跨域,有很多种方式,比如用nodejs中间件,nginx,cors等等不详细说了
99. 怎么配置负载均衡,CDN,及缓存。
-
配置CDN:就是后端那边配好了后给你个域名,,你根据需要加呗,Nginx 主要的负载均衡策略有以下四种:轮询策略,最少连接数负载均衡策略,ip-hash 负载均衡策略,权重负载均衡策略;
-
看是要配置什么缓存,如果是浏览器缓存加在请求头,http缓存,文件图片缓存,proxy_cache等等,我就不一一说了,老生常谈的问题
2022.04.22
98. 只适配了pc的管理系统后期突然要求适配ipad等移动端的解决方案?
也就是监听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")
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
76. 单点登录原理,具体的流程是怎样的
SSO
75. 公共组件需要考虑哪些因素
API 好用,文档友好,可迭代
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 要快的
72 说说对React事件机制的理解?(react)
2022.04.11
71. 常见的图片格式及使用场景 (CSS)
苹果图片大多是heic
70. 浏览器是如何对 HTML5 的离线储存资源进行管理和加载?(HTML)
service worker
69. 手写实现将虚拟 Dom 转化为真实 Dom (JS)
输入代码:
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的异同和各自优缺点(技术选型时如何考虑)
| vue | react |
|---|---|
| 支持模板语法 | |
| 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。
64.对ts的理解
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. 定时器的清除
最近有小伙伴提出,有时候能看到以上两种清除定时器的方式,他们都可以清除吗?
其实clearTimeout是官方提供的关闭定时器的方式,而赋值null只是给这个变量换了个地址,”凭啥你换个地址我就要停止“ 所以~ 赋值null没用!!再给大家实践一下
一般最好先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中普通插槽和作用域插槽实现的区别
-
普通插槽是在父组件编译和渲染阶段生成 vnodes,所以数据的作用域是父组件实例,子组件渲染的时候直接拿到这些渲染好的 vnodes。
-
作用域插槽,父组件在编译和渲染阶段并不会直接生成 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.浏览器包含哪些进程,浏览器渲染进程里包含哪些主要的线程?
-
浏览器进程包括插件进程,渲染进程,网络进程,浏览器主进程,gpu进程等
-
渲染进程包括 定时器线程,浏览器渲染,异步请求线程,事件触发等;
进程间 数据交互要通过管道或者ipc,线程间数据交互是共享当前进程的
53.哪些操作会造成内存泄漏?
- 使用了未声明的变量,从而意外的创建了一个全局变量,而使得这个变量在内存中一直无法回收
- 设置了setInterval 忘记取消,如果在循环函数中对外部变量有引用时,那么这个变量会被留在内存中
- 获取了一个DOM元素的引用,而后面这个元素被删除,但是因为我们的引用 导致一直无法被回收
- 不合理的使用闭包,导致其中的变量一直留在内存中
// 不合理的使用闭包
function outer() {
var largeObject = {longStr: Date.now() + Array(10000000000).join('*')};
function help() {
largeObject;
}
return function () {};
}
var inner = outer();
inner();
闭包里内存泄漏的场景:
1. 嵌套的函数中是否有使用该变量。
2. 嵌套的函数中是否有直接调用eval。
3. 是否使用了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()) }
- 尾递归: 尾递归就是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源码:
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.用一个函数来实现模板字符串中变量的替换
参考:
33. 如何检测到对象中有循环引用
try catch包裹JSON.stringify(obj), 有报错就存在对象引用,刷了下面那个算法就知道了
拓展:找出循环的那个节点
其中可以从JSON.stringfiy还有第二第三个参数去尝试一下~
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的实现原理
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);
}
})
}
2022.03.15
24.JSON.parse(JSON.stringify(obj) ) 深拷贝对象 会出现什么问题
如果有date和函数,undefined,symbol,会变成null
23. 说说看你知道的JS的作用域和执行上下文(不限于某个方向)
作用域是指程序源代码中定义变量的区域。作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。当 JavaScript 执行一段可执行代码时,会创建对应的执行上下文。对于每个执行上下文,都有三个重要属性:
- 变量对象:存储了在上下文中定义的变量和函数声明
- 作用域链:当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链
- this
执行上下文: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 的话,后续重绘是不会比 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 真 正的价值从来都不是性能,而是它
- 为函数式的 UI 编程方式打开了大门; 2) 可以渲染到 DOM 以外的 backend,比如 ReactNative。
5. 总结
以上这些比较,更多的是对于框架开发研究者提供一些参考。
主流的框架 + 合理的优化,足以应对绝大部分应用的性能需求。如果是对性能有极致需求的特殊情况,其实应该牺牲一些可维护性采取手动优化:比如 Atom 编辑器在文件渲染的实现上放弃了 React 而采用了自己实现的 tile-based rendering;又比如在移动端需要 DOM-pooling 的虚拟滚动,不需要考虑顺序变化,可以绕过框架 的内置实现自己搞一个
15.Vue3.0 是如何变得更快的?
从以下几点来说
-
diff 方法优化 :Vue2.x 中的虚拟 dom 是进行全量的对比。
-
Vue3.0 中新增了静态标记(PatchFlag):在与上次虚拟结点进行对比的时候,值对比带有 patch flag 的节点,并且可以通过 flag 的信息 得知当前节点要对比的具体内容化。hoistStatic 静态提升
Vue2.x : 无论元素是否参与更新,每次都会重新创建。
Vue3.0 : 对不参与更新的元素,只会被创建一次,之后会在每次渲染时候被不停的复用。
- 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
9. 简述 vue2 和 vue3 diff 区别,并且这样做的好处是什么,v3 为什么切换成了最长递增子序列
8. 说下 infer作用
infer 主要是用于类型推断,可以通过 infer 去推断 类型的部分值,推断后的结果,会被保存到 右边的变量中,从而在后续判断进行使用
7. 说下 逆变和斜变,如何判断?
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])
}
}
特点:
- 不会立即执行,返回的是一个函数;
- 如果是通过new调用的话,那么this并不会指向bind的第一个参数,而是指向新实例
- 一旦确定了this值,就不会改
4. 作用域插槽的实现原理
作用域插槽: 编译的时候并不会渲染slot,会把它解析成函数,当函数渲染的时候才会调用这个函数在子组件下进行渲染,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。
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 - 。-
- 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,浏览器可以直接运行~