3、Vue 使用

高级特性:
自定义 v-model:双向数据绑定

1
2
<input :value="text" @input="$emit('change', $event.target.value}" />
props:{prop:'text',default(){}} model:{text:String,event: 'change'}

$nextTick:DOM 渲染之后触发,获取最新 DOM 节点
refs
slot
动态组件 :is
异步组件
keep-alive
mixin

问题:

1、Vue 组件如何通信
2、描述组件渲染和更新的过程:
首次渲染 → 更新渲染 → 异步更新
解析模板 → 生成 render→ 生成 vnode→ 触发 patch 和 diff→vnode 更新触发 set→ 更新 dom

  • 1、解析模板为 render(vue-loader)
  • 2、触发响应式,监听 data 属性 getter setter
  • 3、执行 render 函数,生成 vnode,patch 创建新 elem:在执行 render 的时候会触发 getter 收集模板的依赖
  • 4、修改 data,触发 setter
  • 5、重新执行 render 函数,生成 newVnode,patch(old, new)

vue原理图

3、双向数据绑定 v-model 原理:object.defineProperty

4、Vue 原理

组件化和 MVVM(数据驱动视图)
响应式
vdom 和 diff
模板编译
组件渲染过程
前端路由

4.1 组件化和 MVVM

组件化:其实就是复用,web component

数据驱动视图:
Vue MVVM
React setState

View ⬅️➡️ View Model ⬅️➡️ Model(data)

4.2 Vue 响应式

监听不到以下操作:object.defineProperty 缺陷
新增属性
删除属性
数组方法
还有缺点:
深度监听需要一次性递归,性能比较差

增加 Vue.set 和 Vue.delete
重新定义数组方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const oldArrayProperty = Array.prototype;
// 创建对象arrProto,这个对象原型指向数组原型
const arrProto = Object.create(oldArrayProperty);
// 重新定义数组方法
['push', 'pop'].forEach((methodName) => {
arrProto[methodName] = function () {
oldArrayProperty[methodName].call(this, ...arguments);
updateView(); // 触发视图
};
});
function observer() {
if (Array.isArray(target)) {
target.__proto__ = arrProto;
}
}

4.3 虚拟 dom 和 diff

h,vnode,patch,diff,key

为什么出现:
DOM 操作比较耗费性能,js 操作比较快,模拟 DOM 结构,减少变更操作 DOM

js 模拟 DOM:

1
2
3
4
5
6
7
8
9
10
var vdom = {
tag: 'div',
props: { className: 'content' },
children: [
{
tag: 'p',
...others,
},
],
};

h 函数:
返回一个 vnode

diff 算法:
最早时间复杂度 O(n^3)不可用:遍历树 1,遍历树 2,排序
最右优化到 O(n):
1、只比较同级,不级别比较
2、tag 不同,则直接删除重建,不再深度比较
3、tag 和 key 都相同,认为是相同组件,不再深度比较

当认为是相同组件后,进行 patch 函数:
先判断是否有 text,有则表示没有 children,则直接替换
没有则认为有 children,则进行新旧 children 对比
新 children 有,旧 children 无,则老分支增加 children addVnodes
旧 children 有,新 children 无,则旧分支删除 children removeVnodes
如果都有,则进行 while 循环向中间靠拢对比 updateVnodes
如果遇到相同则再进行 patch
如果都不同,则拿 newKey 来和老的同级别的 key 循环对比看有没有相同的

4.4 模板编译

里边含有指令,插值,js 表达式

js 的 with 语法:打破作用域规则,易读性差,不建议用
vue template complier 将模板编译成 render 函数
执行 render 函数生成 vnode

模板转义:
vue-loader 处理

1
2
3
4
5
6
7
8
9
const template = `<p>{{message}}</p>`;
// 转化后
with (this) {
return h('p', [_v(_s(message))]);
}
with (this) {
// 返回一个vnode
return createElement('p', [createTextVNode(toString(message))]);
}

写一个 render 函数:

1
2
3
4
5
6
7
8
9
Vue.component('heading', {
render: function (createElement) {
return createElement(); // 上边的转化
},
});
// vue直接用的方法
render(h){
return ( <div><div> )
}

4.5 异步渲染

$nextTick
汇总 data 修改,一次性渲染
减少 DOM 操作,提升性能

4.6 路由

hash:
触发页面跳转,即前进后退
不刷新页面
不提交到 server 端
监听 hashChange
history:
不刷新页面
pushState
popState 只能监听浏览器按钮的前进后退
优势就是 seo

注意:
不触发 popstate 可以重写 pushState 来增加一个钩子
https://segmentfault.com/q/1010000010612425
https://www.xiabingbao.com/post/fe/hash-history-router.html

5、Vue 面试真题

1、必须用 key,不能用 index
diff 算法是通过 tag 和 key 来判断是否相同的
因为如果插入整个 index 都会变化
减少渲染次数,提升渲染性能
2、声明周期及父子组件声明周期
创建阶段(注册实例与挂载): beforeCreate、created、beforeMount、mounted
运行阶段:beforeUpdate、updated
注销阶段:beforeDestroy、destroyed

父子组件:
parent create //先创建父组件间
parent beforeMount
child create //父组件挂载之前创建子组件
child beforeMount
child mounted //先挂载子组件
parent mounted //挂载父组件

3、父子组件如何通信
:on=和他还 this.$emit
this.$parents
eventBus
vuex
Vue.observable()

4、渲染更新过程

5、v-model 的原理
input 的 value 绑定到一个 name
input 增加绑定事件 this.name = event.target.value
data 触发 re-render 更新

6、computed 特点 可扩展原理
缓存,data 不变不会重新计算
提高性能

7、data 为什么是一个函数
export default 最后是一个 class,使用的时候是实例化
放在闭包中,数据不会互相影响

8、将所有 props 传给子组件
$prop

9、自己实现 v-model

10、mixin

11、使用异步组件
加载大组件
路由异步加载

12、何时试用 keep-alive 可扩展原理
缓存组件,不需要重复渲染
多个 tab

13、何时试用 beforeDestory
解绑自定义事件 this.$off
清除定时器
解绑自定义 dom 事件,scroll

14、什么是作用域插槽

15、vuex 的 action 和 mutation 有什么区别
action 要处理异步,mutation 不可以
mutation 做原子操作,每次只有一个事
action 可以整合多个 mutation

16、用 vnode 描述一个 dom 结构

1
2
3
var vnode = {
tag: 'div',
};

17、diff 时间复杂度
由 O(n^3) → O(n)

5.6 vue 常见性能优化

合理使用 v-show 和 v-if:频繁切换使用 v-show
合理使用 computed
v-for 时加 key,避免和 v-if 同时使用:v-for 优先级高,每次 v-for 都要 v-if
自定义事件、DOM 事件及时销毁-导致内存泄漏
合理使用异步组件
合理使用 keep-alive
data 层级不要太深-监听太深执行太多,最好扁平一些
其他:
webpack 层优化
通用性能优化

6.Vue3.0 Proxy

缺点:
兼容性不好,无法 polyfill
优点:
深度监听性能更好
可监听新增、删除属性
可监听数组变化

1、特点:
全部 ts 重写
性能提升,代码量少
调整部分的 api

2、vue2.过时了吗?
vue3 推广还需要时间
vue2 应用广泛,大量项目需要维护
proxy 没有 polyfill

3、Proxy 重写响应式
Reflect.set/Reflect.deleteProperty 会返回设置成功失败

Proxy 的深度监听是再 get 时触发的,而 Object.definedProperty 是最开始递归监听的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function reactive(target = {}) {
if (typeof target != 'object' || target == null) {
return target;
}
// 代理配置
const proxyConf = {
//receiver指的是原始的操作行为所在的那个对象,一般情况下是proxy实例本身
get(target, key, receiver) {
const ownKeys = Reflect.ownKeys(target);
if (ownKeys.includes(key)) {
// 监听逻辑push
}
const result = Reflect.get(target, key, receiver);
// 深度监听-只有get时候才会触发
return reactive(result);
},
set(target, key, val, receiver) {
// 重复数据不处理
if (val === target[key]) return true;

const ownKeys = Reflect.ownKeys(target);
if (ownKeys.includes(key)) {
// 已有key
} else {
// 新增key
}
const result = Reflect.set(target, key, val, receiver);
return result; // 是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
return result; // 是否删除成功
},
};
// 生成代理对象
const observed = new Proxy(target, proxyConf);
return observed;
}

10、Webpack 原理

1、前端代码为何要进行构建和打包?
代码层:体积更小,加载更快,编译高级语法,兼容性,错误检查
工程化:高效的开发环境、统一的构建流程和产出标准,集成公司构建规范,工程化管理
2、module chunk bundle 分别是什么意思,有何区别?
module(源码)-各个源码文件,可以 import 引入的都是 module
chunk(中间态 webpack)-多模块合并成的,entry,import(),splitChunk
bundle(最终)-最终输出的文件
3、loader 和 plugin 区别
loader 模块转换器, plugin 是扩展插件
4、babel 和 webpack 的区别
babel-JS 新语法编译工具 webpack-打包构建工具
5、webpack 如何实现懒加载
import()
6、webpack 常见性能优化
构建+生产的优化
7、babel-runtime 和 babel-polyfill 区别
不会污染全局,适合开发 lib 库,polyfill 会污染全局
8、为何 Proxy 不能被 Polyfill?
因为现有的方法无法模拟

2.1 基本配置

安装配置
dev-server: 用 devServer/whistle
解析 ES6: 用 loader
解析样式 : 用 loader
解析图片文件 : 用 loader
常见 loader 和 plugin
loader 执行顺序:从后往前

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
entry: '',
output: {
filename: 'bundle.[hash:8].js',
},
module: {
rules: [{ test: /(\.vue)$/, use: 'vue-loader' }],
},
plugins: [],
optimization: {},
devServer: {},
};

2.2 高级配置

1、多入口
2、抽离和压缩 css: 默认是打包到 js 中的,需要配置 rule+plugin+optimization 来抽离,mini-css-extract-plugin+压缩 css terser-webpack-plugin 和 optimize-css-assets-webpack
3、抽离公共代码: optimize 中设置 splitChunks
4、懒加载:import(‘’).then(res=>{res 是内容})
5、处理 React 和 Vue:用 babel 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
module.exports = {
entry: { // 多页面
index '',
other '',
},
output: {
filename: '[name].[hash:8].js' // name以来entry的属性名
path: distPath,
publicPath: '',
},
module: {
rules: [{ babel: '' }],
},
plugins: [
// chunks 表示该页面要引用那些chunk,主要这个chunk来不引入多余的js
new HtmlWebpackPlugin({template: 'index.html',filename:'index.html', chunks:['index']}),
new HtmlWebpackPlugin()
],
optimization:{
minimizer:[], // 处理css压缩
splitChunks:{ // 分割代码块
chunks: 'all',
cacheGroups:{
vendor:{}, // 第三方模块
common:{} // 公共模块配置
}
}
},
devServer: {},
};

抽离 css

2.3 优化打包-构建速度

优化 babel-loader:cacheDirectory 开启缓存+明确范围 include/exclude
IgnorePlugin:手动引入中文包,忽略掉这个整体的包
noParse:无第三方依赖,不用解析。noParse: ‘/jquery|lodash/‘
happyPack:开启多进程打包(项目小,不需要,因为进程开销)
ParallelUglifyPlugin:开启多进程压缩 js(项目小,不需要,因为进程开销)
DllPlugin:稳定第三方库,webpack4 性能很好,vue,react 已删除,和 split 也冲突
自动刷新:整个页面刷新,watch 属性设置为 true,watchOptions 设置,
热更新:不刷新,状态不丢失

详细过程
1、使用 express 启动本地服务,当浏览器访问资源时对此做响应。
2、服务端和客户端使用 websocket 实现长连接
3.webpack 监听源文件的变化,即当开发者保存文件时触发 webpack 的重新编译。

  • 每次编译都会生成 hash 值、已改动模块的 json 文件、已改动模块代码的 js 文件
  • 编译完成后通过 socket 向客户端推送当前编译的 hash 戳

    4.客户端的 websocket 监听到有文件改动推送过来的 hash 戳,会和上一次对比

  • 一致则走缓存

  • 不一致则通过 ajax 和 jsonp 向服务端获取最新资源,对 bundle 进行替换

https://blog.csdn.net/chern1992/article/details/106893227

2.4 优化产出代码

小图片 base64 编码
bundle 加 hash
IgnorePlugin
使用 CDN
提取公共代码
懒加载
production:自动压缩代码+删除调试代码+开始 Tree-Shaking(删除无用代码,必须用 es6 module,因为是静态引入)
scope hosting:合并模块,减少函数作用域

2.5 构建流程

2.6 babel

presets:集合了大部分 es6 转 es 的 plugins 的集合
plugins:是 presets 的扩展

按需加载组件:

1
2
3
4
5
// 正常
import Button from 'antd/lib/button'; // 需要结构赋值的方式引入
// 开发babel插件,通过语法分析让自动指向antd/lib
// 改进后
import { Button } from 'antd';

默认只是语法转换:例如箭头函数变 function

polyfill:直接用 core.js 和 regenerator,补全 API,例如 Promise, includes 等
按需引入:presets 内 useBuiltIns 配置 usage
污染全局环境

runtime:不会污染全局环境,加了个_Promise, 开发第三方库

12. 项目流程

1、PM 开发过程中增加需求,怎么办?
不能拒绝,走需求变更流程
有规则走规则
没有规则,发起项目组和 leader 重新评审,重新评估

2、项目即将延期,怎么办?
和 leader 汇报
给出原因
给出解决方案
给出排期

3、如何保证项目质量?
符合开发规范
写开发文档
单元测试(看清空)
Code Review

4、我的电脑没问题呀
不要说这句话
让 QA 帮你复现
让 QA 提供设备

12.1 流程细节

1、需求评审:
评估需求背景,目标,收益,合理性
闭环-结果统计实现
开发难度评估,风险点
是否需要其他支持-客户端(调研)
项目优先级

2、技术方案设计
求简,不过度设计
产出文档
找准设计重点-组件怎么设计
组内评审-看有没有公司更好的方法工具(新人)
和 RD 和客户端沟通
发出会议结论(看公司,表示沟通完成)
给排期-预留 buffer(1/4 时间)+并行工作+协作人时间 UI、RD 客户端

3、开发
符合开发规范:git、注释、模块命名
写开发文档
单元测试(看清空)
Mock API
Code Review

4、联调
技术联调
UE 确定视觉效果
PM 确定产品功能

6、测试
提测发邮件,抄送项目组
测试问题要详细记录
有问题及时沟通

7、上线
同步 PM 和项目组
通知 QA 回归
有问题,立即回滚

← Prev Next →