Vue - 以$、_开头命名的问题及源码梳理

news/2024/9/24 0:25:16/

在 Vue 中,以$、_开头的属性,例如:$private、_private;它们是不会被 Vue 代理。

所以在 this 上是访问不到它们的,watch 监听 $private、_private 也是不会生效的。

如果要访问这些属性,可以通过 this.$data、this._data 中访问到这些属性。同样的,可以通过 watch $data.$private、_data.$private 监听到它的变化。

源码梳理

proxy _data:代理 _data

在我们的 vue 文件中定义: data( ) => ( { name: 1, _private: 0 } )

Vue 内部代码执行:new Vue( ) -> initState -> initData

initData( )

initData() 方法会对实例上的 data:vm._data 代理。

通过 isReserved() 方法判断 data 的 key 是否以$、_开头,如果不是以$、_开头就对执行 proxy(vm, '_data', key) 方法。

while 循环至 key === name 时执行 proxy;key === _private 时不会执行 proxy;

# vue/src/core/instance/state.js
function initData (vm: Component) {let data = vm.$options.datadata = 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 instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (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 dataobserve(data, true /* asRootData */)
}

isReserved ( )

isReserved() 方法:工具函数。判断 str 是否以 $、_ 开头。

# vue/src/core/util/lang.js 
/*** Check if a string starts with $ or _*/
export function isReserved (str: string): boolean {const c = (str + '').charCodeAt(0)return c === 0x24 || c === 0x5F
}

proxy( )

参数:

  • target:vm ( this );
  • sourceKey:_data;
  • key:data 中的 key,即 name;

proxy() 方法:将 vm._data 上的属性代理至 vm: this 上。

通过 Object.defineProperty 为 target:this 上绑定了 key,访问 this.name 相当于是访问 this[sourceKey][key],即 this._data.name;所以赋值 this.name = 1,也是为 this._data.name 赋值。

# vue/src/core/instance/state.js
export function proxy (target: Object, sourceKey: string, key: string) {sharedPropertyDefinition.get = function proxyGetter () {return this[sourceKey][key]}sharedPropertyDefinition.set = function proxySetter (val) {this[sourceKey][key] = val}Object.defineProperty(target, key, sharedPropertyDefinition)
}

由于 isReserved( ) 方法只 proxy 了 name,并没有 proxy _private。

所以此时,this = { name: 1, _data: { name: 1, _private: 0 } }

observe _data:响应式

observe(data, true),即 observe(vm._data, true)。对 vm._data 对象观察,设置为响应式的。

# vue/src/core/instance/state.js
function initData (vm: Component) {let data = vm.$options.datadata = vm._data = typeof data === 'function'? getData(data, vm): data || {}// code...// proxy data// observe dataobserve(data, true /* asRootData */)
}

 observe( )

参数:

  • value:vm._data

调用 new Observer(value)

# vue/src/core/observer/index.js
export function observe (value: any, asRootData: ?boolean): Observer | void {if (!isObject(value) || value instanceof VNode) {return}let ob: Observer | voidif (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
}

class Observer

walk( ) 方法遍历 value:vm._data 的 key,将所有的属性设置为响应式的。

# vue/src/core/observer/index.js
export class Observer {value: any;dep: Dep;vmCount: number; // number of vms that have this object as root $dataconstructor (value: any) {this.value = valuethis.dep = new Dep()this.vmCount = 0def(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])}}
}

defineReactive( )

通过 Object.defineProperty 为 obj:vm._data 的 key 设置 set、get

# vue/src/core/observer/index.js
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/settersconst getter = property && property.getconst setter = property && property.setif ((!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) : valif (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 setterif (getter && !setter) returnif (setter) {setter.call(obj, newVal)} else {val = newVal}childOb = !shallow && observe(newVal)dep.notify()}})
}

虽然 _private 没有被 proxy 代理到 this 上,但是它仍然被设置为响应式的。

_data 与 $data

# vue/src/core/instance/index.js
function Vue (options) {if (process.env.NODE_ENV !== 'production' &&!(this instanceof Vue)) {warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options)
}stateMixin(Vue)

stateMixin( )

stateMixin( ) 方法是为 Vue 的原型 prototype 上绑定 $data,即 this._data;所以$data 和 _data 的指向是一样的。

# vue/src/core/instance/state.js
export function stateMixin (Vue: Class<Component>) {// flow somehow has problems with directly declared definition object// when using Object.defineProperty, so we have to procedurally build up// the object here.const dataDef = {}dataDef.get = function () { return this._data }const propsDef = {}propsDef.get = function () { return this._props }if (process.env.NODE_ENV !== 'production') {dataDef.set = function () {warn('Avoid replacing instance root $data. ' +'Use nested data properties instead.',this)}propsDef.set = function () {warn(`$props is readonly.`, this)}}Object.defineProperty(Vue.prototype, '$data', dataDef)Object.defineProperty(Vue.prototype, '$props', propsDef)Vue.prototype.$set = setVue.prototype.$delete = delVue.prototype.$watch = function (expOrFn: string | Function,cb: any,options?: Object): Function {const vm: Component = thisif (isPlainObject(cb)) {return createWatcher(vm, expOrFn, cb, options)}options = options || {}options.user = trueconst watcher = new Watcher(vm, expOrFn, cb, options)if (options.immediate) {try {cb.call(vm, watcher.value)} catch (error) {handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)}}return function unwatchFn () {watcher.teardown()}}
}

所以可以通过 _data、$data 方式访问到 $、_ 的属性。

总结:

  • data 中以 $、_ 开头的属性不会被代理到 this 上,但是可以通过 _data、$data 访问

  • 因为 this 上没有 $、_ 的属性,所以 watch 也无法监听 $、_ 属性的变化

  • 由于 vm._data 被 defineReactive 处理过,所以可以通过 watch _data.$xxx、_data._xxx 监听属性的变化


http://www.ppmy.cn/news/1454517.html

相关文章

Go语言的切片(slice)和数组(array)有什么不同?

文章目录 数组&#xff08;Array&#xff09;示例代码数组的缺点 切片&#xff08;Slice&#xff09;示例代码切片的优点原因和解决方案 总结 在Go语言中&#xff0c;数组和切片&#xff08;slice&#xff09;都是用来存储一系列相同类型数据的集合&#xff0c;但它们之间存在一…

java设计模式三

工厂模式是一种创建型设计模式&#xff0c;它提供了一个创建对象的接口&#xff0c;但允许子类决定实例化哪一个类。工厂模式有几种变体&#xff0c;包括简单工厂模式、工厂方法模式和抽象工厂模式。下面通过一个简化的案例和对Java标准库中使用工厂模式的源码分析来说明这一模…

spss 数据分析 ,6项数据,根据第一项数据分成2组,统计2组中其他数据的对比 医学spss数据分析

在SPSS中&#xff0c;你想要根据第一项数据&#xff08;假设它是变量名如Var1&#xff09;将数据分成两组&#xff0c;并统计这两组在其他五项数据&#xff08;Var2到Var6&#xff09;上的差异&#xff0c;你可以按照以下步骤进行&#xff1a; 数据分组&#xff1a; 首先&…

[Unity实战]热更新如何预防过度裁剪

情景再现 假设你现在有一个游戏客户端&#xff0c;客户端只打包了入口场景&#xff0c;游戏场景都存放在了AB包。 你打的热更包里使用了协程中的waituntil修复游戏场景中空投补给资源加载时机问题&#xff0c;但是打出来的热更在真机跑报如下错误&#xff1a; TypeLoadExcep…

电商中文场景多模态测试prompt

魔搭社区汇聚各领域最先进的机器学习模型&#xff0c;提供模型探索体验、推理、训练、部署和应用的一站式服务。https://www.modelscope.cn/datasets 多模态大模型Yi-VL-plus体验 效果很棒 - 知乎最近测了一下零一万物的多模态大模型Yi-VL-plus的效果&#xff0c;发现多模态理解…

我已经受够了“系统异常”!

作为用户&#xff0c;你有没有这样的经验&#xff1a;用个软件&#xff0c;隔三岔五弹个框&#xff1a;系统异常&#xff01; 作为程序员&#xff0c;你有没有这样的经验&#xff1a; 运营同学又屁颠屁颠跑来求助&#xff1a;“用户不能下单了&#xff01;” “报什么错&…

【JVM】class文件格式,JVM加载class文件流程,JVM运行时内存区域,对象分配内存流程

这篇文章本来只是想讲一下class文件格式&#xff0c;讲着讲着越讲越多。JVM这一块吧&#xff0c;知识比较散比较多&#xff0c;如果深研究下去如死扣《深入理解Java虚拟机》&#xff0c;这本书很深很细&#xff0c;全记住是不可能的&#xff0c;其实也没必要。趁这个机会直接把…

Linux常用软件安装(JDK、MySQL、Tomcat、Redis)

目录 一、上传与下载工具Filezilla1. filezilla官网 二、JDK安装1. 在opt中创建JDK目录2.上传JDK压缩文件到新建目录中3.卸载系统自代jdk4.安装JDK5.JDK环境变量配置6. 验证是否安装成功 三、安装MySQL1.创建mysql文件夹2.下载mysql安装压缩包3.上传到文件夹里面4. 卸载系统自带…