1.Vue ä¸ $set() ä¸ Vue.set() åçå使ç¨
2.Vue2.6x源码解析(一):Vue初始化过程
3.Vue2源码解析?源码2?初始化
4.Vue2源码学习笔记 - 7.响应式原理一基础
5.Vue2剥丝抽茧-响应式系统之set和delete
Vue ä¸ $set() ä¸ Vue.set() åçå使ç¨
é®é¢ï¼å¨ä½¿ç¨ vue è¿è¡å¼åçè¿ç¨ä¸ï¼å¯è½ä¼éå°ä¸ç§æ åµï¼å½çævueå®ä¾åï¼å次ç»æ°æ®èµå¼æ¶ï¼ææ¶å并ä¸ä¼èªå¨æ´æ°å°è§å¾ä¸å»ãä¹å°±æ¯ å¦æå¨å®ä¾å建ä¹åæ·»å æ°çå±æ§å°å®ä¾ä¸ï¼å®ä¸ä¼è§¦åè§å¾æ´æ°ãæ¡ä¾ï¼
å½ç¹å»æé®å页é¢ï¼
[å¾çä¸ä¼ 失败...(image-e-)]
å½ç¹å»æé®åæ§å¶å°ï¼
åå ï¼
å ES5 çéå¶ï¼Vue.js ä¸è½æ£æµå°å¯¹è±¡å±æ§çæ·»å æå é¤ãå 为 Vue.js å¨åå§åå®ä¾æ¶å°å±æ§è½¬ä¸º getter/setterï¼æ以 å±æ§å¿ é¡»å¨ data 对象ä¸æè½è®© Vue.js 转æ¢å®ï¼æè½è®©å®æ¯ååºçã
å æ¤ï¼
Vue ä¸è½æ£æµä»¥ä¸åå¨çæ°ç»ï¼
å½ä½ å©ç¨ç´¢å¼ç´æ¥è®¾ç½®ä¸ä¸ªé¡¹æ¶ï¼ä¾å¦ï¼vm.items[indexOfItem] = newValue
å½ä½ ä¿®æ¹æ°ç»çé¿åº¦æ¶ï¼ä¾å¦ï¼vm.items.length = newLength
egï¼
ä½¿ç¨ this.arr[0] å»æ´æ° array çå 容ï¼è§å¾æ²¡æå·æ°
ä½¿ç¨ Vue.set(this.arr, 0, !this.arr[0]) å»æ´æ° array çå 容ï¼è§å¾è¢«å·æ°
ä½¿ç¨ this.arr[0] = !this.arr[0] å this.obj.a = !this.obj.a åæ¶æ´æ°ï¼è§å¾è¢«å·æ°
ç»è®ºï¼
å¦ææ¹æ³éé¢å纯çæ´æ°æ°ç» Array çè¯ï¼è¦ä½¿ç¨ Vue.set()ï¼
å¦ææ¹æ³éé¢åæ¶ææ°ç»å对象çæ´æ°ï¼ç´æ¥æä½ data å³å¯;
æ¯ä¸ªç»ä»¶å®ä¾é½æç¸åºç watcher å®ä¾å¯¹è±¡ï¼å®ä¼å¨ç»ä»¶æ¸²æçè¿ç¨ä¸æå±æ§è®°å½ä¸ºä¾èµï¼ä¹åå½ä¾èµé¡¹ç setter 被è°ç¨æ¶ï¼ä¼éç¥ watcher éæ°è®¡ç®ï¼ä»èè´ä½¿å®å ³èçç»ä»¶å¾ä»¥æ´æ°ã
åç°ä»£ JavaScript çéå¶ (èä¸ Object.observe ä¹å·²ç»è¢«åºå¼)ï¼Vue ä¸è½æ£æµå°å¯¹è±¡å±æ§çæ·»å æå é¤ãç±äº Vue ä¼å¨åå§åå®ä¾æ¶å¯¹å±æ§æ§è¡ getter/setter 转åè¿ç¨ï¼æ以å±æ§å¿ é¡»å¨ data 对象ä¸åå¨æè½è®© Vue 转æ¢å®ï¼è¿æ ·æè½è®©å®æ¯ååºçã
3.1 éè¿ Vue.set() æ¹å
è¯æ³ï¼
å½ç¹å»æé®å页é¢ï¼
[å¾çä¸ä¼ 失败...(image-6f-)]
å½ç¹å»æé®åæ§å¶å°ï¼
3.2 éè¿ $set() æ¹å
è¯æ³ï¼
å½ç¹å»æé®å页é¢ï¼
å½ç¹å»æé®åæ§å¶å°ï¼
3.3 Vue.set() å this.$set() çåºå«
Vue.set() æºç ï¼
this.$set() æºç
æç« è½¬èª Vue ä¸ $set() ä¸ Vue.set() åçå使ç¨
Vue2.6x源码解析(一):Vue初始化过程
Vue2.6x源码解析(一):Vue初始化过程
Vue.js的核心代码在src/core目录,它在任何环境都能运行。源码项目入口通常在src/main.js,源码引入的源码Vue构造函数来自dist/vue.runtime.esm.js,这个文件导出了Vue构造函数,源码允许我们在创建Vue实例前预置全局API和原型方法。源码菠菜源码资源分享
初始化前,源码Vue构造函数在src/core/instance/index.js中定义,源码它预先挂载了全局API如set、源码delete等。源码即使不通过new Vue初始化,源码Vue本身已具备所需功能。源码
当执行new Vue时,源码实际上是源码调用了_init方法,这个过程会在src/core/index.js的源码initGlobalAPI(Vue)中初始化全局API和原型方法。接着,组件实例的初始化与根实例基本一致,包括组件构造函数的定义,以及组件的生命周期、渲染和挂载。源码后门查找
组件初始化过程中,关键步骤包括数据转换为响应式、事件注册和watcher的创建。例如,组件的渲染函数会触发渲染方法,而watcher的更新则通过异步更新队列机制确保性能。
在开发环境,Vue-template-compiler插件负责模板编译,然后runtime中的$mount方法负责实际的渲染和挂载。整个过程涉及组件的构建、渲染函数生成、依赖响应式数据的更新和异步调度。
Vue2源码解析?2?初始化
活着,最有意义的事情,就是不遗余力地提升自己的认知,拓展自己的认知边界。在搭建源码调试环境一节中,我们已经找到了Vue的构造函数,接下来开始探索Vue初始化的源码程序猫流程。
一个小测试在精读源码之前,我们可以在一些重要的方法内打印一下日志,熟悉一下这些关键节点的执行顺序。(执行npmrundev后,源码变更后会自动生成新的Vue.js,我们的测试html只需要刷新即可)
在初始化之前,Vue类的构建过程?在此过程中,大部分都是原型方法和属性,意味着实例vm可以直接调用
注意事项:
1、以$为前缀的属性和方法,在调用_init原型方法的那一刻即可使用
2、以_为前缀的原型方法和属性,谨慎使用
3、本章旨在了解Vue为我们提供了哪些工具(用到时,深入研究,不必要在开始时花过多精力,后边遇到时会详细说明)
4、类方法和属性在newVue()前后都可以使用,原型方法和属性只能在newVue()后使用
定义构造函数//src/core/instance/index.jsfunctionVue(options){ //形式上很简单,weex sdk 源码就是一个_init方法this._init(options)}挂载原型方法:_init//src/core/instance/init.jsVue.prototype._init=function(options?:Object){ }挂载与state相关的原型属性和原型方法//src/core/instance/state.jsconstdataDef={ }dataDef.get=function(){ returnthis._data}constpropsDef={ }propsDef.get=function(){ returnthis._props}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{ //略}挂载与事件相关的原型方法//src/core/instance/events.jsconsthookRE=/^hook:/Vue.prototype.$on=function(event:string|Array<string>,fn:Function):Component{ }Vue.prototype.$once=function(event:string,fn:Function):Component{ }Vue.prototype.$off=function(event?:string|Array<string>,fn?:Function):Component{ }Vue.prototype.$emit=function(event:string):Component{ }挂载与生命周期相关的原型方法//src/core/instance/lifecycle.jsVue.prototype._update=function(vnode:VNode,hydrating?:boolean){ }Vue.prototype.$forceUpdate=function(){ }Vue.prototype.$destroy=function(){ }挂载与渲染相关的原型方法//installruntimeconveniencehelpersinstallRenderHelpers(Vue.prototype)Vue.prototype.$nextTick=function(fn:Function){ }Vue.prototype._render=function():VNode{ }挂载Vue类方法和类属性//src/core/global-api/index.js//configconstconfigDef={ }configDef.get=()=>configObject.defineProperty(Vue,'config',configDef)Vue.util={ warn,extend,mergeOptions,defineReactive}Vue.set=setVue.delete=delVue.nextTick=nextTick//2.6explicitobservableAPIVue.observable=<T>(obj:T):T=>{ observe(obj)returnobj}Vue.options=Object.create(null)ASSET_TYPES.forEach(type=>{ Vue.options[type+'s']=Object.create(null)})Vue.options._base=Vueextend(Vue.options.components,builtInComponents)initUse(Vue)//挂载类方法use,用于安装插件(特别特别重要)initMixin(Vue)//挂载类方法mixin,用于全局混入(在Vue3中被新特性取代)initExtend(Vue)//实现Vue.extend函数initAssetRegisters(Vue)//实现Vue.component,Vue.directive,Vue.filter函数挂载平台相关的属性,挂载原型方法$mount//src/platforms/web/runtime/index.js//installplatformspecificutilsVue.config.mustUseProp=mustUsePropVue.config.isReservedTag=isReservedTagVue.config.isReservedAttr=isReservedAttrVue.config.getTagNamespace=getTagNamespaceVue.config.isUnknownElement=isUnknownElement//installplatformruntimedirectives&componentsextend(Vue.options.directives,platformDirectives)extend(Vue.options.components,platformComponents)//installplatformpatchfunctionVue.prototype.__patch__=inBrowser?patch:noopconsole.log('挂载$mount方法')//publicmountmethodVue.prototype.$mount=function(el?:string|Element,hydrating?:boolean):Component{ }拓展$mount方法//src/platforms/web/entry-runtime-with-compiler.jsconstmount=Vue.prototype.$mount//保存之前定义的$mount方法Vue.prototype.$mount=function(el?:string|Element,hydrating?:boolean):Component{ //执行拓展内容returnmount.call(this,el,hydrating)//执行最初定义的$mount方法}Vue的初始化过程(很重要哦!!!)熟悉了初始化过程,就会对不同阶段挂载的实例属性了然于胸,了解Vue是如何处理options中的数据,将初始化流程抽象成一个模型,从此,当你看到用户编写的options选项,都可以在这个模型中演练。
前边我们提到过,Vue的构造函数中只调用了一个_init方法
执行_init方法//src/core/instance/init.jsVue.prototype._init=function(options?:Object){ constvm:Component=this//此刻,Vue的实例已经创建,只是雏形,但Vue的所有原型方法可以调用//aflagtoavoidthisbeingobserved//(observe会在后面的响应式章节详细说明)vm._isVue=true//mergeoptionsif(options&&options._isComponent){ //在后面的Vue组件章节会详细说明//optimizeinternalcomponentinstantiation//sincedynamicoptionsmergingisprettyslow,andnoneofthe//internalcomponentoptionsneedsspecialtreatment.initInternalComponent(vm,options)}else{ vm.$options=mergeOptions(//合并optionsresolveConstructorOptions(vm.constructor),//主要处理包含继承关系的实例()options||{ },vm)}//exposerealselfvm._self=vminitLifecycle(vm)//初始化实例中与生命周期相关的属性initEvents(vm)//处理父组件传递的事件和回调initRender(vm)//初始化与渲染相关的实例属性callHook(vm,'beforeCreate')//调用beforeCreate钩子,即执行beforeCreate中的vue music源码代码(用户编写)initInjections(vm)//resolveinjectionsbeforedata/props获取注入数据initState(vm)//初始化props、methods、data、computed、watchinitProvide(vm)//resolveprovideafterdata/props提供数据注入callHook(vm,'created')//执行钩子created中的代码(用户编写)if(vm.$options.el){ //DOM容器(通常是指定id的div)vm.$mount(vm.$options.el)//将虚拟DOM转换成真实DOM,然后插入到DOM容器内}}initLifecycle:初始化与生命周期相关的实例属性//src/core/instance/init.jsVue.prototype._init=function(options?:Object){ }0initEvents(vm):处理父组件传递的事件和回调//src/core/instance/init.jsVue.prototype._init=function(options?:Object){ }1initRender(vm):初始化与渲染相关的实例属性//src/core/instance/init.jsVue.prototype._init=function(options?:Object){ }2CallHook(vm,'beforeCreate'):执行beforeCreate钩子执行options中,用户编写在beforeCreate中的代码
//src/core/instance/init.jsVue.prototype._init=function(options?:Object){ }3initInjections(vm):resolveinjectionsbeforedata/props获取注入数据//src/core/instance/init.jsVue.prototype._init=function(options?:Object){ }4initState(vm):初始化props、methods、data、computed、watch(划重点啦!!!)//src/core/instance/init.jsVue.prototype._init=function(options?:Object){ }5initProps:初始化props此处概念比较多,propsData、props、vm._props、propsOptions,后续会结合实例来分析其区别,此处只做大概了解。
//src/core/instance/init.jsVue.prototype._init=function(options?:Object){ }6initMethods:初始化methods//src/core/instance/init.jsVue.prototype._init=function(options?:Object){ }7initData:初始化data//src/core/instance/init.jsVue.prototype._init=function(options?:Object){ }8initComputed:初始化computed选项//src/core/instance/init.jsVue.prototype._init=function(options?:Object){ }9initWatch:初始化watchcreateWatcher:本质上执行了vm.$watch(expOrFn,handler,options)
//src/core/instance/state.jsconstdataDef={ }dataDef.get=function(){ returnthis._data}constpropsDef={ }propsDef.get=function(){ returnthis._props}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{ //略}0initProvide(vm):提供数据注入为什么provide初始化滞后与inject,后续补充
//src/core/instance/state.jsconstdataDef={ }dataDef.get=function(){ returnthis._data}constpropsDef={ }propsDef.get=function(){ returnthis._props}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{ //略}1CallHook(vm,'created'):执行created钩子中的代码callHook的相关逻辑,参考上面的callHook(vm,'beforeCreate')
执行挂载执行$mount扩展通过下面的代码可知:当用户代码中同时包含render,template,el时,它们的优先级依次为:render、template、el
//src/core/instance/state.jsconstdataDef={ }dataDef.get=function(){ returnthis._data}constpropsDef={ }propsDef.get=function(){ returnthis._props}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{ //略}2$mount方法中,首先获取挂载容器,然后执行mountComponent方法
//src/core/instance/state.jsconstdataDef={ }dataDef.get=function(){ returnthis._data}constpropsDef={ }propsDef.get=function(){ returnthis._props}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{ //略}3//src/core/instance/state.jsconstdataDef={ }dataDef.get=function(){ returnthis._data}constpropsDef={ }propsDef.get=function(){ returnthis._props}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{ //略}4在_update方法中,通过_vnode属性判断是否初次渲染,patch其实就是patch方法,关于patch的详细逻辑,将在diff算法章节详细说明。
//src/core/instance/state.jsconstdataDef={ }dataDef.get=function(){ returnthis._data}constpropsDef={ }propsDef.get=function(){ returnthis._props}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{ //略}5原文:/post/Vue2源码学习笔记 - 7.响应式原理一基础
深入研究 Vue 的响应式核心,了解响应式机制在 Vue 中的核心地位。Vue 的响应式原理,让数据模型简单直接地管理状态,无需侵入性操作。
当你将普通 JavaScript 对象作为 Vue 实例的 data 选项时,Vue 会遍历对象属性并使用 Object.defineProperty 转换为 getter 和 setter。此特性仅在 ES5 中可用,不支持 IE8 及以下浏览器。
这些 getter 和 setter 在内部追踪依赖,当属性被访问或修改时,会通知 Vue。类似于 PHP 的魔术方法或 Java 的 getXXX\setXXX,但实现上存在差异。Java 可能拥有更接近的实现,比如 CGLib。
每个 Vue 组件实例对应一个 watcher,记录接触过的数据属性为依赖。当依赖项的 setter 触发时,watcher 被通知,组件重新渲染。
简单 demo 通过 defineReactive 实现响应式设置,允许访问 data 中的属性,设值触发 setter,引用触发 getter。此方法依赖于 Object.defineProperty,是响应式原理的核心。
Proxy 是 ES 定义的类,用于创建对象代理,实现基本操作拦截和自定义。通过简单的 demo 可见,更新和引用数据时会调用 setter/getter 方法。Vue2 使用 Proxy,但用途不多。
总结,学习 Object.defineProperty 和 Proxy 实现响应式的底层方法。它们在数据更新和引用时触发特定方法,执行期望的操作实现响应式。下篇深入 Vue 响应式实现。
Vue2剥丝抽茧-响应式系统之set和delete
深入解析 Vue2 源码,理解响应式系统中的 set 和 delete 方法。
首先,数组的set 和 delete 方法并不直接触发组件更新。数组的响应性需要通过数组方法如 push 或 splice 来实现。
若需替换数组元素,可利用 splice 方法间接实现。同时,提供 set 方法供操作,简化数组元素替换流程。
对于对象的 set 方法,Vue2 通过观察对象属性变化触发更新。在 updateComponent 方法中,对象属性未直接参与观察,导致 c 属性非响应式。
通过 set 方法添加响应式属性 c,但 Watcher 未被重新触发。这是因为 c 属性的 Dep 对象在 set 函数中并未收集到相关依赖。解决办法是手动调用 Dep 对象,使 c 属性收集依赖,进而触发 Watcher。
将触发 Watcher 的逻辑整合至 set 函数中,通过修改 Dep 收集所有对象属性的依赖。虽然 a 和 b 属性的依赖被收集,但 c 属性的依赖可能被遗漏。手动执行 Dep 可增加 c 属性收集依赖的机会。
对象的 del 方法则需执行对象的 Dep 来删除属性。由于 Dep 存在于闭包中,无法直接访问,执行对象的 Dep 可实现属性删除的响应式。
综上所述,通过为对象收集依赖,结合 set 和 del 方法,使得数组、对象的修改和删除操作也变为响应式。这不仅增强了 Vue2 的灵活性,也为开发者提供了更为简便的使用体验。