Object.defineProperty()
Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
var obj = {}
Object.defineProperty(obj, 'a', {
get() {
console.log('访问obj的a属性')
},
set() {
console.log('改变obj的a属性')
}
})
console.log(obj.a)
obj.a = 10
get:
getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象。该函数的返回值会被用作属性的值。默认为 undefined。
set:
属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为 undefined。
//设置 temp 变量来完成数据响应式
var temp
Object.defineProperty(obj, 'a', {
get() {
console.log('访问obj的a属性')
return temp
},
set(newValue) {
console.log('改变obj的a属性', newValue)
temp = newValue
}
})
defineReactive()
为了避免声明多余变量的问题
function defineReactive(data, key, val) {
if(arguments.length == 2){ val = data[key] }
Object.defineProperty(data, key, {
get() {
console.log(`访问${data}的${key}属性`)
return val
},
set(newValue) {
console.log('改变${data}的${key}属性', newValue)
if(val === newValue) return;
val = newValue
}
})
}
defineReactive(obj, 'a', 10)
递归监测对象的全部属性
当监测的对象有多层时,上面的方法就监测不到了
obj: { a: { m: { n: 1 } } }
这时候我们就需要一个 Observer 类将每一个正常的Object转换为每个层级的属性都是响应式的
function observe(value){
if(typeof value !== 'Object') return
var ob
if(typeof value.__ob__ !== 'undefined'){
ob = value.__ob__
} else {
ob = new Observer(value)
}
return ob
}
observe(obj)
const def = function(obj, key, value, enuertable){
Object.defineProperty(obj, key, {
value,
enumerable,
writable: true,
configurable: true
})
}
class Observer(){
constructor(value){
//给实例添加__ob__属性
def(value,'__ob__', this, false)
this.walk(value)
}
walk(value){
for(let k in value){
defineReactive(value, k)
}
}
}
此时我们对defineReactive进行改造,使有多层对象的obj也能被监测到
function defineReactive(data, key, val) {
if(arguments.length == 2){ val = data[key] }
let childOb = observe(val)
Object.defineProperty(data, key, {
get() {
return val
},
set(newValue) {
if(val === newValue) return;
val = newValue
childOb = observe(newValue)
}
})
}
数组的响应式处理
在上面写的响应式中,是不能对数据进行响应式的,所以Vue改写了7个数组方法
Vue中的做法: 以Array.prototype为原型,创建了一个新对象,将需要监听的数组指向这个新对象
const ayrrayPrototype = Array.prototype
export const arrayMethods = Object.create(arrayPrototype)
const methodsNeedChange = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ]
methodsNeedChange.forEach(methodName => {
//备份原来的方法
const original = arrayPrototype[methodName]
def(arrayMethods,methodName,function(){
const args = [...arguments]
const ob = this.__ob__
let inserted = []
switch(methodName){
case 'push':
case 'unshft':
inserted = args
break;
case 'splice':
inserted = args.slice(2)
break;
}
if(inserted){
//让新项也变为响应式
ob.observeArray(inserted)
}
return original.apply(this, arguments)
},false)
})
此时也要完善Observer类中对数组的检查
class Observer(){
constructor(value){
//给实例添加__ob__属性
def(value,'__ob__', this, false)
if(Array.isArray(value)) {
Object.setPrototypeOf(value, arrayMethods)
//让数组变成observe
this.observeArray(value)
} else {
this.walk(value)
}
}
walk(value){
for(let k in value){
defineReactive(value, k)
}
}
observeArray(arr) {
for(let i = 0, l = arr.length; i < l; i++) {
observe(arr[i])
}
}
}
依赖收集
需要用到数据的地方,称为依赖
Vue1.x中,细粒度依赖,用到数据的DOM都是依赖
Vue2.x中,中等粒度依赖,用到数据的组件都是依赖
在getter中收集依赖,在setter中触发依赖
Dep类和Watcher类
把依赖收集的代码封装成一个Dep类,它专门用来管理依赖,每个Observer的实例,成员中都有一个Dep的实例
Watcher是一个中介,数据发生变化时通过Watcher中转,通知组件
- 依赖就是Watcher。只有Watcher触发的getter才会收集依赖,哪个Watcher触发了getter,就把哪个Watcher收集到Dep中
- Dep使用发布订阅模式,当数据发生变化时,会循环依赖列表,把所有的Watcher都通知一遍
- 代码实现的巧妙之处:Watcher把自己设置到全局的一个指定位置,然后读取数据,因为读取了数据,所以会触发这个数据的getter。在getter中就能得到当前正在读取数据的Watcher,并把这个Watcher收集到Dep中
Dep类
var uid = 0
class Dep{
constructor(){
this.id = uid++
this.subs = []
}
//添加订阅
addSub(sub){
this.subs.push(sub)
}
//添加依赖
depend(){
if(Dep.target) {
this.addSub(Dep.target)
}
}
//通知更新
notify(){
const subs = this.subs.slice()
for(let i = 0, l = subs.length; i < 1; i++){
subs[i].update()
}
}
}
Watcher类
var uid = 0
class Watcher{
constructor(target, expression, callback){
this.id = uid++
this.target = target
this.getter = parsePath(expression)
this.callback = callback
this.value = this.get()
}
update(){
}
get(){
Dep.target = this
const obj = this.target
try{
value = this.getter(obj)
} finally {
Dep.target = null
}
return value
}
run(){
this.getAndInvoke(this.callback)
}
getAndInvoke(cb){
const value = this.get()
if(value !== this.value || typeof value == 'object'){
const oldValue = this.value
this.value = value
cb.callback(this.target, value , oldValue)
}
}
}
function parsePath(str){
var segments = str.split('.')
return (obj) => {
for(let i = 0; i < segments.length; i++){
if(!obj) return
obj = obj[segments[i]]
}
return obj
}
}
在Observer类和defineReactive中实例化,以及数组和set中使用发布订阅模式(notify()),get中收集依赖
class Observer(){
constructor(value){
this.dep = new Dep()
def(value,'__ob__', this, false)
if(Array.isArray(value)) {
Object.setPrototypeOf(value, arrayMethods)
//让数组变成observe
this.observeArray(value)
} else {
this.walk(value)
}
}
//...
}
function defineReactive(data, key, val) {
this.dep = new Dep()
if(arguments.length == 2){ val = data[key] }
let childOb = observe(val)
Object.defineProperty(data, key, {
get() {
if(Dep.target){
dep.depend()
if(childOb){
childOb.dep.depend()
}
}
return val
},
set(newValue) {
if(val === newValue) return;
val = newValue
childOb = observe(newValue)
dep.notify()
}
})
}
methodsNeedChange.forEach(methodName => {
//备份原来的方法
const original = arrayPrototype[methodName]
def(arrayMethods,methodName,function(){
const args = [...arguments]
const ob = this.__ob__
let inserted = []
switch(methodName){
case 'push':
case 'unshft':
inserted = args
break;
case 'splice':
inserted = args.slice(2)
break;
}
if(inserted){
//让新项也变为响应式
ob.observeArray(inserted)
}
ob.dep.notify()
return original.apply(this, arguments)
},false)
})