网页的本质就是超级文本标记语言,通过结合使用其他的Web技术(如:脚本语言、公共网关接口、组件等),可以创造出功能强大的网页。因而,超级文本标记语言是万维网(Web)编程的基础,也就是说万维网是建立在超文本基础之上的。超级文本标记语言之所以称为超文本标记语言,是因为文本中包含了所谓“超级链接”点。
本篇文章给大家带来的内容是关于Vue中computed的实现原理是什么?有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
虽然目前的技术栈已由 Vue 转到了 React,但从之前使用 Vue 开发的多个项目实际经历来看还是非常愉悦的,Vue 文档清晰规范,api 设计简洁高效,对前端开发人员友好,上手快,甚至个人认为在很多场景使用 Vue 比 React 开发效率更高,之前也有断断续续研读过 Vue 的源码,但一直没有梳理总结,所以在此做一些技术归纳同时也加深自己对 Vue 的理解,那么今天要写的便是 Vue 中最常用到的 API 之一 computed
的实现原理。
基本介绍
话不多说,一个最基本的例子如下:
<div id="app">
<p>{{fullName}}</p>
</div>
new Vue({
data: {
firstName: 'Xiao',
lastName: 'Ming'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})Vue 中我们不需要在 template 里面直接计算 {{this.firstName + ' ' + this.lastName}}
,因为在模版中放入太多声明式的逻辑会让模板本身过重,尤其当在页面中使用大量复杂的逻辑表达式处理数据时,会对页面的可维护性造成很大的影响,而 computed
的设计初衷也正是用于解决此类问题。
对比侦听器 watch
当然很多时候我们使用 computed
时往往会与 Vue 中另一个 API 也就是侦听器 watch
相比较,因为在某些方面它们是一致的,都是以 Vue 的依赖追踪机制为基础,当某个依赖数据发生变化时,所有依赖这个数据的相关数据或函数都会自动发生变化或调用。
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch
选项提供了一个更通用的方法来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。从 Vue 官方文档对 watch
的解释我们可以了解到,使用 watch
选项允许我们执行异步操作(访问一个 API)或高消耗性能的操作,限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态,而这些都是计算属性无法做到的。
下面还另外总结了几点关于 computed
和 watch
的差异:
computed
是计算一个新的属性,并将该属性挂载到 vm(Vue 实例)上,而 watch
是监听已经存在且已挂载到 vm
上的数据,所以用 watch
同样可以监听 computed
计算属性的变化(其它还有 data
、props
)
computed
本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed
属性,才会计算新的值,而 watch
则是当数据发生变化便会调用执行函数
从使用场景上说,computed
适用一个数据被多个数据影响,而 watch
适用一个数据影响多个数据;
以上我们了解了 computed
和 watch
之间的一些差异和使用场景的区别,当然某些时候两者并没有那么明确严格的限制,最后还是要具体到不同的业务进行分析。
原理分析
言归正传,回到文章的主题 computed
身上,为了更深层次地了解计算属性的内在机制,接下来就让我们一步步探索 Vue 源码中关于它的实现原理吧。
在分析 computed
源码之前我们先得对 Vue 的响应式系统有一个基本的了解,Vue 称其为非侵入性的响应式系统,数据模型仅仅是普通的 JavaScript 对象,而当你修改它们时,视图便会进行自动更新。
当你把一个普通的 JavaScript 对象传给 Vue 实例的 data
选项时,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty
把这些属性全部转为 getter/setter
,这些 getter/setter
对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化,每个组件实例都有相应的 watcher
实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter
被调用时,会通知 watcher
重新计算,从而致使它关联的组件得以更新。Vue 响应系统,其核心有三点:observe
、watcher
、dep
:
observe
:遍历 data
中的属性,使用 Object.defineProperty 的 get/set
方法对其进行数据劫持;
dep
:每个属性拥有自己的消息订阅器 dep
,用于存放所有订阅了该属性的观察者对象;
watcher
:观察者(对象),通过 dep
实现对响应属性的监听,监听到结果后,主动触发自己的回调进行响应。
对响应式系统有一个初步了解后,我们再来分析计算属性。
首先我们找到计算属性的初始化是在 src/core/instance/state.js
文件中的 initState
函数中完成的
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 */)
}
// computed初始化
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}调用了 initComputed
函数(其前后也分别初始化了 initData
和 initWatch
)并传入两个参数 vm
实例和 opt.computed
开发者定义的 computed
选项,转到 initComputed
函数:
const computedWatcherOptions = { computed: true }
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]
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
)
}
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter 关键词:Vue中computed的完成原理是啥?