原文 Vue.js Internals: How computed properties workjavascript
这篇文章咱们我会用很简单的方法来实现相似计算属性的效果,以此学习Vue.js的计算属性的运行机制。html
这个例子只说明运行机制,不支持对象、数组、watching/unwatching等Vue.js已实现的一大堆优化vue
看完源代码带着我有限的理解写的这篇文章,可能会有一些错误,如发现错误,请联系我java
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”。如下是一个实现响应式属性的例子缓存
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';
有趣的是,25
和Brazil
还是闭包变量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 } }) }
这个函数有两个问题
每当属性被访问,计算函数都会运行
这个函数不知道什么时候应该更新
// 咱们但愿达到这个效果 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
(计算属性的get()
函数第二行)调用了计算函数computeFunc
,而这个计算函数又调用了age属性,也就是触发了age属性的get()
看回age属性的get()
,若是Dep.target
当前是有值的话,这个值就会被push到依赖列表(因此如今onDependencyUpdated
就在age属性的依赖列表里咯),以后Dep.target
会被赋值为null
如今,每当person.age
被赋值,都会通知person.status
啦
某译者的胡说八道
如做者所说这个例子只是简化版,像官网说计算属性是基于它们的依赖进行缓存的这点没有表现出来,因此更多细节请研究Vue的源码
可是读了这篇文章咱们能够知道计算属性更新是依赖data的属性通知的,因此必须调用了data的属性才会“从新计算”,不然永远不会更新
这就是为何官网说
若是计算函数里面调用了多个属性,那么这些属性更新时都会通知这个计算函数。
其余参考 Vue 响应式原理探析