Vue 源码分析之proxy代理
当我们在使用Vue进行数据设置时,通常初始化格式为:
let data = {
age: 12,
name: 'yang'
}
// 实例化Vue对象
let vm = new Vue({
data
})
// 输出
vm.age // 12
vm.name // 'yang'
vm.data.age // throw error
我们发现,我们访问vm实例的对象属性时,是直接通过 vm.age
访问,而通过vm.data.age
访问则会报错。
处于好奇,初窥Vue的源码,才了解一二,这里就与大家一同分享一下:
实现步骤
看完源码大致梳理一下流程,主要是下面三点:
- 实例化Vue
- 挂载data
- 代理data
那么我们来简单的实例一个Viwe(仿Vue)类,来实现文章开头展示的功能。
1. 实例化Vue
即创建一个vm实例对象,给View类传入data对象
// 创建一个View类
const View = function (options) {
console.log(this, options)
}
// 实例化View
const vm = new View({
data: {
age: 12,
name: 'yang'
}
})
// 此时,无法访问data中的属性
vm // {}
vm.age // undefined
vm.name // undefined
2. 挂载data
紧接上一步,我们开始为vm挂载_data对象(私有对象以’_'起始),那么,我们就要准备改造我们的View类了。
const vm = function (options) {
// 取出data对象
const data = options.data || {}
// 将data 指向this._data
this._data = data
}
// 实例化View
const vm = new View({
data: {
age: 12,
name: 'yang'
}
})
// 此时,无法访问data中的属性
vm // {_data:{...}}
vm._data // {age:12,name:'yang'}
vm._data.age // 12
vm._data.name // 'yang'
vm.age // undefined
vm.name // undefined
3. 代理data
在挂载data的时候,可以通过vm._data
访问data中的对象属性了,但是这和我们的预期还是差一点。
这里,我们使用Object.defineProperty
来代理vm._data
中的对象属性。
关于Object.defineProperty
使用教程,请参考MDN文档
下面我们继续改造我们的View类
// 定义一个空函数
const noop = function () {}
// 定义一个默认属性配置
const defineProperty = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
const View = function (options) {
this._data = options.data || {}
// 获取this._data中的属性名
Object.keys(this._data).forEach(key => {
// 代理
defineProperty.get = function dataGetter() {
return this._data[key]
}
defineProperty.set = function dataSetter (val) {
this._data[key] = val
}
// 关键点,代理this._data中的属性
Object.defineProperty(this,key,defineProperty)
})
}
const vm = new View({
age: 12,
name: 'yang'
})
vm // {_data:{...},age:12,name:'yang'}
vm._data // {age:12,name:'yang'}
vm.age // 12
vm.name // 'yang'
改到这里,一个基本的代理实现就完成啦~,下面,我们看看是否能够使用其他方式达到相同的改造效果呢?
二、使用ES6原生 Proxy实现
先来了解一下Proxy的简单用法:
例子一:
/**
* 定义:
* target type: Object
* handler type: Object
**/
let proxy = new Proxy(target,handler)
// 用法
let target = {}
let proxy = new Proxy(target,{
get: function () {
return 'yang'
},
set: function () {
return 12
}
})
proxy.a // 'yang'
proxy.xxx // 'yang'
proxy.a = 1 // 12
例子二:
const target = {
data: {
age: 12,
name: 'yang'
}
}
const proxy = new Proxy(target, {
get: function dataGetter (target, key) {
return target.data[key] || {}
},
set: function dataSetter (target, key, val) {
target.data[key] = val
return true
}
})
proxy.age = 1
console.log('p', proxy.age)
好了,掌握了基本语法之后,那么开始进行构建:
// 定义View类
const View = function (options) {
// 挂载_data
this._data = options.data || {}
// 返回一个Proxy对象
return new Proxy(this, {
// get: function (target, key) {
// return target._data[key]
// },
// set: function (target, key, val) {
// target._data[key] = val
// return true
// }
// 简化写法
get: (target, key) => this._data[key],
set: (target, key, val) => this._data[key] = val // eslint-disable-line
})
}
let vm = new View({
data: {
age: 12,
name: 'yang'
}
})
vm // Proxy {...}
vm.age += 1
console.log('vm', vm.age) // 13