1、vue响应式:
虽然 Vue 2 与 Vue 3 实现响应式系统的方式不同,但是他们的核心思想还是一致的,都是通过 发布-订阅模式 来实现(因为发布者和观察者之间多了一个 dependence 依赖收集者,与传统观察者模式不同)。
个人理解,观察者模式 与 发布-订阅模式 都是 消息传递的一种实现方式,用来实现 对象间的通信(消息传递),只是发布订阅模式可以看做是观察者模式的 升级版,避免了 被观察对象与观察者之间的直接联系。
对比起 Vue 2 通过 Object.defineProperty 在数据定义时为指定具体属性添加 getter/setter 拦截、为每个对象添加 deps 依赖数组的响应式系统实现方式,Vue 3 采用 ES6 的 Proxy 结合 WeakMap、WeakSet ,通过代理在运行时拦截对象的基本操作来收集依赖并全局管理,在性能上就得到了很大的提升,也为开发者带来了更好的开发体验。
并且通过全局的依赖管理模式,也让 Vue 3 的组合式 API 和 hooks 开发模式得以实现,在进行大型项目开发时,公共逻辑拆分也会变得更加清晰。
- ref:定义基本数据类型、引用数据类型的响应式。封装数据类型为ref类型,主要就是创建了RefImpl实例对象。
- reactive:定义引用类型数据的响应式,不支持基本数据类型,如果需要写基本数据类型只能是放在对象中。封装数据为reactive类型,主要是创建了Proxy实例对象,通过Reflect实现数据的获取与修改。
2、性能优化:
(1)使用 externals 来提取公共依赖包,告诉 webpack 这些依赖是外部环境提供的,在打包时可以忽略它们,就不会再打到 chunk-vendors.js 中;(vue.config.js 中配置,在 index.html 中使用 CDN 引入依赖)
(2)组件库的按需引用(安装 babel-plugin-component,babel.config.js中引入)
(3)减小三方依赖的体积(使用 moment-locales-webpack-plugin 插件,剔除掉无用的语言包)
(4)HappyPack 就能实现多线程打包,它把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程,来提升打包速度
(5)使用 webpack 进行 gzip 压缩的方式,使用 compression-webpack-plugin 插件
(6)DllPlugin 与 externals 的作用相似,都是将依赖抽离出去,节约打包时间。区别是 DllPlugin 是将依赖单独打包,这样以后每次只构建业务代码,而 externals 是将依赖转化为 CDN 的方式引入
3、箭头函数为什么不能用作构造函数:
构造函数是通过new关键字来生成对象实例,生成对象实例的过程也是通过构造函数给实例绑定this的过程,而箭头函数没有自己的this。创建对象过程,new 首先会创建一个空对象,并将这个空对象的__proto__指向构造函数的prototype,从而继承原型上的方法,但是箭头函数没有prototype。因此不能使用箭头作为构造函数,也就不能通过new操作符来调用箭头函数。
4、promise:
https://juejin.cn/post/6844903873925120008
https://www.yuque.com/baiyueguang-rfnbu/tr4d0i/to8pca
5、typescript泛型:
泛型就是“模板”,更准确地说,泛型是函数和类的模板。它提供了复用数据结构和代码逻辑成的支持,也使我们构建大型的类型系统成为可能。
函数泛型:使用接口定义函数泛型;借助接口定义的函数泛型具体化一个函数泛型
类泛型:类泛型即类的模板,类的属性和方法可以是“泛泛、抽象的类型”,即先定义类的数据结构和方法逻辑,一些属性和方法参数、返回值等数据类型作为参数在使用的时候再具体化。
接口泛型:和函数泛型及类泛型类似,接口泛型在接口名字后面的尖括号内定义类型变量,在接口实现中使用类型变量。在使用接口泛型时候,也是需要通过尖括号指定具体的类型,即给类型变量赋值。
泛型约束:对抽象的类型加一些约束,以便让TypeScript对代码进行更好的检查。对类型约束通过extends关键字来实现,T extends EmbedType,T就被约束为具体的EmbedType类型。
交叉类型:交叉类型是将多个类型合并为一个类型,合并后的类型是现有类型的父类型,即如果有3个类型A、B、C,那么交叉后的类型A & B & C,即是A,也是B,也是C。对于接口,交叉后的类型必须包含每个接口中声明的属性。
联合类型:联合类型也是将多个类型进行运算得到的新类型,联合后的类型,可以是多个类型中的一个。
例如有两个类型A、B,那么它们联合后的类型 A | B,可以是A或者B中的一个。对于接口定义的对象类型,联合类型只要兼容任意一个接口即可。
映射类型:映射类型可以简单理解为“类型的函数”,给它传入类型作为参数,它会返回一个类型。
使用type关键字,加映射类型名,再加尖括号“<>”(尖括号中是映射类型的“形参”),然后在大括号内通过key:value形式指定从旧类型属性到新类型属性的映射关系。
// 将传入的类型的属性都转成只读的
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
类型映射的使用和调用函数类似。
type ReadonlyPerson = Readonly<Person>;
6、如何理解闭包,为什么有这种特性?为什么需要闭包?闭包的缺点?
对闭包的理解:
1. 什么是闭包?函数和函数内部能访问到的变量的总和,就是一个闭包。
2. 如何生成闭包? 函数嵌套 + 内部函数被引用。
3. 闭包作用?隐藏变量,避免放在全局有被篡改的风险。
4. 使用闭包的注意事项?不用的时候解除引用,避免不必要的内存占用。
为什么有闭包的这种特性:如果形成闭包,外部函数执行完后,其中的局部变量可能被内部函数使用,所以不能销毁,因此内部函数能一致访问到这些局部变量,直到引用解除。
为什么需要闭包?隐藏变量,避免放在全局有被篡改的风险。
闭包的缺点:使用时候不注意的话,容易产生内存泄漏。
7、如何理解原型与原型链:
构造函数有个prototype对象(原型),该对象有个“constructor”属性,指向构造函数。
每个对象都有一个“proto”属性,指向它的构造函数的“prototype”属性。
构造函数的prototype对象,也有一个“proto”对象,它指向Object的prototype对象。
当我们访问对象中的属性时候,会先访问该对象中的本身的属性(私有属性),如果访问不到,会查找对象的“proto”指向的构造函数的prototype对象,如果其中有要访问的属性,就使用该值,否则继续访问prototype的“proto”,在其中查找要访问属性。这样一直上溯到Object对象。这个就是“原型链”。
8、js继承:
继承是建立在面向对象基础上的一种代码复用方式,子类通过继承来复用父类的代码。大部分情况,类的普通属性应该作为私有属性,而函数属性作为原型属性。
- 原型继承
原型继承的实现:子类的prototype = 父类的实例
原型继承特点:可以继承父类的私有属性和原型属性
原型继承的缺点:1. 无法向父类构造函数传参。2. 继承的属性都是原型属性,不能继承私有属性
(无法继承私有属性,只是子类的原型中包含了父类的私有属性。)
- 借用构造函数继承
实现:在子类构造函数中使用call/apply调用父类构造函数,将父类构造函数指向子类实例。
借用构造函数继承的特点:1. 在子类中可以给父类传参。2. 可以继承父类的私有属性。
借用构造函数继承的缺点:只能继承父类的私有属性,不能继承父类的原型属性。
- 组合继承
组合继承的实现:同时使用原型继承和借用构造函数继承
组合继承的特点:1. 子类可以继承父类原型属性和私有属性。2. 子类可以给父类传参
组合继承的缺点:对于父类的私有属性,子类继承时候同时存在于私有属性和原型属性中,造成了冗余
- 寄生组合式继承
寄生组合继承方法实际是将组合寄生中原型继承去掉,使用另一种方法让子类只继承父类的原型。这样就实现了:1. 子类继承父类的私有属性和原型属性。2. 子类可以向父类传递参数。3. 继承后没有冗余属性。
寄生组合继承是js继承的最佳实践。
9、https的通信过程是怎样的?
- 约定非对称加密算法及hash算法
①客户端→服务器
客户端将自己支持的一套加密规则(包括对称加密算法和hash算法)发送给服务器
②服务器→客户端
选定对称加密和hash算法
服务器将选定的算法和公钥发送给客户端
- 传递非对称加密秘钥
③客户端→服务器
生成随机数秘钥(对称加密秘钥),并用公钥加密,得到加密后的随机数
计算握手消息
用随机数对握手消息加密,得到加密后的握手消息
用约定好的hash算法计算握手消息,得到hash后的握手消息
客户端将加密后的随机数、加密后的握手消息、hash后的握手消息发送到服务器
④服务器→客户端
用私钥解密随机数
用随机数解密握手消息
验证hash后的握手消息与客户端发来的hash是否一致
用随机数加密一段握手消息
用hash加密握手消息
服务器将加密后的握手消息和hash后的握手消息发送给客户端
⑤客户端→服务器
客户端解密握手消息并验证hash一致
客户端用随机数加密握手消息
用hash加密握手消息
客户端将加密后的握手消息和hash后的握手消息发送给服务器
握手结束,客户端开始向服务器发送通信信息(使用随机数进行通信)
10、性能优化:https://www.yuque.com/baiyueguang-rfnbu/tr4d0i/lfg9g9#cd5294f9
页面展示可以分为3个阶段,请求页面,加载和解析页面,渲染。
1、请求数据阶段主要指标是服务器响应时间,从服务器角度优化。
2、加载和解析页面阶段,性能优化的主要思路是减少请求数量、降低资源的大小和避免阻塞。
3、渲染阶段优化思路是避免重绘和重排。
11、new一个对象发生了什么(构造函数的执行过程?)
- 创建了一个空的js对象(即{})
- 将空对象的原型prototype指向构造函数的原型
- 将空对象作为构造函数的上下文(改变this指向)
- 判断构造函数的返回值,以决定最终返回的结果。
a. 如果返回值是基础数据类型,则忽略返回值;
b. 如果返回值是引用数据类型,则使用return 的返回,也就是new操作符无效;
12、双向绑定原理(v-model):
- 创建订阅者,当组件的data属性改变时候,修改表单元素的value。
- 给表单元素创建事件(change事件或者input事件),事件的回调中,修改组件的数据。
13、依赖收集的过程:
简单总结为,在组件初始化时候遍历data的属性,为每个属性设置get方法和set方法,在get方法中收集依赖,在set方法里通知订阅者,更新视图时候创建订阅者,更新视图时候如果依赖了data的某个属性,就会触发这个属性的get方法时候,该订阅者(更新视图的方法)就会被data的属性收集,在更新属性时候触发set方法,从而触发界面更新。
14、vue的diff算法:
diff算法的大体过程是,从组件根节点开始对比两棵虚拟DOM树。
判断新旧节点是否值得比较,值得比较的话,就进行对比操作,否则用新的节点替换旧的节点。新旧节点比较完后,如果有子节点,还要比较子节点(child),在旧的child中找到和新的child匹配的节点,如果找到(说明这两个child“值得比较”)并且顺序不同则调整顺序,然后再对两个child执行对比操作。
“值得比较”是个很重要的概念,值得比较意味着Vue认为旧节点经过更新就可以得到新的节点,而不需要完全用新的节点代替旧的节点;
15、什么是MVVM?与MVC有什么区别?
MVVM是一种软件架构设计模式,它抽离了视图、数据和逻辑,并限定了Model和View只能通过VM进行通信,VM订阅Model并在数据更新时候自动同步到视图。
MVC将应用抽象为数据层(Model)、视图层(View)、逻辑层(controller),降低了项目耦合。但MVC并未限制数据流,Model和View之间可以通信。
MVP则限制了Model和View的交互都要通过Presenter,这样对Model和View解耦,提升项目维护性和模块复用性。
而MVVM是对MVP的P的改造,用VM替换P,将很多手动的数据=>视图的同步操作自动化,降低了代码复杂度,提升可维护性。
16、react useState工作的过程如下:
1、首次调用函数组件,执行useState,React获取到当前正在遍历的节点,创建_state对象挂载到节点上,根据调用useState的顺序,在数组中加入状态,并根据传入的初始值初始化状态。
2、当调用状态的set方法时候,React修改_state上面的状态,并更新相应的组件。
3、后面再调用函数组件时候,useState找到对应的节点的_state,并根据useState的调用顺序,找到修改后的状态返回,这样组件就可以拿到正确的状态了。
17、webpack工作的原理是什么?工作流程是什么?
webpack读取配置,根据入口开始遍历文件,解析依赖,使用loader处理各模块,然后将文件打包成bundle后输出到output指定的目录中。
webpack的工作流程是
1、Webpack CLI 启动打包流程,解析配置项参数。
2、载入 Webpack 核心模块,创建 Compiler 对象。
3、注册plugins。
4、使用 Compiler 对象开始编译整个项目。
5、从入口文件开始,解析模块为AST,分析模块依赖,形成依赖关系树。
6、递归依赖树,将每个模块交给对应的 Loader 处理。
7、合并 Loader 处理完的结果,将打包结果输出到 dist 目录。
18、怎么理解webpack的tree-shaking和css-treeshaing?
摇树js:
Tree-shaking的本质是消除没有用到的代码。主要的效果是,引用了但没有使用的模块,不会被打包到最终的bundle中。
Tree-shaking要求模块是ESM,ES6模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,这就是tree-shaking的基础。
所谓静态分析就是不运行代码,从语法上对代码进行分析,ES6之前的模块化,比如我们可以动态require一个模块,只有执行后才知道是否要引用某个模块,引用的是什么模块,这个就不能通过静态分析去做优化。
webpack默认支持Tree-shaking,如果mode为"production"webpack在构建会做Tree-shaking的操作。
摇树css:
摇树css的基本思路是,给定content和css,分析content中用到的选择器,然后分析css文件中没有用到的选择器,将其移除。
摇树css工具有:
1、PurifyCSS
使用purgecss-webpack-plugin
2、uncss
19、webpack优化方法有哪些?
提升构建速度:
使用高版本的webpack和nodejs
多进程多实例构建
多进程多实例并行压缩
进一步分包:预编译资源模块
充分利用缓存提升二次构建速度
缩小构建目标
使用oneOf
提升加载和运行时性能:
使用Tree Shaking擦除无用的js和css
scope-hoisting
使用webpack进行图片压缩
优化polyfill方案
20、常见的webpack plugin和loader
loader:
1、babel-loader // 处理js
2、file-loader、url-loader // 处理图片、字体图标
3、less-loader、sass-loader、stylus-loader // 处理各种css预处理器
4、css-loader // 解析css模块
5、style-loader // 将样式插入到dom中
plugin:
1、html-webpack-plugin // 生成html文件并自动将js bundle引入到html
2、clean-webpack-plugin // 每次打包时候清空上次打包结果
3、copy-webpack-plugin // 执行拷贝操作
4、mini-css-extract-plugin // 提取css文件
21、intanceof 操作符的实现原理
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
22、讲一下vue框架的原理?
我们使用Vue开发应用,实际上是编写若干Vue组件,实现模板、data、生命周期钩子等,然后执行new Vue(),将根组件挂载到指定的DOM节点上面,当我们编写的组件中生命周期钩子里面的或者在模板的元素事件中改变数据时候,视图会响应地更新。这样就实现了应用。
那么Vue是如何实现上面的效果的呢?
new Vue()之后,Vue会从根组件开始,遍历整个组件树,对每个组件进行处理。
对于一个Vue组件,Vue首先会进行模板编译,将模板编译为render函数,render函数返回虚拟DOM,如果遇到子组件,也对子组件做同样操作,最终形成一个虚拟DOM树。
Vue会把虚拟DOM映射到真实DOM并渲染到指定节点上,这样就实现了视图的渲染。
Vue在组件初始化时候还会设置数据为响应式,并将依赖于数据的渲染方法、computed、watch收集起来。
当数据改变后,Vue会根据初始化时候收集的依赖,更新视图,这时候我们就看到最新的界面了。
23、vuex的数据流向?vuex整个触发过程(actions,state,view)?vuex的工作原理?
Vuex的数据流是组件中触发action,action提交mutations,mutations修改states。 组件根据 states或getters来渲染页面。
Vuex是个状态管理器。
它Vuex通过createStore创建了一个数据中心,然后通过发布-订阅模式来让订阅者监听到数据改变。
Vuex的store注入 vue的实例组件的方式,是通过vue的 mixin机制,借助vue组件的生命周期钩子beforeCreate 完成的。这样Vue组件就能通过this.$store获取到store了。
Vuex使用vue中的reactive方法将state设置为响应式,这样组件就可以通过computed来监听状态的改变了。
24、路由有哪两种模式?默认是哪种模式?两种模式区别是什么?讲一下vue-router原理?
前端路由有两种模式,HTML5和hash,这两种模式本质是不同的底层浏览器技术,但是上层Vue Router做了统一化的封装,因此在我们开发组件和配置路由时候使用这两种模式的区别并不大。默认是hash模式。
这两种模式有几个主要区别
1. HTML5模式的路由没有"#"字符,而是在域名后直接写路径,更加优雅
2. 由于#后面的字符不会发给服务器,因此hash路由SEO比较差
3. HTML5需要服务器在访问不同的路径时候都能fallback到index.html,因此相对麻烦
前端路由的原理关键有2点
1. 可以修改url,但不会引起刷新,从而在不刷新的页面的情况下跳转路由。
2. 监听url改变,根据url渲染对应组件。
hash模式和history模式的原理都是基于这两点。hash是通过浏览器提供的location API修改url,通过onhashchange方法监听hash改变;history通过浏览器提供的history.pushState或者history.replacestate修改url,通过popState事件监听url改变。
25、 vue的computed和watch的实现原理
computed:
computed是data属性的一个订阅者,它在初始化时候被data属性收集依赖,当computed依赖的data属性改变后,标记该computed为dirty,即数据更改过,当渲染使用到computed时候,再计算出computed的值从而得到最新的正确的值。
watch:
在组件初始化时候,遍历所有的watch,对每个watch创建订阅者,绑定依赖的data属性,当data属性改变后发布给订阅者,然后会执行相应地回调。
26、ES6 模块与 CommonJS 模块有什么异同?
不同点:
CommonJS 是对模块的浅拷⻉,ES6 Module 是对模块的引⽤,即 ES6
Module 只存只读,不能改变其值,也就是指针指向不能变,类似 const;
import 的接⼝是 read-only(只读状态),不能修改其变量值。 即
不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对
commonJS 对重新赋值(改变指针指向),但是对 ES6 Module 赋值会
编译报错。
共同点:
CommonJS 和 ES6 Module 都可以对引⼊的对象进⾏赋值,即对对象内
部属性的值进⾏改变。
27、 ajax、axios、fetch 的区别
(1)AJAX
Ajax 即“AsynchronousJavascriptAndXML”(异步 JavaScript 和
XML),是指一种创建交互式网页应用的网页开发技术。它是一种在
无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在
后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。
这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行
更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整
个网页页面。其缺点如下:
本身是针对 MVC 编程,不符合前端 MVVM 的浪潮
基于原生 XHR 开发,XHR 本身的架构不清晰
不符合关注分离(Separation of Concerns)的原则
配置和调用方式非常混乱,而且基于事件的异步模型不友好。
(2)Fetch
fetch 号称是 AJAX 的替代品,是在 ES6 出现的,使用了 ES6 中的
promise 对象。Fetch 是基于 promise 设计的。Fetch 的代码结构比
起 ajax 简单多。fetch 不是 ajax 的进一步封装,而是原生 js,没有
使用 XMLHttpRequest 对象。
fetch 的优点:
语法简洁,更加语义化
基于标准 Promise 实现,支持 async/await
更加底层,提供的 API 丰富(request, response)
脱离了 XHR,是 ES 规范里新的实现方式
fetch 的缺点:
fetch 只对网络请求报错,对 400,500 都当做成功的请求,服务器
返回 400,500 错误码时并不会 reject,只有网络错误这些导致请
求不能完成时,fetch 才会被 reject。
fetch 默 认 不 会 带 cookie , 需 要 添 加 配 置 项 : fetch(url,
{credentials: 'include'})
fetch 不 支 持 abort , 不 支 持 超 时 控 制 , 使 用 setTimeout 及
Promise.reject 的实现的超时控制并不能阻止请求过程继续在后台
运行,造成了流量的浪费
fetch 没有办法原生监测请求的进度,而 XHR 可以
(3)Axios
Axios 是一种基于 Promise 封装的 HTTP 客户端,其特点如下:
浏览器端发起 XMLHttpRequests 请求
node 端发起 http 请求
支持 Promise API
监听请求和返回
对请求和返回进行转化
取消请求
自动转换 json 数据
客户端支持抵御 XSRF 攻击