工具推荐

推荐vue/cli的一个工具,零配置直接运行vue文件。

安装方法:

npm install -g @vue/cli-service-global
复制代码

使用方法:

vue serve
# or
vue serve MyComponent.vue
复制代码

computed

这篇文章分享一下computed计算属性的实现原理。首先分享一个工作中遇到的code review问题!利用3分钟先看一个例子:

<template><div><p>valueText:{{ valueText }}</p><p>xxx:{{ xxx }}</p><button @click="changeValue">改变 abc 和 xxx 的值</button></div>
</template><script>export default {data () {return {xxx: false}},computed: {valueText () {return this.abc && this.xxx;}},created () {this.abc = false;},methods:{changeValue () {this.abc = true;this.xxx = true;}}}
</script>
复制代码

按钮点击前后分别输出什么?

上面的答案是不是如你所想呢?

又或者有为什么点击后valueText不是true的疑问?如果有,就继续往下通过源码分析computed的原理!

// new Vue之后就调用_init方法Vue.prototype._init = function (options?: Object) {const vm: Component = this......vm._self = vm// 初始化生命周期initLifecycle(vm)// 初始化事件中心initEvents(vm)// 初始化渲染initRender(vm)callHook(vm, 'beforeCreate')// 初始化注入initInjections(vm) // resolve injections before data/props// 初始化状态// 初始化 props、data、methods、watch、computed 等属性initState(vm)initProvide(vm) // resolve provide after data/propscallHook(vm, 'created')......}
}
复制代码

在Vue实例初始化时,注意到有一个 initState 的方法。这个方法就是初始化 props、data、methods、watch、computed 等属性。进入函数体里面继续看:

export function initState (vm: Component) {vm._watchers = []const opts = vm.$optionsif (opts.props) initProps(vm, opts.props) // 初始化propsif (opts.methods) initMethods(vm, opts.methods) // 初始化事件if (opts.data) {initData(vm)  // 初始化data} else {observe(vm._data = {}, true /* asRootData */)}if (opts.computed) initComputed(vm, opts.computed)  // 初始化computedif (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch)}
}
复制代码

非常简单,就是对一些属性做初始化调用,本文重点是 computed !好,继续看 initComputed 的内容:

const computedWatcherOptions = { computed: true }// 定义computed属性
function initComputed (vm: Component, computed: Object) {// 先声明一个computedWatcher空对象const watchers = vm._computedWatchers = Object.create(null)// 判断是不是服务端渲染const isSSR = isServerRendering()// 遍历computed中的对象for (const key in computed) {const userDef = computed[key]// 获取属性的getter方法,这里有两种情况:是函数就直接获取,是对象就获取get值const getter = typeof userDef === 'function' ? userDef : userDef.getif (process.env.NODE_ENV !== 'production' && getter == null) {warn(`Getter is missing for computed property "${key}".`,vm)}// 非服务端渲染,new一个computed watcher实例。if (!isSSR) {watchers[key] = new Watcher(vm,getter || noop,noop,computedWatcherOptions)}// 不存在Vue实例中,就去定义if (!(key in vm)) {defineComputed(vm, key, userDef)} else if (process.env.NODE_ENV !== 'production') {// 开发环境下判断computed的key不能跟data或props同名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)}}}
}
复制代码

到这里,我们先不去深入 computed watcher 实例的声明,先看 defineComputed :

export function defineComputed (target: any,key: string,userDef: Object | Function
) {// 不是服务端渲染shouldCache为trueconst shouldCache = !isServerRendering() // 如果是用户定义的函数if (typeof userDef === 'function') {sharedPropertyDefinition.get = shouldCache? createComputedGetter(key): userDefsharedPropertyDefinition.set = noop// 不是函数,也就是对象,那么获取用户定义的getter和setter方法} else {sharedPropertyDefinition.get = userDef.get? shouldCache && userDef.cache !== false? createComputedGetter(key): userDef.get: noopsharedPropertyDefinition.set = userDef.set? userDef.set: noop}if (process.env.NODE_ENV !== '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结构如下:

const sharedPropertyDefinition = {enumerable: true,configurable: true,get: noop,set: noop
}
复制代码

sharedPropertyDefinition 的 get 函数也就是 createComputedGetter(key) 的结果:

// 创建计算属性的getter方法
function createComputedGetter (key) {return function computedGetter () {const watcher = this._computedWatchers && this._computedWatchers[key]if (watcher) {watcher.depend()  // 收集依赖return watcher.evaluate() // 计算属性的值}}
}
复制代码

当计算属性被调用时便会执行 get 访问函数,从而关联上观察者对象 watcher 然后执行 wather.depend() 收集依赖和 watcher.evaluate() 计算求值。

OK,下面我们回到computed watcher的实例化:

watchers[key] = new Watcher(vm,getter || noop,noop,computedWatcherOptions)
复制代码

这里的参数意义都比较清晰。 vm 指的 Vue 实例, getter 指的是计算属性的 getter 方法, computedWatcherOptions 即表明这是一个 computed watcher 。

export default class Watcher {constructor (vm: Component,expOrFn: string | Function,cb: Function,options?: ?Object,isRenderWatcher?: boolean) {this.vm = vmif (isRenderWatcher) {vm._watcher = this}vm._watchers.push(this)// optionsif (options) {this.deep = !!options.deepthis.user = !!options.userthis.computed = !!options.computedthis.sync = !!options.syncthis.before = options.before} else {this.deep = this.user = this.computed = this.sync = false}this.cb = cbthis.id = ++uid // uid for batchingthis.active = truethis.dirty = this.computed // for computed watchersthis.deps = []this.newDeps = []this.depIds = new Set()this.newDepIds = new Set()this.expression = process.env.NODE_ENV !== 'production'? expOrFn.toString(): ''// 对于computed watcher这里是一个getter函数,赋值给getterif (typeof expOrFn === 'function') {this.getter = expOrFn} else {this.getter = parsePath(expOrFn)if (!this.getter) {this.getter = function () {}process.env.NODE_ENV !== 'production' && warn(`Failed watching path: "${expOrFn}" ` +'Watcher only accepts simple dot-delimited paths. ' +'For full control, use a function instead.',vm)}}// 计算属性执行到这里为trueif (this.computed) {// 和其他watcher的区别,计算属性这里并不会立刻返回求值this.value = undefined// 创建了该属性的消息订阅器this.dep = new Dep()} else {this.value = this.get()}}
复制代码
export default class Dep {static target: ?Watcher;id: number;subs: Array<Watcher>;constructor () {this.id = uid++this.subs = []}addSub (sub: Watcher) {this.subs.push(sub)}removeSub (sub: Watcher) {remove(this.subs, sub)}depend () {if (Dep.target) {Dep.target.addDep(this)}}notify () {// stabilize the subscriber list firstconst subs = this.subs.slice()for (let i = 0, l = subs.length; i < l; i++) {subs[i].update()}}
}
复制代码

这里Watcher和Dep的关系就是:

watcher 中实例化了 dep 并向 dep.subs 中添加了订阅者,dep 通过 notify 遍历了 dep.subs 通知每个 watcher 更新。

当获取到计算属性的值时,就会执行getter函数,即

if (watcher) {watcher.depend() return watcher.evaluate()
}
复制代码

再看到watcher对象中的depend和evaluate方法:

depend () {// Dep.target代表的是渲染watcher// 在获取计算属性值时,触发其他响应式数据的getter,此时Dep.target代表的是computed watcherif (this.dep && Dep.target) {// 渲染watcher订阅computed watcher的变化this.dep.depend()}}
复制代码
evaluate () {if (this.dirty) {// 这里的get就是computed上的getter函数this.value = this.get()this.dirty = false}// 返回getter的值return this.value}
复制代码

以上就是计算属性getter的整个过程,这里稍微总结一下:

  • new Vue 或 Vue.extend 实例化一个组件时,data 或 computed 会各自建立响应式系统,Observer 遍历 data 中每个属性设置 get/set 数据拦截。对于 computed 属性,会调用 initComputed 函数
  • 实例化一个computed watcher,其中会注册依赖对象dep
  • 调用计算属性的 watcher 执行 depend() 方法向自身的消息订阅器 dep 的 subs 中添加其他属性的 watcher
  • 调用 watcher 的 evaluate 方法(进而调用 watcher 的 get 方法)让自身成为其他 watcher 的消息订阅器的订阅者,首先将 watcher 赋给 Dep.target,然后执行 getter 求值函数,当访问求值函数里面的属性(比如来自 data、props 或其他 computed)时,会同样触发它们的 get 访问器函数从而将该计算属性的 watcher 添加到求值函数中属性的 watcher 的消息订阅器 dep 中,当这些操作完成,最后关闭 Dep.target 赋为 null 并返回求值函数结果。

计算属性的setter的流程比较简单:

  • 调用 set 拦截函数,然后调用自身消息订阅器 dep 的 notify 方法,遍历当前 dep 中保存着所有订阅者 wathcer 的 subs 数组,并逐个调用 watcher 的 update 方法,完成响应更新。

现在再回头去看第一个问题的答案就一清二楚了。计算属性 valueText 因为 this.abc 为 undefined 并没有收集到 this.abc 的变化。所以点击之后是 valueText 并不会改变。如果将两个属性调换位置,那么就如我们所愿了:

computed: {valueText () {return this.xxx && this.abc;}
}
复制代码

这里可以得出结论:

computed属性getter中,保证第一次调用时能执行到你所希望的监听的绑定数据。

觉得有帮助的可以关注下我的博客!!!给个star支持一下吧!

computed的原理相关推荐

  1. 解析Vue.js中的computed工作原理

    我们通过实现一个简单版的和Vue中computed具有相同功能的函数来了解computed是如何工作的.写的十分的全面细致,具有一定的参考价值,对此有需要的朋友可以参考学习下.如有不足之处,欢迎批评指 ...

  2. computed vue 不 触发_Vue Computed 原理

    Computed 计算属性是 Vue 中常用的一个功能,在使用中发现他像是介于data和method之间的一个属性.他可以像是data一样在模板中{{message}}一样去使用,也可以通过this. ...

  3. Vue开发入门看这篇文章就够了

    摘要: 很多值得了解的细节. 原文:Vue开发看这篇文章就够了 作者:Random Fundebug经授权转载,版权归原作者所有. 介绍 Vue 中文网 Vue github Vue.js 是一套构建 ...

  4. Vue -渐进式JavaScript框架

    介绍 vue 中文网 vue github Vue.js 是一套构建用户界面(UI)的渐进式JavaScript框架 库和框架的区别 我们所说的前端框架与库的区别? Library 库,本质上是一些函 ...

  5. 前端填空题_一年前端面试总结|入职字节|2020.8

    站在未来看现在 你当像鸟飞向你的山 前言     普通本科,软件工程专业,2019年毕业进入奇安信集团(前360企业安全),实习期间遇到一群可以一起嗨的朋友,感觉很幸福,也很庆幸能够遇到hin nic ...

  6. data的值 如何初始化vue_理解Vue响应式系统

    深入理解 Vue 响应式系统 理解 Vue 响应式原理,到 computed.vuex 原理 前言 众所周知,一说到 vue 的响应式系统,就能马上想到 Object.defineProperty.数 ...

  7. vue开发看这篇文章就够了 vue知识大全

    Vue -渐进式JavaScript框架 介绍 vue 中文网 vue github Vue.js 是一套构建用户界面(UI)的渐进式JavaScript框架 库和框架的区别 我们所说的前端框架与库的 ...

  8. 曹汛:计算摄像学研究 | VALSE2017之十六

    点击上方"深度学习大讲堂"可订阅哦! 编者按:摄像,摄万物之象.经典摄像方法在成像的各个维度--空间分辨率.时间分辨率.视角及深度.颜色(光谱)等均已达到瓶颈,而计算摄像能够突破经 ...

  9. 2021 前端 VUE 面试题总汇

    文章目录 1.vue的生命周期 2.Vue2.x 双向绑定原理 3.Vue3.x 响应式原理 4.v-for 为什么要加上 key 5.Vdom的理解 6.vuex 的结构,以及 actiion 和 ...

最新文章

  1. 关于新技术的引入原则 ——从零开始学架构
  2. MySQL还原数据库提示Unknown MySql server host
  3. Android系统版本与API级别对照表
  4. python调用github_Python调用GithubAPI并进行初步的数据分析
  5. Array 对象-sort()
  6. 【优化算法】混合蛙跳算法(SLFA)【含Matlab源码 300期】
  7. Cisco ASA 9.17.1 Full ( bin, ova, qcow2, SPA, vhdx ) 下载 - 思科防火墙
  8. Android实战简易教程-第五十六枪(模拟美团客户端进度提示框)
  9. Python学习笔记(11)-Python进阶11-函数
  10. 金色传说:SAP-FICO-COPA:创建销售订单时,获利能力段不自动带出,报错消息号KE0C133:特性值 2720000100000001对于特性 EXTWG 不存在 (外部物料组)
  11. 基于python的博客设计与开发_基于python的博客设计与开发毕业设计
  12. 义隆循环左移c语言,义隆单片机EM78PXXX的乘除的运算法
  13. 科普丨什么是语言?什么是自然语言?
  14. Java常用基础知识点总结(最全)
  15. android关闭硬件动画加速器,Android中的硬件加速
  16. 推荐一个程序员必备官方 App ,名字叫:力扣
  17. 一直在构建工作空间_国土空间规划一周知识整理(2020.11.09-2020.11.15)
  18. 打造自己的 APP「冰与火百科」(一):分析定位
  19. Android Graphics Tests 程序学习01
  20. 搭建简单的struts2框架

热门文章

  1. 算法题存档20200505
  2. 计算机补丁的概念,补丁是什么意思?网上说的打补丁什么意思
  3. 一个有趣的问题 : 如何设计一个线程池
  4. Java 的 安全性 体现在哪里?面试题
  5. Spring Data JPA 动态拼接条件的通用设计模式
  6. include做配置文件
  7. php加本地音乐代码,WordPress添加音乐播放器(纯代码实现)
  8. Shiro系列-Shiro如何实现身份验证
  9. 35岁前需要完成的事
  10. scrapy模拟登陆人人网