这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战
更多文章
[vue2]熬夜编写为了让你们通俗易懂的去深入理解nextTick原理
[vue2]熬夜编写为了让你们通俗易懂的去深入理解vue-router并手写一个
[vue2]熬夜编写为了让你们通俗易懂的去深入理解vuex并手写一个
[vue2]熬夜编写为了让你们通俗易懂的去深入理解v-model原理
熬夜不易,点个赞再走吧
双向绑定
-
首先通过一次渲染操作触发Data的getter进行依赖收集
-
在data发生变化的时候会触发它的setter
-
setter通知Watcher
-
Watcher进行回调通知组件重新渲染的函数
-
diff算法来决定是否发生视图的更新
Observe
-
每个数据都有一个标记,防止重复绑定
-
Observer为数据加上响应式属性进行双向绑定,如果是对象,则进行深度遍历,为每一个子对象都绑定上方法,如果是数组,对每个成员进行遍历绑定方法
Observer源码逐步解析:
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
this.value = value
this.dep = new Dep() // 建立发布者
this.vmCount = 0
def(value, '**ob**', this)
if (Array.isArray(value)) {
// 是数组对每个成员进行遍历绑定方法
if (hasProto) {
// **proto**指向重写过后的原型
protoAugment(value, arrayMethods)
} else {
//遍历 arrayMethods 把它身上的这些方法直接给 value
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
// 是对象,则进行深度遍历,为每一个子对象都绑定上方法
// defineReactive 通过 Object.defineProperty 定义 getter 和 setter 收集依赖通知更新
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
Watcher
观察者对象
-
依赖收集后保存在deps里
-
变动的时候deps作为发布者通知watcher watcher进行回调渲染
Dep
-
发布者,可以订阅多个观察者
-
收集依赖后会有一个或者多个watcher
-
一旦有变动便通知所有watcher
Watch监听Array数组变化
监听对象:
data(){
return {
objVal: {
name: 'obj',
type: 'obj'
}
}
},
watch:{
objVal:{
handler(val,oldval){
},
deep: true,
immediate:true
}
},
methods:{
changeObj(){
this.objVal.name = 'newobj';
}
}
deep: 当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,深入监听,即监听对象里面的值的变化
immediate: watch默认当值第一次绑定的时候,不会执行监听函数,immediate的作用就是首次获取值也执行函数
以上demo是监听对象,如果换成数组的话,会出现vue不会响应数据变化而重新去渲染页面,则监听失败
解决方法:
// Vue.set
Vue.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
原理: Object.defineProperty对数组进行响应式化是有缺陷的 Vue使用了重写原型的方案代替
- 先获取原生 Array 的原型方法,因为拦截后还是需要原生的方法帮我们实现数组的变化。
- 对 Array 的原型方法使用 Object.defineProperty 做一些拦截操作。
- 把需要被拦截的 Array 类型的数据原型指向改造后原型
const arrayProto = Array.prototype // 获取Array的原型
function def (obj, key) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
value: function(...args) {
console.log(key); // 控制台输出 push
console.log(args); // 控制台输出 [Array(2), 7, "hello!"]
// 获取原生的方法
let original = arrayProto[key];
// 将开发者的参数传给原生的方法,保证数组按照开发者的想法被改变
const result = original.apply(this, args);
// do something 比如通知Vue视图进行更新
console.log('我的数据被改变了,视图该更新啦');
this.text = 'hello Vue';
return result;
}
});
}
// 新的原型
let obj = {
push() {}
}
// 重写赋值
def(obj, 'push');
let arr = [0];
// 原型的指向重写
arr.__proto__ = obj;
// 执行push
arr.push([1, 2], 7, 'hello!');
console.log(arr);
源码解析 array.js
Vue在array.js中重写了methodsToPatch中七个方法,并将重写后的原型暴露出去。
// Object.defineProperty的封装
import { def } from '../util/index'
// 获得原型上的方法
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
// Vue拦截的方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
// 1.拦截方法
// 2.将开发者的参数传给原生的方法
// 3.重写
// 4.视图更新
methodsToPatch.forEach(function (method) {
// 原型方法进行赋值,不会去重新改写Array.prototype
const original = arrayProto[method]
//ob为成员唯一标识
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
}
//判断后observeArray为每个成员绑定方法
if (inserted) ob.observeArray(inserted)
// 通知视图更新
ob.dep.notify()
return result
})
})
问答
问:遇到过改变对象或者数组的时候视图没有更新的情况吗?为什么?怎么解决?
场景:
1.利用索引直接设置一个项时:vm.items[indexOfItem] = newValue
2.修改数组的长度时: vm.items.length = newLength
3.Vue 不能检测到对象属性的添加或删除
原因:
-
因为没有用被重写的方法去修改数组,导致没有响应式的监听到
-
而vue官方文档有明确说明,Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的
解决方法:
// 1. 利用this.$set(this.object,key,value)
this.$set(this.obj,"sex","man")
// 2. 利用this.$delete(target, propertyName/index )
this.$delete(this.testData,"name")
// 3. 利用Object.assign({},this.obj)
this.obj = Object.assign({},this.obj,{"myName","jojo"})
set()通过defineReactive(ob.value, key, val)触发响应式