架构的思考(1)

定目标,有了目标才有发力的方向。实现,敲代码或者不敲代码,或者是只分析。这个过程是循环往复的,

目标给实现定了一个方向,而在实现过程中会发现定的目标不是那么清晰,这时会去修正目标。所以这个过程是循环往复的,在不断循环中,我们的认知会不断提高。

从这种循环往复的过程出来,以架构师的视角,分析vue响应式系统是如何开发的😏

目标:视图与数据的关联

首先,vue的最大特点就是 视图与数据 的关联。那么就可以设置一个初始的目标,当然这个目标是很粗略的,我们需要在循环过程中不断细化它,在这个过程中反馈回来很多问题,这些问题会让我们去审视我们的目标是否合理。

视图

什么是视图,在程序里表现出来的是什么样的东西?如果使用一个dom来表现视图,那是视图在计算机里表现出来的就是对象,vue的虚拟dom也是个对象,那字符串也可以表示视图,看到的就是一串字符串,所以视图,本质上就是数据。那我们的目标就出现偏差了,这么分析,应该是数据与数据的关联。

那我们在平常工作和生活中,哪里有这种关联呢?Execel大家都用过,起码在学校做各种文档的时候用过,里面就有了大量的数据与数据的关联。

前三个数据与7是不是发生关联了?但仔细想想,他们之间有个运算公式,那好像不存在数据与数据之间的关联啊,应该是数据跟某个计算发生了关联。回到我们的 目标 ,尽量把 视图 往 计算方向去靠,那 视图 就应该是和 某个计算 是有关系的,理解起来就是,创建视图的过程 与 数据发生了关联。当数据发生变化之后, 我们会重新执行创建视图的过程。那创建视图的过程是什么呢?在不同语言里,过程都变现为函数。

那我们的目标便可以改为 创建视图的函数 与 数据 的关联。

一定是创建视图的函数和数据的关联吗?比如这个函数是render,如果能实现这个函数与数据的关联,那通过类比,可不可以通过这种机制 实现任意函数与数据的关联呢?我们看render函数和普通函数没啥区别,就是通过一些数据,然后返回一个视图。而且在实际开发中,也需要把某些函数与数据发生关联,当某些数据改变的时候,重新拿到新的值。就好像上面Excel的求和。

这么看来,我们定的目标就有点狭隘了,仅仅局限于视图,那通过这么分析,我们就可以修正目标:函数与数据的关联。

数据

在语言里有各种各样的数据,那这里的数据是什么数据呢?是定义的所有数据吗?有data1、data2、data3,当data1改变的时候,我需要执行函数,得到新的值。那这里说的数据,应该是函数用到的数据。回到render函数,比如说用到了name,那就是只有当name改变时,才去创建视图。 那怎么理解用到,比如一个判断语句,会走不同的分支,也就是说用到的数据不确定,所以说,一个函数和什么样的数据发生关联,取决于函数的运行过程。

那我们的目标可以进一步修正,目标:函数 与 函数运行过程中用到的数据 的关联。

好像隐隐约约摸到那个点了啊,再想想,函数运行中用到的数据一定要发生关联吗?就是这个数据发生变化之后,就一定要执行函数吗? 这样的机制是不是不应该固定死?那从设计的角度上来看,应该由用户决定。用户提供数据,提供函数,我们把它们关联起来,另外用户还要告诉我们哪些数据是需要关联的。

那就应该找到一种机制,让用户告诉我们什么样的数据是要发生关联的,我们可以认为需要关联的数据被打上了标记,这个打上标记的过程看做是一个函数。

js 复制代码
var a = true,
  b,
  c;

  tag(b);//打标记

  function fn(){
    if(a){
      b
    }else{
      c
    }
  }

在这个过程中,用到了a,可能用到b 或 c,那这些数遍改变,都要关联吗? 这时可以做个标记,说明那个数据变动时需要进行关联。

那对目标进行进一步修正,目标:函数 与 函数运行过程中用到的标记数据的关联。

有没有,已经和vue响应式系统搭边了。

实现

好了,目标明确了,那如何去实现这个关系呢?如何建立对应关系呢?来看这个关联,这种关系之所以能够建立,就是函数运行期间,读到了这个数据且打上标记了,那我们就必须去监听数据的读取。还有这个数据发生变化了,还要重新执行函数,js不对自动去做,需要我们手动去执行,因此还需要知道数据什么时候被修改了。另外,当数据被读和被修改的时候,我们还得知道是被哪个函数读了。

因此这里涉及两方面:1.监听数据的读取和修改 2.如何知晓数据对应的函数

在js里,提供了两种方法去监听数据的读取和修改。

defineProperty

它监听范围很窄,只能通过属性描述符去监听已有属性的读取和赋值,不过兼容性不错。

proxy

监听的范围很广。对于对象的操作,在语言层面上比如.a读取或者赋值等,都是会转变成对象内部方法的调用,这些内部方法的调用就是对象的基本操作。而Proxy可以拦截全部的操作。

那我们这个标记函数就是去监听数据的各种操作,那因为上面两个方法都需要对象,所以监听的数据必须是对象。

js 复制代码
  function tag(target){
    return new Proxy(target,{
      get(target,key){

      },
      set(target,key,value){

      }
    })
  }

那我们在使用的时候,就不能使用原始的对象了,只能使用代理的对象。

js 复制代码
const proxy = tag(b); //打标记

function fn() {
  if (a) {
    proxy //代理对象
  } else {
    c;
  }
}

那在vue里这个标记函数命名为reactive,而是把这个函数返回的对象称作响应式数据。

这一过程是非常复杂了,涉及的细节很多,现在只是个雏形,先暂时写点简单的代码。

  • 读取

在读取的时候,其中一件事就是把值返回

js 复制代码
 get(target, key) {
      return target[key]; //返回对象属性值
    },

另外就是哪个函数读取了这个属性,要把它找出来,在vue里把这个过程称为依赖收集

js 复制代码
//effect.js

/**
 * @description: 依赖收集(建立对应关系)
 * @param {* object} target
 * @param {* string} key
 * @return {*}
 */
export function track(target,key) {}
js 复制代码
 get(target, key) {
      track(target, key); //依赖收集
      return target[key]; //返回对象属性值
    },
  • 修改

第一件事就是修改属性值了

js 复制代码
 set(target, key, value) {
      Reflect.set(target, key, value); //设置对象的响应属性
    },

然后属性发生改变了,那就要重新运行函数,vue把这一过程称为派发更新。

js 复制代码
/**
 * @description: 派发更新
 * @param {* object} target
 * @param {* stirng} key
 * @return {*}
 */
export function trigger (target,key){
  console.log('派发更新',key);
}
js 复制代码
 set(target, key, value) {
      trigger(target, key); //派发更新
      return Reflect.set(target, key, value); //设置对象的响应属性
    },

看看代码效果

js 复制代码
import { reactive } from "./reactive.js";

const state = reactive({ a: 1, b: 2 });

function fn() {
  state.a;
  state.b;//读取
}
fn();

state.a = "gag"; //修改
相关推荐
Lank_M几秒前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写2 分钟前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李4 分钟前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易18 分钟前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
清晨Coding22 分钟前
删了 Cookie 还能自动登录?Google系网站登录态自动恢复的原理分析
javascript
风骏时光牛马29 分钟前
前沿视界
前端
kyriewen34 分钟前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易1 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员
IT_陈寒1 小时前
Vue的这个响应式陷阱,差点让我加班到天亮
前端·人工智能·后端
程序员大阳2 小时前
HTML+CSS+JS对话式实战(9)-项目总结与问题分析
javascript·css·html·实战·项目总结