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

Vue2响应式原理与实现

时间:2023-03-31 19:06:14 vue.js

1.Vue的初始化Vue本质上是一个全局暴露的名为Vue的函数。使用时,通过newVue函数创建一个Vue实例,并传入一个配置对象。Vue函数中需要做的就是根据传入的配置对象进行初始化,例如://src/index.jsfunctionVue(options){this._init(options);}这里,_init()方法是通过this调用的,this就是创建的Vue实例对象,但是目前Vue实例上没有_init()方法,所以我们需要在Vue的原型中添加一个_init()方法。为了方便模块管理,我们需要单独创建一个init.js进行初始化。init.js中需要暴露一个initMixin()方法,它接收Vue在Vue的原型上添加一个原型方法,比如//src/init.jsexportfunctioninitMixin(Vue){Vue.prototype._init=function(options){//在这里初始化Vue}}//src/index.jsimport{initMixin}from"./init";functionVue(options){this._init(options);}initMixin(Vue);//传入Vue以在其原型上添加_init()方法。这时_init()方法可以获取到用户传入的options配置对象,然后开始初始化工作:①将options对象挂载到Vue实例options属性的$;②初始化状态数据;③判断用户是否传递了el属性。如果有,它会主动调用\$mount()方法挂载它。如果没有,用户需要自己调用$mount()方法挂载。//src/init.jsexportfunctioninitMixin(Vue){Vue.prototype._init=function(options){constvm=this;vm.$options=选项;//将选项挂载到Vue实例的$options属性//beforeCreate这里执行Vue的beforeCreate生命周期initState(vm);//初始化状态//这里创建的执行Vue的created生命周期if(options.el){//如果配置了el对象,那么必须执行//主动调用$mount()挂载}}Vue.prototype.$mount=function(el){//挂载这里}}二、Vue状态数据接下来就是初始化状态,也就是实现初始化状态()方法。状态的初始化是一个独立而复杂的过程。我们需要单独放在一个state.js中,主要是根据options中配置的属性进行具体的初始化操作,如:exportfunctioninitState(vm){constoptions=vm.$options;if(options.data){//如果配置了data属性initData(vm);}if(options.computed){//如果配置了computed属性initComputed(vm);}if(options.watch){//如果用户的手表已配置initWatch(vm);}}functioninitData(vm){//这里,数据属性被初始化}functioninitComputed(vm){//这里是计算属性initialization}functioninitWatch(vm){//这里初始化用户手表}三、将data属性初始化为响应式数据属性是vue响应式系统的核心,即初始化data对象中的每个属性观察监控用户传入的数据可能是一个对象,也可能是一个返回对象的函数。因此,需要判断数据的类型。如果是函数,则传入Vue实例,执行这个函数,得到返回的对象,作为观察的数据。同时,为了方便Vue实例对data中的数据进行操作,还需要将data中的属性一一定义到Vue实例中,比如//src/state.js来实现initData()方法import{proxy}from"./utils/index";import{observe}from"./observer/index";functioninitData(vm){letdata=vm.$options.data;//可能是一个函数//给Vue实例添加一个_data属性和$data属性保存用户的数据data=vm._data=vm.$data=typeofdata==="function"?data.call(vm):数据;for(letkeyindata){//遍历数据的所有属性proxy(vm,"_data",key);//将data中的属性代理给Vue实例,方便对data的操作}observe(data);//观察数据}上面使用了一个代理工具方法将数据中的属性Proxy到Vue实例中,主要是通过Object.defineProperty()方法将数据中的属性代理到Vue实例中,比如//src/utils/index.jsexportfunctionproxy(vm,source,key){Object.defineProperty(vm,key,{get(){returnvm[source][key];},set(newVal){vm[source][key]=newVal;}});}这里的vm[source]是vm._data对象,是用户传入的数据,这样当用户通过Vue实例操作数据时,实际操作是用户传入的数据对象。然后就是观察整个数据数据。进行数据观察时,数据必须是对象或数组,否则不进行观察。如果这个对象中某个key的属性值不是对象,那么也需要观察,所以这里会进行递归操作,这也是影响Vue性能的一个重要原因。数据观察也是一个独立复杂的过程,需要单独管理,比如//src/observer/index.jsimport{isObject}from"../utils/index";exportfunctionobserve(data){if(!isObject(data)){//只观察对象和数组return;}//如果要观察的数据是对象或数组,为其创建一个Observer对象returnnewObserver(data);}//src/utils/index.jsexportfunctionisObject(data){returndata&&typeofdata==="object";}接下来,Vue会观察匹配到对象或数组的数据,并为其创建一个Observer对象。观察时,对象和数组的处理会有所不同。对于对象来说,遍历对象中的每一个属性,定义为响应式类型即可;对于数组,由于数组中可能有很多项,为了避免性能影响,并不是所有的索引都定义为响应式的,而是观察数组中属于对象或数组的元素。//src/utils/index.jsexportfunctiondef(data,key,value){Object.defineProperty(data,key,{enumerable:false,//防止这个属性被枚举get(){returnvalue;},set(newVal){value=newVal;}});}//src/observer/index.jsimport{isObject,def}from"../utils/index";import{arrayMethods}from"./array";classObserver{构造函数(数据){this.data=data;def(数据,“__ob__”,这个);//为每一个被观察对象添加一个__ob__属性,如果是数组,那么这个数组也会有一个__ob__属性if(Array.isArray(data)){//观察数组data.__proto__=arrayMethods;//覆盖数组方法this.observeArray(data);//遍历数组Observation中每一项的值}else{this.walk(data);//观察对象}}walk(data){for(letkeyindata){//遍历对象中的所有键,定义为响应式数据defineReactive(data,key,data[key]);}}observeArray(arr){arr&&arr。forEach((item)=>{observe(item);//观察数组中的每一项}}}functiondefineReactive(data,key,value){letob=observe(value);//递归观察传入对象的属性值Object.defineProperty(data,key,{get(){returnvalue;},set(newVal){if(newVal===value){return;}observe(newVal);//如果用户修改了值,那么也传入给用户观察新值的新值,因为传入的可能是对象也可能是数组,只有当有新值时才能检测到被修改了,比如:push,pop,shift,unshift,sort,reverse,splice,这样数组变化后可以通知观察者,push,unshift,splice会给数组增加新的元素,我们还需要要知道添加了什么数据,需要观察这些新数据constarrayProto=Array.prototype;//获取数组的原型对象exportconstarrayMethods=Object.create(arrayProto);//根据数组的原型对象创建新的原型对象,避免方法无限循环执行constmethods=["push","pop","shift","unshift","splice","sort","reverse"];methods.forEach((method)=>{arrayMethods[method]=function(...args){constresult=arrayProto[method].apply(this,args);//执行原方法thearrayconstob=this.__ob__;letinserted;//用于记录用户插入数组的新元素switch(method){case"push":case"unshift":inserted=args;break;case"splice":inserted=args.slice(2);//splice对应的第三个参数为用户break要插入的数据;default:console.log("拦截的方法不存在");}if(inserted){//在数组方法中,唯一能获取到的就是数组的数据,所以我们需要给被观察的数组对象添加一个key,值为Observer对象,才能获取到方法上的观察者对象ob.observeA阵列(插入);//观察新插入的元素}返回结果;}});