vue 中的潜规则

  • \_ 开头的数据表示私有属性,不能被访问
  • 以 $ 开头的数据表示只读数据

proxy 数据代理

相当于用 this.name 访问 this.\_data.name

例如:

1
2
3
4
5
6
7
8
9
10
var o1 = { name: "张三" };
// 要有一个对象 o2 ,要使用 o2.name 访问 o1.name

Object.defineProperty("o2", "name", {
get() {
return o1.name;
},
});

// 这样代理后,访问 `o2.name` 就相当于访问 `o1.name`

发布订阅模式

目的:解耦,让各个模块之间没有紧密的联系。

在 vue 中,整个更新是按照组件的单位进行判断,以节点为单位进行更新。

  • 如果代码中没有自定义组件,则在比较算法的时候,会将全部的模板对应的虚拟 dom 进行比较
  • 如果代码中含有自定义组件,则在比较算法的时候,会判断是那一个组件进行了更新,只会判断更新数据的组件,其他组件不会更新。

特征:

  • 有一个中间的全局容器,用来存储可以被触发的的东西(函数,对象)
  • 有一个方法可以往容器中传入东西,(函数,对象)
  • 有一个方法可以将容器中的东西出来并且使用,如果是函数则是函数调用,如果是对象,则是对象的方法调用

js 中的类型比较

  • 基本类型比较值
  • 引用类型比较引用地址
  • 引用类型和基本类型比较时转化为基本类型行进行比较,如果是 === 严格比较,不会转换
1
2
[] == [] // false
{} == {} // false

JavaScript 是解释型语言,从左到右解析时碰到第一个 [] 会在内存中声明一个引用地址来存放一个空数组,接着往右继续解析时会在内存中继续声明一个引用地址来比较,由于两个空数组不是同一个引用地址,所以判断结果为 false

1
2
3
4
5
let a = [];
a == a; // true

let f = () => {};
f == f; // true

上面代码首先声明了 a 为一个空数组,下面判断时相当于都指向了同一个引用地址,所以结果为 true

事件移除分析

1
2
3
4
5
6
7
8
9
10
11
12
// 获取按钮实例
let btn = document.querySelector("#btn");

// 给按钮添加click点击事件,触发一个回调方法
btn.addEventListener("click", () => {
console.log("点击了");
});

// 删除click点击事件,并移除回调方法
btn.removeEventListener("click", () => {
console.log("点击了");
});

上面代码并不会移除按钮的点击事件,因为两个方法不是同一个引用地址,所以不会被移除,点击按钮仍然可以出发打印

改进:

1
2
3
4
5
6
7
8
let f = () => {
console.log("点击了");
};
let btn2 = document.querySelector("#btn2");
// 按钮添加时间
btn2.addEventListener("click", f);
// 按钮移除事件
btn2.removeEventListener("click", f);

改进后让添加的方法和移除的方法都执向同一个引用地址,这样可以移除按钮绑定的事件

引入 Watcher

  • 实现(分成两部)
    • 修改后刷新(响应式)
    • 依赖收集

在 Vue 中提供有构造函数 Watcher ,有以下几个方法

  • get() 用来计算执行处理函数
  • update() 公共的外部方法,该方法会触发内部的run方法
  • run() 运行,用来判断内部运行是异步运行还是同步运行,这个方法最终会调用内部的get方法
  • cleanupDep() 清除队列

其中 get() 方法来完成页面渲染操作

引入 Dep 对象

该对象提供 依赖收集(depend),和派发更新(notify)的功能

在notify中调用 Watcher 的 update 方法

Watcher 和 Dep

之前将渲染 watcher 放在全局作用域中,这样的处理是有问题的。

  • vue项目中包含很多组件,每个组件都是自治
    • 那么 watcher 可能会有多个
    • 每一个 watcher 描述一个渲染行为或计算行为
      • 子组件发生数据更新,页面就要进行渲染,(vue中是局部更新)
      • 例如:vue中推荐使用 计算属性 代替复杂的 插值表达式
        • 计算属性会伴随其使用的属性变化而变化
        • name() => this.firstName + this.lastName
          • 上述表达式中计算属性 name 依赖于 firstName 和 lastName
          • 只要被依赖的属性发生变化,那么就会促使计算属性重新计算,这种重新计算行为就是 watcher 来完成的。
  • 依赖收集派发更新 是怎么运行起来的???
    • 在 vue 访问页面的时候就收集到了用到的属性,修改的时候进行更新,收集到什么,就更新什么

所谓的依赖收集实际上就是告诉当前的 watcher 什么属性被访问到了,那么这个 watcher 在 计算 或者 渲染页面 的时候就会将这些收集的属性进行更新没有收集到的属性不会被更新,这样就起到的局部更新的作用。

如何将 属性 与 当前 watcher 关联起来???

  • 在全局准备一个 targetStack(target 栈,简单理解为一个 watcher “数组”,吧一个操作中使用到的watcher存储起来)
  • 在watcher调用get的时候,将当前watcher放到全局,在get调用结束之前,将这个全局的 watcher 移除,方便后面的watcher进行更新,由此提供两个方法:pushTarget,popTarget
  • 在每一个属性中都有一个Dep对象
    • 我们在访问对象属性的时候( get方法 ),我们的渲染 watcher 就在全局中,将属性与 watcher 关联,就是将当前的 watcher 存储到 Dep 中,同时在watcher中也存储当前的Dep对象,是互相引用的关系
    • 属性引用的当前的渲染 watcher ,就知道谁渲染它
    • 当前渲染 watcher 引用当前访问的属性(Dep对象),知道要去渲染什么属性

在Dep中有一个方法为notify(),内部就是将subs里面的东西取出来,依次调用其 uodate 方法。

  • subs里面存储的就是知道要渲染什么属性的 watcher

梳理 watcher 和 dep 之间的关系