一年前端菜鸟,开始记录一下自己学习的心得体会,欢迎指点,大神轻喷
最近碰到一个需求,需要存储每个页面的数据,刷新之后也要有,因此写了一个自动存储数据的mixin
简单的实现思路:
vue对data里面的数据通过Object.defineProperty做了双向绑定,我们可以通过Reflect.getOwnPropertyDescriptor去拿到vue定义的描述符对象,也就拿到了get,set函数,通过定义自己的get,set函数做一些想要的扩展,比如存储数据,然后代理vue的get,set,重新defineProperty。
下面上代码:
// 先写一个工具函数用来判断对象和数组
const createJudgeType = typeStr => obj => Object.prototype.toString.call(obj) === `[object ${type}]`
// 创建判断对象和数组的函数
const isObject = createJudgeType('Object')
const isArray = createJudgeType('Array') // 可以使用Array.isArray
// 定义数组变异方法数组后面会使用
const arrayMethods = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse',
]
class AutoSave {
// 判断__proto__能否使用
static hasProto = Reflect.has({}, '__proto__')
// 实例化
static of (vm) {
return new AutoSave(vm)
}
constructor (vm) {
this.vm = vm // 当前vue实例
this.arrayProto = null // 根据hasProto来决定是vue定义的数组原型还是当前数组本身
this.init()
}
init () {
// 先判断是否需要这个功能
if (this.isKeepData()) {
const data = this.getData()
// 如果数据存在初始化数据否则存储数据
data ? this.initData(data) : this.setData()
this.reactive(this.vm.$data, this.vm)
}
}
initData (str) {
// 初始化数据
const data = JSON.parse(str)
Object.assign(this.vm, data)
}
isKeepData () {
// 这里可以做一些判断来决定是否要存储数据
// 比如通过路由meta配置变量,
return this.vm.$route.meta.keepData
}
getData () {
// 获取当前路由对应的data
const routeName = this.vm.$route.name
return sessionStorage.getItem(routeName)
}
setData () {
// 通过当前路由名字作为键去存储这个路由对应组件的data
const routeName = this.vm.$route.name
sessionStorage.setItem(routeName, JSON.stringify(this.vm.$data))
}
reactive (obj, vm) {
// 如果data是一个深层对象,只有第一次调用的时候会传递vm实例
// 原因是vue在vm实例上对data对象的每个属性做了一层代理,即this.a实际上访问的是this._data.a
const target = vm || obj
Object.entries(obj).forEach(([key, val]) => {
// 遍历obj给每个属性设置自己的代理
this.proxy(target, key, val)
//继续判断每个值
this.judgeType(val)
})
}
proxy (target, key, val) {
// 获取vue的描述符对象,定义自己的描述符对象
const {
get: vGet,
set: vSet,
} = Reflect.getOwnPropertyDescriptor(target, key)
const that = this
const descriptor = {
enumerable: true,
configurable: true,
get: vGet.bind(target),
set (value) {
vSet.call(target, value)
that.setData()
},
}
Reflect.defineProperty(target, key, descriptor)
}
judgeType (val) {
//如果是对象递归调用reactive
isObject(val) && this.reactive(val)
//如果是数组则代理数组
isArray(val) && this.proxyArray(val)
}
def (target, key, val, enumerable) {
Reflect.defineProperty(target, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true,
})
}
createArrayProto (arr) {
// 如果__proto__可用 并且数组新的原型对象已经存在直接返回
if (AutoSave.hasProto && this.arrayProto) {
return this.arrayProto
}
// 如果__proto__可用,获取vue定义的数组原型,否则拿到数组本身,以此创建新的原型对象
// 并给这个原型对象定义自己的变异方法,这个变异方法里面调用了存储数据的功能和vue的变异方法
const vArrayProto = AutoSave.hasProto ? Reflect.getPrototypeOf(arr) : arr
this.arrayProto = Object.create(vArrayProto)
const that = this
arrayMethods.forEach(method => {
this.def(this.arrayProto, method, function (...args) {
const result = vArrayProto[method].call(this, ...args)
that.setData()
return result
})
})
return this.arrayProto
}
proxyArrayProto (target, src) {
/* eslint-disable */
// 如果__proto__可用,直接给当前数组设置一个新的原型
target.__proto__ = src
}
proxyArrayMethods (target, src) {
// 如果__proto__不可用,在当前数组上定义新的变异方法,这个变异方法里面调用了存储数据的功能和vue的变异方法
arrayMethods.forEach(method => {
this.def(target, method, src[method])
})
}
proxyArray (arr) {
// 根据__proto__是否可用来决定对数组的处理方式
const handle = AutoSave.hasProto ? this.proxyArrayProto : this.proxyArrayMethods
handle(arr, this.createArrayProto(arr))
// 循环数组递归判断里面的每一项
arr.forEach(item => this.judgeType(item))
}
}
export default {
beforeRouteEnter (to, from, next) {
next(vm => AutoSave.of(vm))
}
}
复制代码
由于beforeRouteEnter会在每次路由切换执行所以需要适当优化,比如换成created
应该有更好的解决方案,欢迎大神指点