vue3
vue3里为什么要用proxy api替代defineProperty api
defineProperty api的局限性最大原因是它只能针对单例属性做监听。监听对象是对对象中的属性做了遍历 + 递归,为每个属性设置了 getter、setter。在vue2中使用下标的方式直接修改属性的值或者添加一个预先不存在的对象属性是无法做到setter监听的。
proxy api的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作, 这就完全可以代理所有属性,将会带来很大的性能提升。
proxy api可以监听数组的索引和 length 属性;
讲讲composition api
查阅我的另一篇博客vue3常用api
composition api与options api使用取舍
不建议共用,会引起混乱。业务逻辑简单,用 Options API。33逻辑复杂,用 Composition API
teleport的作用
teleport可以指定to属性,将其包裹的dom元素移动至指定的to属性dom内。
按需引入api
import { reactive, toRefs } from ‘vue’;
vue2打包不管写不写相应的钩子函数都会被打包,vue3引入什么才会被打包什么,相应vue3的打包体积会相对较小。
vue2
为什么data是一个函数
组件的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一分新的data。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。
vue组件通讯有哪些方式?
- props 和 $emit
- $parent 和 $children
- provide 和 inject
- $refs
- envetBus
- vuex
- 本地存储
一般在哪一步发送请求?
如果不需要依赖dom,在created中调用异步请求。
v-if和v-show 的区别
v-if 条件不满足时不渲染此节点,v-show 条件不满足时控制样式将此节点隐藏(display:none)。
$route 和 $router的区别
$route为路由信息对象,主要用于获取路由信息,如path、params、query等。
$router为路由实例对象,可以调用路由的跳转方法操作路由,如push、go等。
常用的vue内置指令
- v-bind
- v-on
- v-html
- v-text
- v-model
- v-if / v-else / v-else-if
- v-show
- v-for
常用的vue修饰符
- stop 阻止事件继续传播
- prevent 阻止标签默认行为
- capture 使用事件捕获模式
- self 只当在 event.target 是当前元素自身时触发处理函数
- once 事件只会触发一次
怎样理解vue的单项数据流
数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据,只能请求父组件对原始数据进行修改。
computed和watch的区别
computed 是计算属性,依赖其它属性计算值,并且 computed 的值有缓存,方法体内不能有异步操作。
watch 监听到值的变化就会执行回调,方法体内可以有异步操作。
computed缓存原理
当首次读取时,computed会执行方法体计算结果。当依赖值无变化时再次读取,computed内部的计算开关是关闭的,会直接返回上一次记录的结果。当依赖值发生变化,computed内部的计算开关会打开,再次读取时会重新计算新的结果。
v-if和v-for为什么不建议一起使用
因为解析时先解析v-for后解析v-if。如果遇到需要同时使用时可以考虑写成计算属性的方式。
v-for为什么要加key
标识组件的唯一性,主要用来做dom diff算法用的。
sameVnode方法中
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
patchVnode方法中
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
如果不设置key,那么undefined === undefined会始终成立,当其他条件也成立时,dom将会就地复用。
vuex的五个api
- state:存储数据
- actions:提交给mutation数据的方法,可以是异步的
- mutations:提交更新state的方法,是同步的
- getters:相当于修饰一个修饰方法,修饰返回state的值
- module:项目业务线大时分模块
为什么actions可以是异步的,mutations必须是同步的?此处引用一段尤大大说的话(来源):
区分 actions 和 mutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化。
事实上在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触发mutation 就行。异步竞态怎么处理那是用户自己的事情。vuex 真正限制你的只有 mutation 必须是同步的这一点(在 redux里面就好像 reducer 必须同步返回下一个状态一样)。
同步的意义在于这样每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。
如果你开着 devtool 调用一个异步的 action,你可以清楚地看到它所调用的 mutation是何时被记录下来的,并且可以立刻查看它们对应的状态。其实我有个点子一直没时间做,那就是把记录下来的 mutations 做成类似 rx-marble 那样的时间线图,对于理解应用的异步状态变化很有帮助。
总结一句话就是同步的mutations可以记录state的变化,倘若不存在mutations由actions直接修改,那么当actions中存在多个异步修改逻辑时,将无法知道state是被什么逻辑修改的,这就是架构设计。
父子组件生命周期钩子函数执行顺序
- 加载渲染过程
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted - 组件更新过程
父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated - 销毁过程
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
vue2双向数据绑定的原理
递归遍历data中的每一个属性,使用Object.defineProperty进行劫持。通过getter收集watcher ,每一个getter对应一个dep ,dep中存放着此属性的所有watcher 。watcher的来源即是模板编译时的{{}}、v-xxx 和 computed、watcher api 。数据变更时通过setter 通知dep递归遍历调用watcher的update函数触发diff算法更新dom。
推荐一个简单的实现的视频,来源bilibili,有助于对源码的理解:简单实现
如何检测数组变化
方法拦截(push、shift、pop、splice、unshift、sort、reverse)
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
数据监听
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
v-model原理
v-model=‘inputvVal’
v-bind:value=‘inputvVal’ v-on:input=‘inputvVal = $event.target.value’
做过哪些vue的性能优化
开发层面
- 不需要响应式的数据不要放在 data 中
- v-if 和 v-show 区分使用场景
- computed 和 watch 区分场景使用
- v-for 遍历必须加 key,key最好是id值
- 组件销毁后把全局变量和定时器销毁
- 图片懒加载
- 路由懒加载
- 第三方插件的按需加载
- 采用 keep-alive 缓存组件
- 防抖、节流的运用
- 图片压缩后再引入项目中
- swiper、html2canvas等插件异步加载
打包层面
- 服务器端开启 gzip
- 生产环境关闭sourcemap
- webpack配置提取公共js文件
- css分离
- 采用可视化工具(BundleAnalyzerPlugin)针对性优化
编译层面
- 路由按需启用(编译时不引入不开发的路由)
- happypack 多线程编译
nextTick使用场景和原理
在修改数据之后立即使用这个方法,获取更新后的 DOM。
nextTick就是采用(promise、mutationObserver、setImmediate、setTimeout)包装的方法。
vue-router中路由模式和实现原理
- hash 模式
就是url中#后面的东西。它的特点在于:hash虽然出现url中,但不会被包含在http请求中,对后端完全没有影响,因此改变hash不会重新加载页面。
可以为 hash 的改变添加监听事件,每一次改变hash都会在浏览器的访问历史中增加一个记录,利用hash的以上特点,就可以实现前端路由“更新视图但不重新请求页面”的功能。 - history 模式
利用 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。这两个方法应用于浏览器的历史记录站,在当前已有的 back、forward、go 的基础上,他们提供了对历史记录进行修改的功能。
当调用他们修改浏览器历史记录栈后,虽然当前url改变了,但浏览器不会刷新页面,这就为单页面应用前端路由“更新视图但不重新请求页面”提供了基础,但是刷新会出现 404 需要后端进行配置。
mvc和mvvm区别
mvc和mvvm的区别并不是vm完全取代了c,只是在mvc的基础上增加了一层vm,只不过是弱化了c的概念,vm存在目的在于抽离c中展示的业务逻辑,而不是替代c,其它业务等还是应该放在c中实现。
比如有一变量num需要展示在页面上,页面上又有点击事件可以改变num的值,这时候就是vm层的事情。但是业务还有一个定时器在执行一些页面上不需要的数据,那这就是c层的事情。
虚拟dom
由于在浏览器中频繁操作真实dom是很耗费性能的,所以虚拟dom本质就是用一个原生的js对象去描述一个dom节点,是对真实dom的一层抽象。
特点:
- 减少对真实DOM的操作。
- 能跨平台渲染。
- 首次渲染大量DOM时,由于多了一层DOM计算,会比innerHTML插入慢。
diff算法
有兴趣的可看一下我之前看过的一个不错的解读,我就不再重复解读了:diff算法
源码解读
这个是一个github地址,也是我看过认为不错的一个:源码解读