vue 的动态响应指的是 当一个数据改变,与之关联的数据也随之改变。
如 b = a+10 当a 变化时候 b也随之变化。web代码层面实现是靠Object.defineProperty的get和set
尤雨溪教你写vue 高级vue教程 源码分析 中文字幕翻译完毕_哔哩哔哩_bilibili
1、简单介绍Object.defineProperty
Object.defineProperty(obj,prop, descriptor)
# 对象,属性,描述符
描述符有两种 数据描述符 和存储描述符 。数据描述符描述的对象的属性 该属性是否可写是否可枚举等。存储描述符是get set 描述当存储时行为。
注意不能两种不能混用
这两种都描述符都是对象,共享下列键值
configurable
键值为 true
时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false
。
enumerable
键值为 true
时,该属性才会出现在对象的枚举属性中。默认为 false
。
数据描述符还具有以下可选键值:
value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
默认为 undefined。
writable
键值为 true
时,属性的值,也就是上面的 value
,才能被赋值运算符 (en-US)改变。
默认为 false
。
存取描述符还具有以下可选键值:
get
属性的 getter 函数,如果没有 getter,则为 undefined
。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this
对象(由于继承关系,这里的this
并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。默认为 undefined。
set
属性的 setter 函数,如果没有 setter,则为 undefined
。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this
对象。
默认为 undefined。
在 get
和 set
方法中,this
指向某个被访问和修改属性的对象。
如果一个描述符不具有 value
、writable
、get
和 set
中的任意一个键,那么它将被认为是一个数据描述符。如果一个描述符同时拥有 value
或 writable
和 get
或 set
键,则会产生一个异常。
2、利用set和get,对数据读取改变做出反应
set 接受属性的新值,
const obj = { foo : 123 }
function isObject(obj){
return typeof obj === 'object' && !Array.isArray(obj)
&& obj !== null
&& obj !== undefined
}
function convert(obj) {
if (!isObject(obj)){
throw new TypeError
}
Object.keys(obj).forEach((key)=>{
inner_value = obj[key]
Object.defineProperty(obj,key,{
get: function(){
console.log(`getting key "${key}": ${inner_value}`)
return inner_value
},
set: function(new_value){
console.log(`setting key "${key}" to: ${old_value}`)
const isChanged = inner_value !== new_value
if (isChanged){
inner_value = new_value
}
}
})
})
}
convert(obj)
obj.foo
obj.foo = 234
obj.foo
结果
getting key "foo": 123
'setting key "foo" to: 234'
getting key "foo": 234
3、收集依赖
依赖是指谁使用了这个数据,xx依赖于这个数据,数据改变,通知谁
创建一个Dep类,构造函数声明一个set集合用来存依赖,该类有一个depend函数set.add用来收集依赖,notiyf用来通知所有依赖更新。
atuorun()接受一个update函数参数,wrappedUpdate()里调用这个函数,activeUpdate告诉Dep对象谁执行了dep.depend, notify通知收集的依赖更新
// 如何添加依赖 用一个变量记录谁执行dep.dend()
class Dep{
constructor(){
this.subcribers = new Set()
}
// register curren active update as a subcriber
depend(){
if(activeUpdate){
this.subcribers.add(activeUpdate)
}
}
// run all subcribers functions
notify(){
this.subcribers.forEach(subcriber=> subcriber())
}
}
let activeUpdate
// autorun 执行update函数,activeUpdate变量记录是哪个函数执行dep.depdend
// 注意javascript 是单线程函数,所有activeUpdate不会出现多线程操作变量改变
function autorun(update){
function wrappedUpdate(){
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
const dep = new Dep()
autorun(()=>{
dep.depend()
console.log("update")
})
// 这里为什么会触发console.log wrappendUpdate保存了对update引用。update不会被销毁。闭包
dep.notify()
4、简化版observer
综合23,只不过将收集依赖放在属性get时候执行,当set的时候会通知依赖内的函数更新
把前面的两个综合起来。监听一个对象的set 和get
// get对象属性的时候添加依赖,set 的时候通知subcribers update
function isObject(obj){
return typeof obj === 'object' && !Array.isArray(obj)
&& obj !== null
&& obj !== undefined
}
function observer(obj) {
if (!isObject(obj)){
throw new TypeError
}
let dep = new Dep()
Object.keys(obj).forEach((key)=>{
inner_value = obj[key]
Object.defineProperty(obj,key,{
get(){
dep.depend()
console.log(`${key} is: ${inner_value}`)
return inner_value
},
set(new_value){
console.log(new_value)
const isChanged = inner_value !== new_value
if (isChanged){
inner_value = new_value
dep.notify()
}
}
})
})
}
class Dep{
constructor(){
this.subcribers = new Set()
}
// register curren active update as a subcriber
depend(){
if(activeUpdate){
this.subcribers.add(activeUpdate)
}
}
// run all subcribers functions
notify(){
this.subcribers.forEach(subcriber=> subcriber())
}
}
let activeUpdate
function autorun(update){
function wrappedUpdate(){
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
const state = {
count: 0
}
observer(state)
// autorunn ->wrappedUpdate->get->dep
autorun(()=>{
console.log(state.count)
})
state.count++
5、总结
动态响应是通过Object.defineProperty的set和get,在get中收集使用这个属性的函数,在set里通知函数更新。