/*
* @Descripttion: vue3.0响应式原理代码实现
* @version: 1.0
* @Author: yuhui
* @Date: 2020-08-02 15:34:38
* @LastEditors: yuhui
* @LastEditTime: 2020-09-13 16:00:36
*/
// vue2.0源码缺陷:1.默认递归 2.数组改变length是无效的 3.对象不存在的属性不能被拦截
// 为了解决情况三出现的问题
let toProxy = new WeakMap(); // { 原对象: 代理过的对象 }
let toRaw = new WeakMap(); // { 代理过的对象: 原对象 }
// 判断是不是对象
function isObject(val) {
return typeof val === 'object' && val !== null;
}
// 响应式的核心方法
function reactive(target) {
return createReactiveObject(target); //响应式对象
}
// 为了解决情况四的问题
function hasOwn(target, key) {
return target.hasOwnProperty(key);
}
// 创建响应式对象
function createReactiveObject(target) {
// 判断是否为对象
if(!isObject(target)){
return target;
}
// 解决情况三的问题,为了防止 reactive({name: {firstName: 'xiao'}}); 已知重复操作
let proxy = toProxy.get(target);
if(proxy) {
return proxy;
}
// 解决情况三的问题,为了防止 reactive(proxy); 已知重复操作
if(toRaw.has(target)) {
return target;
}
let baseHandler = {
get(target, key, receiver) {
// Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。不会报错,有返回值,会替代掉Object.
// 与return target[key];实现效果类似,不过有一个Boolean类型的返回值,表示结果是否成功;
let res = Reflect.get(target, key, receiver); // receiver可以简单理解为this上下文
// 依赖收集 订阅 ;把当前的key和effect对应起来。
track(target, key); // 如果目标的key对应的值变化了,重新执行数组中的effect
return isObject(res) ? reactive(res) : res; //为了解决情况二的突发情况
},
set(target, key, value, receiver) {
let hadKey = hasOwn(target, key);
let oldValue = target[key];
let res = Reflect.set(target, key, value, receiver);
// 为了解决情况四的问题.需要判断是新增属性,还是修改属性。屏蔽修改属性。
if(!hadKey) {
trigger(target, 'add', key);
console.log('新增属性');
}else if(oldValue !== value){ //表示数值已经被赋值过了
trigger(target, 'revise', key);
console.log('修改属性');
}
return res;
},
deleteProterty(target, key){
return Reflect.deleteProperty(target, key);
}
}
let observed = new Proxy(target, baseHandler); // 之前没有被用,是因为兼容性比较差,IE11都不兼容
//返回Proxy之前要先判断是不是存在情况三的情况
toProxy.set(target, observed);
toRaw.set(observed, target);
return observed;
}
/*****************响应式过程中可能出现的一些情况*******************/
//1. 情况一:只有一层
// let proxy = reactive({name: 'xiao hui'});
// proxy.name = "da hui";
// console.log(proxy.name);
// 2.情况二:里面嵌套着多层对象。递归解决
// let proxy = reactive({name: {firstName: 'xiao'}});
// proxy.name.firstName = "da";
// console.log(proxy.name.firstName);
// 3.情况三: 一直调用reactive(object)。通过WeakMap()解决该问题
// let proxy = reactive({name: {firstName: 'xiao'}});
// reactive({name: {firstName: 'xiao'}}); //没有必要,可能会触发几次更新
// reactive({name: {firstName: 'xiao'}}); //没有必要
// reactive({name: {firstName: 'xiao'}}); //没有必要
// reactive({name: {firstName: 'xiao'}}); //没有必要
// reactive(proxy); // 没有必要,可能会触发几次更新
// reactive(proxy); // 没有必要,可能会触发几次更新
// reactive(proxy); // 没有必要,可能会触发几次更新
// 4. 情况四://以下操作会触发两次更新视图的操作,push操作和更新length操作。因此,需要屏蔽掉最后面那一次操作。
let arr = [1, 2, 3];
let proxy = reactive(arr);
proxy.push(10);
proxy.length = 100;
/*****************依赖收集 && 发布订阅*******************/
// 栈 先进后出 {name: [effect]}
let activeEffectStacks = []; //栈型结果
let targetsMap = new WeakMap(); // 集合和hash表
/**
* 如果target中的key变化了,就执行数组中的方法
* @param {*} target
* @param {*} key
*/
function track(target, key) {
let effect = activeEffectStacks[activeEffectStacks.length - 1];
//动态创建依赖关系
if(effect) { //有对应关系,才创建关联
let depsMap = targetsMap.get(target);
if(!depsMap) {
targetsMap.set(target, depsMap = new Map());
}
let deps = depsMap.get(key);
if(!deps) {
depsMap.set(key, deps = new Set());
}
if(!deps.has(effect)) {
deps.add(effect);
}
}
}
function trigger(target, type, key) {
let depsMap = targetsMap.get(target);
if(depsMap){
let deps = depsMap.get(key);
if(deps) { //当前key对应的effect 依次执行
deps.forEach(effect => {
effect();
});
}
}
}
function effect(fn) {
// 需要把fn这个函数 变成响应式的函数
let effect = createReactiveEffect(fn);
effect(); //默认执行一次
}
// 高阶函数
function createReactiveEffect(fn) {
let effect = function() { //创建响应式的effect
return run(effect, fn); //让fn执行以及把effect存到栈中
}
return effect;
}
//让fn执行以及把effect存到栈中
function run(effect, fn) {
try {
activeEffectStacks.push(effect);
fn(); //和Vue2一样,都利用了js是单线程的特性
} finally {
activeEffectStacks.pop(); //执行完之后清空掉
}
}
let proxy = reactive({name: '最帅的男人'});
effect(()=>{ //这个方法是我自己暴露出来的
console.log(proxy.name); //会调用get方法
})
// 问题一: 没有使用Vue.effect,直接调用console.log
// console.log(proxy.name); //会调用get方法
// 问题二:执行多次
// Vue.effect(()=>{ //这个方法是我自己暴露出来的
// console.log(proxy.name); //会调用get方法
// })
// Vue.effect(()=>{ //这个方法是我自己暴露出来的
// console.log(proxy.name); //会调用get方法
// })
// proxy.name = "更新名字"; // 调用set方法
vue3响应式原理简单手写
最新推荐文章于 2024-07-02 10:39:52 发布