Vue的实例化 会先 经过 options的合并 (将mixin的数据等)
import { initGlobalApi } from './global-api/index';
import { initMixin } from "./init";
import { lifecycleMixin } from "./lifecycle";
import { stateMixin } from './state';
import { renderMixin } from "./vdom/index";
// 用Vue的构造函数 创建组件
function Vue(options){
this._init(options); //入口方法,做初始化操作
}
// 原型方法
initMixin(Vue); // init方法
lifecycleMixin(Vue); // _update
renderMixin(Vue); // _render
stateMixin(Vue);
// 静态方法 Vue.component Vue.directive Vue.extend Vue.mixin ...
initGlobalApi(Vue);
// 初始化方法
export default Vue;
Vue的初始化方法
import { compileToFunctions } from "./compiler/index";
import { callHook, mountComponent } from "./lifecycle";
import { initState } from "./state";
import { mergeOptions } from "./util";
export function initMixin(Vue) {
Vue.prototype._init = function(options) {
const vm = this;
console.log("全局的options:",vm.constructor.options)
// 所谓的全局的 options 就是 挂载在 Vue 上的 options
// 就是 使用 Vue.mixin 挂载的 options vm.constructor.options
console.log("---------------------------------------------------")
// 之所以 这么写 vm.constructor.options
// 是因为 这个 构造函数 也有可能 是 子类 (组件) 来调用 这样 就应该 使用子类的
vm.$options = mergeOptions(vm.constructor.options, options); // 需要将用户自定义的options 和全局的options做合并
console.log("用户 自定义的options:",options)
console.log("---------------------------------------------------")
console.log("合并后的的options:",vm.$options)
callHook(vm, 'beforeCreate')
initState(vm); // 初始化状态
callHook(vm, 'created')
if (vm.$options.el) { // 挂载的逻辑
vm.$mount(vm.$options.el)
}
}
// 1.render 2.template 3.外部template (el存在的时候)
Vue.prototype.$mount = function(el) {
// 挂载操作
const vm = this;
const options = vm.$options;
// el 代表 节点
el = document.querySelector(el);
if (!options.render) {
let template = options.template;
if (!template && el) {
template = el.outerHTML;
}
// template => render方法
// 1.处理模板变为ast树 2.标记静态节点 3.codegen=>return 字符串 4.new Function + with (render函数)
const render = compileToFunctions(template);
//render = new Function(`width(this){_c(,_v(),...)}`)
options.render = render
}
// 渲染时用的都是这个render方法
// 需要挂载这个组件
mountComponent(vm, el);
}
}
再对合并后的参数 options 中的 data 进行属性的劫持
拦截的方法是 使用 observe 方法 先判定 该 变量 是否 是 对象或者为null。如果不是直接返回原对象
export function observe(data){
// typeof null 也是object
// 不能不是对象 并且不是null
if(typeof data !== 'object' || data == null){
return;
}
if(data.__ob__){
return data;
}
return new Observer(data)
// 只观测存在的属性 data:{a:1,b:2}
// 数组中更改索引和长度 无法被监控
// vm.a = {a:1}
}
如果 是 对象 那么就 以 该对象 为参数 创建 一个 Observer 类的 实例 并使用 Object.defineProperties 新建一个 不可枚举的属性 ob 该属性 指向 observer 本身 可以用来 解决 循环引用的问题 并给 对象 本身创建一个dep(在使用对象本身的时候保存观察者) 然后 循环 对象的key 给每个 属性 都 加上 get 和 setter 方法 并创建该 属性的dep 实例 在 get 方法中 收集 依赖 在 set 方法中 通知 依赖对应的 watcher 进行 更新
Observer 类
import { arrayMethods } from "./array";
import { defineProperty } from "../util";
import Dep from "./dep";
class Observer{
constructor(value){
this.dep = new Dep(); // value ={} value = []
// 使用defineProperty 重新定义属性
// 判断一个对象是否被观测过看他有没有 __ob__这个属性
defineProperty(value,'__ob__',this);
if(Array.isArray(value)){
// 我希望调用push shift unshift splice sort reverse pop
value.__proto__ = arrayMethods;
this.observeArray(value); // 数组中普通类型是不做观测的
}else{
this.walk(value);
}
}
observeArray(value){
value.forEach(item=>{
observe(item); // 观测数组中的对象类型
})
}
walk(data){
let keys = Object.keys(data); // 获取对象的key
keys.forEach(key=>{
defineReactive(data,key,data[key]); // Vue.util.defineReactive
});
}
}
数据劫持之后会进行 组件的挂载 会先查看 option 里 有没有 render 函数、再看有没有 template 模板 再看有没有 el 没有 render 函数 就 创建 render函数(使用 template 或者 el 对应的html 模版来创建) 这时就有了 render 函数 由render函数 执行 获得 虚拟Dom,vm._update()函数 将 虚拟Dom 改为 真实Dom
如上 以updateComponent为参数 创建该vue 组件实例的 watcher 并在构造函数调用 本watcher的 get 方法
get() {
// Dep.target = watcher
pushTarget(this); // 当前watcher实例
let result = this.getter(); // 调用exprOrFn 渲染页面 取值(执行了get方法) render方法 with(vm){_v(msg)}
popTarget(); //渲染完成后 将watcher删掉了
return result
}
渲染本组件
let updateComponent = ()=>{
vm._update(vm._render()); // 渲染 、 更新
}
在 渲染组件的时候 会发生 取值操作 这时 属性的set方法 就会发挥作用 把 当前 watcher 保存在 取到的 属性的dep对象的一个 set 中 这时就实现了 依赖的搜集(一个属性可能 被多个 组件调用 就会有 多个 watcher 同时也会被 单个组件使用多次,所以 需要 去重)
之后在进行每次赋值 都会 调用 该属性 对应的 dep.notify() 方法 而 该属性的 dep 的 notify 方法的功能是 遍历调用 该dep存储的 wacther 的 update 方法 而 每个watcher 的 update方法的功能是将 本实例watcher 放入一个 全局的队列 queue , 并且 在 内部 使用了 id 进行去重(在某段时间内会 修改 多个 属性 所以 watcher 可能会 重复)
vue 有一个方法 flushSchedulerQueue 是将这个 保存着 watcher的队列(就是数组) 进行 循环调用 watcher的run 方法 这个 方法 就是 组件的 render 函数 这个 函数一调用 对应的 组件 就会重新渲染 随后 把这个flushSchedulerQueue 方法 放进 nextTick 进行 异步调用
同样 nextTick 这个方法 也 维护着 一个 队列 ,每次 调用 nextTick 都会 向这个 队列中 加入 其回调方法 vue 有一个方法 flushCallbacks 是将这个 保存着 nextTick 函数的回调方法的队列(就是数组) 进行 循环调用
nextTick 异步的 原理 就是 把 flushCallbacks 方法 放入 异步任务中 (逐步降级 promisw.then、 mutationObserver、setImediate、setTimeout) 最后 就达到了 异步更新的 效果
import { pushTarget, popTarget } from "./dep";
import { nextTick } from "../util";
// 每次新组件实例化 都会调用 构造方法 进行 数据初始化 之后进行 挂载
// 在挂载 mountComponent 函数里 会使用 new Watcher 调用 Watcher的构造函数
//其中 第二个 参数 exprOrFn 一般是
// let updateComponent = ()=>{
// vm._update(vm._render()); // 渲染 、 更新
// }
// 这样一个 渲染 、更新的 函数
// 一个 组件 只有一个 watcher 用 id 来进行标识
let id = 0;
class Watcher { // vm.$watch
// vm实例
// exprOrFn vm._update(vm._render())
constructor(vm, exprOrFn, cb, options) {
this.vm = vm;
this.exprOrFn = exprOrFn;
this.cb = cb;
this.options = options;
this.user = options.user; // 这是一个用户watcher
this.id = id++; // watcher的唯一标识
this.deps = []; // watcher记录有多少dep依赖他
this.depsId = new Set();
if (typeof exprOrFn == 'function') {
this.getter = exprOrFn
}else{
this.getter = function () { // exprOrFn 可能传递过来的是一个字符串a
// 当去当前实例上取值时 才会触发依赖收集
let path = exprOrFn.split('.'); // ['a','a','a']
let obj = vm;
for(let i = 0; i < path.length;i++){
obj = obj[path[i]]// vm.a // vm.a.a
}
return obj;
}
}
// 默认会先调用一次get方法 ,进行取值 将结果保留下来
this.value = this.get(); // 默认会调用get方法
}
addDep(dep) {
let id = dep.id;
if (!this.depsId.has(id)) {
this.deps.push(dep);
this.depsId.add(id);
dep.addSub(this)
}
}
get() {
// Dep.target = watcher
pushTarget(this); // 当前watcher实例
let result = this.getter(); // 调用exprOrFn 渲染页面 取值(执行了get方法) render方法 with(vm){_v(msg)}
popTarget(); //渲染完成后 将watcher删掉了
return result
}
run() {
let newValue = this.get(); // 渲染逻辑
let oldValue = this.value;
this.value = newValue; // 更新一下老值
if(this.user){
this.cb.call(this.vm,newValue,oldValue);
}
}
update() {
// 这里不要每次都调用get方法 get方法会立刻重新渲染页面
queueWatcher(this); // 暂存的概念
//this.get(); // 重新渲染
}
}
watch功能的实现原理
Vue在初始化的时候 对options中的各种数据进行了 初始化 ,其中是这样 处理watch的
function initWatch(vm) {
let watch = vm.$options.watch;
for(let key in watch){
const handler = watch[key]; // handler可能是
if(Array.isArray(handler)){ // 数组 、
handler.forEach(handle=>{
createWatcher(vm,key,handle);
})
}else{
createWatcher(vm,key,handler); // 字符串 、 对象 、 函数
}
}
}
通过options.watch 的 key 拿到了 watch中各个key的回调函数,并使用createWatcher 调用vm.$watch 来创建了用户watcher,c 注意如果是 数组写法(一个key 多个回调函数),那么数组的每个元素都会创建一个watcher
function createWatcher(vm,exprOrFn,handler,options){ // options 可以用来标识 是用户watcher
if(typeof handler == 'object'){
options = handler
handler = handler.handler; // handler 是handler写法的handler 函数
}
if(typeof handler == 'string'){
handler = vm[handler]; // 将实例的方法作为handler
}
// key handler 用户传入的选项
return vm.$watch(exprOrFn,handler,options)
}
export function stateMixin(Vue){
Vue.prototype.$nextTick = function (cb) {
nextTick(cb);
}
Vue.prototype.$watch = function (exprOrFn,cb,options = {}) {
// 数据应该依赖这个watcher 数据变化后应该让watcher从新执行
let watcher = new Watcher(this,exprOrFn,cb,{...options,user:true});
if(options.immediate){
cb(); // 如果是immdiate应该立刻执行
}
}
}
key 一般是一个data中的属性 我们想要监控 此属性 ,当这个 属性变化时 执行操作(如果加了 如果是immdiate应该立刻执行) 可以看到new Watcher()的参数分别是 watch的key
从代码中 我们可以看到 key 传给了形参 exprOrFn
class Watcher
class Watcher { // vm.$watch
// vm实例
// exprOrFn vm._update(vm._render())
constructor(vm, exprOrFn, cb, options) {
this.vm = vm;
this.exprOrFn = exprOrFn;
this.cb = cb;
this.options = options;
this.user = options.user; // 这是一个用户watcher
this.id = id++; // watcher的唯一标识
this.deps = []; // watcher记录有多少dep依赖他
this.depsId = new Set();
if (typeof exprOrFn == 'function') {
this.getter = exprOrFn
}else{
this.getter = function () { // exprOrFn 可能传递过来的是一个字符串a
// 当去当前实例上取值时 才会触发依赖收集
let path = exprOrFn.split('.'); // ['a','a','a']
let obj = vm;
for(let i = 0; i < path.length;i++){
obj = obj[path[i]]// vm.a // vm.a.a
}
return obj;
}
}
// 默认会先调用一次get方法 ,进行取值 将结果保留下来
this.value = this.get(); // 默认会调用get方法
}
addDep(dep) {
let id = dep.id;
if (!this.depsId.has(id)) {
this.deps.push(dep);
this.depsId.add(id);
dep.addSub(this)
}
}
get() {
// Dep.target = watcher
pushTarget(this); // 当前watcher实例
let result = this.getter(); // 调用exprOrFn 渲染页面 取值(执行了get方法) render方法 with(vm){_v(msg)}
popTarget(); //渲染完成后 将watcher删掉了
return result
}
run() {
let newValue = this.get(); // 渲染逻辑
let oldValue = this.value;
this.value = newValue; // 更新一下老值
if(this.user){
this.cb.call(this.vm,newValue,oldValue);
}
}
update() {
// 这里不要每次都调用get方法 get方法会立刻重新渲染页面
queueWatcher(this); // 暂存的概念
//this.get(); // 重新渲染
}
}
从代码里我们看到,watcher 的构造函数默认会先调用一次get方法 ,进行取值,将结果保留下来,当去当前实例上取值时 就会触发依赖收集。当该属性更改时 就会 触发 异步更新而组件进行 异步刷新的时候会调用 该watcher的 run 方法 在run方法中 如果 是 用户watcher 就会执行 回调
核心就是 将 watch 的 key 变成 取值表达式 来实现依赖搜集 在key 被重新赋值的时候执行回调函数 响应式
run() {
let newValue = this.get(); // 渲染逻辑
let oldValue = this.value;
this.value = newValue; // 更新一下老值
if(this.user){
this.cb.call(this.vm,newValue,oldValue);
}
}
Dom diff
export function patch(oldVnode, vnode) {
// 默认初始化时 是直接用虚拟节点创建出真实节点来 替换掉老节点
if (oldVnode.nodeType === 1) { // 真实的节点
let el = createElm(vnode); // 产生真实的dom
let parentElm = oldVnode.parentNode; // 获取老的app的父亲 =》 body
parentElm.insertBefore(el, oldVnode.nextSibling); // 当前的真实元素插入到app的后面
parentElm.removeChild(oldVnode); // 删除老的节点
return el;
} else {
// 在更新的时 拿老的虚拟节点 和 新的虚拟节点做对比 ,将不同的地方更新真实的dom
// 更新功能
// 那当前节点 整个
// 1.比较两个元素的标签 ,标签不一样直接替换掉即可
if (oldVnode.tag !== vnode.tag) {
// 老的dom元素
return oldVnode.el.parentNode.replaceChild(createElm(vnode), oldVnode.el);
}
// 2.有种可能是标签一样 <div>1</div> <div>2</div>
// 文本节点的虚拟节点tag 都是undefined
if (!oldVnode.tag) { // 文本的比对
if (oldVnode.text !== vnode.text) {
return oldVnode.el.textContent = vnode.text
}
}
// 3.标签一样 并且需要开始比对标签的属性 和 儿子了
// 标签一样直接复用即可
let el = vnode.el = oldVnode.el; // 复用老节点
// 更新属性,用新的虚拟节点的属性和老的比较,去更新节点
// 新老属性做对比
updateProperties(vnode, oldVnode.data)
// 比较孩子
let oldChildren = oldVnode.children || [];
let newChildren = vnode.children || [];
if (oldChildren.length > 0 && newChildren.length > 0) {
// 老的有儿子 新的也有儿子 diff 算法
updateChildren(oldChildren, newChildren, el);
} else if (oldChildren.length > 0) { // 新的没有
el.innerHTML = '';
} else if (newChildren.length > 0) { // 老的没有
for (let i = 0; i < newChildren.length; i++) {
let child = newChildren[i];
// 浏览器有性能优化 不用自己在搞文档碎片了
el.appendChild(createElm(child));
}
}
// 儿子比较分为以下几种情况
// 老的有儿子 新的没儿子
// 老的没儿子 新的有儿子
}
}
computed的实现原理
function initComputed(vm) {
let computed = vm.$options.computed;
// 1.需要有watcher 2. 还需要通过defineProperty 3.dirty
const watchers = vm._computedWatchers = {}; // 用来稍后存放计算属性的watcher
for(let key in computed){
const userDef = computed[key]; // 取出对应的值来,可能是函数或者 对象
// 获取get方法
const getter = typeof userDef == 'function' ? userDef : userDef.get; // watcher使用的
watchers[key] = new Watcher(vm,getter,()=>{},{lazy:true}); // watcher很懒?
defineComputed(vm,key,userDef)// defineReactive();
}
}
第一步是取出所有 定义的 computed 属性 ,给每个属性生成一个watcher,(new Watcher()的四个参数当前vm , 对应的getter方法,回调函数是watch 才会使用的,使用空函数,option 里的 {lazy:true:}可以代表这是一个 计算属性的watcher),并定义一个对象 来存储之后computed 的每一个属性的watcher ,之后是给这个计算属性进行数据的劫持
我们可能 想当然的会这样写,这样确实进行了 数据 拦截,但是 确是没有缓存的,每次使用计算属性的时候都是 需要重新执行计算的
function defineComputed(target,key,userDef){ // 这样写是没有缓存的
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get:()=>{},
set:()=>{}
}
if(typeof userDef == 'function'){
sharedPropertyDefinition.get = userDef // dirty 来控制是否调用userDef
}else{
//userDef 可能是对象的写法
sharedPropertyDefinition.get = userDef.get; // 需要加缓存
sharedPropertyDefinition.set = userDef.set;
}
Object.defineProperty(target,key,userDef)
}
如上,我们可以将 userDef.get 包装进 一个函数 在 在函数内部使用 dirty 来控制 是否调用 userDef(即重新计算) ,
createComputedGetter函数
function createComputedGetter(key){
return function (){ // 此方法是我们包装的方法,每次取值会调用此方法
const watcher = this._computedWatchers[key]; // 拿到这个属性对应watcher
if(watcher){
if(watcher.dirty){ // 默认肯定是脏的 会重新求值
watcher.evaluate(); // 对当前watcher求值,即调用计算属性的get
}
debugger;
if (Dep.target) { // 说明还有渲染watcher,也应该一并的收集起来
watcher.depend();
}
return watcher.value; // 默认返回watcher上存的值
}
}
}
这个方法默认返回 watcher 上存的值,从上面我们知道计算属性 watcher的值 就是watcher.evaluate()的值
evaluate() {
this.value = this.get();
this.dirty = false; // 取过一次值之后 就表示成已经取过值了
}
Clas watcher 的构造函数里
this.lazy = options.lazy; // 如果watcher上有lazy属性 说明是一个计算属性
this.dirty = this.lazy; // dirty代表取值时是否执行用户提供的方法
// 原本默认会先调用一次get方法 ,进行取值 将结果保留下来
// 现在改为 发现是 计算属性 就不调用 this.get
this.value = this.lazy ? void 0 : this.get(); // 默认会调用get方法 现在改为 发现是 计算属性 就不调用 this.get
实际上 在第一次 取值的时候 还是会通过 watcher.evaluate() 调用 this.get() 从而 调用传进来的计算函数 ,并把当前watcher置为全局Dep.target(这里有个问题,依赖属性没有搜集 计算属性的渲染 watcher,后面再说), 调用计算属性函数(也就是实例化watcher传入的getter,注意,这个getter不是计算属性劫持的createComputedGetter)的时候会对该计算属性依赖的属性进行取值,这时就把该 watcher 加入到了所依赖的属性的dep里 例如:firstName和lastName 就会吧 fullName的watcher 放进 他们的 dep中
computed:{ //内部也使用了defineProperty, 内部有一个变量 dirty
// computed还是一个watcher,内部依赖的属性会收集这个watcher
fullName(){
// this.firstName ,this.lastName 在求值时, 会记住当前计算属性的watcher
return this.firstName + this.lastName
}
}
现在如何判断计算属性的值重新计算,肯定是依赖的属性的值改了,但是如何在依赖的属性的值改了的时候重新调用watcher的this.get()重新计算呢 如上例,当 this.firstName 更改的时候 会调用该属性的对应的dep.notify()
dep.notify(); // 异步更新 防止频繁操作
dep.notify() 会 循环调用 其保存的watcher的update 方法
notify(){
this.subs.forEach(watcher=>watcher.update());
}
update 方法
update() {
if (this.lazy) { // 是计算属性
this.dirty = true;// 页面重新渲染就可以获得最新的值了
}else{
// 这里不要每次都调用get方法 get方法会重新渲染页面
queueWatcher(this); // 暂存的概念
//this.get(); // 重新渲染
}
}
在update 方法里 会把this.dirty 设置为 true ,这就意味着下次重新取值计算属性fullName的时候,fullName对应的 watcher 就会执行watcher.evaluate() ,这时候就会重新计算
if(watcher.dirty){ // 默认肯定是脏的 会重新求值
watcher.evaluate(); // 对当前watcher求值,即调用计算属性的get
}
现在还有一个问题 ,在this.firstName 修改之后,fullName如果重新取值就会重新计算 可是 我们希望他自动重新计算,也就是说我们希望他被重新取值,也就是说我们希望this.firstName这个属性的dep 搜集该计算属性fullName 的渲染watcher
这时,我们可以去更改Dep
Dep.target = null; // 静态属性 就一份
let stack = [];
export function pushTarget(watcher){
Dep.target = watcher;// 保留watcher
stack.push(watcher); // 有渲染watcher 其他的watcher
console.log(stack);
}
export function popTarget(){
stack.pop();
Dep.target = stack[stack.length-1]; // 将变量删除掉
}
所以,在渲染 fullName 所在的组件时,肯定会有一个对应的渲染 watcher ,在渲染中,对fullName取值,就会生成对应的fullName计算属性 watcher 如上更改 Dep.target 放进栈中,在取值fullName时把fullName的watcher 进栈(渲染watcher 还在栈中) 在执行完计算之后 popTarget 就把 Dep.target 变为了渲染属性
此时我们再看 createComputedGetter 生成的 fullName 属性被拦截的 get 方法 他在 计算后 会再去看看 Dep.target是否为空,如果不为空 ,那么 说明还有渲染watcher,也应该一并的收集起来,搜集进 fullName 的计算属性 watcher 中
function createComputedGetter(key){
return function (){ // 此方法是我们包装的方法,每次取值会调用此方法
const watcher = this._computedWatchers[key]; // 拿到这个属性对应watcher
if(watcher){
if(watcher.dirty){ // 默认肯定是脏的
watcher.evaluate(); // 对当前watcher求值
}
debugger;
if (Dep.target) { // 说明还有渲染watcher,也应该一并的收集起来
watcher.depend();
}
return watcher.value; // 默认返回watcher上存的值
}
}
}
watcher的depend方法(不是Dep的depend),我们可以看到,它会拿这个 计算属性 的 dep (是的,计算属性fullName 依赖 的属性 firstName、lastName的 Dep 会存储fullName的计算属性 watcher ,且该watcher也会搜集dep )实现原理 如下 ,
class Dep 的depend 方法 会拿到 Dep.target 也就是当前的 watcher ,调用 其addDep(this) 并把自己dep传过去
depend(){
// 我们希望 watcher 可以存放dep
Dep.target.addDep(this); // 实现双向记忆的,让watcher记住dep的同时 ,让dep 也记住watcher
// this.subs.push(Dep.target);
}
class Watcher 的addDep 会 把此 dep 存入 this.deps ,并调用 dep.addSub(this) 把这个watcher 存入 dep
addDep(dep) {
let id = dep.id;
if (!this.depsId.has(id)) {
this.deps.push(dep);
this.depsId.add(id);
dep.addSub(this)
}
}
这就实现了 dep 和 watcher 的双向 记忆 ,watcher 存储 dep 还进行了 去重 后续我们知道 Dep 也通过了 set 来 去重 watcher
所以 下文的 代码 就是 使用 计算属性的watcher的 this.deps 去存储fullName的 渲染 watcher 也就是 在 firstName、lastName 的dep 里存储 fullName的 渲染 watcher ,自此 整个计算属性的功能完成;
depend(){
// 计算属性watcher 会存储 dep dep会存储watcher
// 通过watcher找到对应的所有dep,让所有的dep 都记住这个渲染watcher
let i = this.deps.length;
while(i--){
this.deps[i].depend(); // 让dep去存储渲染watcher
}
}
当首次渲染的时候会取值fullName 属性 ,因为 该属性在初始化阶段就创建了自己的计算属性watcher,并用key :value存储在了Vue的实例上,并劫持了(Object.defineProperty)自身属性的get,因此,在取值 fullName 的时候,就会调用 fullName的 get 方法,该方法 会拿到 这个 属性 的计算属性 watcher 并 查看 watcher.dirty (初始取值 肯定是 true) ;
如果watcher.dirty 是true 就执行 watcher.evaluate(),在这个方法中 会执行 计算 操作 ,并把 watcher的dirty 赋值为 false 把计算出来的 值 赋给 watcher的 value 属性
这样就实现了缓存
在执行 evaluate(),watcher 会 调用 get() 并把自身这个 计算属性watcher 放入 栈中 并设为 Dep.target 然后执行 计算 ,计算时肯定会对 该计算属性 fullName 的 firstName、lastName 取值,这时 会调用两个属性对应的Dep.append 把 fullName 的watcher 存入到dep中,同时也把dep 存入到 fullName 的watcher 中 然后 该watcher 出栈,Dep.target 重新置为渲染watcher
evaluate() 执行完毕
get() {
// Dep.target = watcher
pushTarget(this); // 当前watcher实例
let result = this.getter.call(this.vm); // 调用exprOrFn 渲染页面 取值(执行了get方法) render方法 with(vm){_v(msg)}
popTarget(); //渲染完成后 将watcher删掉了
return result //赋给 了 watcher 的 value
}
但在 evaluate() 执行完之后 还会看当前 Dep.target
if (Dep.target) { // 说明还有渲染watcher,也应该一并的收集起来
watcher.depend();
}
如果有,就让 计算属性 watcher 存储的dep 都把 该Dep.target(也就是 渲染 watcher )存入dep 中, 同时也在 Dep.target 中存了 对应的 dep
这样firstName 改变的时候 就会执行 dep.notyify,调用 fullName 的 计算属性watcher的update 方法 ,把 dirty 设为true ,调用fullName 的 渲染watcher的update 方法,重新渲染,之然就会重新取直,由于dirty 已经重新置为true ,就会重新计算,