模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护,比如:

    <div id="example">{{ message.split('').reverse().join('') }}</div>
<script>
var app = new Vue({
el:'#example',
data:{message:'hello world'}
})
</script>

这样模板不再是简单的声明式逻辑,必须看一段时间才能意识到,对于这些复杂逻辑,需要使用计算属性,例如:

    <div id="example">{{ reversedMessage}}</div>
<script>
var app = new Vue({
el:'#example',
data:{message:'hello world'},
computed:{
reversedMessage:function(){return this.message.split('').reverse().join('')}
}
})
</script>

在模板中可以把computed当作data属性来使用

computed是一个对象,每个键是计算属性的值,值有两种使用方法:值是一个函数,或者值是一个包含get和set的对象

源码分析


Vue实例后会先执行_init()进行初始化(4579行)时,会执行initState()进行初始化,如下:

function initState (vm) { //第3303行
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
if (opts.methods) { initMethods(vm, opts.methods); }
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); }   //如果定义了computed,则调用initComputed初始化computed
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
initComputed函数如下:
var computedWatcherOptions = {lazy: true};                  //计算属性的配置信息
function initComputed (vm, computed) { //第3424行
// $flow-disable-line
var watchers = vm._computedWatchers = Object.create(null); //定义一个空对象,没有原型的,用于存储所有计算属性对应的watcher
// computed properties are just getters during SSR
var isSSR = isServerRendering(); for (var key in computed) { //遍历每个计算属性
var userDef = computed[key]; //将计算属性的值保存到userDef里面
var getter = typeof userDef === 'function' ? userDef : userDef.get; //如果userDef是一个函数则赋值给getter,否则将userDef.get赋值给getter
if ("development" !== 'production' && getter == null) {
warn(
("Getter is missing for computed property \"" + key + "\"."),
vm
);
} if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher( //创建一个内部的watcher给计算属性用
vm,
getter || noop,
noop,
computedWatcherOptions
);
} // component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) { //如果key在vm中没有定义 注:组件的计算属性在模块加载的时候已经被定义在了原型上面了
defineComputed(vm, key, userDef); //则执行defineComputed()函数
} else {
if (key in vm.$data) {
warn(("The computed property \"" + key + "\" is already defined in data."), vm);
} else if (vm.$options.props && key in vm.$options.props) {
warn(("The computed property \"" + key + "\" is already defined as a prop."), vm);
}
}
}
}

writer by:大沙漠 QQ:22969969


注:对于计算属性的Watcher来说,它的lazy属性为true,因此new watcher()结尾时不会执行get()方法,而是直接返回undefined(在3127行)(求值会等到该计算属性被调用时才求值的)

回到initComputed()函数,defineComputed()定义如下:

function defineComputed (     //第3465行
target,
key,
userDef
) {
var shouldCache = !isServerRendering();
  if (typeof userDef === 'function') {            //如果userDef为函数,则默认为get
    sharedPropertyDefinition.get = shouldCache        //保存到get里
      ? createComputedGetter(key)
      : userDef;
    sharedPropertyDefinition.set = noop;              //将set设为noop
  } else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop;
sharedPropertyDefinition.set = userDef.set
? userDef.set
: noop;
}
if ("development" !== 'production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
("Computed property \"" + key + "\" was assigned to but it has no setter."),
this
);
};
}
Object.defineProperty(target, key, sharedPropertyDefinition); //设置访问器属性,这样当我们在模板里访问计算属性时就会执行sharedPropertyDefinition的get方法了
}

初始化完成了,当我们的模板渲染成render函数时会执行如下函数

(function anonymous(   //这是模板编译后生成的render函数
) {
with(this){return _c('div',{attrs:{"id":"example"}},[_v(_s(reversedMessage))])}
})

、获取reversedMessage属性时就会执行到计算属性的get访问器属性,也就是上面在3465行定义的defineComputed里的访问器属性,如下:

function createComputedGetter (key) {   //第3498行
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key]; //获取key对应的计算watcher
if (watcher) {
if (watcher.dirty) { //当watcher.dirty为true时
watcher.evaluate(); //执行watcher.evaluate()函数
}
if (Dep.target) { //这个Dep.target存在(这是个渲染watcher)
watcher.depend(); //则执行watcher.depend();
}
return watcher.value //最后返回计算属性的值
}
}
}
watcher函数对象的evaluate()和depend()对象都是为计算属性量身定制的,也就是说是它独有的,对于evaluate()来说,如下:
    Watcher.prototype.evaluate = function evaluate() {      //为计算watcher量身定制的
this.value = this.get(); //调用计算属性的get方法,此时如果有依赖其他属性,则会在其他属性的dep对象里将当前计算watcher作为订阅者
this.dirty = false; //修正this.dirty为false,即一个渲染watcher渲染多个计算属性时,只会执行一次
};
例子里执行完evaluate之后,Vue实例data里的message:'hello world'对应的subs就保存了当前的计算watcher,如下:

这样当message修改了之后就会触发计算watcher的更新了,回到createComputedGetter 里还会执行watcher.depend();,如下:

Watcher.prototype.depend = function depend () { //第3254行
var this$1 = this; var i = this.deps.length; //获取计算watcher的所有deps
while (i--) {
this$1.deps[i].depend(); //为该deps增加渲染watcher
}
};

执行完后会为当前计算属性所依赖的所有其它数据的订阅者subs里添加一个渲染watcher,执行完后Vue实例data里的message:'hello world'对应的subs又保存了当前的渲染watcher,如下:

现在计算watcher依赖的data属性对应的subs里存在两个订阅者了,当message进行修改时,会分别触发两个watcher的更新操作,reversedMessage也会进行相应的更新操作,然后DOM也更新了。

Vue.js 源码分析(六) 基础篇 计算属性 computed 属性详解的更多相关文章

  1. Vue.js 源码分析(十三) 基础篇 组件 props属性详解

    父组件通过props属性向子组件传递数据,定义组件的时候可以定义一个props属性,值可以是一个字符串数组或一个对象. 例如: <!DOCTYPE html> <html lang= ...

  2. Vue.js 源码分析(四) 基础篇 响应式原理 data属性

    官网对data属性的介绍如下: 意思就是:data保存着Vue实例里用到的数据,Vue会修改data里的每个属性的访问控制器属性,当访问每个属性时会访问对应的get方法,修改属性时会执行对应的set方 ...

  3. Vue.js 源码分析(三) 基础篇 模板渲染 el、emplate、render属性详解

    Vue有三个属性和模板有关,官网上是这样解释的: el ;提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标 template ;一个字符串模板作为 Vue 实例的标识使用.模板将会 ...

  4. Vue.js 源码分析(二) 基础篇 全局配置

    Vue.config是一个对象,包含Vue的全局配置,可以在启动应用之前修改下列属性,如下: ptionMergeStrategies        ;自定义合并策略的选项silent         ...

  5. Vue.js 源码分析(九) 基础篇 生命周期详解

    先来看看官网的介绍: 主要有八个生命周期,分别是: beforeCreate.created.beforeMount.mounted.beforeupdate.updated   .beforeDes ...

  6. Vue.js 源码分析(八) 基础篇 依赖注入 provide/inject组合详解

    先来看看官网的介绍: 简单的说,当组件的引入层次过多,我们的子孙组件想要获取祖先组件的资源,那么怎么办呢,总不能一直取父级往上吧,而且这样代码结构容易混乱.这个就是这对选项要干的事情 provide和 ...

  7. Vue.js 源码分析(七) 基础篇 侦听器 watch属性详解

    先来看看官网的介绍: 官网介绍的很好理解了,也就是监听一个数据的变化,当该数据变化时执行我们的watch方法,watch选项是一个对象,键为需要观察的数据名,值为一个表达式(函数),还可以是一个对象, ...

  8. Vue.js 源码分析(十一) 基础篇 过滤器 filters属性详解

    Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化.过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持).过滤器应该被添加在 JavaScrip ...

  9. Vue.js 源码分析(十) 基础篇 ref属性详解

    ref 被用来给元素或子组件注册引用信息.引用信息将会注册在父组件的 $refs 对象上.如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素:如果用在子组件上,引用就指向组件实例,例如: ...

随机推荐

  1. TortoiseSVN 无法查看日志 日期显示1970-01-01的解决方案

    对有问题的目录, 在右键菜单中, 使用 TortoiseSVN 的 Revision Graph 命令, 等待该命令执行完毕, 再重新使用 Show Log 命令, 即可查看日志了

  2. 踏实型的TOGAF企业架构学习者要学什么?

    下载:本文PDF版 昨天有帮友在IT帮大本营微信群了问到 我打开ea网站的常见问题,截图回复了一下 如果让我再简单说一下该学什么,我想用下面这张图来说明 实操型的看 企业架构,从理论到实践 认证型的看 ...

  3. PostgreSQL 中字段类型varchar

    PostgreSql数据库中varchar类型与sql server中字段用法有差别,PostgreSql中如果字段设置为varchar类型长度为10,则无论存字母.数字或其它符号,长度最大为10个, ...

  4. FCC---Use the CSS Transform scale Property to Scale an Element on Hover

    The transform property has a variety of functions that let you scale, move, rotate, skew, etc., your ...

  5. 18-搭建本地 Registry

    Docker Hub 虽然非常方便,但还是有些限制,比如: 需要 internet 连接,而且下载和上传速度慢. 上传到 Docker Hub 的镜像任何人都能够访问,虽然可以用私有 reposito ...

  6. GCN 实现3 :代码解析

    1.代码结构 ├── data // 图数据 ├── inits // 初始化的一些公用函数 ├── layers // GCN层的定义 ├── metrics // 评测指标的计算 ├── mode ...

  7. Go语言交叉编译工具gox

    基本介绍 交叉编译是为了在不同平台编译出其他平台的程序,比如在Linux编译出Windows程序,在Windows能编译出Linux程序,32位系统下编译出64位程序,今天介绍的gox就是其中一款交叉 ...

  8. 201871010113-刘兴瑞《面向对象程序设计(java)》第十周学习总结

    项目 内容 这个作业属于哪个课程 <任课教师博客主页链接> https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 <作业链接地址>htt ...

  9. LeetCode 5273. 搜索推荐系统 Search Suggestions System

    地址 https://leetcode-cn.com/problems/search-suggestions-system/ 题目描述给你一个产品数组 products 和一个字符串 searchWo ...

  10. CF1269A Equation

    题目链接 题意 要找两个合数,使他们两个的差为\(n\),\(n\)为题目给出的数 思路 我们可以枚举减数\(now\),判断一下是不是质数,如果是质数就让\(now++\),然后用一个数\(tot\ ...