Vue框架
Vue与传统DOM框架的对比
传统DOM框架
如果有两处修改:
- 遍历DOM树获取DOM对象
- 确认需要操作的视图属性
- 更改对象的属性触发视图更新
- 视图执行一次更新
- ......(重复上面的工作)
- 更改对象的属性触发视图更新
- 视图执行一次更新 所有视图内容更新完毕
Vue框架
如果有两处修改:
- 初始化框架构建虚拟DOM树
- 将响应式数据对象映射到虚拟DOM树中
- 更改需要更新的属性
- diff出变更项,得出更新进入更新队列
- 更改需要更新的属性
- diff出变更项,得出更新进入更新队列(4和6或可合并成一次diff)
- 将更新队列中的任务一次进入虚拟DOM中
- 将虚拟DOM中需要变更的部分一次设置到DOM对象中
对比总结出: 当页面有N步数据更新的时候就算N步数据是同时更改的,视图部分也会进行N次渲染。 而Vue框架是采用非直接DOM操作的方式进行页面渲染,这样可以将视图更新时的多次更改放到一个任务队列中,最终将本次更改的N次变化统一由一次渲染替换真实的DOM对象,这样可以实现N次变更,一次渲染,在性能上更优于传统的DOM框架。
Vue好处:
- 响应式视图更新。
- 多次更改一次渲染
- 无需直接操作DOM。
Vue2
依赖收集原理
- 在初始化过程中(beforeCreate 和 created 之间) 拦截器(Object.defineProperty) 劫持了数据。
// 劫持的过程中定义了观察者 dep var dep = function Dep () { this.id = uid++; this.subs = []; };
- 然后在元素挂载过程中(beforeMount 和 mounted 之间)触发了拦截器(Object.defineProperty) get ,get 方法里含有 dep.depend();
- 观察者 dep 关联被观察者 watcher 的动作。
Dep.prototype.depend = function depend () { if (Dep.target) { Dep.target.addDep(this); } };
- watcher 的结构如下
var Watcher = function Watcher(vm, expOrFn, cb, options, isRenderWatcher) { this.vm = vm; // 保存被观察的实例 ... this.cb = cb; this.id = ++uid$2; // uid for batching ... this.expression = expOrFn.toString();// 触发实例的render方法 };
- 完成了依赖收集。最终就是我们熟知的触发流程,更改state时,拦截器的 set 触发了 dep.notify() 通知了所有被观察者 Wacher,而一番排队操作后需而触发 watcher 里的表达式,就去重新渲染这个组件。
- 观察者和被观察者就是这样的结构:一个依赖下包含多个watcher,
Dep { id: n, subs: [ 0: Watcher { ... // render 函数就是就是输出虚拟节点的 expression: "function () { vm._update(vm._render(), hydrating); }" ... } ] }
响应式原理
function updateView() {
console.log('视图更新')
}
function defineReactive(target, key, value) {
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue !== value) {
value = newValue
updateView()
}
}
})
}
function observe(target) {
if (target === null || typeof target !== 'object') {
return target
}
for(let key in target) {
defineReactive(target, key, target[key])
}
}
const data = {
name: '张三',
age: 21
}
observer(data)
data.name = 'lisi'
oberve函数判断源数据是否是对象,然后遍历,将源数据、键值、属性值传入defineReactive函数中。
defineReactive函数中通过Object.defineProperty对对象的每一项进行监听,如果修改了对象就触发了updateView函数。
Object.defineProperty实现的响应式
Object.defineProperty:方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty存在的缺点:
- Object.defineProperty不能深度监听data变化
- 在vue初始化时会递归data对象中每一个值,给予绑定get、set方法
- Object.defineProperty不能监听新增、删除属性
data.sex = 'man' delete data.name
- vue2通过Vue.set和Vue.delete来进行新增和删除属性
- 也可通过使用$forceUpdate强制渲染
- Object.defineProperty不能监听数组变化
const data = { nums: [1, 2, 3] } data.nums.push(4)
-
vue2通过重写数组的方法属性实现监听数组变化
const arrProto = Object.create(Array.property) // 数组的方法名称 let arrFn = [ 'push', 'pop', 'shift', 'unshift', ... ] arrFn.forEach(method => { arrProto[method] = function() { // 视图更新 updateView() Array.propery[method].call(this, ..arguments) } })
-
当data中的某一项是数组时,在observe函数中遍历进入defineReactive中,然后进行深度监听,如果是数组的话,就会将此数组的原型指向为arrProto,此时当前数组调用的数组方法,就会执行updateView()和数组原型的方法。
function observe(target) { if (target === null || typeof target !== 'object') { return target } if (Array.isArray(target)) }{ target.__proto__ = arrProto } for(let key in target) { defineReactive(target, key, target[key]) } }
- demo: 当执行数组方法时,data.nums.push(4):
① 进入observe函数 … ② for…in…循环遍历数组每一个值 ③ 进入defineReactive ④ 深度监听,进入observe … ⑤ 判断是否是数组 ⑥ 将数组的原型指向arrProto ⑦ 执行updateView ⑧ 执行Array.prototype的push方法
- demo: 当执行数组方法时,data.nums.push(4):
-
存在的问题
当我们使用vm对象更新对象中中初始化就存在的属性时 视图会自动触发更新,而当我们对对象内部不存在的 属性设置值的时候视图不更新,这是因为Vue2.x底层的数 据响应式系统使用的是Object.defineProperty()来实现的。 Vue在初始化的时候会递归的将data选项中的属性绑定 setter和getter,通过两者来观察属性的行为,一旦初始 化完成便不会再次执行本操作,所以更改已知属性的时候 Vue实例是有感知并执行默认处理行为的,当我们操作对 象内部不存在的属性时由于其没有setter和getter,Vue将 无法观察到这些属性的赋值和取值,视图更新操作也无法进行。
Vue3
Vue2都出了,Vue3还会远吗
作者正在编写中...