理解computed
computed基本使用
- 当模板中存在复杂计算逻辑的时候使用
- 多个地方使用只会执行一次
- 只有当计算属性内部所依赖的数据发生了改变的话才会重新触发方法
- 计算属性会缓存上一次计算出来的值
computed中包含了getter 和 setter方法, 默认是getter
method
使用几次就会执行几次
下面先看一个computed基本使用例子:
var App = {
data() {
return {
studentName: '张三'
}
},
template: `
<div>
<!-- 当多个地方需要使用时,会执行多次 -->
<!--
<h1>{{ '我是一名学生,名叫:' + studentName }}</h1>
<h3>{{ '我是一名学生,名叫:' + studentName }}</h3>
-->
<h1>computed计算出来的值:{{ studentInfo }}</h1>
<h3>computed计算出来的值:{{ studentInfo }}</h3>
<h1>methods计算出来的值:{{ studentInfoFn() }}</h1>
<h3>methods计算出来的值:{{ studentInfoFn() }}</h3>
<button @click="changeStudent">CHANGE STUDENT</button>
</div>
`,
computed: {
// studentInfo() {
// console.log('computed')
// return this.studentName ? '我是一名学生,名叫:' + this.studentName
// : '学生不存在'
// }
studentInfo: {
get() {
console.log('computed')
return this.studentName ? '我是一名学生,名叫:' + this.studentName
: '学生不存在'
},
set(newValue) {
this.studentName = newValue
}
}
},
methods: {
changeStudent() {
this.studentName = ''
},
studentInfoFn() {
console.log('methods')
return this.studentName ? '我是一名学生,名叫:' + this.studentName
: '学生不存在'
}
}
}
var vm = Vue.createApp(App).mount('#app')
vm.studentInfo = '李四'
从浏览器输出可以看出computed里面的属性调用了两次只打印了一次computed,而mthoeds里面的方法却打印了两次,由此可看出computed多个地方使用只会执行一次。
简单实现computed
app.js
import Vue from './Vue'
var vm = new Vue({
el: '#app',
data() {
return {
a: 1,
b: 2
}
},
template: `
<span>{{ a }}</span>
<span>+</span>
<span>{{ b }}</span>
<span>=</span>
<span>{{ total }}</span>
`,
computed: {
total() {
console.log('computed')
return this.a + this.b
},
// total: {
// get() {
// console.log('computed')
// return this.a + this.b
// }
// }
}
})
console.log(vm)
console.log(vm.total)
console.log(vm.total)
console.log(vm.total)
vm.a = 100
vm.b = 100
console.log(vm.total)
console.log(vm.total)
console.log(vm.total)
vue.js
var Vue = (function() {
/**
* computedData
* 存储计算属性的值 方法 依赖的数据
* {
* value: value,
* get: fn,
* dep: [a, b, ...]
* }
*/
var computedData = {},
dataPool = {};
function Vue(options) {
this.$el = document.querySelector(options.el)
this.$data = options.data()
this._init(this, options.computed, options.template)
}
Vue.prototype._init = function(vm, computed, template) {
dataReactive(vm)
computedReactive(vm, computed)
render(vm, template)
}
function dataReactive(vm) {
var _data = vm.$data
for(var key in _data) {
(function(key) {
Object.defineProperty(vm, key, {
get() {
return _data[key]
},
set(newValue) {
_data[key] = newValue
update(vm, key)
_updateComputedData(vm, key, function(key) {
update(vm, key)
})
}
})
})(key)
}
}
function computedReactive(vm, computed) {
_initComputedData(vm, computed)
// 监听计算属性数据 将计算属性total...挂载到vm实例上
for(var key in computedData) {
(function(key) {
Object.defineProperty(vm, key, {
get() {
return computedData[key].value
},
set(newValue) {
computedData[key].value = newValue
}
})
})(key)
}
}
function render(vm, template) {
// 创建容器
var container = document.createElement('div'),
_el = vm.$el;
container.innerHTML = template
// 替换模板内容
var domTree = _compileTemplate(vm, container)
// 插入到app中
_el.appendChild(domTree)
}
function update(vm, key) {
dataPool[key].textContent = vm[key]
}
function _compileTemplate(vm, container) {
// 获取container里面所有的元素
var allNodes = container.getElementsByTagName('*'),
item = null,
var_reg = /\{\{(.+?)\}\}/g; // 匹配模板插值 {{ xxx }}
for(var i = 0; i < allNodes.length; i++) {
item = allNodes[i]
// 匹配出模板中所使用的变量 {{ a }}
var matched = item.textContent.match(var_reg)
if(matched) {
item.textContent = item.textContent.replace(var_reg, function(node, key) {
dataPool[key.trim()] = item
return vm[key.trim()]
})
}
}
return container
}
function _updateComputedData(vm, key, update) {
var _dep = null
for(var _key in computedData) {
_dep = computedData[_key].dep
// 对比数据 更新
for(var i = 0; i < _dep.length; i++) {
if(_dep[i] === key) {
vm[_key] = computedData[_key].get()
update(_key)
}
}
}
}
function _initComputedData(vm, computed) {
for(var key in computed) {
var descriptor = Object.getOwnPropertyDescriptor(computed, key), // 获取属性描述符 value configurable...
descriptorFn = descriptor.value.get || descriptor.value;
computedData[key] = {}
computedData[key].value = descriptorFn.call(vm) // 计算属性首次计算出来的值
computedData[key].get = descriptorFn.bind(vm) // 计算属性的方法
computedData[key].dep = _collectDep(descriptorFn) // 收集计算属性方法所依赖的数据
}
}
function _collectDep(fn) {
// 将计算属性方法转成字符串 收集其中this.xxx数据
var _collection = fn.toString().match(/this.(.+?)/g)
if(_collection.length) {
for(var i = 0; i < _collection.length; i++) {
// 转成变量名 a b...
_collection[i] = _collection[i].split('.')[1]
}
}
return _collection
}
return Vue
})()
export default Vue