Vue中computed分析
Vue中computed分析
在Vue中computed是计算属性,其会根据所依赖的数据动态显示新的计算结果,虽然使用{{}}模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的,在模板中放入太多的逻辑会让模板过重且难以维护,所以对于任何复杂逻辑,都应当使用计算属性。计算属性是基于数据的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值,也就是说只要计算属性依赖的数据还没有发生改变,多次访问计算属性会立即返回之前的计算结果,而不必再次执行函数,当然如果不希望使用缓存可以使用方法属性并返回值即可,computed计算属性非常适用于一个数据受多个数据影响以及需要对数据进行预处理的条件下使用。
描述
computed计算属性可以定义两种方式的参数,{ [key: string]: Function | { get: Function, set: Function } },计算属性直接定义在Vue实例中,所有getter和setter的this上下文自动地绑定为Vue实例,此外如果为一个计算属性使用了箭头函数,则this不会指向这个组件的实例,不过仍然可以将其实例作为函数的第一个参数来访问,计算属性的结果会被缓存,除非依赖的响应式property变化才会重新计算,注意如果某个依赖例如非响应式property在该实例范畴之外,则计算属性是不会被更新的。
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
a: 1,
b: 2
},
template:`
<div>
<div>{{multiplication}}</div>
<div>{{multiplication}}</div>
<div>{{multiplication}}</div>
<div>{{multiplicationArrow}}</div>
<button @click="updateSetting">updateSetting</button>
</div>
`,
computed:{
multiplication: function(){
console.log("a * b"); // 初始只打印一次 返回值被缓存
return this.a * this.b;
},
multiplicationArrow: vm => vm.a * vm.b * 3, // 箭头函数可以通过传入的参数获取当前实例
setting: {
get: function(){
console.log("a * b * 6");
return this.a * this.b * 6;
},
set: function(v){
console.log(`${v} -> a`);
this.a = v;
}
}
},
methods:{
updateSetting: function(){ // 点击按钮后
console.log(this.setting); // 12
this.setting = 3; // 3 -> a
console.log(this.setting); // 36
}
},
})
</script>
</html>
分析
首先在Vue中完成双向绑定是通过Object.defineProperty()实现的,Vue的双向数据绑定,简单点来说分为以下三个部分:
Observer: 这里的主要工作是递归地监听对象上的所有属性,在属性值改变的时候,触发相应的Watcher。Watcher: 观察者,当监听的数据值修改时,执行响应的回调函数,在Vue里面的更新模板内容。Dep: 链接Observer和Watcher的桥梁,每一个Observer对应一个Dep,它内部维护一个数组,保存与该Observer相关的Watcher。
Vue源码的实现比较复杂,会处理各种兼容问题与异常以及各种条件分支,文章分析比较核心的代码部分,精简过后的版本,重要部分做出注释,commit id为0664cb0。
首先在dev/src/core/instance/state.js中定义了初始化computed以及initComputed函数的实现,现在暂不考虑SSR服务端渲染的computed实现。
// dev/src/core/instance/state.js line 47
export function initState (vm: Component) {
vm._watchers = []
const 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属性则进行初始化
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
// dev/src/core/instance/state.js line 169
function initComputed (vm: Component, computed: Object) {
// $flow-disable-line
const watchers = vm._computedWatchers = Object.create(null) // 创建一个没有原型链指向的对象
// computed properties are just getters during SSR
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key] // 获取计算属性的key值定义
const getter = typeof userDef === 'function' ? userDef : userDef.get // 由于计算属性接受两种类型的参数 此处判断用以获取getter
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
if (!isSSR) {
// create internal watcher for the computed property.
// 生成computed watcher(vm, getter, noop, { lazy: true })
watchers[key] = new Watcher( // 计算属性创建观察者watcher和消息订阅器dep
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)) { // 检查重名属性
defineComputed(vm, key, userDef) // 定义属性
} else if (process.env.NODE_ENV !== 'production') {
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)
}
}
}
}
defineComputed传入了三个参数,vm实例、计算属性的key以及userDef计算属性的定义,属性描述符sharedPropertyDefinition在初始化定义之后经过userDef和shouldCache等多重判断后被重写,进而通过Object.defineProperty(target, key, sharedPropertyDefinition)进行属性的定义。
// dev/src/core/instance/state.js line 31
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
// dev/src/core/instance/state.js line 210
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.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 = {
enumerable: true,
configurable: true,
get: function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
},
set: userDef.set || noop
}
当计算属性被调用时便会执行 get 访问函数,从而关联上观察者对象 watcher 然后执行 wather.depend() 收集依赖和 watcher.evaluate() 计算求值。
*/
每日一题
https://github.com/WindrunnerMax/EveryDay
参考
https://cn.vuejs.org/v2/api/#computed
https://juejin.im/post/6844903678533451783
https://juejin.im/post/6844903873925087239
https://cn.vuejs.org/v2/guide/computed.html
https://zheyaoa.github.io/2019/09/07/computed/
https://www.cnblogs.com/tugenhua0707/p/11760466.html
Vue中computed分析的更多相关文章
- vue中computed(计算属性)和watch在实现父子组件props同步时的实际区分
vue中computed和watch的对比是一个很有意思的话题. 看过官网教程以后,我们往往更倾向多使用computed.computed优点很多,却在某些时候不太适用. 今天我们就稍微讨论一下,当我 ...
- Vue中computed和watch的区别
在vue中computed和watch的真正区别是:computed产生于它的依赖,而watch产生于它的依赖的变化.只要依赖存在,我们就能访问到其对应的computed属性:但只有依赖发生了改变,我 ...
- vue中computed的作用以及用法
在vue中computed是计算属性,主要作用是把数据存储到内存中,减少不必要的请求,还可以利用computed给子组件的data赋值. 参考地址:https://www.jianshu.com/p/ ...
- Vue中computed,methods 和watch
Vue中的计算属性和方法属性 1.计算属性 computed 模版中可以使用表达式 <div id="example"> {{ message.split('').re ...
- vue中computed与watch的异同
一.computed 和 watch 都可以观察页面的数据变化.当处理页面的数据变化时,我们有时候很容易滥用watch. 而通常更好的办法是使用computed属性,而不是命令是的watch回调. ...
- vue中computed/method/watch的区别
摘要:本文通过官方文档结合源码来分析computed/method/watch的区别. Tips:本文分析的源码版本是v2.6.11,文章中牵涉到vue响应式系统原理部分,如果不是很了解,建议先阅读上 ...
- vue中computed计算属性与methods对象中的this指针
this 指针问题 methods与computed中的this指针 应该指向的是它们自己,可是为什么this指针却可以访问data对象中的成员呢? 因为new Vue对象实例化后data中的成员和c ...
- vue中computed和watch
computed 计算属性 能够监听vue数据上的变化,页面上来就执行一次,每改变一次数据就又触发.在操作数据的时候,会派生出另一个事情 1.函数形式 computed:{ listenArr(){ ...
- vue中computed和watch的用法
computed用来监控自己定义的变量,该变量不在data里面声明,直接在computed里面定义,然后就可以在页面上进行双向数据绑定展示出结果或者用作其他处理: computed比较适合对多个变量或 ...
随机推荐
- 牛客网数据库SQL实战解析(31-40题)
牛客网SQL刷题地址: https://www.nowcoder.com/ta/sql?page=0 牛客网数据库SQL实战解析(01-10题): https://blog.csdn.net/u010 ...
- WPF新手快速入门系列 1.布局
[概要] 该系列文章主要描述,新手如何快速上手做wpf开发.看过网上部分的教程,主要讲述的是介绍控件.这并没有问题,但是没有把自己的使用经验也完整的描述出来. 所以特此编写此系列文章希望能帮助到,因为 ...
- Inno Setup Compiler 中文使用教程
一.概要 该文章主要解决,Inno Setup Compiler工具的使用问题. 如有什么建议欢迎提出,本人及时修改.[如有任何疑惑可以加Q群:580749909] 二.步骤 (1)下载地址:http ...
- 火狐Firefox 52.90版是最后一个支持WinXP和Vista的版本
Firefox 52.90版是最后一个支持 Windows XP 和 Windows Vista 的升级版.(参考:https://support.mozilla.org/zh-CN/kb/firef ...
- C++从LPEXCEPTION_POINTERS获取调用堆栈
#pragma once #include <map> #include <vector> struct FunctionCall { DWORD64 Address; std ...
- POJ - 3037-Skiing(邻接表+Dijkstra)
Bessie and the rest of Farmer John's cows are taking a trip this winter to go skiing. One day Bessie ...
- js转换人民币金额 小写到大写
u.bigNum=function(n) { var fraction = ['角', '分']; var digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒 ...
- 微服务实战SpringCloud之Spring Cloud Feign替代HTTP Client
简介 在项目中我们有时候需要调用第三方的API,微服务架构中这种情况则更是无法避免--各个微服务之间通信.比如一般的项目中,有时候我们会使用 HTTP Client 发送 HTTP 请求来进行调用,而 ...
- 01 fs模块
1 fs.readFile 异步执行函数 /** fs 读取文件相对路径是相对终端命令行所在的路径 process.cwd()返回终端命令行的绝对路径 * */ fs = require('fs') ...
- 知识点干货——CSS动画
CSS动画 (transition.animation) //2D动画 transform:translate(); /*偏移*/ transform:rotate(); /*旋转角度*/ trans ...