1.Vue的基本原理
https://blog.csdn.net/weixin_42429718/article/details/104472989
当一个Vue实例创建时,vue会遍历data选项的属性,用 Object.defineProperty(vue3.0使用proxy )将它们转为 getter/setter 并且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每个组件实例都有相应的 watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。
由此仿写vue内部对数据的监听
- 基于发布订阅模式。
- 创建一个Observer来劫持所有属性,充当‘发布者’角色,通知变化。
- 创建一个Watcher充当订阅者角色,订阅data数据,当data数据发生变化的时候,更新视图。
- 创建一个Dep的类用于给Watcher和Observer之间解耦合。
class Vue {
constructor(options) {
this.$el = document.querySelector(options.el);
this.$data = options.data
observer(this.$data)
//获取插值表达式
let mustach = this.$el.innerText;
console.log(mustach);
let regExp = /\{\{(.+)\}\}/
let prop = regExp.exec(mustach);
prop = prop[1]
this.$el.innerText = this.$data[prop]
//Watcher 订阅者 订阅某个属性的变化,当属性值发生变更的时候,触发视图更新
class Watcher {
constructor(data, key, fn) {
Dep.target = this;
this.data = data;
this.key = key;
this.fn = fn;
this.value = data[key] //添加订阅 交给了defineProperty中的get完成
Dep.target = null; //添加完订阅之后 清空target
}
update() { //更新视图
//获取更新之后的值
this.value = this.data[this.key];//获取更新后的值
this.fn(this.value)
}
}
let _this = this;
new Watcher(this.$data, 'name', function (value) {
//更新视图
console.log(this);
_this.$el.innerHTML = value
})
this.proxy_data() //属性代理
}
proxy_data() { //将$data里面的属性代理到根实例
Object.keys(this.$data).forEach(key => {
Object.defineProperty(this, key, {
configurable: true,// 当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
enumerable: true,// 当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。
get() {
return this.$data[key]
},
set(newVal) {
if (newVal !== this.$data[key]) {
this.$data[key] = newVal
}
}
})
})
}
}
//Observer 发布者,用于劫持data中的属性
function observer(data) {
if (typeof data !== "object") {
return
}
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
//Dep 一个用于解耦合的类
class Dep {
constructor() {
this.subs = [];//储存订阅者
}
addSub(watcher) { //添加订阅
this.subs.push(watcher)
}
notify() { //通知变化
this.subs.forEach(watcher => {
watcher.update()
})
}
}
//defineReactive 定义响应
function defineReactive(obj, key, value) {
observer(value)
let dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
console.log(`${key}被访问了`);
Dep.target && dep.addSub(Dep.target)
return value
},
set(newVal) {
value = newVal
dep.notify()//通知变化
}
})
}`
以上仿写vue2.X采用了Object.defineProperty() 有一些对属性的操作,使用这种方法无法拦截,比如说通过下标方式修改数组数据或者给对象新增属性,vue 内部通过重写函数解决了这个问题。 在Vue中,响应式处理利用的是Object.defineProperty对数据进行拦截,而这个方法并不能监听到数组内部变化,数组长度变化,数组的截取变化等,所以我们需要对这些操作进行hack,让Vue能监听到其中的变化。 具体实现: 改写其中部分方法,如下图
- 存储数组原型上的方法
- 改写数组原型方法
- 再调用原生方法,并将结果返回
// 缓存数组原型
const arrayProto = Array.prototype;
// 实现 arrayMethods.__proto__ === Array.prototype
export const arrayMethods = Object.create(arrayProto);
// 需要进行功能拓展的方法
const methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse" ];
/** * Intercept mutating methods and emit events */
methodsToPatch.forEach(function(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) {
// push、unshift会新增索引,所以要手动observer
case "push":
case "unshift":
inserted = args;
break;
// splice方法,如果传入了第三个参数,也会有索引加入,也要手动observer。
case "splice": inserted = args.slice(2); break; }
//
if (inserted) ob.observeArray(inserted);
// 获取插入的值,并设置响应式监听
// notify change
ob.dep.notify();
// 通知依赖更新
// 返回原生数组方法的执行结果
return result;
});
});
简单来说就是,重写了数组中的那些原生方法,首先获取到这个数组的__ob__,也就是它的Observer对象,如果有新的值,就调用observeArray继续对新的值观察变化(也就是通过target__proto__ == arrayMethods来改变了数组实例的型),然后手动调用notify,通知渲染watcher,执行update。
Proxy
在 Vue3.0 中已经不使用这种方式了,而是通过使用 Proxy 对对象进行代理,从而实现数据劫持。使用Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为这是 ES6 的语法。
什么是 Proxy?
Proxy 这个词翻译过来就是“代理”,用在这里表示由它来“代理”某些操作。 Proxy 会在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可 以对外界的访问进行过滤和改写。
先来看下 proxy 的基本语法
const proxy = new Proxy(target, handler)
target :您要代理的原始对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
handler :一个对象,定义将拦截哪些操作以及如何重新定义拦截的操作
一个简单的Demo
let array = [1, 2, 3, 4, 5, 6];
let p = new Proxy(array, {
get() {
console.log('array get被触发了');
},
set(val) {
console.log('set被触发了');
console.log(val);
}
})
Proxy 还可以做什么?
我们已经看到了 Proxy 在 Vue3 中的应用场景,其实在使用了Proxy后,对象的行为基本上都是可控的,所以我们能拿来做一些之前实现起来比较复杂的事情。
实现访问日志
let api = {
getUser: function(userId) {
/* ... */
},
setUser: function(userId, config) {
/* ... */
}
};
// 打日志
function log(timestamp, method) {
console.log(`${timestamp} - Logging ${method} request.`);
}
api = new Proxy(api, {
get: function(target, key, proxy) {
var value = target[key];
return function(...arguments) {
log(new Date(), key); // 打日志
return Reflect.apply(value, target, arguments);
};
}
});
api.getUsers();
校验模块
let numObj = { count: 0, amount: 1234, total: 14 };
numObj = new Proxy(numObj, {
set(target, key, value, proxy) {
if (typeof value !== 'number') {
throw Error('Properties in numObj can only be numbers');
}
return Reflect.set(target, key, value, proxy);
}
});
// 抛出错误,因为 "foo" 不是数值
numObj.count = 'foo';
// 赋值成功
numObj.count = 333;
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
作者: 佩吉吉
原文链接:<a href='https://juejin.im/post/6960953125117263886'>https://juejin.im/post/6960953125117263886</a>
</p>