IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vue源码学习记录-Vue响应式原理 -> 正文阅读

[JavaScript知识库]Vue源码学习记录-Vue响应式原理

????????本文根据?李永宁 的个人主页 (juejin.cn) 的教程进行学习并总结。?

?? ??? 响应式原理的入口为 src/core/instance/state.js 中的 initState() 方法。首先从实例上获取到配置项,然后依次开始处理。
????????initProps? ,从配置项上获取 prop 配置后遍历其配置,缓存 prop 配置的每个 key 值,达到性能优化的目的。所以该方法做了两件事:对 props 配置做响应式处理;代理 props 配置上的 key 到 vue 实例上,支持通过 this.propsKey 的方式访问。
initProps 方法代码:
function initProps (vm: Component, propsOptions: Object) {
?? ?//从配置项获取prop配置后遍历配置
?? ?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) {
?? ??? ?// 缓存 key
?? ??? ?keys.push(key)
?? ??? ?// 获取 prop[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 {
?? ??? ??? ??? ?//对prop数据做响应式处理
?? ??? ??? ??? ?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.
?? ??? ??? ?// 代理 this.propsKey,将propsKey的key代理到实例上
?? ??? ??? ?if (!(key in vm)) {
?? ??? ??? ??? ?// 将 key 代理到 vm 实例上
? ???? ??? ??? ?proxy(vm, `_props`, key)
?? ??? ??? ?}
?? ??? ?}
?? ??? ?toggleObserving(true)
?? ?}
?? ?? ? 关于 proxy 代理:该方法将 key 代理到 vue 实例上,同时为 this._props.key 定义 getter 和 setter 方法,后通过 Object.defineProperty 方法实现拦截对 this.key 的访问。
proxy 方法代码:
//将 key 代理到 vue 实例上
export function proxy (target: Object, sourceKey: string, key: string) {
    sharedPropertyDefinition.get = function proxyGetter () {
        //this._props.key
        return this[sourceKey][key]
    }
    sharedPropertyDefinition.set = function proxySetter (val) {
        this[sourceKey][key] = val
    }
    //拦截 对 this.key 的访问
    Object.defineProperty(target, key, sharedPropertyDefinition)
}
????????initMethods?先做判重处理,methods 对象中定义的属性不能可 props 对象中的属性重复, props 优先级大于 methods 优先级;后将 methods 中的配置赋值到 vue 实例上,支持通过 this.methodKey 的方式访问。
initMethods 方法代码:
function initMethods (vm: Component, methods: Object) {
    const props = vm.$options.props
    //判重处理, methods 中的 key 不能与 props 中的 key 重复
    for (const key in methods) {
        if (process.env.NODE_ENV !== 'production') {
        // 检验 methods[key], 其必须为一个函数
            if (typeof methods[key] !== 'function') {
                warn(
                    `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
                    `Did you reference the function correctly?`,
                    vm
                )
            }
            if (props && hasOwn(props, key)) {
                warn(
                    `Method "${key}" has already been defined as a prop.`,
                    vm
                )
            }
        if ((key in vm) && isReserved(key)) {
                warn(
                    `Method "${key}" conflicts with an existing Vue instance method. ` +
                    `Avoid defining component methods that start with _ or $.`
                )
        }
    }
        //将 methods 中的所有方法赋值到vue实例上,支持通过 this.methodKey 的方法访问定义的方法
        vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
    }
}

?????????initData?首先做处理,保证后续处理时 data 是一个对象;然后进行判重处理,使 data 中的属性不能与 props 以及 methods 中的属性重复;之后将 data 中的属性代理到 vue 实例上(支持通过 this,methodKey 的方式访问),对 data 做响应式处理。

initData 方法代码:
function initData (vm: Component) {
    let data = vm.$options.data
    // 保证后续处理的 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]
        //判重处理,data 中的属性不能和 props 以及 methods 中的属性重复
        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)) {
        //代理 data 中的属性到 vue 实例上,支持通过 this.key 的方式访问。
        proxy(vm, `_data`, key)
        }
    }
// observe data
//响应式处理
observe(data, true /* asRootData */)
}

export function getData (data: Function, vm: Component): any {
    // #7573 disable dep collection when invoking data getters
    pushTarget()
    try {
        return data.call(vm, vm)
    } catch (e) {
        handleError(e, vm, `data()`)
        return {}
    } finally {
        popTarget()
    }
}

????????initComputed?compoted 是通过 watcher 来实现的,对每个 computedKey 实例化一个 watcher (默认懒执行),将 computedKey 代理到 Vue 实例上,使其支持通过 this.computedKey 的方式访问computed.key。!!! dirty 为 computed 缓存的实现原理。

initComputed 方法代码:
function initComputed (vm: Component, computed: Object) {
?? ?// $flow-disable-line
?? ?const watchers = vm._computedWatchers = Object.create(null)
?? ?// computed properties are just getters during SSR
?? ?// 是否为服务端渲染
?? ?const isSSR = isServerRendering()

?? ?// {
?? ?// ?? ? computed: {
?? ?// ?? ? ?? ? msg: function () {
?? ?//
?? ?// ?? ???? ?}
?? ?// ?? ? }
?? ?// }
?? ?//遍历 computed 对象
?? ?for (const key in computed) {
?? ??? ?//获取key对应的值
?? ??? ?const userDef = computed[key]
?? ??? ?const getter = typeof userDef === 'function' ? userDef : userDef.get
?? ??? ?if (process.env.NODE_ENV !== 'production' && getter == null) {
?? ??? ??? ?warn(
?? ??? ??? ??? ?`Getter is missing for computed property "${key}".`,
?? ??? ??? ??? ?vm
?? ??? ??? ?)
?? ??? ?}

?? ??? ?if (!isSSR) {
?? ??? ??? ?// create internal watcher for the computed property.
?? ??? ??? ?//实例化一个 watcher,computed 就是通过 watcher 实现的
?? ??? ??? ?watchers[key] = new Watcher(
?? ??? ??? ?// vue实例
?? ??? ??? ?vm,
?? ??? ??? ?// getter函数
?? ??? ??? ?getter || noop,
?? ??? ??? ?// 空的值
?? ??? ??? ?noop,
?? ??? ??? ?// 配置对象(默认配置,懒执行,不可更改)
?? ??? ??? ?computedWatcherOptions
?? ??? ??? ?)
?? ??? ?}

?? ??? ?// component-defined computed properties are already defined on the
?? ??? ?// component prototype. We only need to define computed properties defined
?? ??? ?// at instantiation here.
?? ??? ?if (!(key in vm)) {
?? ??? ??? ?defineComputed(vm, key, userDef)
?? ??? ?} else if (process.env.NODE_ENV !== 'production') {
?? ??? ??? ?if (key in vm.$data) {
?? ??? ??? ??? ?warn(`The computed property "${key}" is already defined in data.`, vm)
?? ??? ??? ?} else if (vm.$options.props && key in vm.$options.props) {
?? ??? ??? ??? ?warn(`The computed property "${key}" is already defined as a prop.`, vm)
?? ??? ??? ?} else if (vm.$options.methods && key in vm.$options.methods) {
?? ??? ??? ??? ?warn(`The computed property "${key}" is already defined as a method.`, vm)
?? ??? ??? ?}
?? ??? ?}
?? ?}
}

????????initComputed 中的 defineComputed 方法需要传入三个参数 vue 实例,key,getter函数,,如果getter函数为函数时会执行 createComputedGetter(key) ,该方法返回一个方法的执行结果,首先拿到 watcher ,如果watcher 存在就执行 watcher.evaluate 方法。该方法将传入的函数执行结果放到 watcher.value 中,同时将 watcher.dirty 设置为 false 。当重新加载页面时(调用了 update() 方法)会将watcher.dirty 重新置为 true。

watcher.evaluate 方法代码:
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate () {
?? ?this.value = this.get()
?? ?this.dirty = false
}

createComputedGetter方法代码:
function createComputedGetter (key) {
?? ?return function computedGetter () {
?? ??? ?//拿到watcher
?? ??? ?const watcher = this._computedWatchers && this._computedWatchers[key]
?? ??? ??? if (watcher) {
?? ??? ??? ??? ?//执行 watcher 的 watcher.evaluate 方法
?? ??? ??? ??? ?//执行 computed.key 的值(函数)得到函数的执行结果 赋值给 watcher.value
?? ??? ??? ??? ?//将 watcher.dirty 置为 false
?? ??? ??? ??? ?//computed 和 methods 的区别:
?? ??? ??? ??? ?//一次渲染当中,只执行一次 computed 函数,后续访问就不会再执行。知道下一次更新之后才会再次执行。(因为dirty被置为false)
?? ??? ??? ??? ?//当执行 watcher.update 方法时(重新渲染页面时调用),dirty会被重新置为true。
?? ??? ??? ??? ?if (watcher.dirty) {
?? ??? ??? ??? ??? ?watcher.evaluate()
?? ??? ??? ??? ?}
?? ??? ??? ??? ?if (Dep.target) {
?? ??? ??? ??? ??? ?watcher.depend()
?? ??? ??? ??? ?}
?? ??? ??? ??? ?return watcher.value
?? ??? ??? ?}
?? ??? ?}
?? ?}

????????initWatch?该方法先遍历 watcher 配置项,然后拿到对应 key 值后赋值给 handler ,判断 handler 是否为一个数组,如果不是 直接执行 createWatcher() 方法,该方法先判断 handler 是否为对象,如果为对象,就从 handler 属性中获取函数;如果为字符串,表示的是一个 methods 方法,直接通过 this.methodskey 拿到这个函数。最后调用 $watch 方法(传入key, handler,配置项)。由于用户可以直接通过调用 this.$watch ,因此 $watch 会先处理 cb 是对象的情况,保证后续处理中 cb 为一个函数。然后标记这是一个用户 watcher ,后实例化 watcher。如果存在 immediate:true 则立即执行回调函数,最终返回一个 unwach。因此该方法的核心原理就是实例化一个 watcher 实例,并返回一个 unwatch 。

initWatch 方法代码:
function initWatch (vm: Component, watch: Object) {
?? ?// 遍历 watcher 配置项
?? ?for (const key in watch) {
?? ??? ?const handler = watch[key]
?? ??? ?if (Array.isArray(handler)) {
?? ??? ??? ?for (let i = 0; i < handler.length; i++) {
?? ??? ??? ?createWatcher(vm, key, handler[i])
?? ??? ?}
?? ??? ?} else {
?? ??? ??? ?createWatcher(vm, key, handler)
?? ??? ?}
?? ?}
}
createWatcher方法代码:
function createWatcher (
?? ?vm: Component,
?? ?expOrFn: string | Function,
?? ?handler: any,
?? ?options?: Object
) {
?? ?//如果为对象,从 handler 属性中获取函数
?? ?if (isPlainObject(handler)) {
?? ??? ?options = handler
?? ??? ?handler = handler.handler
?? ?}
?? ?//如果为字符串,表示的是一个 methods 方法,直接通过 this.methodsKey 拿到这个函数
?? ?if (typeof handler === 'string') {
?? ??? ?handler = vm[handler]
?? ?}
?? ?return vm.$watch(expOrFn, handler, options)
}
$watch方法代码:
Vue.prototype.$watch = function (
?? ?expOrFn: string | Function,
?? ?cb: any,
?? ?options?: Object
): Function {
?? ?const vm: Component = this
?? ?// 处理 cb 是对象的情况,保证后续处理的 cb 肯定是一个函数
?? ?if (isPlainObject(cb)) {
?? ??? ?return createWatcher(vm, expOrFn, cb, options)
?? ?}
?? ?options = options || {}
?? ?// 标记,这是一个用户 watcher
?? ?options.user = true
?? ?// 实例化 watcher
?? ?const watcher = new Watcher(vm, expOrFn, cb, options)
?? ?// 存在 immediate:true 则立即执行回调函数
?? ?if (options.immediate) {
?? ??? ?const info = `callback for immediate watcher "${watcher.expression}"`
?? ??? ?pushTarget()
?? ??? ?invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
?? ??? ?popTarget()
?? ?}
?? ?// 返回一个 unwatch
?? ?return function unwatchFn () {
?? ?watcher.teardown()
?? ?}
}

? ? 响应式处理 observe,observe为响应式处理的入口,先判断传入进来的 value 是否为一个对象或者 VNode 实例,如果不是就直接结束。若为一个对象,先判断对象上是否存在 __ob__ ,如果存在,就是该对象已经进行过响应式处理,直接返回。如果不存在,则实例化一个 Observer ,进行响应式处理。
observe方法代码:
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
* 响应式处理的入口
*/
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 先判断传进来的 value 是否为对象,如果不是,直接结束。
if (!isObject(value) || value instanceof VNode) {
return
}
// 定义一个 ob ,然后判断 value 上是否已经存在 __ob__ ,如果有,则已经进行过响应式处理,直接把结果赋值给 ob 后返回
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
) {
// 如果没有,则实例化一个 Observer ,进行响应式处理
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}

? ? ? ? Observer类,实例化后,根据 value 的值分别处理数组响应式以及处理对象响应式。对象响应式:通过遍历对象中的 key ,然后调用 defineReactive 方法对每个 key 进行响应式处理。数组响应式:首先判断有没有原型(hasProto:看对象上是否存在 _proto_ 属性),如果存在,执行 protoAugment(value, arrayMethods) 方法,即用经过增强的数组与阿逆行方法覆盖默认的原型方法,之后再执行七个数组方法时就觉有了依赖通知更新的能力,以达到实现数组响应式的目的;如果不存在,就执行 copyAugment(value, arrayMethods, arrayKeys) 方法,即将七个增强方法直接赋值到数组。?关于?arrayMethods :其实就是基于数组的原型对象创建一个新的对象,复写(实质上为增强)数组原型的方法,使其具有以来更新的能力。

array.js 代码:
import { def } from '../util/index'

// 基于数组的原型对象创建一个新的对象
// 复写(增强)数组原型方法,使其具有依赖通知更新的能力
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]

/**
* Intercept mutating methods and emit events
* 遍历七个方法
*/
methodsToPatch.forEach(function (method) {
// cache original method
// 以 push 方法为例,获取 arrayProto.push 的原生方法
const original = arrayProto[method]
// 分别在 arrayMethods 对象上定义七个方法
// 比如后续执行 arr.push
def(arrayMethods, method, function mutator (...args) {
// 先执行原生的 push 方法,往数组中放置新的数据。
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 如果执行的是 push unshift splice 操作的话,进行响应式处理
if (inserted) ob.observeArray(inserted)
// notify change
// 执行 dep.notify() 进行依赖通知更新
ob.dep.notify()
return result
})
})
observer 类代码:
/**
* Observer class that is attached to each observed
* object. Once attached, the observer converts the target
* object's property keys into getter/setters that
* collect dependencies and dispatch updates.
*/
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)) {
// 处理数组响应式
// 判断是否存在 __proto__ 属性
// obj.__proto__访问对象的原型链
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++) {
// 遍历对象中的 key ,然后调用 defineReactive 对每个 key 进行响应式处理。
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])
}
}
}

// helpers

/**
* Augment a target Object or Array by intercepting
* the prototype chain using __proto__
*/
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
// 用经过增强的数组原型方法覆盖默认的原型方法,之后再执行七个数组方法时 就具有了依赖通知更新的能力,以达到实现数组响应式的目的。
target.__proto__ = src
/* eslint-enable no-proto */
}

/**
* Augment a target Object or Array by defining
* hidden properties.
* 将增强的七个方法直接赋值到数组
*/
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
?? ??? ?defineReactive 方法:处理响应式的核心地方。首先实例化一个 dep, 一个? key 对应一个 dep ,然后获取 dep 的属性描述符。最后通过递归的方式处理 val 为对象的情况,即处理嵌套对象。处理过程:get方法为先拦截 obj[key] 的访问和设置,拦截 obj.key ,进行依赖收集(实质上收集的是一个 watcher,执行 watcher.addDep。如果 dpe 已经被收集过了,就跳过;如果没有收集,则将 dep 放到 watcher 自己里面。结束后将 watcher 本身放到 dep 中,即双向收集。)以及返回最新的值。同时对嵌套对象也进行依赖收集,如果嵌套对象为数组时,遍历数组,判断是否存在 __ob__ ,存在的话这个元素为一个对象,就对该元素进行依赖收集。set方法为拦截 obj.key = newVal 的操作,首先获取老值,如果新值和老值相同的话直接结束,如果不相同,只存在 getter 不存在 setter 的情况也是直接结束;要是存在 setter 方法,就用新值替换老值,然后对新值进行响应式处理,dep.notify() 方法会在响应式数据更新时,做依赖通知更新。
defineReactive 方法代码:
/**
* Define a reactive property on an Object.
* 处理响应式处理的地方 核心
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 实例化一个 dep ,一个 key 对应一个 dep
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]
}

// 通过递归的方式处理 value 为对象的情况,即处理嵌套对象。
let childOb = !shallow && observe(val)
// 拦截 ojb[key] 的访问和设置
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// 拦截 obj.key,进行依赖收集以及返回最新的值
get: function reactiveGetter () {
// obj.key 的值
const value = getter ? getter.call(obj) : val
if (Dep.target) {
// 读取时进行的依赖收集,将 dep 添加到 watcher 中,也将 watcher 添加到 dep 中。
dep.depend()
if (childOb) {
// 对嵌套对象也进行依赖收集
childOb.dep.depend()
if (Array.isArray(value)) {
// 处理嵌套的值为数组的情况
dependArray(value)
}
}
}
return value
},
// 拦截 obj.key = newVal 的操作
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()
}
})
}

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-11 12:13:37  更:2021-08-11 12:18:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/18 5:43:46-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码