├── .gitignore ├── .eslintignore ├── server ├── favicon.ico ├── css │ └── index.css ├── localStorage.html ├── sessionStorage.html ├── index.html └── js │ ├── index.js │ └── webstorage-proxy.js ├── src ├── .babelrc ├── util │ ├── map.js │ ├── update.js │ ├── rewrite.js │ ├── merge.js │ ├── proxy.js │ └── util.js ├── index.js └── WebStorageProxy.js ├── .size-snapshot.json ├── .eslintrc ├── package.json ├── rollup.config.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | server -------------------------------------------------------------------------------- /server/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinchengnuo/webstorage-proxy/HEAD/server/favicon.ico -------------------------------------------------------------------------------- /src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ] 10 | } -------------------------------------------------------------------------------- /server/css/index.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-align: center; 3 | } 4 | 5 | pre { 6 | font-family: "微软雅黑" 7 | } 8 | p { 9 | text-align: center; 10 | } 11 | a { 12 | margin: 0 20px; 13 | } 14 | -------------------------------------------------------------------------------- /.size-snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "dist\\webstorage-proxy.min.js": { 3 | "bundled": 11172, 4 | "minified": 11159, 5 | "gzipped": 3086 6 | }, 7 | "server\\js\\webstorage-proxy.js": { 8 | "bundled": 24313, 9 | "minified": 11442, 10 | "gzipped": 3219 11 | }, 12 | "dist\\webstorage-proxy.js": { 13 | "bundled": 24313, 14 | "minified": 11442, 15 | "gzipped": 3219 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "parserOptions": { 8 | "ecmaFeatures": { 9 | "jsx": false 10 | }, 11 | "sourceType": "module" 12 | }, 13 | "extends": [ 14 | "eslint:recommended" 15 | ], 16 | "rules": { 17 | "no-useless-escape": 0, 18 | "no-empty": 0 19 | } 20 | } -------------------------------------------------------------------------------- /server/localStorage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | localStorage 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /server/sessionStorage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | sessionStorage 8 | 9 | 10 | sessionStorage 11 | 23 | 24 | -------------------------------------------------------------------------------- /server/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | WebStorageProxy开发测试页面 9 | 10 | 11 | 12 | 13 | 14 |

WebStorageProxy开发测试页面

15 |

16 | ./sessionStorage.html 17 | http://www.yinchengnuo.com 18 | ./localStorage.html 19 |

20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@yinchengnuo/webstorage-proxy", 3 | "version": "1.1.1", 4 | "description": "一个可以用操作对象的方式操作sessionStorage/localStorage的JavaScript库。你可以使用它在客户端存储数据,并且支持自定义方法对这些数据进行加密。主要用于监听storage变化、SPA的组件/路由之间和同域名不同页面之间的通信。", 5 | "main": "./src/index.js", 6 | "scripts": { 7 | "test": "npx rollup -c --watch --environment NODE_ENV:development", 8 | "start": "npx rollup -c --environment NODE_ENV:production" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/yinchengnuo/webstorage-proxy.git" 13 | }, 14 | "keywords": [ 15 | "sessionStorage", 16 | "localStorage", 17 | "proxy", 18 | "namespace" 19 | ], 20 | "author": "yinchengnuo", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/yinchengnuo/webstorage-proxy/issues" 24 | }, 25 | "homepage": "https://github.com/yinchengnuo/webstorage-proxy#readme", 26 | "devDependencies": { 27 | "@babel/core": "^7.5.5", 28 | "@babel/preset-env": "^7.5.5", 29 | "eslint": "^6.1.0", 30 | "rollup": "^1.17.0", 31 | "rollup-plugin-babel": "^4.3.3", 32 | "rollup-plugin-commonjs": "^10.0.1", 33 | "rollup-plugin-eslint": "^7.0.0", 34 | "rollup-plugin-flow-entry": "^0.3.1", 35 | "rollup-plugin-livereload": "^1.0.1", 36 | "rollup-plugin-node-resolve": "^5.2.0", 37 | "rollup-plugin-serve": "^1.0.1", 38 | "rollup-plugin-size-snapshot": "^0.10.0", 39 | "rollup-plugin-uglify": "^6.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import serve from 'rollup-plugin-serve' 2 | import babel from 'rollup-plugin-babel' 3 | import { eslint } from 'rollup-plugin-eslint' 4 | import { uglify } from 'rollup-plugin-uglify' 5 | import commonjs from 'rollup-plugin-commonjs' 6 | import resolve from 'rollup-plugin-node-resolve' 7 | import livereload from 'rollup-plugin-livereload' 8 | import { sizeSnapshot } from "rollup-plugin-size-snapshot" 9 | 10 | const production = process.env.NODE_ENV === 'production' 11 | const FormatName = { 12 | format: 'umd', 13 | name: 'WebStorageProxy' 14 | } 15 | let output = [{ 16 | file: 'server/js/webstorage-proxy.js', 17 | ...FormatName 18 | }, { 19 | file: 'dist/webstorage-proxy.js', 20 | ...FormatName 21 | }] 22 | 23 | if (production) { 24 | output = { 25 | file: 'dist/webstorage-proxy.min.js', 26 | ...FormatName 27 | } 28 | } 29 | 30 | export default { 31 | input: 'src/index', 32 | output, 33 | plugins: [ 34 | commonjs(), 35 | resolve(), 36 | babel({ 37 | exclude: 'node_modules/**' 38 | }), 39 | eslint({ 40 | throwOnError: false, 41 | throwOnWarning: true, 42 | include: ['src/**'], 43 | exclude: ['node_modules/**'] 44 | }), 45 | production && uglify(), 46 | !production && serve({ 47 | open: true, 48 | contentBase: 'server/', 49 | port: 80, 50 | }), 51 | !production && livereload({ 52 | watch: 'server/' 53 | }), 54 | sizeSnapshot() 55 | ], 56 | } -------------------------------------------------------------------------------- /src/util/map.js: -------------------------------------------------------------------------------- 1 | import { isFunction } from './util' 2 | 3 | export default function() { //将sessionStorage/localStorage映射到state上 4 | if (this._NAMESPACE) { //当配置对象有命名空间时 5 | let data = window[this._TYPE][this._GETITEM](`${this._WEBSTORAGEPROXY_NAMESPACE}:${this._NAMESPACE}`) 6 | if (data) { //如果 sessionStorage/localStorage 里有命名空间标记的数据就映射到state上 7 | if (isFunction(this.encryption) && isFunction(this.decryption)) { 8 | Object.assign(this.state, JSON.parse(this.decryption(data))) 9 | } else { 10 | Object.assign(this.state, JSON.parse(data)) 11 | } 12 | data = null 13 | } else { //如果没有就在 sessionStorage/localStorage 创建值为空的命名空间 14 | window[this._TYPE][this._SETITEM](`${this._WEBSTORAGEPROXY_NAMESPACE}:${this._NAMESPACE}`, '') 15 | } 16 | } else { //当配置对象没有命名空间时,就把 sessionStorage/localStorage 里面的非命名空间数据遍历到state上 17 | let storage = window[this._TYPE] 18 | let regExp1 = new RegExp(`${this._WEBSTORAGEPROXY_NAMESPACE}:`) 19 | let regExp2 = new RegExp(`${this._WEBSTORAGEPROXY_INDENT_STORAGE}`) 20 | for (let i = 0; i < storage.length; i++) { 21 | if (!storage.key(i).match(regExp1) && !storage.key(i).match(regExp2)) { 22 | try { 23 | this.state[storage.key(i)] = JSON.parse(storage[this._GETITEM](storage.key(i))) 24 | } catch { 25 | this.state[storage.key(i)] = storage[this._GETITEM](storage.key(i)) 26 | } 27 | } 28 | } 29 | storage = null 30 | regExp1 = null 31 | regExp2 = null 32 | } 33 | } -------------------------------------------------------------------------------- /src/util/update.js: -------------------------------------------------------------------------------- 1 | import { dispatch, nameSpaceDispatch, isArray, isObject } from './util' 2 | 3 | export default function (oldValue, ...args) { 4 | let storage = window[this._TYPE] 5 | let key = args[1] 6 | let value = storage[this._GETITEM](key) 7 | if (this._NAMESPACE) { 8 | let spacename = `_WEBSTORAGEPROXY_INDENT_STORAGE:${this._NAMESPACE}` 9 | storage[this._SETITEM](spacename, this.encryption(JSON.stringify(this.state))) 10 | nameSpaceDispatch.call(storage, this, this._TYPE.toLowerCase() + 'change', storage, spacename, this.encryption(JSON.stringify(this.state)), value) 11 | // dispatch.call(storage, this._TYPE.toLowerCase() + 'change', storage, spacename, this.encryption(JSON.stringify(this.state)), value) //开发时使用 12 | storage = null 13 | key = null 14 | value = null 15 | return true 16 | } else { 17 | let keys = Object.keys(this.state) 18 | for (let i = 0; i < keys.length; i ++) { 19 | if (isArray(this.state[keys[i]]) || isObject(this.state[keys[i]])) { 20 | if (JSON.stringify(oldValue[keys[i]]) !== JSON.stringify(this.state[keys[i]])) { 21 | storage[this._SETITEM](key, JSON.stringify(this.state[keys[i]])) 22 | dispatch.call(storage, this._TYPE.toLowerCase() + 'change', storage, key, JSON.stringify(this.state[keys[i]]), value) 23 | return true 24 | } 25 | } else { 26 | if (oldValue[keys[i]] !== this.state[keys[i]]) { 27 | storage[this._SETITEM](key, this.state[keys[i]]) 28 | dispatch.call(storage, this._TYPE.toLowerCase() + 'change', storage, key, JSON.stringify(this.state[keys[i]]), value) 29 | return true 30 | } 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/util/rewrite.js: -------------------------------------------------------------------------------- 1 | import { dispatch, isPrivate } from './util' 2 | 3 | export default function(proto) { 4 | localStorage.setItem(proto._WEBSTORAGEPROXY_INDENT_STORAGE, proto._WEBSTORAGEPROXY_INDENT_LOCALSTORAGE) 5 | sessionStorage.setItem(proto._WEBSTORAGEPROXY_INDENT_STORAGE, proto._WEBSTORAGEPROXY_INDENT_SESSIONSTORAGE) 6 | Storage.prototype[proto._CLEAR] = Storage.prototype.clear 7 | Storage.prototype[proto._GETITEM] = Storage.prototype.getItem 8 | Storage.prototype[proto._SETITEM] = Storage.prototype.setItem 9 | Storage.prototype[proto._REMOVEITEM] = Storage.prototype.removeItem 10 | Storage.prototype.clear = function() { 11 | const clear = i => { 12 | while (i < this.length) { 13 | if (!isPrivate(proto, this.key(i))) { 14 | this[proto._REMOVEITEM](this.key(i)) 15 | } else { 16 | i++ 17 | } 18 | } 19 | return clear 20 | } 21 | clear(0)(0) 22 | } 23 | Storage.prototype.getItem = function(key) { 24 | if (!isPrivate(proto, key)) { 25 | return this[proto._GETITEM](key) 26 | } 27 | return false 28 | } 29 | Storage.prototype.setItem = function(key, value) { 30 | if (!isPrivate(proto, key)) { 31 | let oldValue = this[proto._GETITEM](key) 32 | if (oldValue !== value) { 33 | this[proto._SETITEM](key, value) 34 | this[proto._GETITEM](proto._WEBSTORAGEPROXY_INDENT_STORAGE).match(/sessionStorage/i) && dispatch.call(this, 'sessionstoragechange', this, key, value, oldValue) 35 | this[proto._GETITEM](proto._WEBSTORAGEPROXY_INDENT_STORAGE).match(/localStorage/i) && dispatch.call(this, 'localstoragechange', this, key, value, oldValue) 36 | return true 37 | } 38 | } 39 | return false 40 | } 41 | Storage.prototype.removeItem = function(key) { 42 | if (!isPrivate(proto, key)) { 43 | let oldValue = this[proto._GETITEM](key) 44 | if (oldValue !== null) { 45 | this[proto._REMOVEITEM](key) 46 | this[proto._GETITEM](proto._WEBSTORAGEPROXY_INDENT_STORAGE).match(/sessionStorage/i) && dispatch.call(this, 'sessionstoragechange', this, key, null, oldValue) 47 | this[proto._GETITEM](proto._WEBSTORAGEPROXY_INDENT_STORAGE).match(/localStorage/i) && dispatch.call(this, 'localstoragechange', this, key, null, oldValue) 48 | return true 49 | } 50 | } 51 | return false 52 | } 53 | } -------------------------------------------------------------------------------- /src/util/merge.js: -------------------------------------------------------------------------------- 1 | import { 2 | ret0, //初始化时没有配置钩子函数时返回的钩子函数列表 3 | ret1, //初始化时配置钩子函数时返回的钩子函数列表 4 | isString, //类型判断:string 5 | isObject, //类型判断:object 6 | isFunction, //类型判断:function 7 | nameSpaceCheck, //检测命名空间类型,如果为字符串就直接赋值,如果为函数就执行,并把所有已经存在的命名空间名称放在数组作为参数传入 8 | defaultLifeCircle, //初始化时没有配置任何钩子函数时的默认操作 9 | proxyLifeCircleList, //代理钩子列表使得只能添加不能删除钩子函数 10 | } from './util' 11 | 12 | export default function(arg) { //合并配置项 13 | if (arg.length == 1) { //当只有一个参数时 14 | if (isString(arg[0])) { //当参数为唯一的字符串时 15 | this._TYPE = arg[0] 16 | this._NAMESPACE = null 17 | defaultLifeCircle(this) 18 | } else if (isObject(arg[0])) { //当参数为唯一的对象时 19 | this._TYPE = arg[0].type 20 | nameSpaceCheck(this, arg[0].nameSpace) 21 | this.beforeCreate = isFunction(arg[0].beforeCreate) ? arg[0].beforeCreate : function() {} 22 | this.created = isFunction(arg[0].created) ? arg[0].created : function() {} 23 | this.beforeGet = isFunction(arg[0].beforeGet) ? proxyLifeCircleList(ret1(arg[0].beforeGet)) : proxyLifeCircleList(ret0()) 24 | this.geted = isFunction(arg[0].geted) ? proxyLifeCircleList(ret1(arg[0].geted)) : proxyLifeCircleList(ret0()) 25 | this.beforeSet = isFunction(arg[0].beforeSet) ? proxyLifeCircleList(ret1(arg[0].beforeSet)) : proxyLifeCircleList(ret0()) 26 | this.proxySeted = isFunction(arg[0].proxySeted) ? proxyLifeCircleList(ret1(arg[0].proxySeted)) : proxyLifeCircleList(ret0()) 27 | this.storageSeted = isFunction(arg[0].storageSeted) ? proxyLifeCircleList(ret1(arg[0].storageSeted)) : proxyLifeCircleList(ret0()) 28 | this.beforeDel = isFunction(arg[0].beforeDel) ? proxyLifeCircleList(ret1(arg[0].beforeDel)) : proxyLifeCircleList(ret0()) 29 | this.proxyDeled = isFunction(arg[0].proxyDeled) ? proxyLifeCircleList(ret1(arg[0].proxyDeled)) : proxyLifeCircleList(ret0()) 30 | this.storageDeled = isFunction(arg[0].storageDeled) ? proxyLifeCircleList(ret1(arg[0].storageDeled)) : proxyLifeCircleList(ret0()) 31 | this.storageChanged = isFunction(arg[0].storageChanged) ? proxyLifeCircleList(ret1(arg[0].storageChanged)) : proxyLifeCircleList(ret0()) 32 | this.beforeDestroy = isFunction(arg[0].beforeDestroy) ? arg[0].beforeDestroy : function() {} 33 | this.destroyed = isFunction(arg[0].destroyed) ? arg[0].destroyed : function() {} 34 | } 35 | } else { //当参数大于一时 36 | this._TYPE = arg[0] 37 | nameSpaceCheck(this, arg[2]) 38 | defaultLifeCircle(this) 39 | } 40 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import WebStorageProxy from './WebStorageProxy' 2 | import rewrite from './util/rewrite' 3 | import { proto, isFunction } from './util/util' 4 | 5 | WebStorageProxy.prototype._CLEAR = Symbol('clear') //原生 Storage.prototype 上的 clear 方法重新放在 Storage.prototype 上的新 key 6 | WebStorageProxy.prototype._GETITEM = Symbol('getItem') //原生 Storage.prototype 上的 getItem 方法重新放在 Storage.prototype 上的新 key 7 | WebStorageProxy.prototype._SETITEM = Symbol('setItem') //原生 Storage.prototype 上的 setItem 方法重新放在 Storage.prototype 上的新 key 8 | WebStorageProxy.prototype._REMOVEITEM = Symbol('removeItem') //原生 Storage.prototype 上的 removeItem 方法重新放在 Storage.prototype 上的新 key 9 | WebStorageProxy.prototype._WEBSTORAGEPROXY_NAMESPACE = '_WEBSTORAGEPROXY_NAMESPACE' //命名空间标记 10 | WebStorageProxy.prototype._WEBSTORAGEPROXY_INDENT_STORAGE = '_WEBSTORAGEPROXY_INDENT_STORAGE' //判断sessionStorage/localStorage标识的 key 11 | WebStorageProxy.prototype._WEBSTORAGEPROXY_INDENT_LOCALSTORAGE = '_WEBSTORAGEPROXY_INDENT_LOCALSTORAGE' //判断localStorage标识的 value 12 | WebStorageProxy.prototype._WEBSTORAGEPROXY_INDENT_SESSIONSTORAGE = '_WEBSTORAGEPROXY_INDENT_SESSIONSTORAGE' //判断sessionStorage标识的 value 13 | 14 | export default new Proxy(WebStorageProxy, { //代理 WebStorageProxy 15 | get (target, key) { //使得通过 WebStorageProxy.crypto 时能够将 encryption/decryption(encryptionFun) 两个方法挂载到原型上 16 | if (key === 'crypto') { 17 | if (!target.prototype.encryption && !Storage.prototype[WebStorageProxy.prototype._GETITEM]) { //只有在原型上没有这两个方法且Storage没有被重写时才允许挂载 18 | return (...args) => { //返回一个接受加密解密函数的函数,执行后挂载 19 | if (args.length == 2 && isFunction(args[0]) && isFunction(args[1])) { 20 | args.forEach((e, i) => { 21 | target.prototype[i ? 'decryption' : 'encryption'] = new Proxy(e, { //挂载前对这两个方法进行代理,使它们不能被外部调用 22 | apply (target, ctx, args) { //只有由 WebStorageProxy 生成的对象才能调用 23 | if (proto(ctx) === WebStorageProxy.prototype) { 24 | return Reflect.apply(target, ctx, args) 25 | } 26 | return false 27 | } 28 | }) 29 | }) 30 | Object.freeze(target.prototype) //部署加密解密函数后冻结WebStorageProxy.prototype 31 | } 32 | } 33 | } else { 34 | return false 35 | } 36 | } 37 | return Reflect.get(target, key) 38 | }, 39 | construct (...args) { //在被 new 时简单判断下是否传参 40 | if (args[1].length) { 41 | if (!Storage.prototype[WebStorageProxy.prototype._GETITEM]) { //检测Storage上的方法是否被重写 42 | rewrite(WebStorageProxy.prototype) //如果没重写就重写 43 | } 44 | return Reflect.construct(...args) 45 | } 46 | return new ReferenceError('the length of arguments in WebStorageProxy can not be 0') //没传参数返回错误对象 47 | } 48 | }) -------------------------------------------------------------------------------- /src/WebStorageProxy.js: -------------------------------------------------------------------------------- 1 | import map from './util/map' 2 | import proxy from './util/proxy' 3 | import merge from './util/merge' 4 | import { clearState, proto, listen, isPrivate } from './util/util' 5 | 6 | export default class WebStorageProxy { //WebStorageProxy 本尊 7 | constructor(...arg) { 8 | return (self => { 9 | merge.call(self, arg) //合并配置项 10 | self.beforeCreate.call(window) //执行beforeCreate钩子函数 11 | self.state = Object.create(self) //定义当前对象的状态 12 | map.call(self) //将storage映射到state上 13 | proxy.call(self) //在state上部署Proxy 14 | Promise.resolve().then(() => { //挂载created钩子函数 15 | self.created() //执行created钩子函数 16 | }) 17 | self._TYPE === 'localStorage' && listen(self) 18 | return self.state //返回state 19 | })(this) 20 | } 21 | all () { 22 | return JSON.parse(JSON.stringify(this.state)) 23 | } 24 | use (namespace) { 25 | if (namespace && namespace !== this._NAMESPACE) { 26 | proto(this)._NAMESPACE = namespace 27 | clearState(this) 28 | map.call(this) 29 | return true 30 | } 31 | } 32 | del (namespace) { 33 | (this._NAMESPACEe && this._NAMESPACE === namespace) && this.use() 34 | window[this._TYPE][proto(this)._REMOVEITEM](`${this._WEBSTORAGEPROXY_NAMESPACE}:${namespace}`) 35 | return true 36 | } 37 | has (key) { 38 | return key in this.state 39 | } 40 | clear () { 41 | if (this._NAMESPACE) { 42 | window[this._TYPE][proto(this)._SETITEM](`${this._WEBSTORAGEPROXY_NAMESPACE}:${this._NAMESPACE}`, '') 43 | return true 44 | } 45 | const clear = i => { 46 | while(i < window[this._TYPE].length) { 47 | if (isPrivate(proto(this), window[this._TYPE].key(i))) { 48 | i ++ 49 | } else { 50 | window[this._TYPE].removeItem(window[this._TYPE].key(i)) 51 | } 52 | } 53 | return clear 54 | } 55 | clear(0)(0) 56 | clearState(this) 57 | return true 58 | } 59 | namespace () { 60 | return this._NAMESPACE 61 | } 62 | namespaces () { 63 | let arr = [] 64 | let storage = window[this._TYPE] 65 | let len = storage.length 66 | for(let i = 0; i < len; i ++) { 67 | if (window[this._TYPE].key(i).split(':')[0] === proto(this)._WEBSTORAGEPROXY_NAMESPACE) { 68 | arr.push(window[this._TYPE].key(i).split(':')[1]) 69 | } 70 | } 71 | return arr 72 | } 73 | destroy (del, b) { 74 | this.beforeDestroy.call(this) //执行beforeDestroy钩子函数 75 | del && this.clear() 76 | if (b) { 77 | let p = Storage.prototype 78 | p.clear = p[this._CLEAR] 79 | delete p[this._CLEAR] 80 | p.setItem = p[this._SETITEM] 81 | delete p[this._SETITEM] 82 | p.getItem = p[this._GETITEM] 83 | delete p[this._GETITEM] 84 | p.removeItem = p[this._REMOVEITEM] 85 | delete p[this._REMOVEITEM] 86 | } 87 | window._WebStorageProxyDestoryedFun = this.destroyed 88 | this.revoke() 89 | Promise.resolve().then(() => { //挂载destroyed钩子函数 90 | window._WebStorageProxyDestoryedFun.call(window) //执行destroyed钩子函数 91 | delete window._WebStorageProxyDestoryedFun 92 | }) 93 | } 94 | } 95 | 96 | -------------------------------------------------------------------------------- /src/util/proxy.js: -------------------------------------------------------------------------------- 1 | import update from './update' 2 | import { 3 | proto, //获取对象原型 4 | isArray, //类型判断:array 5 | isObject, //类型判断:object 6 | isPrivate, 7 | isFunction, //类型判断:function 8 | callLifeCircleList, //遍历执行钩子函数列表 9 | lifeCircleNameCheck //检查要追加或获取的钩子函数名称是否存在 10 | } from './util' 11 | 12 | export default function() { //递归代理state 13 | const self = this //保存this引用 14 | const proxy = state => { //定义proxy函数 15 | Object.keys(state).forEach(e => { //遍历传进来的对象 16 | if (isArray(state[e]) || isObject(state[e])) { //属性值是对象或数组就递归 17 | state[e] = proxy(state[e]).proxy 18 | } 19 | }) 20 | return Proxy.revocable(state, { //代理传进来的对象或数组 21 | get(target, key) { //代理get 22 | if (lifeCircleNameCheck(key && proto(state) === self)) { //当key为钩子函数时返回钩子函数列表 23 | return self[key] 24 | } else { //否则就是正常取值 25 | callLifeCircleList(self.beforeGet, target, key) //遍历执行beforeGet钩子函数列表 26 | Promise.resolve().then(() => { 27 | callLifeCircleList(self.geted, target, key) //遍历执行geted钩子函数列表 28 | }) 29 | return target[key] 30 | } 31 | }, 32 | set(target, key, value) { //代理set 33 | if (isFunction(value) && lifeCircleNameCheck(key) && proto(state) === self) { //当key为钩子函数时将其放入钩子函数列表 34 | self[key][self[key].length] = value 35 | return true 36 | } else if (lifeCircleNameCheck(key)) { 37 | Reflect.set(target, key, value) 38 | return true 39 | } else { 40 | if (target[key] !== value) { //当newValue !== oldValue 时才进行下一步 41 | callLifeCircleList(self.beforeSet, target, key, value) //遍历执行beforeSet钩子函数列表 42 | const oldState = JSON.parse(JSON.stringify(self.state)) //set赋值之前先备份当前state 43 | if (isArray(value) || isObject(value)) { //当set的属性值为对象或数组时 44 | target[key] = proxy(value) //对属性值做代理再赋值给目标对象 45 | } else { //当set的属性值为原始值时直接赋值 46 | target[key] = value 47 | } 48 | callLifeCircleList(self.proxySeted, target, key, value) //遍历执行proxySeted钩子函数列表 49 | update.call(self, oldState, target, key, value) //更新storage 50 | callLifeCircleList(self.storageSeted, target, key, value) //遍历执行storageSeted钩子函数列表 51 | return true 52 | } 53 | } 54 | return false 55 | }, 56 | deleteProperty (target, key) { 57 | if (key in target && !self._DELETENOMAPTOSTORAGE) { 58 | if (isPrivate(key)) { 59 | return false 60 | } 61 | const oldState = JSON.parse(JSON.stringify(self.state)) //set赋值之前先备份当前state 62 | callLifeCircleList(self.beforeDel, target, key) //遍历执行beforeSet钩子函数列表 63 | Reflect.deleteProperty(target, key) 64 | callLifeCircleList(self.proxyDeled, target, target, key) //遍历执行proxySeted钩子函数列表 65 | update.call(self, oldState, target, key) //更新storage 66 | callLifeCircleList(self.storageDeled, target, key) //遍历执行storageSeted钩子函数列表 67 | return true 68 | } else if (self._DELETENOMAPTOSTORAGE) { 69 | Reflect.deleteProperty(target, key) 70 | return true 71 | } 72 | return false 73 | } 74 | }) 75 | } 76 | let cancalProxy = proxy(this.state) 77 | this.state = cancalProxy.proxy 78 | this.revoke = cancalProxy.revoke 79 | } -------------------------------------------------------------------------------- /src/util/util.js: -------------------------------------------------------------------------------- 1 | export const ret0 = () => ({ length: 0 }) //初始化时没有配置钩子函数时返回的钩子函数列表 2 | export const ret1 = fun => ({ //初始化时配置钩子函数时返回的钩子函数列表 3 | 0: fun, 4 | length: 1 5 | }) 6 | export const callLifeCircleList = (funArr, that, ...arg) => { //遍历执行钩子函数列表 7 | for (let i = 0; i < funArr.length; i ++){ 8 | funArr[i].call(that, ...arg) 9 | } 10 | } 11 | export const lifeCircleNameCheck = name => { //检查要追加或获取的钩子函数名称是否存在 12 | return name === 'beforeGet' || 13 | name === 'geted' || 14 | name === 'beforeSet' || 15 | name === 'proxySeted' || 16 | name === 'storageSeted' || 17 | name === 'storageChanged' || 18 | name === '_NAMESPACE' || 19 | name === '_DELETENOMAPTOSTORAGE' 20 | } 21 | export const proxyLifeCircleList = state => { //代理钩子列表使得只能添加不能删除钩子函数 22 | return new Proxy(state, { 23 | set(target, key, value) { 24 | if (key == target.length && typeof value === 'function') { 25 | Reflect.set(target, key, value) 26 | target.length = target.length += 1 27 | return true 28 | } 29 | }, 30 | deleteProperty() { 31 | return false 32 | } 33 | }) 34 | } 35 | export const defaultLifeCircle = that => { //初始化时没有配置任何钩子函数时的默认操作 36 | that.beforeCreate = function() {} 37 | that.created = function() {} 38 | that.beforeGet = proxyLifeCircleList(ret0()) 39 | that.geted = proxyLifeCircleList(ret0()) 40 | that.beforeSet = proxyLifeCircleList(ret0()) 41 | that.proxySeted = proxyLifeCircleList(ret0()) 42 | that.storageSeted = proxyLifeCircleList(ret0()) 43 | that.beforeDel = proxyLifeCircleList(ret0()) 44 | that.proxyDeled = proxyLifeCircleList(ret0()) 45 | that.storageDeled = proxyLifeCircleList(ret0()) 46 | that.storageChanged = proxyLifeCircleList(ret0()) 47 | that.beforeDestroy = function() {} 48 | that.destroyed = function() {} 49 | } 50 | export const dispatch = (type, that, key, newValue, oldValue) => { //触发sessionstoragechange/localstoragechange事件 51 | window.dispatchEvent(new StorageEvent(type, { 52 | key, 53 | newValue, 54 | oldValue, 55 | storageArea: that, 56 | url: window.location.href.split('/')[0] + '//' + window.location.href.split('/')[2] 57 | })) 58 | } 59 | export const nameSpaceDispatch = (that, type, storageArea, key, newValue, oldValue) => { //触发sessionstoragechange/localstoragechange事件 60 | let len = that.storageChanged.length 61 | for(let i = 0; i < len; i ++) { 62 | that.storageChanged[i].call(that, new StorageEvent(type, { 63 | key, 64 | newValue, 65 | oldValue, 66 | storageArea, 67 | url: window.location.href.split('/')[0] + '//' + window.location.href.split('/')[2] 68 | })) 69 | } 70 | } 71 | export const nameSpaceCheck = (that, nameSpace) => { //检测命名空间类型, 72 | if (typeof nameSpace === 'string') { //如果为字符串就直接赋值 73 | that._NAMESPACE = nameSpace 74 | } else if (typeof nameSpace === 'function') { //如果为函数就执行,并把所有已经存在的命名空间名称放在数组作为参数传入 75 | let nameSpacesArr = [] 76 | let regExp = new RegExp(`${that._WEBSTORAGEPROXY_NAMESPACE}:`) 77 | for (let i = 0; i < window[that._TYPE].length; i ++) { 78 | if (window[that._TYPE].key(i).match(regExp)) { 79 | nameSpacesArr.push(window[that._TYPE].key(i).replace(regExp, '')) 80 | } 81 | } 82 | that._NAMESPACE = typeof nameSpace(nameSpacesArr) === 'string' ? nameSpace(nameSpacesArr) : null 83 | nameSpacesArr = null 84 | regExp = null 85 | } else { 86 | that._NAMESPACE = null 87 | } 88 | } 89 | export const isString = i => { //类型判断:string 90 | return typeof i === 'string' 91 | } 92 | export const isBoolean = i => typeof i === 'boolean' 93 | export const isFunction = i => Object.prototype.toString.call(i) === '[object Function]' 94 | export const isObject = i => Object.prototype.toString.call(i) === '[object Object]' 95 | export const isArray = i => Object.prototype.toString.call(i) === '[object Array]' 96 | export const isPrivate = (proto, key) => (key.split(':')[0] === proto._WEBSTORAGEPROXY_NAMESPACE) || (key.split(':')[0] === proto._WEBSTORAGEPROXY_INDENT_STORAGE) 97 | export const proto = i => { //获取对象原型 98 | return Object.getPrototypeOf(i) 99 | } 100 | export const listen = that => { //监听 localStorage 变动 101 | window.addEventListener('storage', e => { 102 | if (that._NAMESPACE) { 103 | let regExp = new RegExp(`${that._WEBSTORAGEPROXY_NAMESPACE}:`) 104 | if (e.key.match(regExp) && e.key.split(':')[1] === that._NAMESPACE) { 105 | if (isFunction(that.decryption)) { 106 | Object.assign(that.state, JSON.parse(that.decryption(e.newValue))) 107 | } 108 | } 109 | } else { 110 | if (!isPrivate(e.key)) { 111 | that.state[e.key] = e.newValue 112 | } 113 | } 114 | }) 115 | } 116 | export const clearState = that => { 117 | proto(that)._DELETENOMAPTOSTORAGE = true 118 | Object.keys(that.state).forEach(e => delete that.state[e]) 119 | proto(that)._DELETENOMAPTOSTORAGE = false 120 | } 121 | -------------------------------------------------------------------------------- /server/js/index.js: -------------------------------------------------------------------------------- 1 | const encryption = str => { 2 | let string = escape(str) 3 | let len = string.length; 4 | let result = '' 5 | for (let i = 0; i < len; i ++) { 6 | result += String.fromCharCode(string.charCodeAt(i) + i + 23) 7 | } 8 | return result 9 | } 10 | const decryption = str => { 11 | let string = str 12 | let len = string.length; 13 | let result = '' 14 | for (let i = 0; i < len; i ++) { 15 | result += String.fromCharCode(string.charCodeAt(i) - i - 23) 16 | } 17 | return unescape(result) 18 | } 19 | 20 | localStorage.setItem('native1', JSON.stringify({ value: 'native1' })) 21 | localStorage.setItem('native2', JSON.stringify({ value: 'native2' })); 22 | 23 | sessionStorage.setItem('native1', 'value-native1'); 24 | sessionStorage.setItem('native2', 'value-native2'); 25 | sessionStorage.setItem('native3', 'value-native3'); 26 | sessionStorage.setItem('native4', 'value-native4'); 27 | 28 | sessionStorage.setItem('name', 'storage'); 29 | sessionStorage.setItem('age', 666); 30 | sessionStorage.setItem('skills', JSON.stringify({ 31 | web: { 32 | base: ['storage'], 33 | back: 'chrome', 34 | time: 2 35 | } 36 | })); 37 | sessionStorage.setItem('_WEBSTORAGEPROXY_NAMESPACE:YinChengNuo', encryption(JSON.stringify({ 38 | name: 'yinchengnuo', 39 | age: 18, 40 | skills: { 41 | web: { 42 | base: ['html', 'css', 'js'], 43 | back: 'nodejs', 44 | time: 2 45 | }, 46 | guitar: 'guitar' 47 | }, 48 | sex: true 49 | }))); 50 | 51 | 52 | // let key = '0' 53 | // for (let i = 0; i < 1024 * 1024 * .5; i ++) { 54 | // key += 0 55 | // } 56 | // sessionStorage.setItem(key, '') 57 | 58 | // let str = '' 59 | // for (let i = 0; i < 1024 * 100; i ++) { 60 | // str += 0 61 | // } 62 | 63 | // let value = '' 64 | // let isFull = false 65 | // do { 66 | // try { 67 | // let value = sessionStorage.getItem(key) 68 | // console.log(value.length / 1024 + 'KB', (value.length / 1024 / 1024).toFixed(2) + 'MB') 69 | // value += str 70 | // sessionStorage.setItem(key, value) 71 | // } catch (e) { 72 | // console.log(e) 73 | // isFull = !isFull 74 | // } 75 | // } while (!isFull) 76 | 77 | 78 | const option = { 79 | type: 'sessionStorage', 80 | // nameSpace: (nameSpaces) => { 81 | // return nameSpaces[0] 82 | // }, 83 | beforeCreate() { 84 | console.log('钩子函数:beforedCreate, this is : ', this) //OK 85 | }, 86 | created() { 87 | console.log('钩子函数:created, this is : ', this) //OK 88 | }, 89 | // beforeGet(targrt, key) { 90 | // console.log('钩子函数:beforeGet, key is : ', key) //OK 91 | // }, 92 | // geted(targrt, key) { 93 | // console.log('钩子函数:geted, key is : ', key) //OK 94 | // }, 95 | beforeSet() { 96 | console.log('钩子函数:beforeSet') 97 | }, 98 | proxySeted() { 99 | console.log('钩子函数:proxySeted') 100 | }, 101 | storageSeted() { 102 | console.log('钩子函数:storageSeted') 103 | }, 104 | beforeDel() { 105 | console.log('钩子函数:beforeSet') 106 | }, 107 | proxyDeled() { 108 | console.log('钩子函数:proxySeted') 109 | }, 110 | storageDeled() { 111 | console.log('钩子函数:storageSeted') 112 | }, 113 | storageChanged() { 114 | console.log('钩子函数:storageChanged') 115 | }, 116 | // beforeBeyond() { 117 | // console.log('钩子函数:beforeBeyond, this is : ', this) 118 | // }, 119 | beforeDestroy() { 120 | console.log('钩子函数:beforeDestroy, this is : ', this) 121 | }, 122 | destroyed() { 123 | console.log('钩子函数:destroyed, this is : ', this) 124 | }, 125 | } 126 | 127 | WebStorageProxy.crypto(encryption, decryption) 128 | 129 | const storage = new WebStorageProxy(option) 130 | document.write("
" + JSON.stringify(storage, null, 8) + "
") 131 | 132 | window.addEventListener('sessionstoragechange', e => { 133 | document.getElementsByTagName('pre')[0].innerHTML = JSON.stringify(storage, null, 8) 134 | }) 135 | window.addEventListener('localstoragechange', e => { 136 | console.log(e) 137 | }) 138 | 139 | 140 | // class Test { 141 | // constructor () { 142 | // this.name = 'Test' 143 | // } 144 | // } 145 | 146 | // Test.age = 18 147 | 148 | // const TestProxy = new Proxy(Test, { 149 | // get (target, key) { 150 | // if (key === 'encryption') { 151 | // return fun => { 152 | // target.prototype.encryption = fun 153 | // } 154 | // } 155 | // return Reflect.get(target, key) 156 | // } 157 | // }) 158 | 159 | // console.log(TestProxy) 160 | 161 | 162 | // function Test () { 163 | // Test.setItem = Symbol('setItem') 164 | // console.log(Test.setItem) 165 | // return Test.setItem 166 | // } 167 | // Test.call(window) 168 | 169 | // const s = new Proxy(Test, { 170 | // get (target, key) { 171 | // if (key === 'setItem') { 172 | // return false 173 | // } 174 | // return Reflect.get(target, key) 175 | // }, 176 | // // apply () { 177 | 178 | // // } 179 | // }) 180 | 181 | setTimeout(() => { 182 | // console.log(23444) 183 | // console.log(WebStorageProxy.prototype.decryption.call((new WebStorageProxy('sessionStorage')).__proto__, sessionStorage[WebStorageProxy.prototype._GETITEM]('_WEBSTORAGEPROXY_NAMESPACE:YinChengNuo'))) 184 | }, 3456) 185 | 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 已停止维护 2 | 3 | # 欢迎使用 webstorage-proxy.js 4 | 5 | webstorage-proxy.js 是一个小巧实用JavaScript工具库。你可以使用它用操作对象的方式操作sessionStorage/localStorage。你可以使用它在客户端存储数据,并且支持自定义方法对这些数据进行加密。主要用于监听 sessionStorage/localStorage 变化、SPA的组件/路由之间和同域名不同页面之间的通信。 6 | 7 | ---- 8 | 9 | ## 简介 10 | 11 | WebStorageProxy 做的就是把 sessionStorage/localStorage 的内容映射到一个对象上,最后返回这个对象的代理器。然后当你想要操作 sessionStorage/localStorage时, 你只需要用操作对象的方式操作这个代理器,操作的结果就会映射到 sessionStorage/localStorage 上。当然这只是 WebStorageProxy 的基础功能。当前版本的 WebStorageProxy 还支持 **生命周期函数**、**数据监听**、**同页面监听sessionStorage/localStorage**、**命名空间**、**数据加密**等。 12 | 13 | ---- 14 | 15 | ## 安装 16 | 17 | ### NPM 18 | 19 | > npm i @yinchengnuo/webstorage-proxy 20 | 21 | ### Git 22 | 23 | > git clone git@github.com:yinchengnuo/webstorage-proxy.git 24 | 25 | ### CDN 26 | 27 | > <script src=""></script> 28 | 29 | ---- 30 | 31 | ## 使用 32 | 33 | 引入webstorage-proxy.js,window上就有了一个叫 WebStorageProxy 的类。你可以这样使用它: 34 | 35 | ```javascript 36 | const storage = new WebStorageProxy('sessionStorage') 37 | console.log(sessionStorage.getItem('name')) //null 38 | 39 | storage.name = 'yinchengnuo' 40 | console.log(sessionStorage.getItem('name')) //yinchengnuo 41 | 42 | delete storage.name //true 43 | console.log(sessionStorage.getItem('name')) //null 44 | ``` 45 | 46 | 或者: 47 | 48 | ```javascript 49 | const storage = new WebStorageProxy('sessionStorage') 50 | storage.data= { 51 | name: 'yinchengnuo', 52 | age: 23, 53 | skills: ['web', 'guitar'] 54 | } 55 | console.log(sessionStorage.getItem('data')) //"{"name":"yinchengnuo","age":23,"skills":["web","guitar"]}" 56 | ``` 57 | 58 | 或者: 59 | 60 | ```javascript 61 | // in main.js 62 | import Vue from 'vue' 63 | import WebstorageProxy from '@yinchengnuo/webstorage-proxy' 64 | 65 | Vue.prototype.$storage = new WebstorageProxy('sessionStorage') 66 | 67 | // in component 68 | this.$storage ... 69 | ``` 70 | 71 | 这样使用,在操作数组或对象类型的数据时就会很方便。 72 | 73 | ### API 74 | 75 | | 方法 | 参数 | 描述 | 76 | | ------------ | ------------- | ---------- | 77 | | ***all*** | null | 返回一个新对象,里面包含实例的所有数据| 78 | | ***has*** | string | 返回一个布尔值,表示实例里面是否有指定的 key| 79 | | ***clear***|null| 清空实例和 webStorage里面的 的所有数据 | 80 | 81 | ### 示例 82 | 83 | ```javascript 84 | const storage = new WebStorageProxy('sessionStorage') 85 | storage.data= { 86 | name: 'yinchengnuo', 87 | age: 23, 88 | skills: ['web', 'guitar'] 89 | } 90 | console.log(storage.all(), storage.has('name')) 91 | //{ 92 | // name: "yinchengnuo", 93 | // age: 23, 94 | // skills: ["web","guitar"] 95 | //} 96 | console.log(storage.has('name')) 97 | //false 98 | 99 | storage.clear() 100 | console.log(storage.all()) 101 | //{} 102 | consle.log(sessionStorage.getItem('data')) 103 | //null 104 | ``` 105 | 106 | ### 实例化配置 107 | 108 | **WebStorageProxy 最多可以接收两个参数**。 109 | 110 | 当参数为两个时,第一个必须是一个值为 'sessionStorage' 或 'localStorage'的字符串,第二个参数为字符串或返回字符串的函数作为命名空间。如果参数为函数时,这个函数会接收一个数组,这个数组里包含当前 storage 所有的命名空间: 111 | 112 | ```javascript 113 | const storage1 = new WebStorageProxy('sessionStorage','namespace1') 114 | const storage2 = new WebStorageProxy('sessionStorage',namespace => { 115 | console.log(namespace) //['namespace1'] 116 | return 'namespace2' 117 | }) 118 | ``` 119 | 120 | 当参数为一个时,这个参数可以是是一个值为 'sessionStorage' 或 'localStorage'的字符串,就像上面据的几个例子一样。同时也可以是一个配置对象。完整的配置对象长这个样子: 121 | 122 | ```javascript 123 | //配置对象中的可配置函数分为两种: 124 | //1. 生命周期函数,每个实例只执行一次 125 | //2. 数据监听函数。可在实例生成后追加多个,非箭头函数时this指向操作的key所在的代理对象。 126 | const storage1 = new WebStorageProxy({ 127 | type: 'sessionStorage', 128 | namespace: 'yinchengnuo', 129 | beforeCreate() { 130 | //生命周期函数。非箭头函数时this指向window。在实例生成之前执行。 131 | }, 132 | created() { 133 | //生命周期函数。非箭头函数时this指向实例对象。在实例生成之后执行。 134 | }, 135 | beforeGet(key) { 136 | //数据监听函数。接收要获取key作为参数。在get操作执行之前执行 137 | }, 138 | geted(key) { 139 | //数据监听函数。接收要获取的key作为参数。在get操作执行之后执行 140 | }, 141 | beforeSet() { 142 | //数据监听函数。接收要设置的key和value作为参数。在set操作执行之前执行。 143 | }, 144 | proxySeted() { 145 | //数据监听函数。接收要设置的key和value作为参数。在set操作执行之后执行。 146 | }, 147 | storageSeted() { 148 | //数据监听函数。接收要设置的key和value作为参数。在代理对象上的数据映射到webStorage上之后执行。 149 | }, 150 | beforeDel() { 151 | //数据监听函数。接收要删除的key作为参数。在delete操作执行之前执行。 152 | }, 153 | proxyDeled() { 154 | //数据监听函数。接收要删除的key作为参数。在delete操作执行之后执行。 155 | }, 156 | storageDeled() { 157 | //数据监听函数。接收要删除的key作为参数。在代理对象上的数据映射到webStorage上之后执行。 158 | }, 159 | storageChanged() { 160 | //数据监听函数。在 type 里指定类型的 Storage 实力发生变化时执行。接收一个事件对象作为参数。 161 | }, 162 | beforeDestroy() { 163 | //生命周期函数。非箭头函数时this指向实例对象 164 | }, 165 | destroyed() { 166 | //生命周期函数。非箭头函数时this指向window 167 | } 168 | }) 169 | ``` 170 | 171 | 这些配置对象里的钩子函数看起来很多,其实只有两类:生命周期函数和数据监听函数。 172 | 173 | 生命周期函数只能在实例化时通过配置对象设置,每个生命周期函数在实例对象的生命周期内只执行一次。有四个,分别是:**beforeCreate**、**created**、**beforeDestroy**、**destroyed**。 174 | 数据监听函数不仅仅可以通过实例配置对象设置,也可以在实例化对象生成之后通过赋值的形式追加多个。有9个。分别用于监听4种行为种:get、set、delete和storagechange。9个函数分别是:**beforeGet**、**geted**、**beforeSet**、**proxySeted**、**storageSeted**、**beforeDel**、**proxyDeled**、**storageDeled**,最后一个是**storageChanged**。 175 | 关于这些配置函数的用法会在后面的部分一一讲解。 176 | 177 | ---- 178 | 179 | ## 生命周期函数 180 | 181 | 通过上面的几个小例子,你大概也能知道。在实例化 WebStorageProxy 时,**beforeCreate**、**created**会被相继触发。 182 | 183 | ```javascript 184 | const storage = new WebStorageProxy({ 185 | type: 'sessionStorage', 186 | beforeCreate() { 187 | console.log('beforeCreate') //'beforeCreate' 188 | }, 189 | created() { 190 | console.log('created') //'created' 191 | } 192 | }) 193 | ``` 194 | 195 | 但是**beforeDestroy**、**destroyed**呢?它们何时触发呢?即,如何销毁一个 WebStorageProxy ?你可以使用 destory(del,bool) 方法: 196 | 197 | ### destory(del, bool) 198 | 199 | ```javascript 200 | const storage = new WebStorageProxy({ 201 | type: 'sessionStorage', 202 | beforeDestroy() { 203 | console.log('beforeDestroy') //'beforeCreate' 204 | }, 205 | destroyed() { 206 | console.log('destroyed') //'created' 207 | } 208 | }) 209 | storage.name = 'yinchengnuo' 210 | storage.name //'yinchengnuo' 211 | storage.destory() 212 | storage.name //Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked 213 | ``` 214 | 215 | destory(del, bool)方法接收两个参数。都为布尔值。 216 | 第一个参数表示是否在销毁 实例化对象时清除 WebStorage 里面的数据。 217 | 第二个参数表示 _是否将 Storage.prototype 上的方法,恢复为原生方法_。(在 **同页面监听sessionStorage/localStorage** 部分会有讲解。不建议使用此参数!)。 218 | 219 | ---- 220 | 221 | ## 数据监听 222 | 223 | WebStorageProxy 提供了丰富的数据监听函数,可以让你时刻监听你的数据动向。 224 | 225 | ***但事实上,我个人并不建议你过多的使用这些函数。这些函数的出现只是为了在开发这个工具库的时候方便监控数据变动。但是考虑到这个工具库还不成熟,可能存在未知的风险。因此索性将这些钩子函数暴露出来,在你使用过程中如果出现未知问题就可以实时调控***。 226 | 227 | 因为是监听数据变动,那么这些钩子函数就不能像实例的生命周期函数一样:一个实例只执行一次,而且只能在实例化时的配置对象里定义。我希望它能够更灵活一些。毕竟这个工具库开发的初衷之一就是为了方便。于是你除了可以在通过在实例化时的配置对象里定义以外,还可以这样做: 228 | 229 | ```javascript 230 | const storage = new WebStorageProxy({ 231 | type: 'sessionStorage', 232 | beforeSet (key, value) { 233 | console.log('beforeSet', key, value) 234 | } 235 | }) 236 | 237 | storage.beforeSet = (key, value) => console.log('beforeSet1', key, value) 238 | storage.beforeSet = (key, value) => console.log('beforeSet2', key, value) 239 | storage.beforeSet = (key, value) => console.log('beforeSet3', key, value) 240 | storage.name = 'yinchengnuo' 241 | //'beforeSet', 'name', 'yinchengnuo' 242 | //'beforeSet1', 'name', 'yinchengnuo' 243 | //'beforeSet1', 'name', 'yinchengnuo' 244 | //'beforeSet1', 'name', 'yinchengnuo' 245 | ``` 246 | 247 | 是的,你不仅可以在实例化时的配置对象里定义的同时,在实例对象上追加钩子函数,而且还可以追加多个。原因很简单: 248 | 249 | ***WebStorageProxy 在实例化对象的过程中会把实例对象的数据监听函数属性包装成一个类数组并添加代理,使得 set 行为变为 push 行为,并在适当的时候遍历这个类数组执行里面的函数**。 250 | 251 | 所以(我们以 beforeSet 为例),实例化对象产生以后。这个对象上的 beforeSet 属性就是一个类数组了, 类数组里面的函数相互独立,互不影响。给 beforeSet 属性赋值就是在向这个类数组里面添加钩子函数。还是上面的例子: 252 | 253 | ```javascript 254 | const storage = new WebStorageProxy({ 255 | type: 'sessionStorage', 256 | beforeSet (key, value) { 257 | console.log('beforeSet', key, value) 258 | } 259 | }) 260 | 261 | storage.beforeSet = (key, value) => console.log('beforeSet1', key, value) 262 | 263 | storage.beforeSets[0] 264 | //beforeSet (key, value) { 265 | // console.log('beforeSet', key, value) 266 | //} 267 | storage.beforeSets[0] //(key, value) => console.log('beforeSet1', key, value) 268 | ``` 269 | 270 | 需要注意的是:***追加的钩子函数名和函数值必须合法。当前版本的 WebStorageProxy 不支持删改数据监听钩子函数类数组里的函数,只允许添加***。 271 | 272 | ---- 273 | 274 | ## 同页面监听sessionStorage/localStorage 275 | 276 | H5在新增了 WebStorage 的同时,也为 WebStorage 提供了事件支持。但是原生的 window 上的 storage 事件只能监听到同域下不同页面操作 localStorage 行为。同一个 session 下操作 sessionStorage 和 localStorage 都是监听不到。因为 WebStorage的读写操作都是同步的,而且不能跨域,都是在一个页面里,确实没什么必要监听。但是,随着前端的发展,各种SPA的出现,应该会有不同路由或组件的状态需要根据 WebStorage 的状态变化的业务场景出现,这也是这个工具库开发的初衷之一,做出来以防万一嘛。 277 | 278 | 那 WebStorageProxy 是如何监听 WebStorage 变化的呢?其实很简单,就是重写 Storage.protoytpe 上面的方法,让它们在适当的时候触发 window 上的自定义事件。这两个自定义事件分别是 sessionstoragechange 和 localstoragechange 。你可以监听他们,前提是必须实例化一次 WebStorageProxy: 279 | 280 | ```javascript 281 | new WebStorageProxy('sessionStorage') 282 | 283 | window.addEventListener('sessionstoragechange', e => { 284 | console.log(` 285 | sessionstoragechange, 286 | key: ${e.key}, 287 | newValue: ${e.newValue}, 288 | oldValue: ${e.olaValue} 289 | `) 290 | }) 291 | window.addEventListener('localstoragechange', e => { 292 | console.log(` 293 | localstoragechange, 294 | key: ${e.key}, 295 | newValue: ${e.newValue}, 296 | oldValue: ${e.olaValue} 297 | `) 298 | }) 299 | sessionStorage.setItem('name', 'yinchengnuo') 300 | //'sessionstoragechange, key: name, newValue: yinchengnuo, oldValue: null' 301 | 302 | localStorage.setItem('name', 'yinchengnuo') 303 | //'localstoragechange, key: name, newValue: yinchengnuo, oldValue: null' 304 | ``` 305 | 306 | 当然,如果你还记得刚刚我们说过的数据监听那块。你应该还记得:数据监听函数中有一个 storageChanged 函数。没错,你也可以这样使用它: 307 | 308 | ```javascript 309 | const storage = new WebStorageProxy('sessionStorage') 310 | 311 | storage.storageChanged = e => console.log('listener1') 312 | storage.storageChanged = e => console.log('listener2') 313 | storage.storageChanged = e => console.log('listener3') 314 | 315 | storage.name = 'yinchengnuo' 316 | //'listener1' 'listener2' 'listener3' 317 | ``` 318 | 319 | ## 命名空间 320 | 321 | 为社么要使用命名空间? 322 | 323 | 1. 命名空间是数据加密的基础 324 | 2. 使用命名空间在数据变动时可以减少一次遍历,提升一些性能 325 | 3. 便于多人协作开发 326 | 327 | 便于多人协作开发这个就不赘述了,我学前端时第一次知道这个命名空间概念时,他就是为了解决多人协作开发了。但是为什么命名空间是数据加密的基础呢?(WebStorageProxy 提供了数据加密功能,但是只能加密命名空间之中的数据。详细介绍在下一部分) 328 | 329 | 假设我们现在没有使用命名空间: 330 | 331 | ```javascript 332 | sessionStorage.name = 'sessionStorage' 333 | const storage = new WebStorageProxy('sessionStorage') 334 | 335 | storage.name //'sessionStorage' 336 | ``` 337 | 338 | 如果在我们实例化 WebStorageProxy 对象之前。WebSorage 中已经存在了一些数据。而实例化 WebStorageProxy 之后,这些数据是会被全部映射到 WebStorageProxy 实例对象上的。如果我们采用了加密策略,那么 WebStorageProxy 实例对象修改这些已经存在的数据势必会启用加密算法。如果此时还有一些别的程序正在依赖这些数据,而它们并没有实例化 WebStorageProxy 对象。那他们在读取这些数据时势必会报错。因为它们没有对称解密函数。 339 | 340 | 你可以把不使用命名空间时 WebStorageProxy 实例对象的状态想象为全局。使用命名空间时 WebStorageProxy 实例对象的状态想象为局部。当全局里面的数据一部分来自原有的,一部分来自 WebStorageProxy 实例对象。那么如果我们支持不使用命名空间也能加密的话,就势必要时刻监控每个变量的状态变化,哪个是原有的数据,哪个是实例的数据,哪个从原有的数据变为了实例的数据。如果这样做,那么程序就会变得极其复杂。而且我们为 WebStorageProxy 实例对象提供了 destory 方法,而在 destory 之前究竟要不要对已经加密的数据进行解密处理?这又是一个问题!所以,出于这么多方面的考虑。我将 WebStorageProxy 设计为在不使用命名空间时, 不能使用加密策略。 341 | 342 | 使用命名空间就意味着私密,只有 WebStorageProxy 实例对象才能访问。事实上也正是如此: 343 | 344 | ```javascript 345 | new WebStorageProxy('sessionStorage','yinchengnuo') 346 | ``` 347 | 348 | 当你执行了上面的代码,打开控制台。你就会发现 sessionStorage 里面多了一条数据,它的 key 为: 349 | 350 | >_WEBSTORAGEPROXY_NAMESPACE:yinchengnuo 351 | 352 | value 为空: 353 | 354 | 现在让我们尝试获取它一下: 355 | 356 | ```javascript 357 | new WebStorageProxy('sessionStorage','yinchengnuo') 358 | sessionStorage.getItem('_WEBSTORAGEPROXY_NAMESPACE:yinchengnuo') //false 359 | ``` 360 | 361 | 是的,是 false。因为在 实例化 WebStorageProxy 的时候,WebStorageProxy 已经重写了 Storage.prototype 上面的 clear()、getItem()、setItem()、removeItem()四个方法。使得它们在处理指定 key 值的数据时会选择忽略。因此使用命名空间就意味着私密,除了 WebStorageProxy 实例,外部无法修改。 362 | 但是并不是真的无法修改,因为我们在重写这四个方法的同时并没有丢弃它们,而是用另外一种方式将它们放在了 Storage.prototype 上。如果这个时候你在控制台输入 Storage。prototype 并回车的话,就会看到 Storage。prototype 上面多了四个属性: 363 | 364 | - Symbol('clear') 365 | - Symbol('getItem') 366 | - Symbol('setItem') 367 | - Symbol('removeItem') 368 | 369 | 没错,这四个属性值就是原生的 clear()、getItem()、setItem()、removeItem()四个方法。如何使用它们呢?看下源码就知道了: 370 | 371 | ```javascript 372 | WebStorageProxy.prototype._CLEAR = Symbol('clear') 373 | WebStorageProxy.prototype._GETITEM = Symbol('getItem') 374 | WebStorageProxy.prototype._SETITEM = Symbol('setItem') 375 | WebStorageProxy.prototype._REMOVEITEM = Symbol('removeItem') 376 | ``` 377 | 378 | 没错,我把 Storage.prototype 上四个存储原生方法的属性名得引用放在 WebStorageProxy.prototype 上。这样就能进一步保证这四个方法的安全。如果你想恢复这四个方法,只需要在销毁实例时,将 destory 方法的第二个参数设置为 true 就好了。那么现在我们再来获取下 _WEBSTORAGEPROXY_NAMESPACE:yinchengnuo 的值看一看: 379 | 380 | ```javascript 381 | new WebStorageProxy('sessionStorage','yinchengnuo') 382 | sessionStorage[WebStorageProxy.prototype._GETITEM]('_WEBSTORAGEPROXY_NAMESPACE:yinchengnuo') //'' 383 | ``` 384 | 385 | 这样就能获取命名空间的值了。当然 WebStorageProxy 也提供了一些 API 来操作命名空间。 386 | 387 | | 方法 | 参数 | 描述 | 388 | | ------------ | ------------- | ---------- | 389 | | ***use*** | string/null | 切换命名空间,参数为空时不使用命名空间(切换到全局)| 390 | | ***del*** | string | 删除命名空间,参数为要删除的命名空间名字。如果为当前命名空间,删除前自动执行use()| 391 | | ***namespace***|null| 返回当前命名空间的名字 | 392 | | ***namespaces***|null| 返回所有命名空间的名字 | 393 | 394 | ## 数据加密 395 | 396 | WebStorageProxy 支持自定义的加密策略。允许使用自定义函数来对命名空间之中的数据进行存储。 397 | 398 | 首先你需要准备两个纯函数,用于加密解密字符串。比如我准备的两个: 399 | 400 | ```javascript 401 | const encryption = str => { 402 | let string = escape(str) 403 | let len = string.length; 404 | let result = '' 405 | for (let i = 0; i < len; i ++) { 406 | result += String.fromCharCode(string.charCodeAt(i) + i + 23) 407 | } 408 | return result 409 | } 410 | const decryption = str => { 411 | let string = str 412 | let len = string.length; 413 | let result = '' 414 | for (let i = 0; i < len; i ++) { 415 | result += String.fromCharCode(string.charCodeAt(i) - i - 23) 416 | } 417 | return unescape(result) 418 | } 419 | ``` 420 | 421 | 然后在实例化 WebStorageProxy 之前调用 WebStorageProxy 上的 crypto() 方法: 422 | 423 | ```javascript 424 | WebStorageProxy.crypto(encryption, decryption) 425 | ``` 426 | 427 | ***一定要保证在全局第一次实例化 WebStorageProxy 之前调用 crypto() ,否则加密策略不生效。也因此一个 session 只允许一种加密策略***。 428 | 429 | 此时我们再来看一看,被加密之后的数据变成什么样了: 430 | 431 | ```javascript 432 | let storage = new WebStorageProxy('sessionStorage','yinchengnuo') 433 | storage.data= { 434 | name: 'yinchengnuo', 435 | age: 23, 436 | skills: ['web', 'guitar'] 437 | } 438 | sessionStorage[WebStorageProxy.prototype._GETITEM]('_WEBSTORAGEPROXY_NAMESPACE:yinchengnuo') 439 | // { 456 | if (args.length == 2 && isFunction(args[0]) && isFunction(args[1])) { 457 | args.forEach((e, i) => { 458 | target.prototype[i ? 'decryption' : 'encryption'] = new Proxy(e, { 459 | apply (target, ctx, args) { 460 | if (proto(ctx) === WebStorageProxy.prototype) { 461 | return Reflect.apply(target, ctx, args) 462 | } 463 | return false 464 | } 465 | }) 466 | }) 467 | Object.freeze(target.prototype) 468 | } 469 | } 470 | } else { 471 | return false 472 | } 473 | } 474 | return Reflect.get(target, key) 475 | } 476 | }) 477 | ``` 478 | 479 | 被写入原型链的 decryption() 和 encryption() 方法不能被外部调用。只能被 WebStorageProxy 的实例对象调用。但是你可能记得刚刚我们调用 webStorage 原生方法时: 480 | 481 | ```javascript 482 | sessionStorage[WebStorageProxy.prototype._GETITEM]('_WEBSTORAGEPROXY_NAMESPACE:yinchengnuo') 483 | ``` 484 | 485 | 就像这样,我们只要稍微改动一下: 486 | 487 | ```javascript 488 | WebStorageProxy.prototype.decryption.call((new WebStorageProxy('localStorage')).__proto__, sessionStorage[WebStorageProxy.prototype._GETITEM]('_WEBSTORAGEPROXY_NAMESPACE:yinchengnuo')) 489 | //"{"name":"yinchengnuo","age":23,"skills":["web","guitar"]}" 490 | ``` 491 | 492 | 就解密了经过加密策略加密的数据!!! 493 | 494 | -------------------------------------------------------------------------------- /server/js/webstorage-proxy.js: -------------------------------------------------------------------------------- 1 | 2 | (function(l, i, v, e) { v = l.createElement(i); v.async = 1; v.src = '//' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; e = l.getElementsByTagName(i)[0]; e.parentNode.insertBefore(v, e)})(document, 'script'); 3 | (function (global, factory) { 4 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 5 | typeof define === 'function' && define.amd ? define(factory) : 6 | (global = global || self, global.WebStorageProxy = factory()); 7 | }(this, function () { 'use strict'; 8 | 9 | function _classCallCheck(instance, Constructor) { 10 | if (!(instance instanceof Constructor)) { 11 | throw new TypeError("Cannot call a class as a function"); 12 | } 13 | } 14 | 15 | function _defineProperties(target, props) { 16 | for (var i = 0; i < props.length; i++) { 17 | var descriptor = props[i]; 18 | descriptor.enumerable = descriptor.enumerable || false; 19 | descriptor.configurable = true; 20 | if ("value" in descriptor) descriptor.writable = true; 21 | Object.defineProperty(target, descriptor.key, descriptor); 22 | } 23 | } 24 | 25 | function _createClass(Constructor, protoProps, staticProps) { 26 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 27 | if (staticProps) _defineProperties(Constructor, staticProps); 28 | return Constructor; 29 | } 30 | 31 | var ret0 = function ret0() { 32 | return { 33 | length: 0 34 | }; 35 | }; //初始化时没有配置钩子函数时返回的钩子函数列表 36 | 37 | var ret1 = function ret1(fun) { 38 | return { 39 | //初始化时配置钩子函数时返回的钩子函数列表 40 | 0: fun, 41 | length: 1 42 | }; 43 | }; 44 | var callLifeCircleList = function callLifeCircleList(funArr, that) { 45 | for (var _len = arguments.length, arg = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { 46 | arg[_key - 2] = arguments[_key]; 47 | } 48 | 49 | //遍历执行钩子函数列表 50 | for (var i = 0; i < funArr.length; i++) { 51 | var _funArr$i; 52 | 53 | (_funArr$i = funArr[i]).call.apply(_funArr$i, [that].concat(arg)); 54 | } 55 | }; 56 | var lifeCircleNameCheck = function lifeCircleNameCheck(name) { 57 | //检查要追加或获取的钩子函数名称是否存在 58 | return name === 'beforeGet' || name === 'geted' || name === 'beforeSet' || name === 'proxySeted' || name === 'storageSeted' || name === 'storageChanged' || name === '_NAMESPACE' || name === '_DELETENOMAPTOSTORAGE'; 59 | }; 60 | var proxyLifeCircleList = function proxyLifeCircleList(state) { 61 | //代理钩子列表使得只能添加不能删除钩子函数 62 | return new Proxy(state, { 63 | set: function set(target, key, value) { 64 | if (key == target.length && typeof value === 'function') { 65 | Reflect.set(target, key, value); 66 | target.length = target.length += 1; 67 | return true; 68 | } 69 | }, 70 | deleteProperty: function deleteProperty() { 71 | return false; 72 | } 73 | }); 74 | }; 75 | var defaultLifeCircle = function defaultLifeCircle(that) { 76 | //初始化时没有配置任何钩子函数时的默认操作 77 | that.beforeCreate = function () {}; 78 | 79 | that.created = function () {}; 80 | 81 | that.beforeGet = proxyLifeCircleList(ret0()); 82 | that.geted = proxyLifeCircleList(ret0()); 83 | that.beforeSet = proxyLifeCircleList(ret0()); 84 | that.proxySeted = proxyLifeCircleList(ret0()); 85 | that.storageSeted = proxyLifeCircleList(ret0()); 86 | that.beforeDel = proxyLifeCircleList(ret0()); 87 | that.proxyDeled = proxyLifeCircleList(ret0()); 88 | that.storageDeled = proxyLifeCircleList(ret0()); 89 | that.storageChanged = proxyLifeCircleList(ret0()); 90 | 91 | that.beforeDestroy = function () {}; 92 | 93 | that.destroyed = function () {}; 94 | }; 95 | var dispatch = function dispatch(type, that, key, newValue, oldValue) { 96 | //触发sessionstoragechange/localstoragechange事件 97 | window.dispatchEvent(new StorageEvent(type, { 98 | key: key, 99 | newValue: newValue, 100 | oldValue: oldValue, 101 | storageArea: that, 102 | url: window.location.href.split('/')[0] + '//' + window.location.href.split('/')[2] 103 | })); 104 | }; 105 | var nameSpaceDispatch = function nameSpaceDispatch(that, type, storageArea, key, newValue, oldValue) { 106 | //触发sessionstoragechange/localstoragechange事件 107 | var len = that.storageChanged.length; 108 | 109 | for (var i = 0; i < len; i++) { 110 | that.storageChanged[i].call(that, new StorageEvent(type, { 111 | key: key, 112 | newValue: newValue, 113 | oldValue: oldValue, 114 | storageArea: storageArea, 115 | url: window.location.href.split('/')[0] + '//' + window.location.href.split('/')[2] 116 | })); 117 | } 118 | }; 119 | var nameSpaceCheck = function nameSpaceCheck(that, nameSpace) { 120 | //检测命名空间类型, 121 | if (typeof nameSpace === 'string') { 122 | //如果为字符串就直接赋值 123 | that._NAMESPACE = nameSpace; 124 | } else if (typeof nameSpace === 'function') { 125 | //如果为函数就执行,并把所有已经存在的命名空间名称放在数组作为参数传入 126 | var nameSpacesArr = []; 127 | var regExp = new RegExp("".concat(that._WEBSTORAGEPROXY_NAMESPACE, ":")); 128 | 129 | for (var i = 0; i < window[that._TYPE].length; i++) { 130 | if (window[that._TYPE].key(i).match(regExp)) { 131 | nameSpacesArr.push(window[that._TYPE].key(i).replace(regExp, '')); 132 | } 133 | } 134 | 135 | that._NAMESPACE = typeof nameSpace(nameSpacesArr) === 'string' ? nameSpace(nameSpacesArr) : null; 136 | nameSpacesArr = null; 137 | regExp = null; 138 | } else { 139 | that._NAMESPACE = null; 140 | } 141 | }; 142 | var isString = function isString(i) { 143 | //类型判断:string 144 | return typeof i === 'string'; 145 | }; 146 | var isFunction = function isFunction(i) { 147 | return Object.prototype.toString.call(i) === '[object Function]'; 148 | }; 149 | var isObject = function isObject(i) { 150 | return Object.prototype.toString.call(i) === '[object Object]'; 151 | }; 152 | var isArray = function isArray(i) { 153 | return Object.prototype.toString.call(i) === '[object Array]'; 154 | }; 155 | var isPrivate = function isPrivate(proto, key) { 156 | return key.split(':')[0] === proto._WEBSTORAGEPROXY_NAMESPACE || key.split(':')[0] === proto._WEBSTORAGEPROXY_INDENT_STORAGE; 157 | }; 158 | var proto = function proto(i) { 159 | //获取对象原型 160 | return Object.getPrototypeOf(i); 161 | }; 162 | var listen = function listen(that) { 163 | //监听 localStorage 变动 164 | window.addEventListener('storage', function (e) { 165 | if (that._NAMESPACE) { 166 | var regExp = new RegExp("".concat(that._WEBSTORAGEPROXY_NAMESPACE, ":")); 167 | 168 | if (e.key.match(regExp) && e.key.split(':')[1] === that._NAMESPACE) { 169 | if (isFunction(that.decryption)) { 170 | Object.assign(that.state, JSON.parse(that.decryption(e.newValue))); 171 | } 172 | } 173 | } else { 174 | if (!isPrivate(e.key)) { 175 | that.state[e.key] = e.newValue; 176 | } 177 | } 178 | }); 179 | }; 180 | var clearState = function clearState(that) { 181 | proto(that)._DELETENOMAPTOSTORAGE = true; 182 | Object.keys(that.state).forEach(function (e) { 183 | return delete that.state[e]; 184 | }); 185 | proto(that)._DELETENOMAPTOSTORAGE = false; 186 | }; 187 | 188 | function map () { 189 | //将sessionStorage/localStorage映射到state上 190 | if (this._NAMESPACE) { 191 | //当配置对象有命名空间时 192 | var data = window[this._TYPE][this._GETITEM]("".concat(this._WEBSTORAGEPROXY_NAMESPACE, ":").concat(this._NAMESPACE)); 193 | 194 | if (data) { 195 | //如果 sessionStorage/localStorage 里有命名空间标记的数据就映射到state上 196 | if (isFunction(this.encryption) && isFunction(this.decryption)) { 197 | Object.assign(this.state, JSON.parse(this.decryption(data))); 198 | } else { 199 | Object.assign(this.state, JSON.parse(data)); 200 | } 201 | 202 | data = null; 203 | } else { 204 | //如果没有就在 sessionStorage/localStorage 创建值为空的命名空间 205 | window[this._TYPE][this._SETITEM]("".concat(this._WEBSTORAGEPROXY_NAMESPACE, ":").concat(this._NAMESPACE), ''); 206 | } 207 | } else { 208 | //当配置对象没有命名空间时,就把 sessionStorage/localStorage 里面的非命名空间数据遍历到state上 209 | var storage = window[this._TYPE]; 210 | var regExp1 = new RegExp("".concat(this._WEBSTORAGEPROXY_NAMESPACE, ":")); 211 | var regExp2 = new RegExp("".concat(this._WEBSTORAGEPROXY_INDENT_STORAGE)); 212 | 213 | for (var i = 0; i < storage.length; i++) { 214 | if (!storage.key(i).match(regExp1) && !storage.key(i).match(regExp2)) { 215 | try { 216 | this.state[storage.key(i)] = JSON.parse(storage[this._GETITEM](storage.key(i))); 217 | } catch (_unused) { 218 | this.state[storage.key(i)] = storage[this._GETITEM](storage.key(i)); 219 | } 220 | } 221 | } 222 | 223 | storage = null; 224 | regExp1 = null; 225 | regExp2 = null; 226 | } 227 | } 228 | 229 | function update (oldValue) { 230 | var storage = window[this._TYPE]; 231 | var key = arguments.length <= 2 ? undefined : arguments[2]; 232 | 233 | var value = storage[this._GETITEM](key); 234 | 235 | if (this._NAMESPACE) { 236 | var spacename = "_WEBSTORAGEPROXY_INDENT_STORAGE:".concat(this._NAMESPACE); 237 | 238 | storage[this._SETITEM](spacename, this.encryption(JSON.stringify(this.state))); 239 | 240 | nameSpaceDispatch.call(storage, this, this._TYPE.toLowerCase() + 'change', storage, spacename, this.encryption(JSON.stringify(this.state)), value); // dispatch.call(storage, this._TYPE.toLowerCase() + 'change', storage, spacename, this.encryption(JSON.stringify(this.state)), value) //开发时使用 241 | 242 | storage = null; 243 | key = null; 244 | value = null; 245 | return true; 246 | } else { 247 | var keys = Object.keys(this.state); 248 | 249 | for (var i = 0; i < keys.length; i++) { 250 | if (isArray(this.state[keys[i]]) || isObject(this.state[keys[i]])) { 251 | if (JSON.stringify(oldValue[keys[i]]) !== JSON.stringify(this.state[keys[i]])) { 252 | storage[this._SETITEM](key, JSON.stringify(this.state[keys[i]])); 253 | 254 | dispatch.call(storage, this._TYPE.toLowerCase() + 'change', storage, key, JSON.stringify(this.state[keys[i]]), value); 255 | return true; 256 | } 257 | } else { 258 | if (oldValue[keys[i]] !== this.state[keys[i]]) { 259 | storage[this._SETITEM](key, this.state[keys[i]]); 260 | 261 | dispatch.call(storage, this._TYPE.toLowerCase() + 'change', storage, key, JSON.stringify(this.state[keys[i]]), value); 262 | return true; 263 | } 264 | } 265 | } 266 | } 267 | } 268 | 269 | function proxy () { 270 | //递归代理state 271 | var self = this; //保存this引用 272 | 273 | var proxy = function proxy(state) { 274 | //定义proxy函数 275 | Object.keys(state).forEach(function (e) { 276 | //遍历传进来的对象 277 | if (isArray(state[e]) || isObject(state[e])) { 278 | //属性值是对象或数组就递归 279 | state[e] = proxy(state[e]).proxy; 280 | } 281 | }); 282 | return Proxy.revocable(state, { 283 | //代理传进来的对象或数组 284 | get: function get(target, key) { 285 | //代理get 286 | if (lifeCircleNameCheck(key && proto(state) === self)) { 287 | //当key为钩子函数时返回钩子函数列表 288 | return self[key]; 289 | } else { 290 | //否则就是正常取值 291 | callLifeCircleList(self.beforeGet, target, key); //遍历执行beforeGet钩子函数列表 292 | 293 | Promise.resolve().then(function () { 294 | callLifeCircleList(self.geted, target, key); //遍历执行geted钩子函数列表 295 | }); 296 | return target[key]; 297 | } 298 | }, 299 | set: function set(target, key, value) { 300 | //代理set 301 | if (isFunction(value) && lifeCircleNameCheck(key) && proto(state) === self) { 302 | //当key为钩子函数时将其放入钩子函数列表 303 | self[key][self[key].length] = value; 304 | return true; 305 | } else if (lifeCircleNameCheck(key)) { 306 | Reflect.set(target, key, value); 307 | return true; 308 | } else { 309 | if (target[key] !== value) { 310 | //当newValue !== oldValue 时才进行下一步 311 | callLifeCircleList(self.beforeSet, target, key, value); //遍历执行beforeSet钩子函数列表 312 | 313 | var oldState = JSON.parse(JSON.stringify(self.state)); //set赋值之前先备份当前state 314 | 315 | if (isArray(value) || isObject(value)) { 316 | //当set的属性值为对象或数组时 317 | target[key] = proxy(value); //对属性值做代理再赋值给目标对象 318 | } else { 319 | //当set的属性值为原始值时直接赋值 320 | target[key] = value; 321 | } 322 | 323 | callLifeCircleList(self.proxySeted, target, key, value); //遍历执行proxySeted钩子函数列表 324 | 325 | update.call(self, oldState, target, key, value); //更新storage 326 | 327 | callLifeCircleList(self.storageSeted, target, key, value); //遍历执行storageSeted钩子函数列表 328 | 329 | return true; 330 | } 331 | } 332 | 333 | return false; 334 | }, 335 | deleteProperty: function deleteProperty(target, key) { 336 | if (key in target && !self._DELETENOMAPTOSTORAGE) { 337 | if (isPrivate(key)) { 338 | return false; 339 | } 340 | 341 | var oldState = JSON.parse(JSON.stringify(self.state)); //set赋值之前先备份当前state 342 | 343 | callLifeCircleList(self.beforeDel, target, key); //遍历执行beforeSet钩子函数列表 344 | 345 | Reflect.deleteProperty(target, key); 346 | callLifeCircleList(self.proxyDeled, target, target, key); //遍历执行proxySeted钩子函数列表 347 | 348 | update.call(self, oldState, target, key); //更新storage 349 | 350 | callLifeCircleList(self.storageDeled, target, key); //遍历执行storageSeted钩子函数列表 351 | 352 | return true; 353 | } else if (self._DELETENOMAPTOSTORAGE) { 354 | Reflect.deleteProperty(target, key); 355 | return true; 356 | } 357 | 358 | return false; 359 | } 360 | }); 361 | }; 362 | 363 | var cancalProxy = proxy(this.state); 364 | this.state = cancalProxy.proxy; 365 | this.revoke = cancalProxy.revoke; 366 | } 367 | 368 | function merge (arg) { 369 | //合并配置项 370 | if (arg.length == 1) { 371 | //当只有一个参数时 372 | if (isString(arg[0])) { 373 | //当参数为唯一的字符串时 374 | this._TYPE = arg[0]; 375 | this._NAMESPACE = null; 376 | defaultLifeCircle(this); 377 | } else if (isObject(arg[0])) { 378 | //当参数为唯一的对象时 379 | this._TYPE = arg[0].type; 380 | nameSpaceCheck(this, arg[0].nameSpace); 381 | this.beforeCreate = isFunction(arg[0].beforeCreate) ? arg[0].beforeCreate : function () {}; 382 | this.created = isFunction(arg[0].created) ? arg[0].created : function () {}; 383 | this.beforeGet = isFunction(arg[0].beforeGet) ? proxyLifeCircleList(ret1(arg[0].beforeGet)) : proxyLifeCircleList(ret0()); 384 | this.geted = isFunction(arg[0].geted) ? proxyLifeCircleList(ret1(arg[0].geted)) : proxyLifeCircleList(ret0()); 385 | this.beforeSet = isFunction(arg[0].beforeSet) ? proxyLifeCircleList(ret1(arg[0].beforeSet)) : proxyLifeCircleList(ret0()); 386 | this.proxySeted = isFunction(arg[0].proxySeted) ? proxyLifeCircleList(ret1(arg[0].proxySeted)) : proxyLifeCircleList(ret0()); 387 | this.storageSeted = isFunction(arg[0].storageSeted) ? proxyLifeCircleList(ret1(arg[0].storageSeted)) : proxyLifeCircleList(ret0()); 388 | this.beforeDel = isFunction(arg[0].beforeDel) ? proxyLifeCircleList(ret1(arg[0].beforeDel)) : proxyLifeCircleList(ret0()); 389 | this.proxyDeled = isFunction(arg[0].proxyDeled) ? proxyLifeCircleList(ret1(arg[0].proxyDeled)) : proxyLifeCircleList(ret0()); 390 | this.storageDeled = isFunction(arg[0].storageDeled) ? proxyLifeCircleList(ret1(arg[0].storageDeled)) : proxyLifeCircleList(ret0()); 391 | this.storageChanged = isFunction(arg[0].storageChanged) ? proxyLifeCircleList(ret1(arg[0].storageChanged)) : proxyLifeCircleList(ret0()); 392 | this.beforeDestroy = isFunction(arg[0].beforeDestroy) ? arg[0].beforeDestroy : function () {}; 393 | this.destroyed = isFunction(arg[0].destroyed) ? arg[0].destroyed : function () {}; 394 | } 395 | } else { 396 | //当参数大于一时 397 | this._TYPE = arg[0]; 398 | nameSpaceCheck(this, arg[2]); 399 | defaultLifeCircle(this); 400 | } 401 | } 402 | 403 | var WebStorageProxy = 404 | /*#__PURE__*/ 405 | function () { 406 | //WebStorageProxy 本尊 407 | function WebStorageProxy() { 408 | for (var _len = arguments.length, arg = new Array(_len), _key = 0; _key < _len; _key++) { 409 | arg[_key] = arguments[_key]; 410 | } 411 | 412 | _classCallCheck(this, WebStorageProxy); 413 | 414 | return function (self) { 415 | merge.call(self, arg); //合并配置项 416 | 417 | self.beforeCreate.call(window); //执行beforeCreate钩子函数 418 | 419 | self.state = Object.create(self); //定义当前对象的状态 420 | 421 | map.call(self); //将storage映射到state上 422 | 423 | proxy.call(self); //在state上部署Proxy 424 | 425 | Promise.resolve().then(function () { 426 | //挂载created钩子函数 427 | self.created(); //执行created钩子函数 428 | }); 429 | self._TYPE === 'localStorage' && listen(self); 430 | return self.state; //返回state 431 | }(this); 432 | } 433 | 434 | _createClass(WebStorageProxy, [{ 435 | key: "all", 436 | value: function all() { 437 | return JSON.parse(JSON.stringify(this.state)); 438 | } 439 | }, { 440 | key: "use", 441 | value: function use(namespace) { 442 | if (namespace && namespace !== this._NAMESPACE) { 443 | proto(this)._NAMESPACE = namespace; 444 | clearState(this); 445 | map.call(this); 446 | return true; 447 | } 448 | } 449 | }, { 450 | key: "del", 451 | value: function del(namespace) { 452 | this._NAMESPACEe && this._NAMESPACE === namespace && this.use(); 453 | 454 | window[this._TYPE][proto(this)._REMOVEITEM]("".concat(this._WEBSTORAGEPROXY_NAMESPACE, ":").concat(namespace)); 455 | 456 | return true; 457 | } 458 | }, { 459 | key: "has", 460 | value: function has(key) { 461 | return key in this.state; 462 | } 463 | }, { 464 | key: "clear", 465 | value: function clear() { 466 | var _this = this; 467 | 468 | if (this._NAMESPACE) { 469 | window[this._TYPE][proto(this)._SETITEM]("".concat(this._WEBSTORAGEPROXY_NAMESPACE, ":").concat(this._NAMESPACE), ''); 470 | 471 | return true; 472 | } 473 | 474 | var clear = function clear(i) { 475 | while (i < window[_this._TYPE].length) { 476 | if (isPrivate(proto(_this), window[_this._TYPE].key(i))) { 477 | i++; 478 | } else { 479 | window[_this._TYPE].removeItem(window[_this._TYPE].key(i)); 480 | } 481 | } 482 | 483 | return clear; 484 | }; 485 | 486 | clear(0)(0); 487 | clearState(this); 488 | return true; 489 | } 490 | }, { 491 | key: "namespace", 492 | value: function namespace() { 493 | return this._NAMESPACE; 494 | } 495 | }, { 496 | key: "namespaces", 497 | value: function namespaces() { 498 | var arr = []; 499 | var storage = window[this._TYPE]; 500 | var len = storage.length; 501 | 502 | for (var i = 0; i < len; i++) { 503 | if (window[this._TYPE].key(i).split(':')[0] === proto(this)._WEBSTORAGEPROXY_NAMESPACE) { 504 | arr.push(window[this._TYPE].key(i).split(':')[1]); 505 | } 506 | } 507 | 508 | return arr; 509 | } 510 | }, { 511 | key: "destroy", 512 | value: function destroy(del, b) { 513 | this.beforeDestroy.call(this); //执行beforeDestroy钩子函数 514 | 515 | del && this.clear(); 516 | 517 | if (b) { 518 | var p = Storage.prototype; 519 | p.clear = p[this._CLEAR]; 520 | delete p[this._CLEAR]; 521 | p.setItem = p[this._SETITEM]; 522 | delete p[this._SETITEM]; 523 | p.getItem = p[this._GETITEM]; 524 | delete p[this._GETITEM]; 525 | p.removeItem = p[this._REMOVEITEM]; 526 | delete p[this._REMOVEITEM]; 527 | } 528 | 529 | window._WebStorageProxyDestoryedFun = this.destroyed; 530 | this.revoke(); 531 | Promise.resolve().then(function () { 532 | //挂载destroyed钩子函数 533 | window._WebStorageProxyDestoryedFun.call(window); //执行destroyed钩子函数 534 | 535 | 536 | delete window._WebStorageProxyDestoryedFun; 537 | }); 538 | } 539 | }]); 540 | 541 | return WebStorageProxy; 542 | }(); 543 | 544 | function rewrite (proto) { 545 | localStorage.setItem(proto._WEBSTORAGEPROXY_INDENT_STORAGE, proto._WEBSTORAGEPROXY_INDENT_LOCALSTORAGE); 546 | sessionStorage.setItem(proto._WEBSTORAGEPROXY_INDENT_STORAGE, proto._WEBSTORAGEPROXY_INDENT_SESSIONSTORAGE); 547 | Storage.prototype[proto._CLEAR] = Storage.prototype.clear; 548 | Storage.prototype[proto._GETITEM] = Storage.prototype.getItem; 549 | Storage.prototype[proto._SETITEM] = Storage.prototype.setItem; 550 | Storage.prototype[proto._REMOVEITEM] = Storage.prototype.removeItem; 551 | 552 | Storage.prototype.clear = function () { 553 | var _this = this; 554 | 555 | var clear = function clear(i) { 556 | while (i < _this.length) { 557 | if (!isPrivate(proto, _this.key(i))) { 558 | _this[proto._REMOVEITEM](_this.key(i)); 559 | } else { 560 | i++; 561 | } 562 | } 563 | 564 | return clear; 565 | }; 566 | 567 | clear(0)(0); 568 | }; 569 | 570 | Storage.prototype.getItem = function (key) { 571 | if (!isPrivate(proto, key)) { 572 | return this[proto._GETITEM](key); 573 | } 574 | 575 | return false; 576 | }; 577 | 578 | Storage.prototype.setItem = function (key, value) { 579 | if (!isPrivate(proto, key)) { 580 | var oldValue = this[proto._GETITEM](key); 581 | 582 | if (oldValue !== value) { 583 | this[proto._SETITEM](key, value); 584 | 585 | this[proto._GETITEM](proto._WEBSTORAGEPROXY_INDENT_STORAGE).match(/sessionStorage/i) && dispatch.call(this, 'sessionstoragechange', this, key, value, oldValue); 586 | this[proto._GETITEM](proto._WEBSTORAGEPROXY_INDENT_STORAGE).match(/localStorage/i) && dispatch.call(this, 'localstoragechange', this, key, value, oldValue); 587 | return true; 588 | } 589 | } 590 | 591 | return false; 592 | }; 593 | 594 | Storage.prototype.removeItem = function (key) { 595 | if (!isPrivate(proto, key)) { 596 | var oldValue = this[proto._GETITEM](key); 597 | 598 | if (oldValue !== null) { 599 | this[proto._REMOVEITEM](key); 600 | 601 | this[proto._GETITEM](proto._WEBSTORAGEPROXY_INDENT_STORAGE).match(/sessionStorage/i) && dispatch.call(this, 'sessionstoragechange', this, key, null, oldValue); 602 | this[proto._GETITEM](proto._WEBSTORAGEPROXY_INDENT_STORAGE).match(/localStorage/i) && dispatch.call(this, 'localstoragechange', this, key, null, oldValue); 603 | return true; 604 | } 605 | } 606 | 607 | return false; 608 | }; 609 | } 610 | 611 | WebStorageProxy.prototype._CLEAR = Symbol('clear'); //原生 Storage.prototype 上的 clear 方法重新放在 Storage.prototype 上的新 key 612 | 613 | WebStorageProxy.prototype._GETITEM = Symbol('getItem'); //原生 Storage.prototype 上的 getItem 方法重新放在 Storage.prototype 上的新 key 614 | 615 | WebStorageProxy.prototype._SETITEM = Symbol('setItem'); //原生 Storage.prototype 上的 setItem 方法重新放在 Storage.prototype 上的新 key 616 | 617 | WebStorageProxy.prototype._REMOVEITEM = Symbol('removeItem'); //原生 Storage.prototype 上的 removeItem 方法重新放在 Storage.prototype 上的新 key 618 | 619 | WebStorageProxy.prototype._WEBSTORAGEPROXY_NAMESPACE = '_WEBSTORAGEPROXY_NAMESPACE'; //命名空间标记 620 | 621 | WebStorageProxy.prototype._WEBSTORAGEPROXY_INDENT_STORAGE = '_WEBSTORAGEPROXY_INDENT_STORAGE'; //判断sessionStorage/localStorage标识的 key 622 | 623 | WebStorageProxy.prototype._WEBSTORAGEPROXY_INDENT_LOCALSTORAGE = '_WEBSTORAGEPROXY_INDENT_LOCALSTORAGE'; //判断localStorage标识的 value 624 | 625 | WebStorageProxy.prototype._WEBSTORAGEPROXY_INDENT_SESSIONSTORAGE = '_WEBSTORAGEPROXY_INDENT_SESSIONSTORAGE'; //判断sessionStorage标识的 value 626 | 627 | WebStorageProxy.prototype = new Proxy(WebStorageProxy.prototype, { 628 | deleteProperty: function deleteProperty() { 629 | return false; 630 | } 631 | }); 632 | var index = new Proxy(WebStorageProxy, { 633 | //代理 WebStorageProxy 634 | get: function get(target, key) { 635 | //使得通过 WebStorageProxy.crypto 时能够将 encryption/decryption(encryptionFun) 两个方法挂载到原型上 636 | if (key === 'crypto') { 637 | if (!target.prototype.encryption && !Storage.prototype[WebStorageProxy.prototype._GETITEM]) { 638 | //只有在原型上没有这两个方法且Storage没有被重写时才允许挂载 639 | return function () { 640 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 641 | args[_key] = arguments[_key]; 642 | } 643 | 644 | //返回一个接受加密解密函数的函数,执行后挂载 645 | if (args.length == 2 && isFunction(args[0]) && isFunction(args[1])) { 646 | args.forEach(function (e, i) { 647 | target.prototype[i ? 'decryption' : 'encryption'] = new Proxy(e, { 648 | //挂载前对这两个方法进行代理,使它们不能被外部调用 649 | apply: function apply(target, ctx, args) { 650 | //只有由 WebStorageProxy 生成的对象才能调用 651 | if (proto(ctx) === WebStorageProxy.prototype) { 652 | return Reflect.apply(target, ctx, args); 653 | } 654 | 655 | return false; 656 | } 657 | }); 658 | }); 659 | Object.freeze(target.prototype); //部署加密解密函数后冻结WebStorageProxy.prototype 660 | } 661 | }; 662 | } else { 663 | return false; 664 | } 665 | } 666 | 667 | return Reflect.get(target, key); 668 | }, 669 | construct: function construct() { 670 | for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 671 | args[_key2] = arguments[_key2]; 672 | } 673 | 674 | //在被 new 时简单判断下是否传参 675 | if (args[1].length) { 676 | if (!Storage.prototype[WebStorageProxy.prototype._GETITEM]) { 677 | //检测Storage上的方法是否被重写 678 | rewrite(WebStorageProxy.prototype); //如果没重写就重写 679 | } 680 | 681 | return Reflect.construct.apply(Reflect, args); 682 | } 683 | 684 | return new ReferenceError('the length of arguments in WebStorageProxy can not be 0'); //没传参数返回错误对象 685 | } 686 | }); 687 | 688 | return index; 689 | 690 | })); 691 | --------------------------------------------------------------------------------