Vue2响应式系统之怎么让数组生效
导读:本文共5575.5字符,通常情况下阅读需要19分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要: 1、场景import{observe}from"./reactive";importWatcherfrom"./watcher";constdata={list:["hello"],};observe(data);constupdateComponent=()=>{for(c... ...
目录
(为您整理了一些要点),点击可以直达。1、场景
import{observe}from"./reactive";importWatcherfrom"./watcher";constdata={list:["hello"],};observe(data);constupdateComponent=()=>{for(constitemofdata.list){console.log(item);}};newWatcher(updateComponent);data.list=["hello","liang"];
先可以一分钟思考下会输出什么。
虽然的值是数组,但我们是对进行整体赋值,所以依旧会触发的,触发进行重新执行,输出如下:list
data.list
data.list
set
Watcher
2、场景 2
import{observe}from"./reactive";importWatcherfrom"./watcher";constdata={list:["hello"],};observe(data);constupdateComponent=()=>{for(constitemofdata.list){console.log(item);}};newWatcher(updateComponent);data.list.push("liang");
先可以一分钟思考下会输出什么。
这次是调用方法,但我们对方法什么都没做,因此就不会触发了。push
push
Watcher
3、方案
为了让 还有数组的其他方法也生效,我们需要去重写它们,通过push
代理模式我们可以将数组的原方法先保存起来,然后执行,并且加上自己额外的操作。
/**nottypecheckingthisfilebecauseflowdoesn'tplaywellwith*dynamicallyaccessingmethodsonArrayprototype*//*exportfunctiondef(obj,key,val,enumerable){Object.defineProperty(obj,key,{value:val,enumerable:!!enumerable,writable:true,configurable:true,});}*/import{def}from"./util";constarrayProto=Array.prototype;exportconstarrayMethods=Object.create(arrayProto);constmethodsToPatch=["push","pop","shift","unshift","splice","sort","reverse",];/***Interceptmutatingmethodsandemitevents*/methodsToPatch.forEach(function(method){//cacheoriginalmethodconstoriginal=arrayProto[method];def(arrayMethods,method,functionmutator(...args){constresult=original.apply(this,args);/*****************这里相当于调用了对象set需要通知watcher************************///待补充/*****************************************************************************/returnresult;});});
当调用了数组的或者其他方法,就相当于我们之前重写属性的,上边待补充的地方需要做的就是通知中的。push
set
dep
Watcher
exportfunctiondefineReactive(obj,key,val,shallow){constproperty=Object.getOwnPropertyDescriptor(obj,key);//读取用户可能自己定义了的get、setconstgetter=property&&property.get;constsetter=property&&property.set;//val没有传进来话进行手动赋值if((!getter||setter)&&arguments.length===2){val=obj[key];}constdep=newDep();//持有一个Dep对象,用来保存所有依赖于该变量的WatcherletchildOb=!shallow&&observe(val);Object.defineProperty(obj,key,{enumerable:true,configurable:true,get:functionreactiveGetter(){constvalue=getter?getter.call(obj):val;if(Dep.target){dep.depend();}returnvalue;},set:functionreactiveSetter(newVal){constvalue=getter?getter.call(obj):val;if(setter){setter.call(obj,newVal);}else{val=newVal;}dep.notify();},});}
如上边的代码,之前的是通过闭包,每一个属性都有一个各自的,负责收集和通知。dep
dep
Watcher
Watcher
那么对于数组的话,我们的放到哪里比较简单呢?dep
回忆一下现在的结构。
constdata={list:["hello"],};observe(data);constupdateComponent=()=>{for(constitemofdata.list){console.log(item);}};newWatcher(updateComponent);
上边的代码执行过后会是下图的结构。
list
属性在闭包中拥有了属性,通过,收集到了包含函数的。Dep
new Watcher
updateCompnent
Watcher
同时因为的是数组,也就是对象,通过上篇list
value
["hello"]
响应式系统之深度响应(opens new window)我们知道,它也会去调用函数。Observer
那么,我是不是在中也加一个就可以了。Observer
Dep
这样当我们调用数组方法去修改的值的时候,去通知中的就可以了。['hello']
Observer
Dep
3、收集依赖代码实现
按照上边的思路,完善一下类。Observer
exportclassObserver{constructor(value){/******新增*************************/this.dep=newDep();/************************************/ this.walk(value);}/***遍历对象所有的属性,调用defineReactive*拦截对象属性的get和set方法*/walk(obj){constkeys=Object.keys(obj);for(leti=0;i<keys.length;i++){defineReactive(obj,keys[i]);}}}
然后在中,当前中的也去收集依赖。get
Oberver
dep
exportfunctiondefineReactive(obj,key,val,shallow){constproperty=Object.getOwnPropertyDescriptor(obj,key);//读取用户可能自己定义了的get、setconstgetter=property&&property.get;constsetter=property&&property.set;//val没有传进来话进行手动赋值if((!getter||setter)&&arguments.length===2){val=obj[key];}constdep=newDep();//持有一个Dep对象,用来保存所有依赖于该变量的WatcherletchildOb=!shallow&&observe(val);Object.defineProperty(obj,key,{enumerable:true,configurable:true,get:functionreactiveGetter(){constvalue=getter?getter.call(obj):val;if(Dep.target){dep.depend();/******新增*************************/if(childOb){//当前value是数组,去收集依赖if(Array.isArray(value)){childOb.dep.depend();}}/************************************/}returnvalue;},set:functionreactiveSetter(newVal){constvalue=getter?getter.call(obj):val;if(setter){setter.call(obj,newVal);}else{val=newVal;}dep.notify();},});}
4、通知依赖代码实现
我们已经重写了方法,但直接覆盖全局的方法肯定是不好的,我们可以在类中去操作,如果当前是数组,就去拦截它的方法。array
arrray
Observer
value
array
这里就回到的原型链上了,我们可以通过浏览器自带的,将当前对象的原型指向我们重写过的方法即可。js
__proto__
考虑兼容性的问题,如果不存在,我们直接将重写过的方法复制给当前对象即可。__proto__
import{arrayMethods}from'./array'//上边重写的所有数组方法/*exportconsthasProto="__proto__"in{};*/exportclassObserver{constructor(value){this.dep=newDep(); /******新增*************************/if(Array.isArray(value)){if(hasProto){protoAugment(value,arrayMethods);}else{copyAugment(value,arrayMethods,arrayKeys);}/************************************/}else{this.walk(value);}}/***遍历对象所有的属性,调用defineReactive*拦截对象属性的get和set方法*/walk(obj){constkeys=Object.keys(obj);for(leti=0;i<keys.length;i++){defineReactive(obj,keys[i]);}}}/***AugmentatargetObjectorArraybyintercepting*theprototypechainusing__proto__*/functionprotoAugment(target,src){/*eslint-disableno-proto*/target.__proto__=src;/*eslint-enableno-proto*/}/***AugmentatargetObjectorArraybydefining*hiddenproperties.*//*istanbulignorenext*/functioncopyAugment(target,src,keys){for(leti=0,l=keys.length;i<l;i++){constkey=keys[i];def(target,key,src[key]);}}
还需要考虑一点,数组方法中我们只能拿到值,那么怎么拿到对应的呢。value
value
Observer
我们只需要在类中,增加一个属性来指向自身即可。Observe
exportclassObserver{constructor(value){this.dep=newDep();/******新增*************************/def(value,'__ob__',this)/************************************/if(Array.isArray(value)){if(hasProto){protoAugment(value,arrayMethods);}else{copyAugment(value,arrayMethods,arrayKeys);}}else{this.walk(value);}} ...}
回到最开始重写的方法中,只需要从中拿到去通知即可。array
__ob__
Dep
Watcher
/**nottypecheckingthisfilebecauseflowdoesn'tplaywellwith*dynamicallyaccessingmethodsonArrayprototype*/import{def}from"./util";constarrayProto=Array.prototype;exportconstarrayMethods=Object.create(arrayProto);constmethodsToPatch=["push","pop","shift","unshift","splice","sort","reverse",];/***Interceptmutatingmethodsandemitevents*/methodsToPatch.forEach(function(method){//cacheoriginalmethodconstoriginal=arrayProto[method];def(arrayMethods,method,functionmutator(...args){constresult=original.apply(this,args);/*****************这里相当于调用了对象set需要通知watcher************************/constob=this.__ob__;//notifychangeob.dep.notify();/*****************************************************************************/returnresult;});});
5、测试
import{observe}from"./reactive";importWatcherfrom"./watcher";constdata={list:["hello"],};observe(data);constupdateComponent=()=>{for(constitemofdata.list){console.log(item);}};newWatcher(updateComponent);data.list.push("liang");
这样当调用方法的时候,就会触发相应的来执行函数了。push
Watcher
updateComponent
当前的依赖就变成了下边的样子:
</div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
Vue2响应式系统之怎么让数组生效的详细内容,希望对您有所帮助,信息来源于网络。