当前位置:网站首页>什么是响应式对象?响应式对象的创建过程?
什么是响应式对象?响应式对象的创建过程?
2022-07-06 16:32:00 【Feather_74】
主题:什么是响应式对象?响应式对象的创建过程?
什么是响应式对象?
Vue.js 实现响应式的核心是利用了 ES5 的 Object.defineProperty。因为 IE8 及以下浏览器没有Object.defineProperty方法,这也是为什么 Vue.js 不能兼容 IE8 及以下浏览器的原因。
Object.defineProperty
Object.defineProperty(obj, prop, descriptor)
var o = {
};
Object.defineProperty(o, "a", {
value : 37,
writable : true,
enumerable : true,
configurable : true
});
// o有了属性a,a的值为37
var bValue = 38;
Object.defineProperty(o, "b", {
get() {
return bValue; },
set(newValue) {
bValue = newValue; },
enumerable : true,
configurable : true
});
// o有了属性b,b的值为bValue。现在,除非重新定义o.b,o.b的值总是与bValue相同
Object.defineProperty(o, 'c', {
value: 39,
writable: true,
enumerable: false,
configurable: true
})
// 当enumerable为false时,Object.keys(ob)不会显示'c'
obj是要在其上定义属性的对象。prop是要定义或修改的属性的名称。descriptor是将被定义或修改的属性描述符。一旦对象拥有了getter和setter属性,我们可以简单地把这个对象称为响应式对象。get是一个给属性提供的getter方法,当我们访问了该属性的时候会触发getter方法;set是一个给属性提供的setter方法,当我们对该属性做修改的时候会触发setter方法。
那么 Vue.js 把哪些对象变成了响应式对象了呢,可以从源码层面分析。
响应式对象的创建过程
先讲原始的数据映射到 DOM 中, props 和 data 初始化的时候就可以定义响应式对象。 props 是通过调用defineReactive()方法把每个props的key变成响应式。 data 是通过observe()监测数据的变化,Observer给对象的属性添加getter和setter。
src/core/instance/init.js
初始化 Vue 时会调用_init方法,init方法中有initState()方法。
src/core/instance/state.js
(1)initState
该方法主要是初始化props、methods、data、computed和 wathcer等属性做了初始化操作。
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {
}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
重点分析initProps()和initData()
(2)initProps
调用defineReactive()方法把每个props的key变成响应式
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {
}
const props = vm._props = {
}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
const hyphenatedKey = hyphenate(key)
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${
hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
defineReactive(props, key, value, () => {
if (!isRoot && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${
key}"`,
vm
)
}
})
} else {
defineReactive(props, key, value)
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
(3)initData
遍历所有的Object.keys(data),在methods或props中定义了就会报警告。把_data的东西代理到vm实例上。observe()方法就是观测data。(深入响应式原理的代码基本上都在src/core/observer上)
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {
}
if (!isPlainObject(data)) {
data = {
}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${
key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${
key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
1)observe
功能:observe的功能就是用来监测数据的变化。
observe方法的作用就是给非 VNode 的对象类型数据添加一个Observer,如果已经添加过则直接返回,否则在满足一定条件下去实例化一个 Observer对象实例。
代码解析:observe方法接收两个参数,value是任意类型,传入的是data,asRootData传入的是true。
判断value是否是Object类型,value是否是VNode实例。
instanceof的作用:AinstanceofB ,判断 A 是否是 B 的实例
然后判断value有没有__ob__属性并且value.__ob__是否是Observer实例。满足条件直接拿到ob并return。否则进行下一步判断。判断shouldObserve。
shouldObserve是全局定义的一个标志,用来决定什么时候执行new Observer。toggleObserving方法可以把shouldObserve值进行改变,该方法在initProps方法中通过判断isRoot对shouldObserve进行修改。一旦shouldObserve是false,就没有办法把value变成Observer对象实例。
export let shouldObserve: boolean = true
export function toggleObserving (value: boolean) {
shouldObserve = value
}
同时还要判断value不是ServerRendering,是数组和对象并且对象是具有可扩展属性的,还要再判断value不是 Vue。才会把value变成Observer对象实例。
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
2)Observer
功能:Observer是一个类,它的作用是给对象的属性添加getter和setter,用于依赖收集和派发更新
代码解析:Observer是个 class ,可以理解为观察者。属性有value、dep、vmCount,构造函数会保留value,实例化dep。
def方法对Object.defineProperty进行封装,是给value添加__ob__属性,该属性的值指向当前实例。为的就是执行observe()的时候,第一次定义以后下一次才会对同样的对象直接return ob。
enumerable?: boolean:作为 Typescript 接口属性数量不确定时的定义方法。要么参数不存在,要么必须符合参数的类型定义。
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
判断value是否是数组,observeArray()方法就是遍历value,然后递归观察。如果不是数组是对象则调用walk()。walk()遍历对象上的所有属性,执行defineReactive()方法。如果直接把__ob__赋值给value,__ob__也会执行defineReactive,是完全没有必要的,因为不会去修改__ob__。为什么__ob__不会执行defineReactive?因为def只传了三个参数,enumerable是 undefined ,所以!!enumerable是false,所以遍历的时候不会被执行到(原型的也是不可被枚举的)。
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/** * Walk through all properties and convert them into * getter/setters. This method should only be called when * value type is Object. */
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/** * Observe a list of Array items. */
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
3)defineReactive
功能:defineReactive的功能就是定义一个响应式对象,给对象动态添加getter和setter。
代码解析:
接受五个参数:对象、对象属性值、初始值、两个可选的。拿到对象属性定义property,拿到属性原有的getter和setter,当某个属性值也是对象时,递归进行observe。这样就保证了无论obj的结构多复杂,它的所有子属性也能变成响应式的对象,这样我们访问或修改obj中一个嵌套较深的属性,也能触发getter和setter。
export function defineReactive (
obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
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) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}

边栏推荐
- PostgreSQL高可用之repmgr(1主2从+1witness)+Pgpool-II实现主从切换+读写分离
- Asset security issues or constraints on the development of the encryption industry, risk control + compliance has become the key to breaking the platform
- How to implement Lua entry of API gateway
- Please help xampp to do sqlilab is a black
- How does win11 restore the traditional right-click menu? Win11 right click to change back to traditional mode
- AI金榜题名时,MLPerf榜单的份量究竟有多重?
- 【2022全网最细】接口测试一般怎么测?接口测试的流程和步骤
- Tourism Management System Based on jsp+servlet+mysql framework [source code + database + report]
- Example code of MySQL split string as query condition
- I've been laid off, and I'll lose money for everything. The days when I once made a monthly salary of 20000 are not coming back
猜你喜欢

Daily question brushing record (XV)

The "white paper on the panorama of the digital economy" has been released with great emphasis on the digitalization of insurance

【自动化测试框架】关于unittest你需要知道的事

After 3 years of testing bytecan software, I was ruthlessly dismissed in February, trying to wake up my brother who was paddling

Talking about the current malpractice and future development

求帮助xampp做sqlilab是一片黑

17、 MySQL - high availability + read / write separation + gtid + semi synchronous master-slave replication cluster

Per capita Swiss number series, Swiss number 4 generation JS reverse analysis

Do you still have to rely on Simba to shout for a new business that is Kwai?

Master binary tree in one article
随机推荐
Matplotlib draws a histogram and adds values to the graph
氢创未来 产业加速 | 2022氢能专精特新创业大赛报名通道开启!
为什么完全背包要用顺序遍历?简要解释一下
每年 2000 亿投资进入芯片领域,「中国芯」创投正蓬勃
JDBC programming of MySQL database
若依请求url中带有jsessionid的解决办法
量子时代计算机怎么保证数据安全?美国公布四项备选加密算法
Résumé des connaissances de gradle
【精品】pinia 基于插件pinia-plugin-persist的 持久化
士大夫哈哈哈
Example code of MySQL split string as query condition
Server SMP, NUMA, MPP system learning notes.
What should I do if the USB flash disk data is formatted and how can I recover the formatted USB flash disk data?
求帮助xampp做sqlilab是一片黑
App general function test cases
DAY TWO
The tutorial of computer reinstallation win10 system is simple and easy to understand. It can be reinstalled directly without U disk
Basic chart interpretation of "Oriental selection" hot out of circle data
Close unregistering application XXX with Eureka with status down after Eureka client starts
Implementation steps of mysql start log in docker