Object.defineProperty
- Object.defineProperty 添加属性
let p = {
name: 'slc',
age: 16
}
// 给p添加一个新的属性sex
Object.defineProperty(p, 'sex', {
value: '男宝宝',
configurable: true, // 控制属性是否能被删除,默认false
writable: true, // 控制属性是否能被修改,默认false
enumerable: true // 控制添加的值是否可以被枚举,默认false
})
console.log(p);// {name: 'slc', age: 16, sec: '男宝宝'}
for (const key in p) {
console.log(key); //name, age
}
- Object.defineProperty getter和setter
let p = {
name: 'slc',
age: 16
}
let x = 15
Object.defineProperty(p, 'age', {
// 当age被读取,get函数(getter)就被调用,并且返回值就是age的值
get() {
console.log('我被读取');
return x
},
// 当age被修改,set函数(setter)就被调用,并且能接受到修改的值
set(val) {
console.log('我被读取');
return x
},
})
- 什么是数据代理 通过一个对象代理对另一个对象属性的操作(读和写)
- vue中的数据代理
我们看到vue实例对象身上有这两个属性,但是一开始的状态是…(三个点),这三个点其实就表示的是该数据是通过Object.defineProperty()来创建的;当然在下面也可以看到name和address这两个数据的getter和setter。
- 在data中定义name 和 address的时候有如下步骤:
在data中定义name 和 address的时候,vue实例对象在其自身添加了一个_data属性(可以理解为就是data),用来存储data中定义的name 和 address; 此时页面只能是通过_data.name的形式才能访问,但是上述代码并不是这样的,是直接访问vue实例对象中的name和address,有意思的来了; 这时候vue会在该vue实例对象上开始加东西(会将data中定义的数据都加到vue实例对象中),首先会先加一个name属性,name的值则是通过getter来读取_data中name的值;如果修改vue实例对象中name的值,则通过setter映射到_data中的name来进行修改; 同理address也是如此,这种通过getter 和 setter将_data中的数据放到vue实例对象中即为数据代理。(目的就是想让开发者编码相对比较方便一些)
原文链接:blog.csdn.net/weixin_4511…
VUE2.x中的数据响应式原理
- 定义对象,导入入口函数并调用
// 定义一个被赋予响应式的对象
let a = {
b: 'NIHAO',
c: [1, 2],
d: {
e: '',
f: {
g: undefined
}
},
h: 0
}
// 入口
observe(a)
- observe入口函数
import Observer from "./Observer"; // Observer类
export default function observe(data) {
// || data === null 后面的中断,是我自己加的,因为当值为null的时候,typeof也是object,会导致报错,先放着看看
if (typeof data != "object" || data === null) return
let ob
if (typeof data.__ob__ !== 'undefined') {
// 没搞懂的地方
ob = data.__ob__
} else {
ob = new Observer(data)
}
return observe
}
- Observer类
import defineReactive from "./defineReactive";
import { def } from "./utils";
import { arrayMethods } from "./array";
export default class Observer {
// 这个类的作用就是把一个普通的对象,变成一个被object.defineProperty 监听的对象
constructor(obj) {
def(obj, '__ob__', this, false) // 工具函数 第四个参数,不可枚举, 将这个对象,添加一个不可枚举的__ob__,值就是当前这个Observer类
this.obj = obj
if (Array.isArray(obj)) {
// 判断是否是数组,如果是,进行方法的重写
obj.__proto__ = arrayMethods
} else {
this.walk()
}
}
walk() {
// 将传进来的对象,每一个属性也进行object.defineProperty
// 例如 {a:1,b:2}, 对象中的a,b值也是需要响应式监听的
Object.keys(this.obj).forEach(item => defineReactive(this.obj, item, this.obj[item]))
}
}
defineReactive方法, 主要是给每一个传进来的对象,赋予被getter和setter的能力,并且在被getter时,收集依赖,并且在setter时通知这些依赖(调用回调函数,在vue中可能是去diff,渲染dom)
import Dep from "./Dep";
import observe from "./observe";
export default function defineReactive(data, key, val = data[key]) {
// 在这里是继续递归对象的val,并不是递归自己,在函数层面,,也是多个类组合进行递归
// 顺序是observe =》 Observer =》 defineReactive => 回到observe
observe(val)
let dep = new Dep() // 创建一个实例,供下面depend使用
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
// 什么是依赖:也就是被watch的地方
// 我们在getter中,组件访问这些数据的时候,我们帮他收集起来(要加以判断,并不是无脑收集)(在getter中收集依赖)
// 当这些数据被改变setter,我们就去通知这些依赖
// 把依赖收集的代码封装成一个Dep类,它专门用来管理依赖,每个observer的实例成员中都有一个Dep的实例;
// watcher是一个中介,数据发生变化时通过watcher中转,通知组件
get() {
// 在getter中收集依赖(也就是收集watch的实例,也就是收集被观察的对象)对应的就是Dep类中的depend方法
// 在这个方法里可以看到,并不是每次getter都会被收集,而是在vue中有watch的才收集
dep.depend()
console.log(`访问${key}属性`);
return val
},
set(newVal) {
if (newVal === val) return
// console.log(`${key}属性被修改为${newVal}`);
dep.notify() // 在setter中通知依赖
val = newVal
observe(val)
}
})
}
Dep类,主要定义一个subs数组,用来存放依赖,也就是watch的实例,notify就是通知watch,被你watch的数据改变了
export default class Dep {
constructor() {
// 先定义一个subs,用来存放watch的实例(依赖)(被watch的数据)
// 因为我们在vue中,可能多个数据被watch,这个就是用来放watch的
this.subs = []
}
depend() {
// console.log('无条件搜集依赖');
// Dep.target 是watch实例,也只有数据被watch的项,才会被收集
if (Dep.target) {
console.log('有条件搜集依赖');
this.subs.push(Dep.target)
}
// console.log(this.subs);
}
notify() {
this.subs.forEach((s) => {
// s 是每一个watch实例,update是实例中的方法
s.update()
})
}
}
Watch类 只有被以下方式调用,才能被watch监听。响应式基础框架和这个无关,只是为了更好的使用vue中watch方法
demo中的调用方式是
new Watch(a, 'b', (val, oldVal) => {
// 这边就是观察数据变化后的回调,在vue中可能是diff dom
console.log(`a.b 从 ${oldVal}(oldVal) 变成了 ${val}(newVal)`)
})
vue中调用
watch:{
a(newVal,oldVal){...}
}
import Dep from "./Dep";
export default class Watch {
constructor(data, expression, cb) {
// data: 数据(实例)对象,如obj
// expression:表达式,如b.c,根据data和expression就可以获取watcher依赖的数据
// cb:依赖变化时触发的回调
this.data = data
this.expression = expression
this.cb = cb
// 初始化watcher实例时订阅数据
this.value = this.get()
}
get() {
Dep.target = this
const value = parsePath(this.data, this.expression)
// console.log(value, '这个是Watch中的get方法的返回值');
return value
}
update() {
// 当收到数据变化的消息时执行该方法,从而调用cb
const oldValue = this.value
// setTimeout(() => {
this.value = parsePath(this.data, this.expression)
this.cb.call(this.data, this.value, oldValue)
// }, 1000);
// this.value, oldValue 对应vue watch中参数的两个值
}
}
function parsePath(obj, expression) {
const segments = expression.split('.')
for (let key of segments) {
if (!obj) return
obj = obj[key]
}
return obj
}
工具函数以及数组重写
// 数组重写
import { def } from "./utils";
const methodsNeedChange = [
'push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'
]
const arrayPrototype = Array.prototype
export const arrayMethods = Object.create(arrayPrototype)
methodsNeedChange.forEach(arrayMethodsName => {
const original = arrayPrototype[arrayMethodsName]
def(arrayMethods, arrayMethodsName, function(params) {
console.log('方法' + arrayMethodsName + '被调用');
return '数组'
}, false)
})
// 工具
export const def = function(obj, key, value, enumerable) {
Object.defineProperty(obj, key, {
enumerable,
configurable: true,
writable: true,
value
})
}