(文章是讨论vue源码,但是代码中和真正的源码会有些不同,参杂个人理解,主要是讲原理,大家可以在理解后自行去看源码)
1.我们回顾一下
把你放到时光机,回到几年前,看看我们写的代码(骗人,啊啊啊,那时候,还没写前端呢),然后再结合当下的一些框架,感受下知识迭代的力量,上图:
这是一个简单的原生态的表单,原谅我差点不会写了,form表单还是很强大的,知道我们的需求,但是时代变了,人类要的更多,由单向数据绑定迁移到了双向数据绑定:
一个简单的vue实例 :
knockout 实例:
你见或不见,他就在那里😂(yuque应该添加个更加的表情功能),虽然形式不同,都是数据绑定,我们先来看看vue是怎么操作的。
2.初见vue
1)vue构造函数
vue初始化的函数_init()里面有这样一个函数:observe(this.$data) ,它就是来做数据监听的,这里体现一种设计模式:发布-订阅模式。
发布者:dep.nodify() 实际上就是将dep收集来的依赖们,自行update()
订阅者:依赖此数据的那些节点 (Watcher们),订阅相应数据的Dep ,dep.add()
总之就是Dep 负责收集所有相关的的订阅者 Watcher,Watcher 负责订阅 Dep 。
2)vue 2.0版本的数据监听
主角:Observer 类 实现对viewModel的监视,当发生变更时发出变更消息
更加基础的内容请移步:https://zhuanlan.zhihu.com/p/37753129
export function observe(data) {
if (!data || typeof data !== 'object') { // 因为用到递归遍历,所以要有终止条件
return;
}
return new Observer(data);
}
class Observer{
constructor(value){
this.value = value
//def函数是给value添加一个__ob__属性,值为当前observe实例
def(value,'__ob__',this)
if(isArray(value)){
this.observeArray(value)//数组的监听有些特别,此处单独处理
}else {
this.walk(value);
}
}
walk(obj){
Object.keys(obj).forEach(key=>{
this.defineReactive(obj,key,obj[key])
})
}
defineReactive(obj, key, val) {
//dep 可以理解为一个容器,或者一个栈都可以,存在某个数据的依赖集
let dep = new Dep();
let childObj = observe(val); // 递归遍历孩子
Object.defineProperty(obj, key, {
enumerable: true, // 枚举
configurable: true, // 不可再配置
get() {
if(Dep.target){
//这里保证只有视图中需要被用到的data才会触发getter
//在解析指令的时候,此处会被调用,Dep.target是当前的那个watcher
dep.addSub(Dep.target);
if(Array.isArray(val)){
if (childObj) { // 一维数组
childObj.dep.addSub(Dep.target);
}
for (let e, i = 0, l = val.length; i < l; i++) { // 多维数组
e = val[i];
e && e.__ob__ && e.__ob__.dep.addSub(Dep.target);
}
}
}
return val;
},
set(newValue) {
if (newValue == val) {
return;
}
//这里用了闭包
val = newValue;
childObj = observe(newValue); // 新添加的属性也要进行转换
dep.notify();
}
});
}
observeArray(arr) {
arr.__proto__ = arrayProxyMethod; //添加方法,直接遍历子属性
arr.forEach(ele => {
observe(ele);
})
}
}
export default class Dep{
constructor(){
this.subs = [];
}
addSub(sub){
this.subs.push(sub);
}
notify(){
this.subs.forEach(ele=>{
ele.update();
})
}
}
3)对于数组的监听 observeArray
let test= {},arr = [1,2,3]
Object.defineProperty(test,'book',{
get:function(){
console.log('get func')
return arr
},
set:function(val){
console.log('set func')
arr = val
}
})
由于js的限制,Object.defineProperty对于数组来说,除非对于test.arr整体赋值,否则,当应用数组的方法将其改变的时候,是无法触发set函数的:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
- vm.items.push(99)
- Vue 不能检测对象属性的添加或删除:
解决第一个问题的方法:
Vue.set(vm.items, indexOfItem, newValue)或者vm.items.splice(indexOfItem, 1, newValue)
解决第二个问题的方法:
vm.items.splice(newLength)
解决第四个问题的方法:
Vue.set(object,propertyName,value)
解决第三个问题的方法:改造下数组上的方法:
const proxyMethods = [
'pop',
'push',
'sort',
'shift',
'splice',
'unshift',
'reverse'
];
const arrayProto = Array.prototype;
export const arrayProxyMethod = Object.create(arrayProto);
proxyMethods.forEach(method => {
const oldMethod = arrayProto[method];
arrayProxyMethod[method] = function() {
let args = toArray(arguments);
let result = oldMethod.apply(this, args);
let insert;
//这里就用到了之前存放的observer实例
const ob = this.__ob__;
switch (method) {
case 'push':
case 'unshift':
insert = args;
break;
case 'splice':
insert = args.slice(2);
}
if (insert) {
ob.observeArray(insert);
}
ob.dep.notify();
return result;
}
});
4)3.0版vue监听策略
new Proxy()是es6里面的东西,用它可以弥补Object.defineProperty的不足,省却了如上许多代码,如我们可以不用在递归遍历属性了,也不用重写数组方法了,旧的事物取代新的事物,确实是它更适应现在的条件。
下面是一个小例子,至于proxy原理还要在学习中
Proxy基本语法
const obj = new Proxy(target, handler);
参数说明如下:
target: 被代理对象。
handler: 是一个对象,声明了代理target的一些操作。
obj: 是被代理完成之后返回的对象。
const target = {name:'doubao'}
const handler = {
get:function(target,key){
return target[key]
},
set:function(){
target[key] = value
}
}
const testobj = new Proxy(target,hanler)
testobj.name //doubao
testobj.name = 2
testobj.name //2
应用场景:
数据过滤:如富文本内容过滤