#说明

本工具包是对localStoragesessionStorage存取的简单封装,方便使用

笔记分享:hongjilin

一、localStorage简单封装

  1. 实现效果:只是对存入取出时自动转换数据格式,减少代码中操作

  2. 代码示例

```jsx //LocalStorage.ts const LocalStorage = { set: (name: string, value) => { if (!value) throw new Error(‘请传入正确的值’); if (typeof value === ‘object’) localStorage.setItem(name, JSON.stringify(value)); else localStorage.setItem(name, value); },

get: (name: string): any => { let temp = localStorage.getItem(name); if (temp) { try { temp = JSON.parse(temp); } catch (err) { console.error(err); } } return temp; },

remove: name => { localStorage.removeItem(name); } };

export default LocalStorage export{ LocalStorage }

  1. > 3. 使用示例
  2. > ```jsx
  3. import { LocalStorage } from '~/utils';
  4. LocalStorage.set("KEY", data);//存
  5. let data=LocalStorage.get('KEY') //取

二、SessionStorage简单封装

  1. 实现效果:只是对存入取出时自动转换数据格式,减少代码中操作
  2. 代码示例

```jsx const SessionStorage = { set: (name: string, value) => { if (!value) throw new Error(‘请传入正确的值’); if (typeof value === ‘object’) sessionStorage.setItem(name, JSON.stringify(value)); else sessionStorage.setItem(name, value); },

get: (name: string): any => { let temp = sessionStorage.getItem(name); if (temp) { try { temp = JSON.parse(temp); } catch (err) { console.error(err); } } return temp; },

remove: name => { sessionStorage.removeItem(name); } }; export{ SessionStorage }

  1. > 2. 调用(同上`localStorage简单封装`)
  2. <a name="920ef7ad"></a>
  3. # 三、利用命名空间封装-实现复杂存储
  4. > 命名空间作用:在多人合作写脚本的时候,会发生方法名冲突的情况,用JS的命名空间能解决这个问题,若全局空间中已有同名对象,则不覆盖该对象;否则创建一个新的命名空间,具体效果可以看下面代码例子。
  5. > js会将大部分需要的功能封装进来,功能更齐全,此封装函数会用到部分`lodash`函数,当然也只是类似防抖这种小功能,但是别人既然写好的而且写的不错就没必要都用自己写的,当然,类似功能我自己也有封装,详见本人[`自封装JavaScript工具包`](https://gitee.com/hongjilin/hongs-study-notes/tree/master/%E7%BC%96%E7%A8%8B_%E8%87%AA%E5%B0%81%E8%A3%85utils%E6%88%96%E7%BB%84%E4%BB%B6%E6%95%B4%E5%90%88%E7%AC%94%E8%AE%B0/)
  6. > 相关知识点详解请看本人[`前端-(html+css+js)笔记中的js部分`](https://gitee.com/hongjilin/hongs-study-notes/tree/master/%E7%BC%96%E7%A8%8B_%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0),内部本人对命名空间及闭包等概念进行了详细记录
  7. <a name="3027249e"></a>
  8. ## Ⅰ-需求场景分析
  9. > 举个栗子:当我们同一项目有两个客户端,登录后我们要分别将其登录数据存入本地,如:token userData等,但明明都属于登录时的数据,为了方便归类也为了防止本地变量混乱,就可以使用此方法,这样我们可以有效区分本地存储数据,并且使用方便
  10. <a name="d9b89dd5"></a>
  11. ## Ⅱ-功能实现
  12. > 本地存储的增删改查,并且通过命名空间,闭包的相关知识,可以很轻松的将不同常量直接添加到一个对象变量中并存到本地
  13. > ![](JavaScirpt%E5%B7%A5%E5%85%B7%E5%B0%81%E8%A3%85%E4%B8%AD%E7%9A%84%E5%9B%BE%E7%89%87/%E6%9C%AC%E5%9C%B0%E5%AD%98%E5%82%A8(LocalStorage%E3%80%81Session)%E7%9B%B8%E5%85%B3%E5%B0%81%E8%A3%85%E4%B8%AD%E7%9A%84%E5%9B%BE%E7%89%87.png#alt=image-20210521202047347)
  14. <a name="1b84ad30"></a>
  15. ## Ⅲ-封装代码实现
  16. > ```tsx
  17. import {
  18. //检查 value 是否是普通对象.也就是说该对象由 Object 构造函数创建,或者 [[Prototype]] 为 null 。
  19. isPlainObject,
  20. //检查 value 是否是 Array 类对象。
  21. isArray,
  22. //创建一个 debounced(防抖动)函数
  23. debounce
  24. } from 'lodash/fp';
  25. interface Params {
  26. key: any;
  27. value?: any;
  28. }
  29. interface StorageOperate {
  30. add?: ({ key, value }: Params) => void;
  31. remove?: ({ key }: Params) => void;
  32. get?: ({ key }: Params) => any;
  33. clear?: () => void;
  34. }
  35. //LocalStorage 类型
  36. const LOCALSTORAGE = 'LOCALSTORAGE';
  37. //Session Storage类型
  38. const SESSIONSTORAGE = 'SESSIONSTORAGE';
  39. //对象类型
  40. const OBJECTSTORAGE = 'OBJECTSTORAGE';
  41. //LocalStorage存储类型的操作函数重写
  42. const _localStorage: StorageOperate = (() => {
  43. //将window的localStorage赋值到_db上
  44. const _db = window.localStorage;
  45. return {
  46. //实际上只是二次封装,统一调用方式
  47. add({ key, value }) {
  48. // 当调用其add方法函数时,实际上是去调用其`setItem方法`
  49. _db?.setItem(key, value);
  50. },
  51. remove({ key }) {
  52. _db?.removeItem(key);
  53. },
  54. get({ key }) {
  55. return _db?.getItem(key) || null;
  56. },
  57. clear() {
  58. _db?.clear();
  59. },
  60. };
  61. })();
  62. //SessionStorage存储类型的操作函数重写
  63. const _sessionStorage: StorageOperate = (() => {
  64. const _db = window.sessionStorage;
  65. return {
  66. add({ key, value }) {
  67. _db?.setItem(key, value);
  68. },
  69. remove({ key }) {
  70. _db?.removeItem(key);
  71. },
  72. get({ key }) {
  73. return _db?.getItem(key) || null;
  74. },
  75. clear() {
  76. _db?.clear();
  77. },
  78. };
  79. })();
  80. //对象存储类型操作函数
  81. const _objectStorage: StorageOperate = (() => {
  82. let _db = {};
  83. return {
  84. add({ key, value }) {
  85. _db[key] = value;
  86. },
  87. remove({ key }) {
  88. delete _db[key];
  89. },
  90. get({ key }) {
  91. return _db[key];
  92. },
  93. clear() {
  94. _db = {};
  95. },
  96. };
  97. })();
  98. //声明私有变量_DB对象 此处`db`在构造函数中已经经过判断,转换为特定类型的存储对象了
  99. const _DB = {
  100. _get({ db, domain, type }) {
  101. //根据传入的已经转换的`db`对象调用其获取方法获取本地存储数据
  102. let _val = db.get({ key: domain });
  103. let _obj;
  104. try {
  105. //如果取得的_val数据是空的,或者获取的类别是对象存储,则赋予`_val(如果_val为空则赋予空对象)`
  106. //如果不为空,将得到的`_val`数据格式转换为json对象
  107. _obj = !_val || type === OBJECTSTORAGE ? _val || {} : JSON.parse(_val);
  108. } catch ($$) {//此处$$只是一个占位,无作用
  109. //如果发生异常,则直接赋予空对象
  110. _obj = {};
  111. }
  112. return _obj;//最后将处理好的json对象数据返回
  113. },
  114. //set写入方法
  115. _set(obj: any, { db, domain, type }) {
  116. //如果传入为空,则返回错误
  117. if (!obj) return false;
  118. //如果传入的是一个对象或者 是一个数组且类型不为对象存储时,就进行类型转换
  119. if (isPlainObject(obj) || (isArray(obj) && type !== OBJECTSTORAGE)) obj = JSON.stringify(obj);
  120. //根据传入的已经转换的`db`对象调用其写入方法写入本地存储数据
  121. db.add({ key: domain, value: obj });
  122. return true;
  123. },
  124. //删除方法
  125. _remove({ db, domain }) {
  126. //根据传入的已经转换的`db`对象调用其删除方法删除本地存储数据
  127. db.remove({ key: domain });
  128. },
  129. };
  130. //定义DB类,这里是对于_DB的调用
  131. class DB {
  132. source: any = {};
  133. option = null;
  134. backup = null;
  135. setOrigin = debounce(10)(function () {
  136. _DB._set(this.source, this.option);
  137. });
  138. /**构造函数
  139. *调用示例 const loginDB = new DB('LOGIN', DB.LOCALSTORAGE);
  140. * @param param0 {用作存储的key名 , 存储类型 }
  141. */
  142. constructor({ domain, storageType = OBJECTSTORAGE }) {
  143. this.option = {
  144. //用作后续存储的key名,可以省略后续输入
  145. domain,
  146. //存储类型
  147. type: storageType,
  148. //通过存储类型判断决定db使用上述什么类型的存储操作函数
  149. //以达到同一函数根据不同参数做出不同反应的效果
  150. db:
  151. storageType === LOCALSTORAGE
  152. ? _localStorage //如果是LocalStorage类型
  153. : storageType === SESSIONSTORAGE
  154. ? _sessionStorage//如果SessionStorage类型
  155. : _objectStorage,//如果是其他类型,则用对象存储
  156. };
  157. //此处是`flag`作用-->当传入的存储类型不是对象存储时为`true`
  158. this.backup = storageType !== OBJECTSTORAGE;
  159. //当类别不为对象存储时 this.source以该配置信息获取数据,此处得到的数据应是json格式对象或者空对象
  160. //此处将其提前存储,后续在构造函数get方法中可以直接调用,节省性能
  161. if (this.backup) this.source = _DB._get(this.option);
  162. }
  163. /** 重新初始化函数
  164. * 针对特殊场景防错方法:
  165. 1. 因为上面_update中调用的`setOrigin()`方法是防抖功能,延迟设置storage来达到防止频繁操作storage(异步操作)
  166. 2. 但也因为如此,在某些特定场景下导致初始化拿不到数据,举个栗子:
  167. 1) 当你同一浏览器开两个本项目页面,当我将storage所有数据清空后或者第一次使用时,先登录一个页面账户,在登陆另一个页面账户
  168. 2) 登录账户的token是存在storage,那么我在登录第二个账户时,第一个账户的token就会被删除,导致不同账号却能挤下线
  169. * 原因:
  170. 1. 当你打开两个页面时,其实两个页面都初始化了,这时`this.source`都为undefined,但我在其中一个页面登录后写入,另外一个页面却不会监听到,
  171. 2. 导致另一个页面按照storage为空处理,直接覆盖,导致上述情况发生
  172. * 解决:
  173. 调用时在重新初始化`this.source`即可
  174. * 该函数调用:如上述特殊情况时调用此方法(初始化使用),通常就是这种登录初始化渲染的极端情况
  175. */
  176. syncSource() {
  177. if (this.backup) this.source = _DB._get(this.option);
  178. }
  179. /**
  180. * 外部调用的获取函数
  181. * @param key 获取数据的KEY
  182. * @param getOriginal 是否强制重新获取 -->因为有可能在构造时传入的是对象存储类型,导致`this.source`为空
  183. * @returns
  184. */
  185. get(key: string, getOriginal?: boolean ) {
  186. //如果`getOriginal`为true时根据构造函数时传入的option获取该类型的Storage 否则直接用调用构造函数时取得的数据
  187. //此处是必要的,因为删除后将其置空了,所以置空后再次get就需要此步,否则取不到值
  188. const source = getOriginal ? _DB._get(this.option) : this.source
  189. //这时候拿到的source其实是一个对象,里面存了多个json对象,这时候根据key获取其中具体的属性,详见运行示例截图
  190. return key !== undefined ? source[key] : this.source;//如果key传入空,则直接返回所有
  191. }
  192. /**
  193. * 私有--更新方法,即重新写入this.source
  194. * @param imd 是否进行防抖更新
  195. */
  196. _update(imd?: boolean) {
  197. if (!imd) this.setOrigin(); //如果为false,则防抖
  198. else _DB._set(this.source, this.option);//为true则不防抖
  199. }
  200. /**
  201. * 外部调用的写入函数
  202. * 此处是给实例化后的该json对象写入特定key于value
  203. * @param key 写入的key
  204. * @param val 要写入的value
  205. * @param imd 是否防抖 默认false
  206. * @returns
  207. */
  208. set(key: any, val: any, imd = false) {
  209. //如果传入的key是空的,返回空字符串,并且不写入
  210. if (key === undefined) return '';
  211. this.source[key] = val; //将source对象中新创一个[key]属性并赋值val
  212. this._update(imd); //调用更新,即将this.source重新写入到本地中
  213. return true;
  214. }
  215. //复制,将传入的对象直接复制到 this.source上,随后直接写入
  216. assign(obj: any, imd?: boolean) {
  217. if (!isPlainObject(obj) && !isArray(obj)) {
  218. console.log('value 必须是 object 或 array 类型');
  219. return false;
  220. }
  221. this.source = obj;
  222. this._update(imd);
  223. return true;
  224. }
  225. //删除本地存储
  226. remove(key?: any, imd?: boolean) {
  227. //如果删除不传入key,则删除当前实例下的数据,然后通过调用` _DB._remove`进行删除
  228. if (key === undefined) {
  229. this.source = {}; //将 this.source置空
  230. _DB._remove(this.option); //删除本地存储
  231. return;
  232. }
  233. //如果当前删除特定属性的不为空
  234. if (this.source[key] !== undefined) {
  235. //如果为数组,且key为下标的话 使用`splice`删除
  236. if (isArray(this.source) && /^([1-9]\d*)|0$/.test(key)) this.source?.splice(key, 1);
  237. else delete this.source[key];
  238. //删除完后再写入本地
  239. this._update(imd);
  240. }
  241. return true;
  242. }
  243. clear() {
  244. return this.remove(); //调用上面的remove,不传值即使置空
  245. }
  246. }
  247. //导出 且此处使用一个自运行函数形成闭包,即命名空间
  248. export default (function () {
  249. //此处是利用闭包的原理,每次调用该实例操作的都是此数,可以理解为此数是盛放所有new DB() 类型的容器
  250. let store = {};
  251. let index = 0;
  252. const Storage = function (domain?: string, storageType?: string): void {
  253. //如果不传入key 则默认随机生成key且不重复
  254. if (!domain) {
  255. domain = +new Date() + '-' + index;
  256. index++;
  257. }
  258. //如果不存在该属性,则进行子实例创建,并挂载到store对象上
  259. if (!store[domain]) store[domain] = new DB({ domain, storageType });
  260. return store[domain];
  261. };
  262. //清理函数
  263. Storage.clear = function (domain?: string) {
  264. //如果不传入参数,就将所有类型的全删除
  265. if (!domain) {
  266. _localStorage.clear();
  267. _sessionStorage.clear();
  268. _objectStorage.clear();
  269. store = {};
  270. } else {
  271. if (store[domain]) {
  272. store[domain].clear();
  273. store[domain] = null;
  274. }
  275. }
  276. };
  277. Storage.LOCALSTORAGE = LOCALSTORAGE;
  278. Storage.SESSIONSTORAGE = SESSIONSTORAGE;
  279. Storage.OBJECTSTORAGE = OBJECTSTORAGE;
  280. //此处是环境变量配置,不需要
  281. // const __DEV__ = process.env.NODE_ENV == 'development';
  282. // if (__DEV__) {
  283. // window['abs'] = store;
  284. // }
  285. return Storage;
  286. })();

Ⅳ-调用与使用

  1. 声明创建实例
  1. import DB from '~/utils/DB';
  2. //此处可以直接调用到内部的常量是因为有做返回处理
  3. //此处可以 `new DB('PLATFORM', "常量名");`这样调用是因为命名空间的原因
  4. export const platformDB = new DB('PLATFORM',"常量名");

此处可以 new DB('PLATFORM', DB.SESSIONSTORAGE);这样调用是因为命名空间的原因

  1. 调用—>此间大写都是常量
  1. //取
  2. const tabPanes = platformDB.get("常量名");
  3. const activeKey = platformDB.get("常量名");
  4. //存
  5. platformDB.set("常量名", this.tabPanes);
  6. //删
  7. platformDB.remove("常量名");
  1. 重新初始化函数调用 —针对特殊场景检查
  1. /**此处有一个BUG :
  2. 1. 使用同一浏览器,同时打开两个页面,清空LicalStrage后
  3. 2. 先后登录 用户端后台和 运维端后台,先登录的那个用户的LicalStrage会被覆盖
  4. 3. 但是随后继续重新登录后就都不会出问题
  5. */
  6. if (isEmpty(loginDB.get())) {
  7. // 因为loginDB使用对象保存key/value延迟设置storage来达到防止频繁操作storage,
  8. //因此在另一个页面清空storage,另一个页面对象在不刷新情况无法初始化感知,针对特殊场景检查。
  9. loginDB.syncSource();
  10. }

Ⅴ-函数概要截图

本地存储(LocalStorage、Session)相关封装 - 图1%E7%9B%B8%E5%85%B3%E5%B0%81%E8%A3%85%E4%B8%AD%E7%9A%84%E6%A6%82%E8%A6%81.png#alt=image-20210521202903681)