We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Object.defineProperty()
Proxy
什么是代理呢,可以理解为在对象之前设置一个“拦截”,当该对象被访问的时候,都必须经过这层拦截。意味着你可以在这层拦截中进行各种操作。比如你可以在这层拦截中对原对象进行处理,返回你想返回的数据结构。
ES6 原生提供 Proxy 构造函数,MDN上的解释为:Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
我们先来看看怎么使用。
const p = new Proxy(target, handler);
target
handler
const p = new Proxy({}, { get(target, propKey) { return '哈哈,你被我拦截了'; } }); console.log(p.name); // 哈哈,你被我拦截了
注意Proxy是用来操作对象的。代理的目的是为了拓展对象的能力。
再看一个例子 我们可以实现一个功能:不允许外部修改对象的name属性。
const p = new Proxy({}, { set(target, propKey, value) { if (propKey === 'name') { throw new TypeError('name属性不允许修改'); } // 不是 name 属性,直接保存 target[propKey] = value; } }); p.name = 'proxy'; // TypeError: name属性不允许修改 p.a = 111; console.log(p.a); // 111
babel是用来转换语法的,像新增的API(比如Array.from, Array.prototype.includes )我们需要安装额外的包来进行支持,比如 core-js/stable 和 regenerator-runtime/runtime (PS:babel 7.x 之后@babel/polyfill已不推荐使用),然后还有一些API(String#normalize、Proxy、fetch等) core-js中是暂时没有提供 polyfill,具体的可查看官方文档 core-js#missing-polyfills。
core-js
Proxy支持的拦截操作一共 13 种,详细的可以查看 MDN。
递归遍历data中的数据,使用 Object.defineProperty()劫持 getter和setter,在getter中做数据依赖收集处理,在setter中 监听数据的变化,并通知订阅当前数据的地方。 部分源码 src/core/observer/index.js#L156-L193, 版本为 2.6.11 如下
let childOb = !shallow && observe(val) // 对 data中的数据进行深度遍历,给对象的每个属性添加响应式 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { // 进行依赖收集 dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { // 是数组则需要对每一个成员都进行依赖收集,如果数组的成员还是数组,则递归。 dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } // 新的值需要重新进行observe,保证数据响应式 childOb = !shallow && observe(newVal) // 将数据变化通知所有的观察者 dep.notify() } })
这么做有什么问题呢?
newProperty
vue.$set
$set
以数组为例说明(PS: 数据的实时响应是指页面的渲染内容,而不是值vm.items本身的数据):
<ul id="example"> <li v-for="item in items"> {{ item }} </li> </ul> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> const vm = new Vue({ el: '#example', data: { items: ['a', 'b', 'c'] }, }) // 直接使用下标修改数据不是实时响应 setTimeout(() => { vm.items[1] = 'x'; vm.items[3] = 'd'; console.log(vm.items); // 此时打印结果为 ['a', 'x', 'c', 'd'],但页面内容没有更新 }, 500); // 使用 $set 修改数据是实时响应 setTimeout(() => { vm.$set(vm.items, 1, 'x1') vm.$set(vm.items, 3, 'd1') console.log(vm.items); // 此时打印结果为 ['a', 'x1', 'c', 'd1'],页面内容更新 }, 1000);
可以点击直接查看代码 codepen
vue3.0还未正式发布,不过vue-next 的相关代码已经开源出来了,目前处于Alpha版本。
为什么使用 Proxy 可以解决上面的问题呢?主要是因为Proxy是拦截对象,对对象进行一个"拦截",外界对该对象的访问,都必须先通过这层拦截。无论访问对象的什么属性,之前定义的还是新增的,它都会走到拦截中,
对象
下面分别用Object.defineProperty() 和 Proxy实现一个简单的数据响应
使用Object.defineProperty() 实现:
class Observer { constructor(data) { // 遍历参数data的属性,给添加到this上 for(let key of Object.keys(data)) { if(typeof data[key] === 'object') { data[key] = new Observer(data[key]); } Object.defineProperty(this, key, { enumerable: true, configurable: true, get() { console.log('你访问了' + key); return data[key]; // 中括号法可以用变量作为属性名,而点方法不可以; }, set(newVal) { console.log('你设置了' + key); console.log('新的' + key + '=' + newVal); if(newVal === data[key]) { return; } data[key] = newVal; } }) } } } const obj = { name: 'app', age: '18', a: { b: 1, c: 2, }, } const app = new Observer(obj); app.age = 20; console.log(app.age); app.newPropKey = '新属性'; console.log(app.newPropKey);
上面代码的执行结果为
// 修改 obj原有的属性 age的输出 你设置了age 新的age=20 你访问了age 20 // 设置新属性的输出 新属性
可以看到,给对象新增一个属性,内部并没有监听到,新增的属性需要手动再次使用Object.defineProperty()进行监听。 这就是为什么 vue 2.x中 检测不到对象属性的添加和删除的原因,内部提供的$set就是通过调用Object.defineProperty()去处理的。
vue 2.x
下面我们使用 Proxy 替代 Object.defineProperty()实现
const obj = { name: 'app', age: '18', a: { b: 1, c: 2, }, } const p = new Proxy(obj, { get(target, propKey, receiver) { console.log('你访问了' + propKey); return Reflect.get(target, propKey, receiver); }, set(target, propKey, value, receiver) { console.log('你设置了' + propKey); console.log('新的' + propKey + '=' + value); Reflect.set(target, propKey, value, receiver); } }); p.age = '20'; console.log(p.age); p.newPropKey = '新属性'; console.log(p.newPropKey); p.a.d = '这是obj中a的属性'; console.log(p.a.d);
可以看到下面输出
// 修改原对象的age属性 你设置了age 新的age=20 你访问了age 20 // 设置新的属性 你设置了newPropKey 新的newPropKey=新属性 你访问了newPropKey 新属性 // 给obj的a属性(是个对象)设置属性d 你访问了a 你访问了a 这是obj中a的属性 // 备注:如果对象的属性是对象,需要返回一个新的Proxy // 稍后会补充一下, 大家也可以先自己考虑一下, 欢迎讨论
PS: 补充一个使用 Proxy处理多层级对象的例子:How to create a Deep Proxy?
可以看到,新增的属性,并不需要重新添加响应式处理,因为 Proxy 是对对象的操作,只要你访问对象,就会走到 Proxy 的逻辑中。
Reflect(ES6引入) 是一个内置的对象,它提供拦截 JavaScript 操作的方法。将Object对象一些明显属于语言内部方法(比如Object.defineProperty())放到Reflect对象上。修改某些Object方法的返回结果,让其变得更合理。让Object操作都变成函数行为。具体内容查看MDN
Reflect
除了即将发布的 vue 3.0 之外,还有哪些库使用了Proxy呢?
vue 3.0
都是使用到了对对象进行读写拦截,在读写中做一些额外的判断和操作。
vue2.x
vue3.0
dobjs/dob
immer
The text was updated successfully, but these errors were encountered:
你好,有个疑问,为什么用了 Proxy 还要走一层 Reflect ,原本的对象操作也可以实现。上述引用不能解答我的疑惑,为了合理,怎么定义合理呢?
Sorry, something went wrong.
No branches or pull requests
目录
Object.defineProperty()
实现数据响应Proxy
Proxy
什么是代理呢,可以理解为在对象之前设置一个“拦截”,当该对象被访问的时候,都必须经过这层拦截。意味着你可以在这层拦截中进行各种操作。比如你可以在这层拦截中对原对象进行处理,返回你想返回的数据结构。
ES6 原生提供 Proxy 构造函数,MDN上的解释为:Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
我们先来看看怎么使用。
target
: 所要拦截的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)handler
:一个对象,定义要拦截的行为注意Proxy是用来操作对象的。代理的目的是为了拓展对象的能力。
再看一个例子
我们可以实现一个功能:不允许外部修改对象的name属性。
Proxy
支持的拦截操作一共 13 种,详细的可以查看 MDN。vue2.x 是怎么实现数据的响应的呢?
递归遍历data中的数据,使用 Object.defineProperty()劫持 getter和setter,在getter中做数据依赖收集处理,在setter中 监听数据的变化,并通知订阅当前数据的地方。
部分源码 src/core/observer/index.js#L156-L193, 版本为 2.6.11 如下
这么做有什么问题呢?
newProperty
,当前新加的这个属性并没有加入vue检测数据更新的机制(因为是在初始化之后添加的)。vue.$set
是能让vue知道你添加了属性, 它会给你做处理,$set
内部也是通过调用Object.defineProperty()
去处理的以数组为例说明(PS: 数据的实时响应是指页面的渲染内容,而不是值vm.items本身的数据):
可以点击直接查看代码 codepen
vue3.0 使用了Proxy
vue3.0还未正式发布,不过vue-next 的相关代码已经开源出来了,目前处于Alpha版本。
为什么使用 Proxy 可以解决上面的问题呢?主要是因为Proxy是拦截对象,对
对象
进行一个"拦截",外界对该对象的访问,都必须先通过这层拦截。无论访问对象的什么属性,之前定义的还是新增的,它都会走到拦截中,举个简单的🌰
下面分别用
Object.defineProperty()
和Proxy
实现一个简单的数据响应使用
Object.defineProperty()
实现:上面代码的执行结果为
可以看到,给对象新增一个属性,内部并没有监听到,新增的属性需要手动再次使用
Object.defineProperty()
进行监听。这就是为什么
vue 2.x
中 检测不到对象属性的添加和删除的原因,内部提供的$set
就是通过调用Object.defineProperty()
去处理的。下面我们使用
Proxy
替代Object.defineProperty()
实现可以看到下面输出
可以看到,新增的属性,并不需要重新添加响应式处理,因为
Proxy
是对对象的操作,只要你访问对象,就会走到Proxy
的逻辑中。Proxy的其他应用
除了即将发布的
vue 3.0
之外,还有哪些库使用了Proxy
呢?都是使用到了对对象进行读写拦截,在读写中做一些额外的判断和操作。
总结
Proxy
是用来操作对象的,Object.defineProperty()
是用来操作对象的属性的。vue2.x
使用Object.defineProperty()
实现数据的响应式,但是由于Object.defineProperty()
是对对象属性的操作,所以需要对对象进行深度遍历去对属性进行操作。vue3.0
用Proxy
是对对象进行拦截操作,无论是对对象做什么样的操作都会走到 Proxy 的处理逻辑中vue3.0
、dobjs/dob
、immer
等库目前都使用到了Proxy
,对对象进行读写拦截,做一些额外的处理。参考
The text was updated successfully, but these errors were encountered: