vue3响应式原理简单手写

/*
 * @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方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值