目录
1. 计算属性的原理
关于计算属性的特性:
(1)相当于是一个defineProperty,详情看如下代码:
const vm = new Vue({
data() {
return {
name: 'aaa',
age: 18,
address: {
ad :1
},
arr:[1,2,3,4,[3]]
}
},
computed: {
// 相当于只执行了get方法
fullName() {
return this.name + this.age
},
fullName1: {
get() {
return this.name + this.age
},
set(newVal) {
console.log(newVal);
}
}
},
el: "#app",
})
(2)多次取值,在不改变原数值的情况下只取一次,也就是有脏值检测
(3)当name属性改变时,计算属性也会改变,所以我们就可以知道计算属性也是一个watcher
下图为计算属性watcher与渲染watcher的关系
实现步骤
1. 将watcher维护成一个队列在Dep中有stack栈,两个方法 PushTarget (watcher) 和 popTarget( ).
2.在state.js看用户选项有无computed,有调.initComputed 初始化
3. 在initComputed内循环遍历.拿到每个计算属性userDef
4.声明 defineComputed 方法 在 vm上加上 key.
写到这里会发现页面也会随着数据而更新,但是没有实现计算实的②特征. 原因是依赖的数据收集到了外层的 watcher.我会给所有计算属性加上 watcher
代码:
1. Dep.js
let stack = []
export function pushTarget(watcher) {
stack.push(watcher)
Dep.target = watcher
}
export function popTarget() {
stack.pop()
Dep.target = stack[stack.length - 1];
}
2,3,4步
export function initState(vm) {
if(vm.$options.data) {
initDate(vm)
}
if(vm.$options.computed) {
initComputed(vm)
}
}
function initComputed(vm) {
// 循环获取用户的computed
let computed = vm.$options.computed
for (const key in computed) {
let userDef = computed[key]
defineComputed(vm,key,userDef)
}
}
// 将计算属性重写在vm上,方便取值
function defineComputed(target,key,userDef) {
let getter = typeof userDef === 'function' ? userDef : userDef.get
let setter = typeof userDef === 'function' ? (()=>{}) : userDef.set
Object.defineProperty(target,key,{
get:getter,
set:setter
})
}
4. new watcher的方法去获取自己的 watcher,参数为. vm. fu(get). {lazy:true}. 将watcher 存放起来
5.将 watcher 类加上 this.lazy和 this、drity,并执行如果 lazy 为 flase才执行 this. get的方法
6.因为要实现多次取同样值只执行一次,就需要对 defineProperty的get 进行包装.有函数 createComputer (key).key是 具体的计算属性因为之前把计算属性列表watcher 放到了vm,_C……中,看 dirty 属性如果是脏的就去watcher的evaluate方法取值,
7. get方法的 this.不正确.需要 call 一下
8.这时候我们会发现属性的Dep只收集到了. 计算属性 watcher 无法去更新 渲染页面.那么就需要让计算属性内的 Dep 收集到上层渲染watcher, 也就是求完值后 调用watcher 的 depend 方法后让 this.Deps. depend. 最后.当更新的时候还需要让 dirty 为true
5,7,8步
class Watcher {
constructor(vm,fn,options) {
// 每一个组件都有一个watcher,要添加唯一标识
this.id = id++;
this.getter = fn;
// 标识是否为初渲染
this.renderWatcher = options
// 用来记住dep方便后续的一些方法
this.deps = []
this.depsId = new Set()
this.lazy = options.lazy
this.drity = this.lazy
this.vm = vm
//调用渲染函数进行渲染
this.lazy ? undefined : this.get()
}
addDep(dep) {
let id = dep.id
if(!this.depsId.has(id)) {
this.deps.push(dep)
this.depsId.add(id)
dep.addSub(this)
}
}
// 针对于计算属性的求值
evaluate() {
this.value = this.get()
this.drity = false
}
// 针对于计算属性的dep绑定上层的渲染watcher
depend() {
this.deps.forEach(dep => {
dep.depend()
})
}
get() {
pushTarget(this)
let value = this.getter.call(this.vm)
// 只对在模板当中使用的变量进行依赖收集
popTarget()
return value
}
update() {
if(this.lazy) {
this.drity = true
}else {
queueWatcher(this)
}
}
run() {
this.get()
}
}
4,6
function initComputed(vm) {
// 循环获取用户的computed
let computed = vm.$options.computed
// 计算属性watcher
let watcher = vm._computedWatcher = {}
for (const key in computed) {
let userDef = computed[key]
let getter = typeof userDef === 'function' ? userDef : userDef.get
watcher[key] = new Watcher(vm,getter,{lazy:true})
defineComputed(vm,key,userDef)
}
}
// 将计算属性重写在vm上,方便取值
function defineComputed(target,key,userDef) {
let setter = typeof userDef === 'function' ? (()=>{}) : userDef.set
Object.defineProperty(target,key,{
get:crateComputedGetter(key),
set:setter
})
}
function crateComputedGetter(key) {
return function () {
let watcher = this._computedWatcher[key]
if(watcher.drity) {
watcher.evaluate()
}
// 将属性的dep放到渲染watcher上
if(Dep.target) {
Dep.target.depend()
}
return watcher.value
}
}
2. watch 的原理
特性:
(1)可以监听一个值的变化,当这个值发生变化时,可以执行一个回调,会传两个参数一个是变化的值另一个是变化之前的值
用法:底层都是调的$watch这个方法
const vm = new Vue({
data() {
return {
name: 'aaa',
age: 18,
}
},
watch: {
// 第一种函数写法
name: function (newV,oldV) {
console.log(oldV,newV);
},
// 第二种数组写法
age: [
(newV,oldV)=>{
console.log(oldV,newV);
}
]
},
el: "#app",
})
// 第三种写法
vm.$watch(()=>vm.name,(newV,oldV)=>{
console.log(oldV,newV);
})
setTimeout(()=>{
vm.name = 'bbb'
vm.age = 30
},1000)
实现步骤
1. 在index文件内,Vue的原形上面加上$watch方法,其参数有experOrFn,cb
2. 在state文件内,判断用户选线有无watch属性,有就使用initWatch对其进行初始化
initWatch函数所做的事情:首先拿到用户的watch选项,循环遍历拿到watch里的属性,属性值一共会有三种情况,1.字符串,2.数组,3.函数,判断如果是数组再进行遍历拿出每一个回调。传给createWatcher不是数组直接调createWatcher
createWatcher函数所做的事情:判断拿到的handler是函数还是字符串,函数直接调vm.$watch函数字符串就再到vm拿到相应函数后再调
3. 在$watch里创建new Watcher 因为watch本来就是一个自定义的观察者。然后对于watcher类的代码进行一些修改。
1.
Vue.prototype.$watch = function (exprOrFn,cb) {
new Watcher(this,exprOrFn,{user:true},cb)
}
2.
// 对于计算属性的操作
function initWatch(vm) {
let watch = vm.$options.watch
// 三种情况,字符串,函数,数组
for (const key in watch) {
let handler = watch[key]
if(Array.isArray(watch[key])) {
for (let i = 0;i < handler.length;i++) {
createWatcher(vm,key,handler[i])
}
}else {
createWatcher(vm,key,handler)
}
}
}
function createWatcher(vm,key,handler) {
// 对于handle是一个字符串,那么函数体就是挂载在method上
if(typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(key,handler)
}
3.
class Watcher {
constructor(vm,exprOrFn,options,cb) {
// 每一个组件都有一个watcher,要添加唯一标识
this.id = id++;
if(typeof exprOrFn === 'string') {
this.getter = function () {
return vm[exprOrFn]
}
}else {
this.getter = exprOrFn;
}
// 标识是否为初渲染
this.renderWatcher = options
// 用来记住dep方便后续的一些方法
this.deps = []
this.depsId = new Set()
this.lazy = options.lazy
this.drity = this.lazy
this.cb = cb
this.user = options.user
this.vm = vm
//调用渲染函数进行渲染
this.value = this.lazy ? undefined : this.get()
}
addDep(dep) {
let id = dep.id
if(!this.depsId.has(id)) {
this.deps.push(dep)
this.depsId.add(id)
dep.addSub(this)
}
}
// 针对于计算属性的求值
evaluate() {
this.value = this.get()
this.drity = false
}
// 针对于计算属性的dep绑定上层的渲染watcher
depend() {
this.deps.forEach(dep => {
dep.depend()
})
}
get() {
pushTarget(this)
let value = this.getter.call(this.vm)
// 只对在模板当中使用的变量进行依赖收集
popTarget()
return value
}
update() {
if(this.lazy) {
this.drity = true
}else {
queueWatcher(this)
}
}
run() {
let oldValue = this.value
let newValue = this.get()
if(this.user) {
this.cb.call(this.vm,newValue,oldValue)
}
}
}