Object.defineProperty()
Object JavaScript参考Object.defineProperty() 静态方法 会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。
const object = {};
// 简单设置新属性和值
Object.defineProperty(object, "age", {
value: 42,
writable: false,
});
object.age = 77;
// Throws an error in strict mode
console.log(object.age);
// Expected output: 42
let name = "xiaoming";
// 重写对象的get和set方法 在get和set操作时 可以设置一些操作
Object.defineProperty(object, "name", {
get() {
console.log("get!");
return name;
},
set(value) {
console.log("set!" + value);
name = value;
},
});
console.log(object.name);
// Expected output: get!
// xiaoming
object.name = "daming";
// Expected output: set!daming
由上图可以得知
当用户通过修改object.name = 'xxx'
时 触发了 set 方法 可以知道属性的值被修改了 在使用{{ object.name }}
的时候 触发了 get 方法 可以知道这个值被使用了
响应式原理
vue 采用数据代理、数据挟持、发布订阅模式实现响应式
数据挟持 + 发布订阅(重点)
响应式流程 在 vue 初始化的时候 会将 data 里的数据进行递归遍历 将每一个未被冻结对象里的字段使用 Object.defineProperty
挟持一遍 通过自定义每个属性的 get、set 方法(也可以说 getter、setter) 当视图中使用了对象的某一项数据时 触发了对象的 get方法 我们可以知道这个数据会影响视图 所以要收集他的依赖 当用户修改对象的数据时 触发了 set 方法 我们可以知道数据发生了变化 此时就需要判断这个数据会不会影响视图 也就是去依赖里去找这个数据的依赖 如果找到了就通知视图更新 从而达到数据响应式驱动视图更新
进阶(面试时回答到重写 get、set 方法即可,如果了解可以多说说):我们在 get 方法里增加了 Dep 依赖收集 收集了系统里被使用到的数据的依赖 当用户设置被挟持的对象的属性值时 set 会触发当前对象的依赖收集(Dep)的 notify 方法 通知页面需要更新
然后vue使用新的虚拟dom和旧的虚拟dom进行对比 操作真实dom 对真实dom进行修改 (这里修改的地方不止一处时 会对nodify 通知更新进行一次批处理更新 也就是一段时间内处理一部分更新 而不是每次有通知更新就立即更新一次)
数据代理(了解)
在 vue 里 我们的 vm (vm = new Vue()) 会代理 data 里的数据
比如 通过访问 vm.name(或者是 this.name) 就可以访问到 data 里的 name 当然这并不是响应式的逻辑 只是为了方便取值 将 data 里的数据代理到了 vm 上 如果通过其他方式去取值的话 就是 this.$options.data().name 需要从传入的选项里获取值 是不是很复杂
vue2 源码解读
!!! success TIPS
tip: 只包含了关键代码
!!!
/**
* Define a reactive property on an Object.
*/
// 因为是ts版本的 如果不会ts的话 可以忽略类型
export function defineReactive(
obj: object, // 需要被响应式的对象
key: string, // 对象的键
val?: any, // 需要设置的值 ?的意思是可以传入变量作为初始值 也可以不传入 简单理解为非必填 非必填参数一般都在必填参数后面
customSetter?: Function | null, // 自定义set 方法
shallow?: boolean, // 是否是浅的 就是对象最外层是响应式的 里面嵌套的不去挟持
mock?: boolean, // 暂时不知道是干嘛的 看样子应该是和ssr有关
observeEvenIfShallow = false // 这个变量没有使用到
) {
// const dep = new Dep() 这个是依赖收集 可以先了解 从这可以看到 没个对象都有自己的dep
const property = Object.getOwnPropertyDescriptor(obj, key);
// 先看看这个属性是否是可以设置的 如果对象被冻结了 或者是属性被设置为不可配置 就不可以挟持了 对象冻结方法 Object.freeze()
if (property && property.configurable === false) {
return;
}
// cater for pre-defined getter/setters
const getter = property && property.get;
const setter = property && property.set;
if ((!getter || setter) && (val === NO_INITIAL_VALUE || arguments.length === 2)) {
// 获取到初始值
val = obj[key];
}
// 如果是shadow 表示浅代理 内层嵌套的childOb就直接挂一个__ob__ 属性 表示已经被些吃过了 不需要些吃了 相对于是一个假的挟持
// 否则就表示需要递归挟持
let childOb = shallow ? val && val.__ob__ : observe(val, false, mock);
// observe 其实就是一个可以对数组、嵌套对象进行数据挟持的一个 defineReactive 就是加了些判读、循环、递归
// 然后再调defineReactive去一个一个对象、数组去劫持
// v2 最新源码地址 https://github.com/vuejs/vue
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 可配置
get: function reactiveGetter() {
// 有get 方法就用get取值 没有就用obj[key] 取值
const value = getter ? getter.call(obj) : val;
dep.depend(); // depend()就是依赖收集的方法 这里是简写的 感兴趣的可以自己去翻看源码
// 简单来说就是这里在收集 需要使用到的数据的依赖 然后数据变化了就可以通过这些记录
// 知道哪个数据变化了 是否需要更新页面
// 这里使用了 isRef 应该是为了兼容V3
return isRef(value) && !shallow ? value.value : value;
},
set: function reactiveSetter(newVal) {
// 同理 也是获取值
const value = getter ? getter.call(obj) : val;
// 如果没变 直接就不修改数据了
if (!hasChanged(value, newVal)) {
return;
}
if (__DEV__ && customSetter) {
customSetter();
}
// 有setter就直接设置值
if (setter) {
setter.call(obj, newVal);
} else if (getter) {
// #7981: for accessor properties without setter
return;
} else if (!shallow && isRef(value) && !isRef(newVal)) {
// 兼容V3吧
value.value = newVal;
return;
} else {
val = newVal;
}
// 这里可能会出现 用户添加了一些新的属性 这些属性没在data里 是后续通过方法调用加的 可能没被代理到 要进行重新代理
childOb = shallow ? newVal && newVal.__ob__ : observe(newVal, false, mock);
dep.notify(); // notify()就是通知数据更新了 需要更新页面
},
});
return dep; // 返回收集的依赖
}