1.computed是计算属性,类似于过滤器,对绑定到视图的数据进行处理。官网的例子:
<div id="example"> <p>Original message: "{{ message }}"</p> <p>Computed reversed message: "{{ reversedMessage }}"</p> </div>
var vm = new Vue({ el: ‘#example‘, data: { message: ‘Hello‘ }, computed: { // 计算属性的 getter reversedMessage: function () { // `this` 指向 vm 实例 return this.message.split(‘‘).reverse().join(‘‘) } } })
结果:
Original message: "Hello"
Computed reversed message: "olleH"
计算属性是基于它们的依赖进行缓存的。只在相关依赖发生改变时它们才会重新求值。
值得注意的是“reversedMessage”不能在组件的props和data中定义,否则会报错。
2.watch是一个侦听的动作,用来观察和响应 Vue 实例上的数据变动。官网上的例子:
<div id="watch-example"> <p> Ask a yes/no question: <input v-model="question"> </p> <p>{{ answer }}</p> </div>
<!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 --> <!-- 提供这些功能以保持精简。这也可以让你自由选择自己更熟悉的工具。 --> <script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script> <script> var watchExampleVM = new Vue({ el: ‘#watch-example‘, data: { question: ‘‘, answer: ‘I cannot give you an answer until you ask a question!‘ }, watch: { // 如果 `question` 发生改变,这个函数就会运行 question: function (newQuestion, oldQuestion) { this.answer = ‘Waiting for you to stop typing...‘ this.debouncedGetAnswer() } }, created: function () { // `_.debounce` 是一个通过 Lodash 限制操作频率的函数。 // 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率 // AJAX 请求直到用户输入完毕才会发出。想要了解更多关于 // `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识, // 请参考:https://lodash.com/docs#debounce this.debouncedGetAnswer = _.debounce(this.getAnswer, 500) }, methods: { getAnswer: function () { if (this.question.indexOf(‘?‘) === -1) { this.answer = ‘Questions usually contain a question mark. ;-)‘ return } this.answer = ‘Thinking...‘ var vm = this axios.get(‘https://yesno.wtf/api‘) .then(function (response) { vm.answer = _.capitalize(response.data.answer) }) .catch(function (error) { vm.answer = ‘Error! Could not reach the API. ‘ + error }) } } }) </script>
在这个示例中,使用 watch 选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
下面来总结下这两者用法的异同:
相同: computed和watch都起到监听/依赖一个数据,并进行处理的作用
异同:它们其实都是vue对监听器的实现,只不过computed主要用于对同步数据的处理,watch则主要用于观测某个值的变化去完成一段开销较大的复杂业务逻辑。能用computed的时候优先用computed,避免了多个数据影响其中某个数据时多次调用watch的尴尬情况。
<div id="demo">{{ fullName }}</div>
var vm = new Vue({ el: ‘#demo‘, data: { firstName: ‘Foo‘, lastName: ‘Bar‘, fullName: ‘Foo Bar‘ }, watch: { firstName: function (val) { console.log(‘第一次没有执行~‘) this.fullName = val + ‘ ‘ + this.lastName } } })
可以看到,初始化的时候watch是不会执行的。看上边的例子,只要当firstName的值改变的时候才会执行监听计算。
但如果想在第一次它在被绑定的时候就执行怎么办?这时候就要修改一下我们的例子:
watch: { firstName: { handler(val) { console.log(‘第一次执行了~‘) this.fullName = val + ‘ ‘ + this.lastName }, // 代表在watch里声明了firstName这个方法之后立即先去执行handler方法 immediate: true } }
打开控制台可以看到打印出了‘第一次执行了~’。
注意到handler了吗,我们给 firstName 绑定了一个handler方法,之前我们写的 watch 方法其实默认写的就是这个handler,Vue.js会去处理这个逻辑,最终编译出来其实就是这个handler。
而immediate:true代表如果在 wacth 里声明了 firstName 之后,就会立即先去执行里面的handler方法,如果为 false就跟我们以前的效果一样,不会在绑定的时候就执行。
watch里还有一个deep属性,代表是否开启深度监听,默认为false,下面来看一个例子:
<div id="app"> <div>obj.a: {{obj.a}}</div> <input type="text" v-model="obj.a"> </div>
var vm = new Vue({ el: ‘#app‘, data: { obj: { a: 1 } }, watch: { obj: { handler(val) { console.log(‘obj.a changed‘) }, immediate: true } } })
默认情况下 在handler方法中 只监听obj这个属性它的引用的变化,我们只有给obj赋值的时候它才会监听到,比如我们在 mounted事件钩子函数中对obj进行重新赋值:
mounted() { this.obj = { a: ‘123‘ } }
这样handler就会执行了,且打印出了‘obj.a changed‘。
但是我们如果需要监听obj里的属性值呢?这时候,deep属性就派上用场了。我们只需要加上deep:true,就能深度监听obj里属性值。
watch: { obj: { handler(val) { console.log(‘obj.a changed‘) }, immediate: true, deep: true } }
deep属性的意思是深度遍历,会在对象一层层往下遍历,在每一层都加上监听器。
在源码中的体现,定义在src/core/observer/traverse.js中:
/* @flow */ import { _Set as Set, isObject } from ‘../util/index‘ import type { SimpleSet } from ‘../util/index‘ import VNode from ‘../vdom/vnode‘ const seenObjects = new Set() /** * Recursively traverse an object to evoke all converted * getters, so that every nested property inside the object * is collected as a "deep" dependency. */ export function traverse (val: any) { _traverse(val, seenObjects) seenObjects.clear() } function _traverse (val: any, seen: SimpleSet) { let i, keys const isA = Array.isArray(val) if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { return } if (val.__ob__) { const depId = val.__ob__.dep.id if (seen.has(depId)) { return } seen.add(depId) } if (isA) { i = val.length while (i--) _traverse(val[i], seen) } else { keys = Object.keys(val) i = keys.length while (i--) _traverse(val[keys[i]], seen) } }
如果this.deep == true,即存在deep,则触发每个深层对象的依赖,追踪其变化。
traverse方法递归每一个对象或者数组,触发它们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系。
这个函数实现还有一个小的优化,遍历过程中会把子响应式对象通过它们的 dep.id 记录到 seenObjects,避免以后重复访问。
但是使用deep属性会给每一层都加上监听器,性能开销可能就会非常大了。这样我们可以用字符串的形式来优化:
watch: { ‘obj.a‘: { handler(val) { console.log(‘obj.a changed‘) }, immediate: true // deep: true } }
直到遇到‘obj.a‘属性,才会给该属性设置监听函数,提高性能。
我们知道new Vue()的时候会调用_init方法,该方法会初始化生命周期,初始化事件,初始化render,初始化data,computed,methods,wacther等等。
今天主要来看以下初始化watch(initWatch)的实现,定义在src/core/instance/state.js中:
// 用于传入Watcher实例的一个对象,即computed watcher const computedWatcherOptions = { computed: true } function initComputed (vm: Component, computed: Object) { // $flow-disable-line // 声明一个watchers且同时挂载到vm实例上 const watchers = vm._computedWatchers = Object.create(null) // 在SSR模式下computed属性只能触发getter方法 const isSSR = isServerRendering() // 遍历传入的computed方法 for (const key in computed) { // 取出computed对象中的每个方法并赋值给userDef const userDef = computed[key] const getter = typeof userDef === ‘function‘ ? userDef : userDef.get if (process.env.NODE_ENV !== ‘production‘ && getter == null) { warn( `Getter is missing for computed property "${key}".`, vm ) } // 如果不是SSR服务端渲染,则创建一个watcher实例 if (!isSSR) { // create internal watcher for the computed property. watchers[key] = new 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)) { // 如果computed中的key没有设置到vm中,通过defineComputed函数挂载上去 defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== ‘production‘) { // 如果data和props有和computed中的key重名的,会产生warning 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) } } } }
通过源码我们可以发现它先声明了一个名为watchers的空对象,同时在vm上也挂载了这个空对象。
之后遍历计算属性,并把每个属性的方法赋给userDef,如果userDef是function的话就赋给getter,接着判断是否是服务端渲染,如果不是的话就创建一个Watcher实例。
不过需要注意的是,这里新建的实例中我们传入了第四个参数,也就是computedWatcherOptions。const computedWatcherOptions = { computed: true },这个对象是实现computed watcher的关键。这时,Watcher中的逻辑就有变化了:
Vue框架,computed和watch是如何工作的(未完成)
原文:https://www.cnblogs.com/magicg/p/12667252.html