专栏首页京程一灯Vue.js源码分析:计算属性如何工作

Vue.js源码分析:计算属性如何工作

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

  1. 这个例子只说明运行机制,不支持对象、数组、watching/unwatching等Vue.js已实现的一大堆优化
  2. 看完源代码带着我有限的理解写的这篇文章,可能会有一些错误,如发现错误,请联系我

JS的属性

JS有Object.defineProperty方法,它能做的事情很多,但我们先关注这一点:

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 propertyconsole.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: minorperson.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的属性才会“重新计算”,否则永远不会更新 这就是为什么官网说

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

本文分享自微信公众号 - 京程一灯(jingchengyideng),作者:ssshooter

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-08-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • CSS粘性定位是怎样工作的 [每日前端夜话0x1F]

    第一,受到浏览器的良好支持需要漫长的等待:浏览器的支持往往需要很长的时间才能完成,到时候它的功能已经被人们遗忘了。

    疯狂的技术宅
  • 令人惊叹的JavaScript装B黑科技

    Javascript是一门很吊的语言,我可能学了假的JavaScript,哈哈,大家还有什么推荐的,补充送那啥邀请码。

    疯狂的技术宅
  • 实用 | 读源码,学JavaScript

    Javascript于1995年由网景公司的Brendan Eich发明。 最初发明的目的是作为一个简单的网站脚本语言,来作为复杂网站应用java的补充。但由于...

    疯狂的技术宅
  • 骚操作系列(ctrl+c 和 ctrl+v 的算法问题)

    第650题:最初在一个记事本上只有一个字符 'A'。你每次可以对这个记事本进行两种操作:Copy All (复制全部) : 你可以复制这个记事本中的所有字符(部...

    帅地
  • JavaScript数据属性和访问器属性

    看《深入理解JavaScript》的this篇时看到“访问器属性”这个不熟悉的名词,百度后找到两篇感觉比较合适的文章,整合记录一下,以参考资料2为主,参考资料1...

    WindCoder
  • android apk 防反编译技术第一篇-加壳技术

    做android framework方面的工作将近三年的时间了,现在公司让做一下android apk安全方面的研究,于是最近就在网上找大量的资料来学习。现在...

    程序员互动联盟
  • 禁用复制粘帖功能

    在SDK3.0中添加了复制粘帖功能,但是有时候这个新功能可能对你的应用造成不必要的麻烦。 今天在网上查到了这个方法,可以在Responder链上禁用复制粘帖功能...

    EltonZheng
  • NodeJS 应用仓库钓鱼

    前言 城堡总是从内部攻破的。再强大的系统,也得通过人来控制。如果将入侵直接从人这个环节发起,那么再坚固的防线,也都成为摆设。 下面分享一个例子,利用应用仓库,渗...

    逸鹏
  • 为什么插入排序比冒泡排序更受欢迎?

    插入排序和冒泡排序的时间复杂度相同,都是 O(n2),在实际的软件开发里,为什么我们更倾向于使用插入排序算法而不是冒泡排序算法呢?

    大猫的Java笔记
  • centos-5.5安装vmvare tools

    centos-5.5安装vmvare tools 虚拟机管理,安装tools 找到VMwareTools压缩包                         ...

    Ryan-Miao

扫码关注云+社区

领取腾讯云代金券