1. 首页
  2. 技术知识

看了vue的源码,我用proxy实现了更强大的storage

前言

前段时间在看vue关于proxy的源码,也在头条看了很多文章,之前也写了 一篇关于proxy讨论的文章 。为了更好地理解proxy,打算实际应用到项目中。Web Storage的操作是相对比较繁琐的,正好可以作为应用对象,来提升更好的开发体验。


项目的大体功能已经实现,代码已上传至 github ,也提交了npm包。欢迎大家下载使用,也可进行pr,更好的是点个star。你的认可是我不断前进的动力。


正文

项目名为 proxy-web-storage 。主要是使用proxyXlocalStorage和sessionStorage,赋值、取值更加便捷,类型不变,并且支持
null

undefined

NaN

Infinity
等特殊值,还有
Date

RegExp

Function
等类型的存取。同时可以监听数据变化和设置过期时间。


基本功能——存取、删除

使用方法:

import { local, session } from ‘proxy-web-storage’;local.test = ‘Hello proxy-web-storage’; // worksdelete local.test; // works复制代码
当然,以上用
web storage
也可以实现。在MDN有指出可以像访问对象一样访问这些值。

localStorage.colorSetting = ‘#a4509b’;localStorage[‘colorSetting’] = ‘#a4509b’;delete localStorage.colorSetting;复制代码
那么,我的实现有什么新的东西吗?是画蛇添足还是画龙点睛?各位看官接着往下看。


类型保持不变

import { local, session } from ‘proxy-web-storage’;// numberlocal.test = 0;local.test === 0; // true// booleanlocal.test = false;local.test === false; // true// undefinedlocal.test = undefined;local.test === undefined; // true// nulllocal.test = null;local.test === null; // true复制代码
正如上所示,你所赋的值是什么类型,访问的也是对应的类型。而
Web Storage
不管是什么类型,获取的必定是字符串。

localStorage.test = false;localStorage.test // ‘false’复制代码
这会带来一定的麻烦,如果对
localStorage.test
进行判断的话,那么会返回
true
,这跟我们所期待的并不符合。


当然, proxy-web-storage 还支持
Date

RegExp

functiоn
类型。

import { local } from ‘proxy-web-storage’;// Datelocal.test = new Date(‘2000-01-01T00:00:00.000Z’);local.test.getTime() === 946684800000; // true// RegExplocal.test = /d(b+)d/g;local.test.test(“cdbbdbsbz”); // true// functiоnlocal.test = functiоn() {  return ‘Hello proxy-web-storage!’;};local.test() === ‘Hello proxy-web-storage!’; // true复制代码
如果是用
localStorage
来操作的话,可以正常保存,但是当再次获取来使用的时候,必须对值进行转换。
Date
还好,
Date
构造函数支持
dateString

RegExp
会比较麻烦,获取到的值是
‘/d(b+)d/g’
,需要拿到
pattern

flags
才能重新生成
RegExp
类型。而
functiоn
则是借助
eval
重新生成。


不管怎么说,
Date

RegExp

functiоn
这几种类型的重新生成,多多少少需要写点逻辑代码,使用 proxy-web-storage 则可以省去不少烦恼,所以确定不点个star吗?


直接操作数据

从上面的
Date

RegExp
的例子,也可以看出 proxy-web-storage 可以对数据直接进行操作。对于
Object

Array
,那么更是方便了,像正常数据一样进行操作,无需进行重复的取值、parse、操作、赋值流程。


proxy-web-storage 让代码更加简洁,舍去重复繁琐逻辑,解放你的双手。

import { local } from ‘proxy-web-storage’;// Objectlocal.test = { hello: ‘world’ };local.test.hello = ‘proxy-web-storage’; // works// Arraylocal.test = [‘hello’];local.test.push(‘proxy-web-storage’); // workslocal.test.length // 2复制代码
监听数据变化

proxy-web-storage 通过
on

once

off
可以监听数据的变化。

import { local } from ‘proxy-web-storage’;local.on(‘test’, functiоn(newVal, oldVal) {  console.log(‘test’, newVal, oldVal);});local.on(‘test.a’, functiоn(newVal, oldVal) {  console.log(‘test.a’, newVal, oldVal);});local.test = {};// test {} undefinedlocal.test.a = 1;// test.a 1 undefined复制代码
对于
Object

Array
类型的数据,支持二级监听。
obj.a
for
Object
and
list[0]
for
Array
。还实现了
list.length
的监听,具体实现可滑动至下文查看。


对于
off
方法,如果没有传入key,则移除所有监听对象;如果没有传入callback,则移除指定key的所有回调函数;如果传了callback参数,则只移除callback方法。


设置过期时间

proxy-web-storage 通过
setExpires

getExpires

removeExpires
可以设置指定项的过期时间。

import { local } from ‘proxy-web-storage’;local.test = ‘hello proxy-web-storage’;local.setExpires(‘test’, Date.now() + 10000);// after 10’slocal.test // undefined复制代码
setExpires
如果传入的时间值小于当前时间,则直接删除指定项。


问题

  • storage.ts 的 createInstrumentations 方法代码冗余:

([‘clear’, ‘key’] as const).forEach(key => {  instrumentations[key] = target[key].bind(target);});instrumentations.getItem = functiоn(keyName: string) {  return get(target, keyName, receiver);};instrumentations.removeItem = functiоn(keyName: string) {  return deleteProperty(target, keyName);};instrumentations.setItem = functiоn(keyName: string, keyValue: any) {  return set(target, keyName, keyValue, receiver);};…复制代码
原意是模仿vue重写Array方法的写法,但是typescript没入门,怎么写都不对,所以请各位老哥指点一下。

  • 关于Array.length的监听

vue是通过
ReactiveEffect
和闭包缓存
oldValue
实现Array.length的监听,可以说watch相关的逻辑都是如此,具体原理可在掘金搜索查看。而我并没有实现
ReactiveEffect
系统,想看通过proxy是否可以进行监听,所以实现上相对会比较繁琐。


length变化的几种情况有:

  • pushpopshiftunshiftsplice
  • let list = []; list.length = 5;
  • let list = []; list[5] = 5;

其中第二种情况会触发handler的set,可以直接拿到length的变化,所以按照set的正常逻辑可以完成监听。而第三种并不会触发set,但先按下不表。


第一种情是通过重写以上方法,记录方法执行前的length,以及执行后的length。

([‘push’, ‘pop’, ‘shift’, ‘unshift’, ‘splice’] as const).forEach(key => {  instrumentations[key] = functiоn (this: unknown[], …args: unknown[]) {    lengthAltering = true;    const oldLength: number = this.length;    const res = (proxyMap.get(this) as any)[key].APPly(this, args)    if(this.length > oldLength) {      selfEmit(this, ‘length’, this.length, oldLength);    }    lengthAltering = false;    return res  }});复制代码
其中有个
lengthAltering
变量,是用来干什么的呢?


这就要结合第三种情况来讨论了。


let list = []; list.push(1, 2);
相当于
list[0] = 1; list[1] = 2;
。也就是说,第一种情况会影响第三种情况的判断,所以加多一个变量辅助。借助这个变量以及
key
是否大于等于
list.length
,来得到第三种情况的变化监听。

let arrayLength: number | undefined;if(isArray(target) && !lengthAltering) {  arrayLength = target.length;}const result = Reflect.set(target, key, value, receiver);  if(isArray(target) && arrayLength !== undefined && Number(key) >= arrayLength) {  selfEmit(target, ‘length’, target.length, arrayLength);}复制代码
以上是关于Array.length的监听实现,脑袋嗡嗡的,极有可能走了弯路。
想请教有没有更简洁的实现?

原创文章,作者:starterknow,如若转载,请注明出处:https://www.starterknow.com/126393.html

联系我们