这里写自定义目录标题
- 什么是链表,谈谈对链表的理解
- 什么是队列,谈谈对队列的理解
- 什么是栈,谈谈对栈的理解
- 什么是线性表,谈谈对线性表的理解
- react怎么捕获异常
- 工作中比较自豪的事
- saas是什么
- cdn是什么
- 输入域名到页面渲染的过程
- dns解析地址发生了什么
- 加入cdn输入域名发生了什么
- http和https的区别
- https是怎么加密的
- 了解什么是对称加密、什么是非对称加密吗
- 对加班是什么样的看法
- 对下个工作的期望是很么
- 什么是hooks
- useMemo和useCallback的区别
- useMemo的作用是什么
- 函数式组件的优势
- react怎么优化
- 什么是服务端渲染,什么是客户端渲染
- 怎么做一个项目发布
- 怎么做小数的精度
- 怎么预防跨站脚本攻击
- http的缓存方式有什么
- cache-control的值有哪些
- 如何让JavaScript不访问cookie
- 常见的8种算法的优劣
- 自定义排序所选择算法的理由
- 常用的设计模式
- 前端如何埋点,埋点作用
- webpack中的map文件是干啥的
- react中的super的作用是什么,和原型链上的继承有什么不同
- 数组中的sort方法是采用什么算法实现的
- 类型判断的方式有哪些
- instanceof 和 Object.prototype.toString.call()谁更好
- webpack中的proxy,在请求的时候请求是否发送到了服务器
- 状态码
- 状态管理redux
- redux中间件chunk、soga
- for和forEach的区别
- setState做了什么操作
- async/await的原理
- 多个async/await是否会阻塞
- webpack4 和webpack5的区别
- 判断数组的方式
- 子元素浮动父元素高度塌陷
- jquery和vue、react直接修改文本,谁的速度更快
- 浅拷贝的实现方式
- eventLoop
- 防抖和节流的区别和使用场景
- BFC是什么
- 为什么要采用前端框架:vue或react
- 定时器是由什么操作的
- 页面渲染由什么操作,数据量太大页面卡顿的原因
- setTimeout时间到了会立即执行吗?
- 宏任务和微任务
- js引擎相关问题
- 路由模式
- hash路由和history路由的区别
- 箭头函数和普通函数的区别
- 原型和原型链
- BOM和DOM是什么
- diff算法的实现原理,采用的算法
- 小程序的正式版本库和草稿库是干嘛的
- react的三大diff策略
- setState什么时候是同步,什么时候是异步
- react17和18的特性是什么
- Stack Reconciler和Fiber Reconciler的去区别
- react hooks舍弃了哪几个生命周期
- vue的生命周期
- hooks的生命周期有哪些
- useState怎么去做回调
- webpack配置
- webpack怎么配置去掉console
- 常用的plugin配置
- 抽离公共组件需要注意什么
- 现在es的版本是多少
- promise 、generator 、 async/await 分别是多少版本号的es产物
- transform 、translate、transition的区别
- 数组map的方法有什么缺点?
- 事件委托是什么?有什么好处?
- 谈谈对this的理解,以及使用场景
- 数组的哪些方法会更改原数组
- 混合端开发的时候,怎么做尺寸适配?
- 前端视频流处理相关
- 浏览器插件怎么实现?
- 知道浏览器端的底层api?
- serverless在前端后端的实现?
- redux的实现原理
- 如何做单元测试
- vue.use 和 vue.component的区别和作用
- vue diff算法
- vue 路由守卫有哪些?分别是什么作用
- vue父组件怎么监听子组件的生命周期
- 如何主动中断promise
- vue中的装饰符
- 怎么做图片预览
- 系统怎么实现一个广告一天只出现一次
- computed和watcher的区别
- vue传参的方式
- 什么是闭包
什么是链表,谈谈对链表的理解
- 链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是根据链表的指针地址去实现的,由一系列节点(指针地址)组成,可动态生成
- 节点包括两个部分:存储数据源的数据域,存储指向下一个节点的指针地址的指针域
- 相对线性表顺序结构,操作更为复杂
- 链表分为单链表、双链表、单向循环链表、双向循环链表
- 链表的作用是:实现元素的存储按一定顺序存储,允许在任意位置插入和删除;包括单向节点、双向节点、循环节点
- 链表和数组的区别:数组是使用一块连续的内存空间去存储数据,实现数据的增删不好操作;链表是使用多个不连续的内存空间去存储数据,可以节省很多内存资源,对数据的增删比较方便
什么是队列,谈谈对队列的理解
队列是一种特殊的线性表,是一种对数据的存和取有严格要求的线性存储,在两端可以操作数据。其特点的先进先出
什么是栈,谈谈对栈的理解
栈是一种线性表,其特点是先进后出
什么是线性表,谈谈对线性表的理解
将具有“一对一”关系的数据“线性”地存储到物理空间中,这种存储结构就称为线性存储结构(简称线性表)
react怎么捕获异常
- 在16版本后,通过componentDidCatch取捕获;
- 可以使用window.onerror全部捕获异常;
- 可以使用try catch捕获;
项目内进行异常判断很少,这是很危险的行为,每次接口出现不符合预期的数据都会出现空白页面的现象,在未来的项目内,异常判断也要加入规范内,是项目的一个很重要的组成部分,最起码的全局有异常捕获,其次是每个具体的页面内有判断是否存在,类型是否正确的操作
工作中比较自豪的事
saas是什么
saas 全称soft as a service 即:软件就是服务的意思。传统软件交付都会有具体的安装包、代码等;而SaaS平台供应商将应用软件统一部署在自己的服务器上,客户可以根据工作实际需求,通过互联网向厂商定购所需的应用软件服务,而改用按定购的服务多少和时间长短向厂商支付费用,且无需对软件进行维护,俗称在线软件。
cdn是什么
CDN是指内容分发网络。CDN是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡,内容分发,调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率
输入域名到页面渲染的过程
输入域名、dns解析地址、发送tcp请求、发送http请求、服务器处理请求、服务器返回数据、浏览器端接收数据、完成渲染
dns解析地址发生了什么
将域名指向真实的ip
加入cdn输入域名发生了什么
在dns解析域名步骤,实现了解析别名。
- 浏览器调用域名解析库对域名进行解析,由于CDN对域名解析过程进行了调整,所以解析函数库一般得到的是该域名对应的CNAME记录,为了得到实际IP地址,浏览器需要再次对获得的CNAME域名进行解析以得到实际的IP地址;在此过程中,使用的全局负载均衡DNS解析,如根据地理位置信息解析对应的IP地址,使得用户能就近访问。
- 此次解析得到 CDN缓存服务器 的IP地址,浏览器在得到实际的IP地址以后,向缓存服务器发出访问请求。
- 存服务器根据浏览器提供的要访问的域名,通过Cache内部专用DNS解析得到此域名的实际IP地址,再由缓存服务器向此实际IP地址提交访问请求。
- 缓存服务器从实际IP地址得得到内容以后,一方面在本地进行保存,以备以后使用,二方面把获取的数据返回给客户端,完成数据服务过程。
- 客户端得到由缓存服务器返回的数据以后显示出来并完成整个浏览的数据请求过程。
总结:实现CDN的主要技术手段是高速缓存、镜像服务器。可工作于DNS解析或HTTP重定向两种方式,通过Cache服务器,或异地的镜像站点 完成内容的传送与同步更新 。
http和https的区别
- https需要到ca申请证书,一般需要费用
- http是超文本传输,信息是明文传输,https则是具有安全性的ssl加密传输协议
- http和https采用完全不同的传输方式,用的端口也不一样,http是80,https是443
- http的连接时无状态的,https协议是由ssl+http协议构建的可进行加密传输、身份认证的网络协议
https是怎么加密的
对传输内容进行一定的规则进行封装
了解什么是对称加密、什么是非对称加密吗
- 对称加密:是指加密和解密都是用同一组密钥;
- 非对称加密:是指使用一对非对称的密钥,一把叫私有密钥,一把叫公有密钥,使用共有密钥加密,使用私有密钥解密。非对称加密的特点是信息传输一对多,服务器只需要维持一个私钥就能够和多个客户端进行加密通信
对加班是什么样的看法
对下个工作的期望是很么
什么是hooks
Hooks是一个新的React特性提案,组件尽量写成纯函数,如果需要外部React特性(比如状态管理,生命周期),就用钩子把外部特性"钩"进来,通常函数名字都是以use开头
- hooks产生的背景
- 跨组件复用stateful logic十分困难 使用Hooks,你可以在将含有state的逻辑从组件中抽象出来,这将可以让这些逻辑容易被测试。同时,Hooks可以帮助你在不重写组件结构的情况下复用这些逻辑。
- 复杂的组件难以理解 Hooks允许您根据相关部分(例如设置订阅或获取数据)将一个组件分割成更小的函数,而不是强制基于生命周期方法进行分割
- 不止是用户,机器也对Classes难以理解 Hooks让你可以在classes之外使用更多React的新特性
常见的hooks:
- useState
- useEffect
- useLayoutEffect
- useContext:直接获取祖先元素通过createContext创建的context
- useReducer
- useMemo
- useCallback
- useRef:获取ref
useMemo和useCallback的区别
- useMemo:缓存数据
- useCallback: 缓存函数
useMemo的作用是什么
函数式组件的性能优化
函数式组件的优势
react怎么优化
- 编译阶段:Babel优化,webpack优化,
- include 或 exclude 限制 loader 范围
- happypack多进程编译
- 缓存babel编译过的文件
- tree Shaking 删除冗余代码
- 按需加载,按需引入
- 路由阶段:实现路由懒加载(asyncRouter)
- 渲染阶段:受控组件颗粒化,独立请求服务渲染单元
- 颗粒化控制可控性组件:类似输入单元,一直输出,做到使用useMemo去缓存
- 建立独立的请求渲染单元
- PureComponent 和 React.memo
- shouldComponentUpdate
- immetable.js
- 细节优化:
- 绑定事件尽量不要使用箭头函数
- 无状态组件hooks-useMemo 避免重复声明
- 懒加载 Suspense 和 lazy
- 学会使用的批量更新
- 合并state
- useMemo React.memo隔离单元
- ‘取缔’state,学会使用缓存。
- useCallback回调
- 状态管理:不变的数据,多个页面可能需要的数据,放在状态管理中,对于时常变化的数据,我们可以直接请求接口
- 海量数据源,长列表优化:
- 时间分片
- 虚拟列表
什么是服务端渲染,什么是客户端渲染
- 服务端渲染:SSR,服务器端在返回html之前,在html特定的区域特定的符号里用数据填充,再给客户端,客户端只负责解析HTML
- 优点:前端耗时少,首次渲染快,利于SEO
- 缺点:网络数据传输大,占用服务器资源较多,用户体验差,不易维护,前端修改css、html后端也要修改
- 使用node框架去构建项目,比如exporess
- 执行流程:浏览器加载html文件 -> 服务端装填好内容 -> 返回浏览器渲染
- 客户端渲染:前端渲染,起源于js的兴起,ajax让前端渲染更加成熟,前端专注于ui,后端专注于逻辑,真正意义上实现了前后端的分离,通过约定好的API来交互,后端提供数据,前端根据数据生成DOM插入HTML页面。初次渲染大都是将原html中的数据标记{undefined{}}替换鼠标右击查看源码,页面代码不可以在源代码中看到,性能消耗在客户端
- 优点:减少服务端压力,可以实现局部刷新,无需每次都请求完成页面,体验更好
- 缺点:前端耗时多,首屏渲染慢,不利于SEO,爬虫无法看到完整代码
- 使用框架脚手架去构建项目,如create-react-app
- 执行流程:浏览器加载html文件 -> 浏览器下载js文件 -> 浏览器运行vue代码 -> 渲染页面
怎么做一个项目发布
怎么做小数的精度
- 小数之间加法:对各个小进行10000倍的放大,相加后再除以10000
- 使用toFixed()方法:效率貌似没有第一种高,这里返回的值一定有两位小数,要么后台用double接收,要么用parseInt()转换为整数
- 自己写算法:转为字符串、进行一些截取操作,最后还原
- toPrecision:返回指定长度四舍五入后的字符串
怎么预防跨站脚本攻击
- 一些开源的防御XSS攻击的代码库
- 将前端输出数据进行转义
- 将输出的字符串中的(反斜杠)进行转义
- 使用cookie的HttpOnly属性
- 多过滤,不要相信用户输入的所有的数据,对核心数据进行加密
http的缓存方式有什么
- 强制缓存
- expires:response header中有过期时间
- cache-control:max-age
- 协商缓存
- Etag:是上一次response header中的唯一标识,前后比较
- if-modified-since:会与last-modified做比较
cache-control的值有哪些
- 请求头
- max-age
- max-stale
- min-fresh
- no-cache
- no-store
- no-transform
- only-if-cached
- 响应头
- must-revalidate
- no-cache
- no-store
- no-transform
- public
- private
- proxy-revalidate
- max-age
- s-maxage
如何让JavaScript不访问cookie
设置cookie的HttpOnly为false
常见的8种算法的优劣
插入排序
- 时间复杂度:O(n ^ 2)
- 原理:两次循环
希尔排序
- 时间复杂度:最坏是O(n ^ 2), 最好是O(n ^ 1.3)
- 原理:希尔排序将插入排序作为它的子进程,它的核心是一个叫步长(gap)的概念,这个步长可以理解为一个类似疏密程度的概念。它共有3层循环,外层是以步长为循环,一般取数组的一半,循环一次再除一半,中层和里层就是插入排序的操作,不过不是跟前一项比,是跟当前索引减去步长后的那一项比。说到这里就可以理解为什么我说步长是类似疏密程度的概念,当步长不断除于2,变得越来越小,直到零停止循环,这个过程中,插入排序的比较项间越来越近,逐渐数组被排列出来
冒泡排序
- 时间复杂度:O(n ^ 2)
- 原理:两次循环
快速排序
- 时间复杂度:最坏是O(n ^ 2), 最好是O(nLogn)
- 原理:快速排序也是运用了分组递归的思想,首先找到这个分组的分界点(一般为当前数组首位),然后将这个分界点在数组中进行循环比较,最后让它回到它正确的位置,以此分界,分为两个数组递归下去,在这样的过程中逐渐完成排序
选择排序
- 时间复杂度:O(n ^ 2)
- 原理:两次循环
堆排序
- 时间复杂度:O(nlogn)
- 原理:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
归并排序
- 时间复杂度:O(nLogn)
- 原理:归并算法可以分为两部分,第一部分就是将一个数组两两分开,递归进行,直到分到只剩下1个,这时候进行第二部分,也就是核心部分,将每个单独项与邻近项再两两归并,并且在归并后进行一个排序,最后不停往回归并,直到归并成一个有序的数组
基数排序
- 时间复杂度:O(n+k),其中k为桶的数量。一般来说n>>k,因此额外空间需要大概n个左右
- 原理:将整数按位数切割成不同的数字,然后按每个位数分别比较
自定义排序所选择算法的理由
常用的设计模式
原则:
- 单一原则:一个对象、方法只做一件事。应用的设计模式有代理模式、迭代器模式、单例模式和装饰者模式
- 最少知识原则:减少对象之间的联系。应用的设计模式有中介者模式和外观模式
- 开放-封闭原则:可扩展类、模块、函数等软件实体,但不能修改原体
- 接口和面向对象编程
常见的设计模式:
- 单列模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点
全局变量不是单例模式,但是在JavaScript中,会把全局变量当作单例来使用
// 通用的惰性单列
const getSingle = function(fn) {
let result;
return funtion() {
return result || (result = fn.apply(this, arguments))
}
}
// 代理实现单例模式
const proxySingle = (function() {
let instance;
return function(html) {
if (!instance) {
instance = new CreateDiv(html);
}
return instance;
}
})
- 策略模式:定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换
// 计算奖金
const calculateBonus = function (level, salary) {
if (level === 'S') {
return salary * 4
}
if (level === 'A') {
return salary * 3
}
if (level === 'B') {
return salary * 2
}
}
- 代理模式:为一个对象提供一个代用品或占位符,以便控制它的访问。分保护代理和虚拟代理两种
保护代理:能被代理拒绝
虚拟代理:把一下开销很大的处理,延迟到真正需要它的时候才处理
// 保护代理 比如xiaomin送花给A,让B代理
Flower = function() {}
var xiaoming = function(target) {
sendFlower: function() {
var flower = new Flower()
target.receiveFlower(flower)
}
}
var A = function() {
receiveFlower: function(flower) {
console.log('receive: ' + flower)
}
}
xiaoming.sendFlower(A)
// 让B代理
var B = {
receiveFlower: function(flower) {
A.listenGoodMood(function(){
A.receiveFlower(flower)
})
}
}
var A = {
receiveFlower:function(flower) {
console.log('receive: ' + flower)
}
listenGoodMood: function(fn) {
setTimeout(() => {
fn()
}, 10000)
}
}
xiaoming.sendFlower(B)
// 虚拟代理 图片加载
var myImage = (function() {
var imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc:function(src) {
imgNode.src = src
}
}
})
myImage.setSrc('https://...')
var proxyImage = (function() {
var img = new Image
img.onload = function() {
myImage.setSrc(this.src)
}
return {
setSrc:function(src) {
myImage.setSrc('https://')
img.src = src
}
}
})
- 迭代器模式
- 发布-订阅模式
- 命令模式
- 组合模式
- 模板方法模式
- 享元模式
- 职责链模式
- 中介者模式
- 装饰者模式
- 状态模式
- 适配器模式
- 外观模式
前端如何埋点,埋点作用
埋点是什么:是网站分析的一种常用的数据采集方法。我们主要用来采集用户行为数据(例如页面访问路径,点击了什么元素)进行数据分析,从而让运营同学更加合理的安排运营计划
埋点类型:
- 手动埋点:手动代码埋点比较常见,需要调用埋点的业务方在需要采集数据的地方调用埋点的方法。优点是流量可控,业务方可以根据需要在任意地点任意场景进行数据采集,采集信息也完全由业务方来控制。这样的有点也带来了一些弊端,需要业务方来写死方法,如果采集方案变了,业务方也需要重新修改代码,重新发布。
- 可视化埋点:可是化埋点是近今年的埋点趋势,很多大厂自己的数据埋点部门也都开始做这块。优点是业务方工作量少,缺点则是技术上推广和实现起来有点难(业务方前端代码规范是个大前提)。阿里的活动页很多都是运营通过可视化的界面拖拽配置实现,这些活动控件元素都带有唯一标识。通过埋点配置后台,将元素与要采集事件关联起来,可以自动生成埋点代码嵌入到页面中。
- 无埋点:无埋点则是前端自动采集全部事件,上报埋点数据,由后端来过滤和计算出有用的数据,优点是前端只要加载埋点脚本。缺点是流量和采集的数据过于庞大,服务器性能压力山大,主流的 GrowingIO 就是这种实现方案。
webpack中的map文件是干啥的
解决打包后无法查看源码的问题
- 打包后产生后缀名为.map的文件是由于配置了sourcemap选项生成的,打包后的文件不容易找到出bug对应的源代码的位置,sourcemap就是来帮我们解决这个问题的,有了map就可以像未压缩的代码一样,准确的输出是哪一行哪一列有错。
- 是做一个对源文件的映射
react中的super的作用是什么,和原型链上的继承有什么不同
- constructor方法是一个特殊的方法,这种方法用于创建和初始化一个由class创建的对象。 一个构造函数可以使用 super关键字来调用一个父类的构造函数
数组中的sort方法是采用什么算法实现的
不同浏览器引擎的sort排序算法不同
浏览器 | 使用的 JavaScript 引擎 | 排序算法 | 源码地址 |
---|---|---|---|
Google Chrome | V8 | 插入排序和快速排序 | sort 源码实现 |
Mozilla Firefox | SpiderMonkey | 归并排序 | sort 源码实现 |
Safari Nitro | (JavaScriptCore ) | 归并排序和桶排序 | sort 源码实现 |
Microsoft Edge 和 IE(9+) | Chakra | 快速排序 | sort 源码实现 |
从sort实现代码和注释中可以看出,sort使用的是插入排序和快速排序结合的排序算法。数组长度不超过10时,使用插入排序。长度超过10使用快速排序。在数组较短时插入排序更有效率这一步最重要的是QuickSort,从代码和注释中可以看出sort使用的是插入排序和快速排序结合的排序算法。数组长度不超过10时,使用插入排序。长度超过10使用快速排序。在数组较短时插入排序更有效率
类型判断的方式有哪些
- typeof无法区分null和object
- instanceof只能判断对象是否存在于目标对象的原型链上
- constructor不能判断undefined和null
- Object.prototype.toString.call()不能区分谁是谁的实例
instanceof 和 Object.prototype.toString.call()谁更好
判断数组的方法:isArray、instanceof、constructor、typeof、Object.prototype.toString.call()
- instanceof:能检测对象(包括自定义实例化对象)和所有基本类型,不能检测iframes
- Object.prototype.toString.call():对象(不包括自定义实例化对象)和所有基本类型,能检测iframes
webpack中的proxy,在请求的时候请求是否发送到了服务器
- 基本行为就是接收客户端发送的请求后转发给其他服务器
- 其目的是为了便于开发者在开发模式下解决跨域问题(浏览器安全策略限制)
- 想要实现代理首先需要一个中间服务器,webpack中提供服务器的工具为webpack-dev-server
状态码
- 2**:操作被成功接受并处理
- 3**:重定向,需要进一步完成操作
- 4**:客户端错误,请求包含语法错误或无法完成请求
- 5**:服务器错误,服务器在处理请求的过程中发生了错误
- 400:客户端请求的语法错误,服务器无法理解
- 401:请求要求用户的身份认证
- 403:接收到请求,但是拒绝执行
- 405:请求方法错误
- 500:服务器内部错误,无法完成请求
- 502:工作网关或者代理执行请求时,接受到了一个无效的反应
- 503:系统超载或维护,暂时无法响应处理客户端请求
- 504:网关请求超时
- 505:http协议版本不支持
状态管理redux
- 单一对象,单一store状态树的形式存储数据。
- 多个reducer来编辑action 通过action对象修改 store,共同维护一个根store。
- redux就是纯函数,纯函数,纯函数,重要的事情说三遍。
- 纯函数作为reducer也就是action返回新的state,更新state,这个是约定。
- 中间件的嵌套要保证最后一个中间件是返回的state对象,然后用这些函数处理并返回对象。
三大原则
- 单一数据源
- state只读
- 使用纯函数进行修改
写法:
import { createStore } from 'redux';
/*
* 这是一个 reducer,形式为 (state, action) => state 的纯函数。
* 描述了 action 如何把 state 转变成下一个 state。
*/
function counter (state=0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
default:
return state;
}
}
// 创建store数据存储对象,提供3个api: getState subScribe dispatch
let store = createStore(counter);
// 手动监听 订阅更新,绑定在视图层
store.subscribe(() => {
console.log(store.getState());
});
// 改变内部state 的唯一方法是触发dispatch一个action
// action 可以被序列化,或者存储下来方便追溯
store.dispatch({ type: 'INCREMENT' });
// 要做的修改变成了一个对象,这个对象就是action
// 写函数编辑这个action,得到想要的store,这个函数就是reducer !
// 只有 一个store 可以有多个reducer,想react有一个root根组件和其他子组件
export default store;
// 过滤,传参是 state action
function visibilityFilter(state = 'SHOW_ALL', action) { //reducer
if (action.type === 'SET_VISIBILITY_FILTER') {
return action.filter;
} else {
return state;
}
}
// 可以修改state并返回
// action上获取条件,改 state并返回
function todos(state = [], action) { // reducer
switch (action.type) {
case 'ADD_TODO':
return state.concat([{ text: action.text, completed: false }]);
case 'TOGGLE_TODO':
return state.map((todo, index) =>
// 用action中的条件,改state
action.index === index ?
{ text: todo.text, completed: !todo.completed } :
todo
)
default:
return state;
}
}
redux中间件chunk、soga
中间件:对dispatch
的一次封装,让dispatch
既能接受对象,也能接受函数。中间件都是对store.dispatch()的增强
redux-chunk
:
- 使用方法
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
const store = createStore(
reducers,
applyMiddleware(thunk)
);
直接将thunk中间件引入,放在applyMiddleware方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。即可以在reducer中进行一些异步的操作.
applyMiddleware就是Redux的一个原生方法,将所有中间件组成一个数组,依次执行。
中间件多了可以当做参数依次传进去
redux-soga
- 使用方法
- 创建一个helloSoga.js
export function * helloSaga() {
console.log('Hello Sagas!');
}
- 在redux中使用redux-soga
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { helloSaga } from './sagas'
const sagaMiddleware=createSagaMiddleware();
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(helloSaga);
//会输出Hello, Sagas!
redux-chunk和redux-soga的区别
- redux-saga 比 redux-thunk负责的多,redux-saga 有非常多的API ,非常大型的项目建议用redux-saga
- redux-thunk 只是返回东西多里一个 函数
- redux-thunk 把异步代码放到 action 里面去, redux-saga 单独写在 sagasl.js 文件里面
for和forEach的区别
- forEach适用循环次数未知
- forEach适用于只是进行集合或数组遍历,for则在较复杂的循环中效率更高
- forEach不能对数组或集合进行修改(添加删除操作),如果想要修改就要用for循环
- forEach循环中不能中断,for可以使用return和break进行中断处理
故for更好用,更为灵活
setState做了什么操作
async/await的原理
- async函数就是generator函数的语法糖。
- async函数,就是将generator函数的*换成async,将yield替换成await。
【async函数对generator的改进】
-
(1)内置执行器,不需要使用next()手动执行。
-
(2)await命令后面可以是Promise对象或原始类型的值,yield命令后面只能是Thunk函数或Promise对象。
-
(3)返回值是Promise。返回非Promise时,async函数会把它包装成Promise返回。(Promise.resolve(value))
-
async/await是一个很重要的generator函数的语法糖,他的作用是用同步的方式,执行异步操作。
-
await只能在async函数中使用,不然会报错
-
async函数返回的是一个Promise对象,有无值看有无return值
-
await后面最好是接Promise,虽然接其他值也能达到排队效果
-
async/await作用是用同步方式,执行异步操作
多个async/await是否会阻塞
async/await 不会阻塞,会阻塞的只是使用await的函数内部
webpack4 和webpack5的区别
判断数组的方式
- instanceof
- constructor
- isArray:es5
- Object.prototype.toString.call:
- Array.prototype.isPrototypeOf:数组原型
- Object.getPrototypeOf():Object.getPrototypeOf() 方法返回指定对象的原型
子元素浮动父元素高度塌陷
- 设置默认高度
- 设置position:absolute或overflow:hidden
- 设置伪类,清除浮动
jquery和vue、react直接修改文本,谁的速度更快
jQuery,直接操作dom,vue和react会有一个渲染虚拟dom的步骤
浅拷贝的实现方式
- object.assign
- {…object}
eventLoop
防抖和节流的区别和使用场景
BFC是什么
为什么要采用前端框架:vue或react
- 前端要做的工作无非就是根据用户的操作将相应的数据展示到视图中
- 为保证状态和ui同步,前端工作人员除了将精力放在业务逻辑上,还要将大量精力放在操作dom上
- 频繁地操作dom,这样地结果导致了代码地臃肿,不易维护,容易出错,且浏览器开销大
- 为了解决ui和状态同步的问题,出现了前端框架
- 前端框架帮助我们减少了dom的操作,同时提高了渲染页面的性能
定时器是由什么操作的
有专门的定时器操作机制
页面渲染由什么操作,数据量太大页面卡顿的原因
有专门的页面渲染线程去操作:GUI渲染线程
JS引擎:js引擎(JS内核)线程(js引擎有多个线程,一个主线程,其它的后台配合主线程)
作用:
- 主要负责处理Javascript脚本程序,例如V8引擎。Javascript引擎线程理所当然是负责解析Javascript脚本,运行代码。
- 等待任务队列的任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS引擎在运行JS程序
GUI渲染线程
作用: - 负责渲染浏览器界面,包括解析HTML、CSS、构建DOM树、Render树、布局与绘制等
- 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
卡顿原因:
- 内存不够
- 如果数据量大,请求时间长,会导致长时间占用js引擎,导致js引擎阻塞,就导致渲染线程无法执行,就会出线页面卡顿
setTimeout时间到了会立即执行吗?
宏任务和微任务
宏任务一般包括:整体代码script,setTimeout,setInterval
微任务:Promise,process.nextTick
js引擎相关问题
路由模式
hash路由
:SEO不友好
- URL 中的 hash 值只是客户端的一种状态,向服务端发送请求的时候,hash 部分不会被发送
- hash 值得改变会在浏览器的历史记增加访问记录,所以可以通过浏览器的回退、前进控制 hash 值的改变
- 可以通过 a 标签设置 href 值或者通过 js 给location.hash 赋值来改变 hash 值
- 可以通过hashchang 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)
history路由
:IE8不支持
- 通过 pushState 和 replaceState 两个API 来操作实现 URL 的变化
- 可以通过 popstate 事假来监听 URL 的变化,从而对页面进行跳转(渲染)
- history.pushState() 或 history.replaceState() 不会触发 popstate事件,需要手动触发页面跳转
memory路由
:没有URL,只对单机有效
hash和history都是把路径存在url上面,而memory模式路径没有放在url上而是在localStorage里,缺点是没有url是一个单机版的路由
hash路由和history路由的区别
- hash 模式较丑,history 模式较优雅
- pushState 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 #后面的部分,故只可设置与当前同文档的 URL
- pushState 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash设置的新值必须与原来不一样才会触发记录添加到栈中
- pushState 通过 stateObject 可以添加任意类型的数据到记录中;而 hash 只可添加短字符串
- pushState 可额外设置 title 属性供后续使用
- hash 兼容IE8以上,history 兼容 IE10 以上
- history 模式需要后端配合将所有访问都指向 index.html,否则用户刷新页面,会导致 404 错误
箭头函数和普通函数的区别
- 箭头函数是匿名函数,不能作为构造函数,不能使用new。
- 箭头函数不绑定arguments,取而代之用rest参数…解决。
- this的作用域不同,箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值。
- 箭头函数没有原型属性。
- 箭头函数不能当做Generator函数,不能使用yield关键字。
原型和原型链
原型:
- 每个函数都有一个prototype属性,被称为显示原型
- 每个实例对象都会有_ proto _属性,其被称为隐式原型
- 每一个实例对象的隐式原型_ proto _属性指向自身构造函数的显式原型prototype
- 每个prototype原型都有一个constructor属性,指向它关联的构造函数。
原型链:获取对象属性时,如果对象本身没有这个属性,那就会去他的原型__proto__上去找,如果还查不到,就去找原型的原型,一直找到最顶层(Object.prototype)为止。Object.prototype对象也有__proto__属性值为null
BOM和DOM是什么
- BOM:浏览器模型对象 — window
- DOM:文档模型对象 — document
diff算法的实现原理,采用的算法
diff实现原理:diff算法将虚拟DOM的某个节点数据改变后生成新的的node节点与旧节点进行比较,并替换为新的节点,具体过程就是调用Patch方法,比较新旧节点,一边比较一边给真实DOM打补丁进行替换
diff算法作用:用来修改DOM的一小段,不会引起dom树的重绘
stack Reconciler — 递归
fiber Reconciler — 大循环方式
小程序的正式版本库和草稿库是干嘛的
react的三大diff策略
react用tree diff(树比较)、component diff(组件比较)、element diff(元素比较)三大策略将O(n^3)的复杂度转化为O(n)的复杂度
setState什么时候是同步,什么时候是异步
- 如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state。合成事件、生命周期内是异步的
- 除此之外的setState调用会同步执行this.state。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。
原因: 在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。
react17和18的特性是什么
react17新特性:替换了内部的“启发式更新算法”
react18新特性:
- Automatic batching — 自动批处理(合并渲染)
- Concurrent APIs — 并发渲染
- SSR for Suspense — 服务端渲染
- New Render API — 新的更友好的语义化render方式。
Stack Reconciler和Fiber Reconciler的去区别
stack reconciler
:
- diff算法采用递归
- 采用分层比较
- 栈调和机制下 Diff 算法的核心逻辑: 树递归
- 虚拟 DOM 中还有一个叫作[batch]的东西. [batch]描述的是[批处理]机制,这个机制和 Diff 一样,在 React 中都可以由 setState 来触发
- diff算法性能突破的关键点在于 分层对比
- 类型一致的节点才有继续Diff的必要性
- key属性的设置,可以帮助我们尽可能重用同一层级内的节点
- O( n 3 n^3 n3) 优化成 O(n)
缺点:一旦任务开始进行,就无法中断,那么 js 将一直占用主线程, 一直要等到整棵 Virtual DOM 树计算完成之后,才能把执行权交给渲染引擎,那么这就会导致一些用户交互、动画等任务无法立即得到处理,就会有卡顿,非常的影响用户体验
解决方案:把渲染更新过程拆分成多个子任务,每次只做一小部分,做完看是否还有剩余时间,如果有继续下一个任务;如果没有,挂起当前任务,将时间控制权交给主线程,等主线程不忙的时候在继续执行。 这种策略叫做 Cooperative Scheduling(合作式调度),操作系统常用任务调度策略之一。产生了fiber Reconciler
fiber Reconciler
:
主要目标:
- 能够把可中断的任务切片处理。
- 能够调整优先级,重置并复用任务。
- 能够在父元素与子元素之间交错处理,以支持 React 中的布局。
- 能够在 render() 中返回多个元素。
- 更好地支持错误边界。
总结
- 许将可拆分的任务拆解为任务单元,由任务单元构成的任务可以被暂停、复用或取消,以便实现如虚拟节点的增量更新
- 允许为任务设置优先级,任务可并发执行,如在低优先级任务中间隙穿插执行高优先级任务,延迟处理低优先级任务不影响体验
fiber 是如何工作的:
- ReactDOM.render() 和 setState 的时候开始创建更新。
- 将创建的更新加入任务队列,等待调度。
- 在 requestIdleCallback 空闲时执行任务。
- 从根节点开始遍历 Fiber Node,并且构建 WokeInProgress Tree。
- 生成 effectList。
- 根据 EffectList 更新 DOM。
react hooks舍弃了哪几个生命周期
componentWillMount、componentWillReceiveProps、componentWillUpdate
废弃的原因,是在React16的Fiber架构中,调和过程会多次执行will周期,不再是一次执行,失去了原有的意义。此外,多次执行,在周期中如果有setState或dom操作,会触发多次重绘,影响性能,也会导致数据错乱
vue的生命周期
hooks的生命周期有哪些
- constructor — useState
- getDerivedStateFromProps — useEffect 手动对比 props, 配合 useState 里面 update 函数
- shouldComponentUpdate — React.memo
- componentDidMount — useEffect 第二个参数为[]
- componentDidUpdate — useState配合useRef使用
- componentWillUnmount — useEffect中的返回函数
useState怎么去做回调
- useEffect
- promise
- useRef
webpack配置
webpack怎么配置去掉console
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false,
pure_funcs: ['console.log']
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),
常用的plugin配置
- html-webpack-plugin:生成htlm文件
- uglifyjs-webpack-plugin:用于压缩js文件
- clean-webpack-plugin:清除构建目录
- css-extract-plugin:将css单独提取到一个文件
- Hot Module Replacement:热更新
- split-chunks-plugin:拆分代码块
抽离公共组件需要注意什么
- 参数配置要灵活且简单
- 相关配置要集中
- 避免出现只能交叉的配置
- 公共组件不应该集成定制性强的功能,可以集成通用或较固定的功能,但应默认关闭那些不常用的功能
现在es的版本是多少
2015 是es6发布,此后基本上是每年都会发布一个版本
promise 、generator 、 async/await 分别是多少版本号的es产物
promise: es6
generator: es7
async/await: es8
transform 、translate、transition的区别
transform — 用于元素2D、3D转换,可以对元素进行旋转、缩放、移动或倾斜
translate — 移动元素:transform的一个方法
translation — 过度:动画曲线(实现动态效果)
数组map的方法有什么缺点?
当数组里面的元素是对象的时候,使用map之后顺序将会打乱,如果数组里面的元素是基本类型的时候,就直接会返回原数组的顺序
事件委托是什么?有什么好处?
事件委托:利用浏览器的冒泡机制,将子元素的事件函数绑定到父元素上
优点:可以节省大量内存,减少事件注册;新增子元素不用再次绑定事件
谈谈对this的理解,以及使用场景
this是一个关键字,它代表函数运行时,自定生成的一个内部对象,只能在函数内部使用
- 作为纯函数使用时,this指向全局对象
- 作为对象的方法调用时,this指向调用对象
- 作为构造函数被调用时,this指向新的对象
- 作为箭头函数被调用时,this指向它外层父级的作用上下文
数组的哪些方法会更改原数组
数组的增删改都会修改原数组:pop、push、shift、unshift、reverse、splice、sort
混合端开发的时候,怎么做尺寸适配?
做一个不同尺寸大小的配置转换,
前端视频流处理相关
浏览器插件怎么实现?
知道浏览器端的底层api?
serverless在前端后端的实现?
redux的实现原理
如何做单元测试
vue.use 和 vue.component的区别和作用
vue.use 是注册插件
vue.component是注册全局组件
vue diff算法
Vue的虚拟DOM核心在于patch过程:
- 首先将新旧VNode进行开始位置和结束位置的标记
- 标记好节点的位置,进行循环处理节点
- 递归处理
- 新旧开始指针比较
- 新旧结束指针比较
- 旧开始指针与新结束指针
- 旧结束指针与新开始指针
总结:每次Diff都会调用updateChildren方法来比较,就这样层层递归下去,
直到将旧VNode和新VNode中所有子节点比对完。DOMDiff的过程更像是两个
树的比较,没找到相同节点时,都会一层一层的往下比较它们的子节点,是
一个深度递归遍历比较的过程。
vue 路由守卫有哪些?分别是什么作用
全局路由守卫:beforeEach、afterEach – 用于权限判断,每次路由进入之前、进入之后会执行函数
单个路由守卫:beforeEnter、afterEnter — 某个路由跳转前后需要执行的逻辑
组件路由守卫:beforeRouteEnter、beforeRouteLeave、beforeRouteUpdate
vue父组件怎么监听子组件的生命周期
- 通过$.emit实现
// 父组件 Parent.vue中
<Child @mounted="handleDoSomething"/>
// 子组件 Child.vue中
mounted() {
this.$emit('mounted',"mounted我被触发了~");
}
缺点:以上方法虽然可行,但每次都需要手动写一次 $emit 触发父组件的事件。
- 通过@hook实现
// Parent.vue
<Child @hook:mounted="handleDoSomething" ></Child>
handleDoSomething() {
console.log('父组件监听到 mounted 钩子函数~');
},
// Child.vue
mounted(){
console.log('子组件触发 mounted 钩子函数~');
},
@hook 方法除了可以监听 mounted,其它的生命周期都可以监听。
如何主动中断promise
promise原则上属于一旦建立就无法取消,但实际中往往由需求要求中断操作
- 中断调用链 — 就是在某个 then/catch 执行以后,不想让后续的链式调用继续执行了
somePromise
.then(() => {})
.then(() => {
// 终止 Promise 链,让下面的 then、catch 和 finally 都不执行
return new Promise((resolve, reject) => {})
})
.then(() => console.log('then'))
.catch(() => console.log('catch'))
.finally(() => console.log('finally'))
- 中断promise — 注意这里是中断而不是终止,由于 Promise 没法终止,这个中断的意思是:在合适的时候,把 pending 状态的 promise 给 reject 掉。例如一个常见的应用场景就是但愿给网络请求设置超时时间,一旦超时就就中断,咱们这里用定时器模拟一个网络请求,随机 3 秒以内返回
const request = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('收到服务端数据')
}, Math.random() * 3000)
})
function timeoutWrapper(p, timeout = 2000) {
const wait = new Promise((resolve, reject) => {
setTimeout(() => {
reject('请求超时')
}, timeout)
})
return Promise.race([p, wait])
}
const req = timeoutWrapper(request)
req.then(res => console.log(res)).catch(e => console.log(e))
更为灵活的方式
function abortWrapper(p1) {
let abort
let p2 = new Promise((resolve, reject) => (abort = reject))
let p = Promise.race([p1, p2])
p.abort = abort
return p
}
const req = abortWrapper(request)
req.then(res => console.log(res)).catch(e => console.log(e))
setTimeout(() => req.abort('用户手动终止请求'), 2000) // 这里能够是用户主动点击
vue中的装饰符
,trim
.stop
.prevent
.once
.lazy
.