当前位置: 首页 > Web前端 > vue.js

从vue构造器说起

时间:2023-03-31 19:23:15 vue.js

1.前言数据响应式、组件化系统现在是前端框架的标配,vue当然也不例外。之前讲过数据响应性的原理,这个问题本来想讨论组件系统的。组件包括根组件和子组件。每个Vue实例都是使用newVue(选项)创建的,但是应用程序的根组件实例是由用户显式创建的,并渲染根组件实例中的子组件。在中隐式创建。那么问题来了,我们写的以vue后缀结尾的文件,到页面渲染的dom结构,是经过什么过程的呢?但是这个问题太大了,涉及到很多前置知识点。本文从Vue构造函数入手梳理流程!为什么需要了解这些数据驱动的多端渲染分层设计vnode设计思路2.业务中很少处理Vue构造函数。vue-cli初始化的项目中有一个main.js文件。一般会看到如下结构newVue({el:'#app',i18n,template:'',components:{App}})记住共享virtual-dom时,vue组件通过获取vnoderender方法,之后通过patch处理渲染到真实的dom中。所以我们的目标就是从vue构造函数开始,来梳理这个主流程vue构造函数函数Vue(options){if(process.env.NODE_ENV!=='production'&&!(thisinstanceofVue)){warn('Vue是一个构造函数,应该使用`new`关键字')}this._init(options)}Vue.prototype.initVue.prototype._init=function(options?:Object){constvm:Component=this//auidvm._uid=uid++//避免这种情况被观察到的标志vm._isVue=true//合并选项if(options&&options._isComponent){//优化内部组件实例化//因为动态选项合并非常慢,//内部组件选项都不需要特殊处理。initInternalComponent(vm,options)}else{vm.$options=mergeOptions(resolveConstructorOptions(vm.constructor),options||{},vm)}//暴露真实的自己vm._self=vminitLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm,'beforeCreate')initInjections(vm)//在data/props之前解析注入initState(vm)initProvide(vm)//在data/props之后解析提供callHook(vm,'created')//对于根组件if(vm.$options.el){vm.$mount(vm.$options.el)}}具体方法先不去关注。一般过程包括合并组件的选项。初始化组件数据生命周期相关数据事件相关数据渲染相关数据调用beforeCreatehookprovide/inject相关数据状态相关数据调用createdhookvm.$mount(vm.$options.el)Vue.prototype.$mount=function(el?:string|Element,hydrating?:boolean):Component{el=el&&inBrowser?query(el):undefinedreturnmountComponent(this,el,hydrating)}mountComponentfunctionexportfunctionmountComponent(vm:Component,el:?Element,hydrating?:boolean):Component{vm.$el=el//非生产environment,使用Vue.jscallHook(vm,'beforeMount')警告运行时版本updateComponent,noop,{before(){if(vm._isMounted){callHook(vm,'beforeUpdate')}}},true/*isRenderWatcher*/)returnvm}调用beforeMounthook创建渲染Watcher,Watcher实例会第一次计算表达式,创建VNodeTree,然后生成DOMTree,这里回顾一下过程响应式依赖收集调用mountedhook返回组件实例vmvm._render函数的作用是调用vm.$options.render函数,返回生成的虚拟节点(vnode)。vm._update函数的作用是将vm._render函数生成的虚拟节点渲染成真实的DOM。vm.xxx可以访问道具和数据吗?通过Object.defineProperty向vm添加了一个新属性。propertyaccessordescriptor的get特性是获取vm._props[key](以props为例)的值并返回。propertyaccessordescriptor的set特性是Setthevalueofvm._props[key]constsharedPropertyDefinition={enumerable:true,configurable:true,get:noop,set:noop}//定义get/setexport函数proxy(target:Object,sourceKey:string,key:string){sharedPropertyDefinition.get=functionproxyGetter(){returnthis[sourceKey][key]}sharedPropertyDefinition.set=functionproxySetter(val){this[sourceKey][key]=val}Object.defineProperty(target,key,sharedPropertyDefinition)}//代理访问proxy(vm,`_props`,key)//proxy(vm,`_data`,key)ininitData访问this.a实际上访问的是this.data.a4.计算属性4.1:计算属性和方法的例子可以参考提供的例子vue官网Computedproperties是根据它们的反应依赖来缓存的,只有当相关的反应依赖发生变化时才会重新计算它们。相反,每当触发重新渲染时,调用方法将始终再次执行函数4.2:Whatdoesproxyaccessactuallydowhenaccessingcomputedpropertiesonainstance4.3:Initializecomputedproperties看一下initComputed方法constcomputedWatcherOptions={lazy:true}functioninitComputed(vm:Component,computed:Object){//在实例上初始化并挂载_computedWatchersconstwatchers=vm._computedWatchers=Object.create(null)//计算属性只是SSR期间的getterconstisSSR=isServerRendering()for(constkeyincomputed){constuserDef=computed[key]constgetter=typeofuserDef==='function'?userDef:userDef.getif(process.env.NODE_ENV!=='production'&&getter==null){warn(`Getterismissingforcomputedproperty"${key}".`,vm)}if(!isSSR){//为计算属性创建内部观察器。//创建计算属性Watcherwatchers[key]=newWatcher(vm,getter||noop,noop,computedWatcherOptions)}//组件定义的计算属性已经定义在//组件原型上。我们只需要在这里定义实例化时定义的计算属性。//这里注意:in运算符会枚举原型上的所有属性,包括继承的计算属性,所以component-specific计算属性和inherited计算属性的访问方式是不同的//1.ComponentsInstance-specificattributes:component-specificcomputedattributeswillbemountonvm//2.Component-inheritedattributes:component-inheritedcomputedattributeshavebeenmountedonvm.constructor.prototypeif(!(keyinvm)){//处理组件实例特有的计算属性defineComputed(虚拟机,密钥,使用rDef)}elseif(process.env.NODE_ENV!=='production'){//计算属性键不能存在于数据和属性中if(keyinvm.$data){warn(`Thecomputedproperty"${key}"已经在数据中定义。`,vm)}elseif(vm.$options.props&&keyinvm.$options.props){warn(`Thecomputedproperty"${key}"isalreadydefinedasaprop.`,vm)}}}}创建vm._computedWatchers属性根据computedkey创建watcher实例,调用computed属性的observerdefineComputed(vm,key,userDef)exportfunctiondefineComputed(target:any,key:string,userDef:Object|Function){constshouldCache=!isServerRendering()if(typeofuserDef==='function'){sharedPropertyDefinition.get=shouldCache?createComputedGetter(key):userDefsharedPropertyDefinition.set=noop}else{sharedPropertyDefinitionget=userDef.get?shouldCache&&userDef.cache!==false?createComputedGetter(key):userDef.get:noopsharedPropertyDefinition.set=userDef.set?userDef.set:noop}if(process.env.NODE_ENV!=='production'&&sharedPropertyDefinition.set===noop){sharedPropertyDefinition.set=function(){warn(`计算属性“${key}”已分配tobutithasnosetter.`,this)}}//AddcomputedaccessorpropertydescriptorobjectObject.defineProperty(target,key,sharedPropertyDefinition)}判断sharedPropertyDefinition.get添加computedaccessor属性描述符对象是什么?最后访问器属性sharedPropertyDefinition大概是sharedPropertyDefinition={enumerable:true,configurable:true,get:createComputedGetter(key),set:userDef.set//ornoop}访问计算属性this.a其实触发getter如下functioncreateComputedGetter(key){returnfunctioncomputedGetter(){constwatcher=this._computedWatchers&&this._computedWatchers[key]if(watcher){if(watcher.dirty){//如果有依赖如果有变化,重新评估watcher.evaluate()}if(Dep.target){//将计算属性的所有依赖添加到当前Dep.targetwatcher.depend()的依赖中}returnwatcher.value()}}}我们来看看watcher的构造函数classWatcher{constructor(vm:Component,expOrFn:string|Function,//触发方式getcb:Function,options?:?Object,isRenderWatcher?:boolean//是否是渲染函数的观察者)if(this.computed){this.value=undefined//computedObserverthis.dep=newDep()}else{//评估,何时收集依赖this.value=this.get()}//收集依赖depend(){//Dep.target值为renderingfunctionObserverobjectif(this.dep&&Dep.target){this.dep.depend()}}//evaluateevaluate(){if(this.dirty){//关键地方this.value=this.get()this.dirty=false}returnthis.value}}回顾响应式原理Dep-watcher的观察者模式将渲染函数的观察者对象收集在计算属性的watcher中。渲染函数的观察者对象在初始化和求值时,会触发属性的获取,从而收集依赖,即计算属性的观察者会在计算属性所依赖的数据发生变化时触发更新。key,而实际的get方法创建watcher实例实现代理访问,定义accessor属性访问computed属性,第一次去evaluate函数,从而触发触发渲染函数的get导致相应守望者合集最后靠一个计算属性的实际例子来分析这个过程,(不过这里好像需要读者熟悉dep和watcher的观察者模式)5.本文的其他思路从Vue的构造函数开始,pay注意初始化过程中的initstate方法,并选择其中的一个计算属性扩展介绍计算属性的初始化处理也是Vue典型的初始化处理方式,其中在很多地方都可以看到Object.defineProperty方法,实例化观察者watcher对象,根据dep和watcher建立观察者模式。在其他数据初始化章节中,这些概念会在响应式处理流程中遇到。最后介绍一个数据流驱动的项目案例H5编辑器案例