vue中computed源码,工作原理

2020-01-04 18:44:26

参考地址 Vue.js中的computed工作原理

JS属性:

JavaScript有一个特性是 Object.defineProperty ,它能做很多事,但我在这篇文章只专注于这个方法中的一个:

?

1
2
3
4
5
6
7
8
9
10
11
12
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内部构建了一个可以将普通的对象转化为可以被观察的值( 响应属性 ),下面为大家展示一个简化版的如何添加响应属性的案例:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function defineReactive (obj, key, val) {
 Object.defineProperty (obj, key, {
  get: function () {
   return val;
  },
  set: function (newValue) {
   val = newValue;
  }
 })
};
// 创建一个对象
var person = {};
// 添加可响应的属性"age"和"country"
defineReactive (person, 'age', 25);
defineReactive (person, 'country', 'Brazil');
// 现在你可以随意使用person.age了
if (person.age < 18) {
 return 'minor';
}
else {
 return 'adult';
}
// 设置person.country的值
person.country = 'Russia';

有趣的是, 25 和 ‘Brazil' 还是一个闭包内部的变量,只有当赋给它们新值的时候 val 才会改变。 person.country 并不拥有 'Brazil' 这个值,而是getter这个函数拥有 'Brazil' 这个值。

声明一个计算属性

让我们创建一个定义计算属性的函数 defineComputed 。这个函数就跟大家平时使用computed时的一样。

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 。

?

1
2
3
4
5
6
7
8
9
10
11
function defineComputed (obj, key, computeFunc, updateCallback) {
 Object.defineProperty (obj, key, {
  get: function () {
   // 执行计算函数并且返回值
   return computeFunc ();
  },
  set: function () {
   // 什么也不做,不需要设定计算属性的值
  }
 })
}

这个函数有两个问题:

每次访问计算属性时都会执行一次计算函数 computeFunc ()

它不知道什么时候更新 (即当我们更新某个data中的属性,计算属性中也会更新这个data属性)

?

1
2
3
4
5
// 我希望最终函数执行后是这个效果:每当person.age更新值的时候,person.status也同步更新
person.age = 17;
// console: status 的值为 minor
person.age = 22;
// console: status 的值为 adult

增加一个依赖项

让我们增加一个全局变量 Dep :

?

1
2
3
var Dep = {
 target: null
};

这是一个依赖项,接着我们用一个骚操作来更新 defineComputed 函数:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 () {
   // 什么也不做,不需要设定计算属性的值
  }
 })
}

现在让我们回到之前设置的响应属性上:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 ();
   });
  }
 })
};

我们可以在计算属性定义的函数触发更新回调后更新 onDependencyUpdated 函数。

?

1
2
3
4
5
var onDependencyUpdated = function () {
 // 再次计算 计算属性的值
 var value = computeFunc ();
 updateCallback (value);
}


把它们整合到一起:

让我们重新访问我们的计算属性 person.status :

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
person.age = 22;
defineComputed (
 person,
 'status',
 function () {
  if (person.age > 18) {
   return 'adult';
  }
 },
 function (newValue) {
  console.log ("status has changed to", newValue)
 }
});
console.log ("Status is ", person.status);


  • 2017-06-18 02:26:17

    Incorrect string value: '\xF0\x9F\x98\x84\xF0\x9F

    我们可以看到错误提示中的字符0xF0 0x9F 0x98 0x84 ,这对应UTF-8编码格式中的4字节编码(UTF-8编码规范)。正常的汉字一般不会超过3个字节,为什么为出现4个字节呢?实际上是它对应的是智能手机输入法中的表情。那为什么会报错呢?

  • 2017-06-18 02:34:22

    谈mysql中utf8和utf8mb4区别

    MySQL在5.5.3之后增加了这个utf8mb4的编码,mb4就是most bytes 4的意思,专门用来兼容四字节的unicode。好在utf8mb4是utf8的超集,除了将编码改为utf8mb4外不需要做其他转换。当然,为了节省空间,一般情况下使用utf8也就够了。

  • 2017-07-05 09:48:51

    CSS 元素垂直居中的 6种方法

    利用CSS进行元素的水平居中,比较简单,行级元素设置其父元素的text-align center,块级元素设置其本身的left 和 right margins为auto即可。本文收集了六种利用css进行元素的垂直居中的方法,每一种适用于不同的情况,在实际的使用过程中选择某一种方法即可。

  • 2017-07-06 10:02:18

    大白话讲解Promise(一)

    去年6月份, ES2015正式发布(也就是ES6,ES6是它的乳名),其中Promise被列为正式规范。作为ES6中最重要的特性之一,我们有必要掌握并理解透彻。本文将由浅到深,讲解Promise的基本概念与使用方法。

  • 2017-07-11 21:54:14

    MYSQL5.7版本sql_mode=only_full_group_by问题

    一旦开启 only_full_group_by ,感觉,group by 将变成和 distinct 一样,只能获取受到其影响的字段信息,无法和其他未受其影响的字段共存,这样,group by 的功能将变得十分狭窄了

  • 2017-07-14 13:51:58

    NodeJS连接MySQL出现Cannot enqueue Handshake after invoking quit.

    原因在于node连接上mysql后如果因网络原因丢失连接或者用户手工关闭连接后,原有的连接挂掉,需要重新连接;如下代码,每次访问结束都关闭,每次开始访问前重连接下,代码中没有监听连接的fatal错误,copy需谨慎