我们通过实现一个简单版的和 vue 中 computed 具有相同功能的函数来了解 computed 是如何工作的.
JS 属性:
JavaScript 有一个特性是 Object.defineProperty , 它能做很多事, 但我在这篇文章只专注于这个方法中的一个:
- var person = {};
- Object.defineProperty (person, 'age', {
- get: function () {
- console.log ("Getting the age");
- return 25;
- }
- });
- console.log ("The age is", person.age);
- // Prints:
- //
- // Getting the age
- // The age is 25
- (Obeject.defineProperty 是 Object 的一个方法, 第一个参数是对象名称, 第二个参数是要设置的属性名, 第三个参数是一个对象, 它可以设置这个属性是否可修改, 可写等, 而这篇文章主要使用的是 Obeject.defineProperty 的访问器属性, 感兴趣的朋友可以自行 google 或者查看 JS 高及程序设计)
尽管 person.age 看起来像是访问了对象的一个属性, 但其实在内部我们是运行了一个函数.
一个基本可响应的 vue.js
Vue.JS 内部构建了一个可以将普通的对象转化为可以被观察的值 ( 响应属性 ), 下面为大家展示一个简化版的如何添加响应属性的案例:
- function defineReactive (obj, key, val) {
- Object.defineProperty (obj, key, {
- get: function () {
- return val;
- },
- set: function (newValue) {
- val = newValue;
- }// 欢迎加入全栈开发交流圈一起学习交流: 864305860
- })
- };
- // 创建一个对象
- var person = {};
- // 添加可响应的属性 "age" 和 "country"
- defineReactive (person, 'age', 25);
- defineReactive (person, 'country', 'Brazil');
- // 现在你可以随意使用 person.age 了
- if (person.age <18) {
- return 'minor';
- }// 欢迎加入全栈开发交流圈一起学习交流: 864305860
- else {
- return 'adult';
- }
- // 设置 person.country 的值
- person.country = 'Russia';
25 和'Brazil' 还是一个闭包内部的变量, 只有当赋给它们新值的时候 val 才会改变. person.country 并不拥有'Brazil' 这个值, 而是 getter 这个函数拥有'Brazil' 这个值.
声明一个计算属性
让我们创建一个定义计算属性的函数 defineComputed . 这个函数就跟大家平时使用 computed 时的一样.
- defineComputed (
- person, // 计算属性就声明在这个对象上
- 'status', // 计算属性的名称
- function () { // 实际返回计算属性值的函数
- console.log ("status getter called")
- if (person.age < 18) {
- return 'minor';
- }
- else {
- return 'adult';
- }
- },
- function (newValue) {
- // 当计算属性值更新时调用的函数
- console.log ("status has changed to", newValue)
- }
- });
- // 我们可以像使用一般的属性一样使用计算属性
- console.log ("The person's status is: ", person.status);
写一个简单的 defineComputed 函数, 它支持调用计算方法, 但目前不需要它支持 updateCallback
- function defineComputed (obj, key, computeFunc, updateCallback) {
- Object.defineProperty (obj, key, {
- get: function () {
- // 执行计算函数并且返回值
- return computeFunc ();
- },
- set: function () {
- // 什么也不做, 不需要设定计算属性的值
- }// 欢迎加入全栈开发交流圈一起学习交流: 864305860
- })
- }// 帮助突破技术瓶颈, 提升思维能力
这个函数有两个问题:
每次访问计算属性时都会执行一次计算函数 computeFunc ()
它不知道什么时候更新 (即当我们更新某个 data 中的属性, 计算属性中也会更新这个 data 属性)
- // 我希望最终函数执行后是这个效果: 每当 person.age 更新值的时候, person.status 也同步更新
- person.age = 17;
- // console: status 的值为 minor
- person.age = 22;
- // console: status 的值为 adult
增加一个依赖项
让我们增加一个全局变量 Dep :
- var Dep = {
- target: null
- };
这是一个依赖项, 接着我们用一个骚操作来更新 defineComputed 函数:
- ```function defineComputed (obj, key, computeFunc, updateCallback) {
- var onDependencyUpdated = function () {
- // TODO
- }
- Object.defineProperty (obj, key, {
- get: function () {
- // 将 onDependencyUpdated 这个函数传给 Dep.target
- Dep.target = onDependencyUpdated;
- var value = computeFunc ();
- Dep.target = null;
- },// 帮助突破技术瓶颈, 提升思维能力
- set: function () {
- // 什么也不做, 不需要设定计算属性的值
- }
- })
- }
回到之前设置的响应属性上:
- function defineReactive (obj, key, val) {
- // 所有的计算属性都依赖这个数组
- var deps = [];
- Object.defineProperty (obj, key, {
- get: function () {
- // 检查是否调用了计算属性, 如果调用了, Department.target 将等于一个 onDependencyUpdated 函数
- if (Dep.target) {
- // 把 onDependencyUpdated 函数 push 到 deos 中
- deps.push (target);
- }
- return val;
- },
- set: function (newValue) {
- val = newValue
- // 通知所有的计算属性, 告诉它们有个响应属性更新了
- deps.forEach ((changeFunction) => {
- changeFunction ();
- });// 欢迎加入全栈开发交流圈一起学习交流: 864305860
- }// 帮助突破技术瓶颈, 提升思维能力
- })
- };
可以在计算属性定义的函数触发更新回调后更新 onDependencyUpdated 函数.
- var onDependencyUpdated = function () {
- // 再次计算 计算属性的值
- var value = computeFunc ();
- updateCallback (value);
- }
把它们整合到一起, 重新访问我们的计算属性 person.status :
- person.age = 22;
- defineComputed (
- person,
- 'status',
- function () {
- if (person.age> 18) {
- return 'adult';
- }// 欢迎加入全栈开发交流圈一起学习交流: 864305860
- },// 帮助突破技术瓶颈, 提升思维能力
- function (newValue) {
- console.log ("status has changed to", newValue)
- }
- });
- console.log ("Status is", person.status);
** 结语 **
感谢您的观看, 如有不足之处, 欢迎批评指正.
来源: http://www.qdfuns.com/article/51117/961ee44492c90e73cde430e2d958b1eb.html