likes
comments
collection
share

【开源项目】看了vue的源码,我用proxy实现了更强大的storage

作者站长头像
站长
· 阅读数 48

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

前言

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

正文

项目名为proxy-web-storage。主要是使用proxy代理localStorage和sessionStorage,赋值、取值更加便捷,类型不变,并且支持null、undefined、NaN、Infinity等特殊值,还有Date、RegExp、Function等类型的存取。同时可以监听数据变化和设置过期时间。

基本功能——存取、删除

使用方法:

import { local, session } from 'proxy-web-storage';

local.test = 'Hello proxy-web-storage'; // works
delete local.test; // works

当然,以上用web storage也可以实现。在MDN有指出可以像访问对象一样访问这些值。

localStorage.colorSetting = '#a4509b';
localStorage['colorSetting'] = '#a4509b';
delete localStorage.colorSetting;

那么,我的实现有什么新的东西吗?是画蛇添足还是画龙点睛?各位看官接着往下看。

类型保持不变

import { local, session } from 'proxy-web-storage';

// number
local.test = 0;
local.test === 0; // true

// boolean
local.test = false;
local.test === false; // true

// undefined
local.test = undefined;
local.test === undefined; // true

// null
local.test = null;
local.test === null; // true

正如上所示,你所赋的值是什么类型,访问的也是对应的类型。而Web Storage不管是什么类型,获取的必定是字符串。

localStorage.test = false;
localStorage.test // 'false'

这会带来一定的麻烦,如果对localStorage.test进行判断的话,那么会返回true,这跟我们所期待的并不符合。

proxy-web-storage还支持Date、RegExp、function类型。

import { local } from 'proxy-web-storage';

// Date
local.test = new Date('2000-01-01T00:00:00.000Z');
local.test.getTime() === 946684800000; // true

// RegExp
local.test = /d(b+)d/g;
local.test.test("cdbbdbsbz"); // true

// function
local.test = function() {
  return 'Hello proxy-web-storage!';
};
local.test() === 'Hello proxy-web-storage!'; // true

如果是用localStorage来操作的话,可以正常保存,但是当再次获取来使用的时候,必须对值进行转换。Date还好,new Date支持dateString。RegExp会比较麻烦,获取到的值是'/d(b+)d/g',需要解析出pattern和flags才能重新生成RegExp类型。而function则是借助eval重新生成。

无论怎么实现,Date、RegExp、function这几种类型的重新生成,多多少少都需要写点逻辑代码,使用proxy-web-storage则可以省去不少烦恼,所以确定不点个star吗?

直接操作数据

从上面的Date和RegExp的例子,也可以看出proxy-web-storage可以对数据直接进行操作。对于Object和Array,那么更是方便了,像正常数据一样进行操作,无需进行重复的取值、parse、操作、赋值流程。

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

import { local } from 'proxy-web-storage';

// Object
local.test = { hello: 'world' };
local.test.hello = 'proxy-web-storage'; // works

// Array
local.test = ['hello'];
local.test.push('proxy-web-storage'); // works
local.test.length // 2

监听数据变化

proxy-web-storage通过ononceoff可以监听数据的变化。

import { local } from 'proxy-web-storage';

local.on('test', function(newVal, oldVal) {
  console.log('test', newVal, oldVal);
});
local.on('test.a', function(newVal, oldVal) {
  console.log('test.a', newVal, oldVal);
});

local.test = {};
// test {} undefined

local.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通过setExpiresgetExpiresremoveExpires可以设置指定项的过期时间。

import { local } from 'proxy-web-storage';

local.test = 'hello proxy-web-storage';
local.setExpires('test', Date.now() + 10000);

// after 10's
local.test // undefined

setExpires如果传入的时间值小于当前时间,则直接删除指定项。

问题

  1. storage.ts中的createInstrumentations方法代码冗余:
(['clear', 'key'] as const).forEach(key => {
  instrumentations[key] = target[key].bind(target);
});

instrumentations.getItem = function(keyName: string) {
  return get(target, keyName, receiver);
};
instrumentations.removeItem = function(keyName: string) {
  return deleteProperty(target, keyName);
};
instrumentations.setItem = function(keyName: string, keyValue: any) {
  return set(target, keyName, keyValue, receiver);
};
...

原意是模仿vue重写Array方法的写法,但是typescript没入门,怎么写都不对,所以请各位老哥指点一下。

  1. 关于Array.length的监听

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] = function (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的监听实现,脑袋嗡嗡的,极有可能走了弯路。想请教有没有更简洁的实现?

结语

第一次发布npm包,心情也是比较激荡。路漫漫其修远兮,吾将上下而求索。项目还存在许多可以优化的地方,也不存在一个项目是只发布一个版本的。希望我可以做出更多开源相关的成绩,也期望你可以提出宝贵的意见,或者一起维护这个项目共同进步。

如果觉得小老弟写得还行,不介意的话,来个三连(点赞、收藏、关注),还有点一点proxy-web-storage的star。

你的认可,是我不断前进的动力。

【开源项目】看了vue的源码,我用proxy实现了更强大的storage

本人两年多前端开发经验,熟悉Vue相关技术栈。

如果各位老哥的公司里有坑位,麻烦踢踢我,带带老弟。

转载自:https://juejin.cn/post/7144142689594769415
评论
请登录