当前位置:网站首页>什么是响应式对象?响应式对象的创建过程?
什么是响应式对象?响应式对象的创建过程?
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()
}
})
}

边栏推荐
- How to answer the dualistic opposition of Zhihu
- [212] what are three methods for PHP to send post requests
- The best sister won the big factory offer of 8 test posts at one go, which made me very proud
- 零代码高回报,如何用40套模板,能满足工作中95%的报表需求
- The largest single investment in the history of Dachen was IPO today
- [boutique] Pinia Persistence Based on the plug-in Pinia plugin persist
- Résumé des connaissances de gradle
- [OFDM communication] OFDM system signal detection based on deep learning with matlab code
- MATLIB reads data from excel table and draws function image
- openresty ngx_lua子请求
猜你喜欢

MATLIB从excel表中读取数据并画出函数图像

Gold three silver four, don't change jobs

资产安全问题或制约加密行业发展 风控+合规成为平台破局关键
Implementation steps of mysql start log in docker

How to find out if the U disk file of the computer reinstallation system is hidden

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

Résumé des connaissances de gradle

Rider离线使用Nuget包的方法

人均瑞数系列,瑞数 4 代 JS 逆向分析

Newsletter L Huobi ventures is in-depth contact with genesis public chain
随机推荐
1000 words selected - interface test basis
在docker中快速使用各个版本的PostgreSQL数据库
Every year, 200 billion yuan is invested in the chip field, and "China chip" venture capital is booming
求帮助xampp做sqlilab是一片黑
Oracle对表进行的常用修改命令
How much does the mlperf list weigh when AI is named?
STM32 enters and wakes up the stop mode through the serial port
Experiment 5: common automation libraries
Talking about the current malpractice and future development
Hydrogen future industry accelerates | the registration channel of 2022 hydrogen energy specialty special new entrepreneurship competition is opened!
Résumé des connaissances de gradle
Interface joint debugging test script optimization v4.0
app通用功能測試用例
DAY ONE
Stop saying that microservices can solve all problems
【2022全网最细】接口测试一般怎么测?接口测试的流程和步骤
Design of short chain
leetcode:236. 二叉树的最近公共祖先
Asset security issues or constraints on the development of the encryption industry, risk control + compliance has become the key to breaking the platform
Zero code and high return. How to use 40 sets of templates to meet 95% of the reporting needs in the work