【翻译】Vue.js源码分析:计算属性如何工做

原文 Vue.js Internals: How computed properties workjavascript

这篇文章咱们我会用很简单的方法来实现相似计算属性的效果,以此学习Vue.js的计算属性的运行机制。html

  1. 这个例子只说明运行机制,不支持对象、数组、watching/unwatching等Vue.js已实现的一大堆优化vue

  2. 看完源代码带着我有限的理解写的这篇文章,可能会有一些错误,如发现错误,请联系我java

JS的属性

JS有Object.defineProperty方法,它能作的事情不少,但咱们先关注这一点:react

var person = {};

Object.defineProperty (person, 'age', {
  get: function () {
    console.log ("Getting the age");
    return 25;
  }
});

console.log ("The age is ", person.age);

// 输出:
//
// Getting the age
// The age is 25

虽然看起来咱们只是访问了对象的一个属性,可是其实咱们是在运行一个函数。数组

基础的Vue.js Observable

Vue.js有一个基础结构,它能够帮你把一个常规的对象转换成一个“被观察”的值,这个值就叫作“observable”。如下是一个实现响应式属性的例子缓存

function defineReactive (obj, key, val) {
  Object.defineProperty (obj, key, {
    get: function () {
      return val;
    },
    set: function (newValue) {
      val = newValue;
    }
  })
};

// 建立对象
var person = {};

// 添加两个响应式属性
defineReactive (person, 'age', 25);
defineReactive (person, 'country', 'Brazil');

// 如今你可使用这两个属性
if (person.age < 18) {
  return 'minor';
}
else {
  return 'adult';
}

// 也能够给他们赋值
person.country = 'Russia';

有趣的是,25Brazil还是闭包变量val,当你赋值时,它们的值也会被修改。这个值不是存在于person.country,而是存在于getter函数闭包。闭包

定义一个计算属性

建立一个计算属性函数defineComputed函数

defineComputed (
  person, // the object to create computed property on
  'status', // the name of the computed property
  function () { // the function which actually computes the property
    console.log ("status getter called")
    if (person.age < 18) {
      return 'minor';
    }
    else {
      return 'adult';
    }
  },
  function (newValue) {
    // called when the computed value is updated
    console.log ("status has changed to", newValue)
  }
});

// We can use the computed property like a regular property
console.log ("The person's status is: ", person.status);

如下是defineComputed的简单实现。这个函数支持调用计算函数,可是暂不支持updateCallback学习

function defineComputed (obj, key, computeFunc, updateCallback) {
  Object.defineProperty (obj, key, {
    get: function () {
      // call the compute function and return the value
      return computeFunc ();
    },
    set: function () {
      // don't do anything. can't set computed funcs
    }
  })
}

这个函数有两个问题

  1. 每当属性被访问,计算函数都会运行

  2. 这个函数不知道什么时候应该更新

// 咱们但愿达到这个效果

person.age = 17;
// console: status has changed to: minor

person.age = 22;
// console: status has changed to: adult

添加一个依赖跟踪器

建立一个全局变量Dep

var Dep = {
  target: null
};

这就是依赖跟踪器,如今咱们把这个关键点融入defineComputed

function defineComputed (obj, key, computeFunc, updateCallback) {
  var onDependencyUpdated = function () {
    // TODO
  }
  Object.defineProperty (obj, key, {
    get: function () {
      // Set the dependency target as this function
      Dep.target = onDependencyUpdated;
      var value = computeFunc ();
      Dep.target = null;
    },
    set: function () {
      // don't do anything. can't set computed funcs
    }
  })
}

咱们如今回到定义响应式对象的步骤

function defineReactive (obj, key, val) {
  // all computed properties that depend on this
  var deps = [];

  Object.defineProperty (obj, key, {
    get: function () {
      // 检查是否有计算属性调用这个getter
      // 顺便检查这个以来是否存在
      if (Dep.target && ) {
        // 添加依赖
        deps.push (target);
      }

      return val;
    },
    set: function (newValue) {
      val = newValue;

      // 通知全部依赖这个值的计算属性
      deps.forEach ((changeFunction) => {
        // 请求从新计算
        changeFunction ();
      });
    }
  })
};

咱们在计算属性的定义里更新onDependencyUpdated函数,用以触发更新回调函数。

var onDependencyUpdated = function () {
  // compute the value again
  var value = computeFunc ();
  updateCallback (value);
}

综合上述代码

重新看回咱们以前定义的计算属性person.status

person.age = 22;

defineComputed (
  person,
  'status',
  function () {
    // compute function
    if (person.age > 18) {
      return 'adult';
    }
  },
  function (newValue) {
    console.log ("status has changed to", newValue)
  }
});

console.log ("Status is ", person.status);

第一步:

person.status被访问,触发了get()函数,Dep.target如今是onDependencyUpdated
clipboard.png

第二步:

(计算属性的get()函数第二行)调用了计算函数computeFunc,而这个计算函数又调用了age属性,也就是触发了age属性的get()
clipboard.png

第三步:

看回age属性的get(),若是Dep.target当前是有值的话,这个值就会被push到依赖列表(因此如今onDependencyUpdated就在age属性的依赖列表里咯),以后Dep.target会被赋值为null

clipboard.png

第四步:

如今,每当person.age被赋值,都会通知person.status
clipboard.png


某译者的胡说八道
如做者所说这个例子只是简化版,像官网说计算属性是基于它们的依赖进行缓存的这点没有表现出来,因此更多细节请研究Vue的源码
可是读了这篇文章咱们能够知道计算属性更新是依赖data的属性通知的,因此必须调用了data的属性才会“从新计算”,不然永远不会更新
这就是为何官网说

clipboard.png

若是计算函数里面调用了多个属性,那么这些属性更新时都会通知这个计算函数。

其余参考 Vue 响应式原理探析

相关文章
相关标签/搜索