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
本文同步发表在个人博客 熟悉 Proxy 及其场景
要想熟悉 Vue 3 源码,熟悉 Proxy 特性必不可少,本文主要内容:
基于 javascript 的复杂数据类型的特点,衍生出的代理的概念,因为对于复杂的数据类型,变量存储的是引用。代理 proxy 就在引用和值之间。
另外还需要注意 Reflect,它拥有的13个方法与 proxy 一致,用来代替 Object 的默认行为。很显然,例如我们用 proxy 修改了对象属性的 getter,那如何使用原本默认行为?
例如以下代码会陷入死循环:
const obj = { name: '' } new Proxy(obj, { get: function (target, prop) { // 错误的 return target[prop] // 应该使用 Reflect 来得到默认行为 return Reflect.get(target, prop) } })
相比于 Object.defineProperty ,Proxy 有 polyfill 可以 hack,兼容性会更好。
Object.defineProperty
和对象描述符中访问器属性 get/set 一样,一般在使用 Object.defineProperty 时重新定义对象属性的描述符。
{ get: function (target, property, receiver) { return Reflect.get(target, property) }, set: function (target, property, value, receiver) { // 必须返回一个 Boolean return Reflect.set(target, property, value) } }
关于 receiver,一般情况下 receiver === proxy 实例 即原对象的代理对象,例如:
receiver === proxy 实例
const proxy = new Proxy({}, { get: function(target, property, receiver) { console.log(receiver === proxy) return receiver } })
⚠️当 proxy 为一个对象的原型时,receiver 就不是 proxy 实例了,而是该对象本身。
const proxy = new Proxy({}, { get: function(target, property, receiver) { // 不要试图在这里获取 receiver,否则会造成死循环 // 因为 receiver === d // console.log(target, receiver) console.log(receiver.name) // ym return receiver } }) const d = { name: 'ym' } d.__proto__ = proxy // const d = Object.create(proxy); console.log(d.a === d) // true
其实也不用想那么多,receiver 就是调用对象本身,而 target 是设置 Proxy 的对象。
apply用于拦截函数调用,construct方法用于拦截new命令。
{ // newTarget 和 receiver 类似 // 这里就只有一种情况,是 proxy 的实例 construct: function (target, args, newTarget) { // 必须返回一个对象 return new target(...args) }, // ctx 是函数调用的上下文 apply: function (target, ctx, args) { } }
它们对应于 in/delete,将这些操作变成函数行为。
in/delete
剩下一些 API 相对简单,使用时可以查看 MDN 文档,没必要刻意记忆。
响应式的三要素:模版、观察者、事件中心。
以下是使用的例子:
<template> <div id="app"></div> </template> <script> new M({ template: `<div><p>输入框的值:{{ name }}</p><input type="text" v-model="name"></div>`, data () { return { name: '' } } }).mount('#app') </script>
我们 M 类的实现:
function M(opts) { // 为 data 设置代理 this.data = observe(opts.data()) // 得到模版节点 this.node = getNodes(opts.template) // 解析模版节点 this.compileElement(this.node) } M.prototype.mount = function (selector) { document.querySelector(selector).appendChild(this.node) } M.prototype.compileElement = function (node) { // 递归处理dom节点 Array.from(node.childNodes).forEach(node => { let text = node.textContent let reg = /\{\{(.*)\}\}/ if (node.nodeType === 1) { this.compile(node) } else if (node.nodeType === 3 && reg.test(text)) { this.compileText(node, reg.exec(text)[1]) } if (node.childNodes && node.childNodes.length > 0) { this.compileElement(node) } }) } // 处理文本节点 M.prototype.compileText = function (node, expression) { let reg = /\{\{.*\}\}/ expression = expression.trim() let value = this.data[expression] const oldText = node.textContent value = typeof value === 'undefined' ? '' : value node.textContent = oldText.replace(reg, value) // 添加事件处理 add(expression, (value, oldValue) => { console.log(value, oldValue) value = typeof value === 'undefined' ? '' : value node.textContent = oldText.replace(reg, value) }) } // 简单的处理 v-model M.prototype.compile = function (node) { Array.from(node.attributes).forEach(attr => { if (attr.name === 'v-model') { node.value = this.data[attr.value] node.addEventListener('input', e => { this.data[attr.value] = e.target.value }) node.removeAttribute(attr.name) } }) return node }
简单的观察者与事件中心:
// 简单的事件中心 const observeMap = {} function add(k, cb) { observeMap[k] = cb } // 观察者,我们使用 proxy function observe(tar) { const handler = { get: function (target, property, receiver) { return Reflect.get(target, property) }, set: function (target, property, value, receiver) { const oldValue = Reflect.get(target, property) const setResult = Reflect.set(target, property, value) // 只是简单的处理下存在与否的判断 if (observeMap[property]) { Reflect.apply(observeMap[property], receiver, [value, oldValue]) } return setResult } } return new Proxy(tar, handler) } function getNodes(str) { const div = document.createElement('div') div.innerHTML = str return div.childNodes[0] }
var obj = { a: { b: 3 } } // var arr = [1] const proxy = new Proxy(obj, { get: function(target, prop, receiver) { console.log('get', prop) return Reflect.get(target, prop) }, set: function(target, prop, value, receiver) { console.log('set', prop, value) return Reflect.set(target, prop, value) } }) // proxy.push(2) proxy.a.b = 1 // 只会打印 get a
所以如果我们要实现代理嵌套对象需要做递归处理。
var arr = [1] const proxy = new Proxy(arr, { get: function(target, prop, receiver) { console.log('get', prop) return Reflect.get(target, prop) }, set: function(target, prop, value, receiver) { console.log('set', prop, value) return Reflect.set(target, prop, value) } }) proxy.push(2) // get push // get length // set 1 2 // set length 2
对于 push 操作,会先去get push 方法,再 set 数组下标 1 的值为 2,然后修改数组 length 属性值为 2,最后返回数组的长度 length。
也就是说,对于代理数组对象去触发回调需要考虑到触发的类型。
在 es5 中,我们常用的获取对象属性的方式:
在熟悉了 Reflect 之后,我们要使用新的方式 Reflect.ownKeys()。
Reflect.ownKeys()
⚠️它们的区别在于是否自身拥有这个属性、或者该属性存在与原型中。当我们在讨论一个对象是否存在某个属性时,已经是在讨论一个实例本身了。
以上提到的 es5 中的4种方法中,只有 in 操作符不区分属性所在的位置,其它都要求对象实例本身拥有该属性。
in
同时,该对象的属性描述符 enumerable 为 true 才能被遍历到。
enumerable
function Person(name, age) { } Person.prototype.name = 'cjh' const person = new Person('ym', 18) 'name' in person // true Object.keys(person) // [] Reflect.ownKeys(person) // [] person.name = 'ym' Reflect.ownKeys(person) // ['ym'] delete person.name Reflect.ownKeys(person) // []
后续会学习 Vue 3 源码,敬请关注。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
概述
要想熟悉 Vue 3 源码,熟悉 Proxy 特性必不可少,本文主要内容:
基于 javascript 的复杂数据类型的特点,衍生出的代理的概念,因为对于复杂的数据类型,变量存储的是引用。代理 proxy 就在引用和值之间。
另外还需要注意 Reflect,它拥有的13个方法与 proxy 一致,用来代替 Object 的默认行为。很显然,例如我们用 proxy 修改了对象属性的 getter,那如何使用原本默认行为?
例如以下代码会陷入死循环:
相比于
Object.defineProperty
,Proxy 有 polyfill 可以 hack,兼容性会更好。熟悉 13 个 API
get / set
和对象描述符中访问器属性 get/set 一样,一般在使用
Object.defineProperty
时重新定义对象属性的描述符。关于 receiver,一般情况下
receiver === proxy 实例
即原对象的代理对象,例如:其实也不用想那么多,receiver 就是调用对象本身,而 target 是设置 Proxy 的对象。
apply/construct
apply用于拦截函数调用,construct方法用于拦截new命令。
has/deleteProperty
它们对应于
in/delete
,将这些操作变成函数行为。剩下一些 API 相对简单,使用时可以查看 MDN 文档,没必要刻意记忆。
defineProperty/ownKeys
getPrototypeOf/setPrototypeOf
isExtensible/preventExtensions
getOwnPropertyDescriptor
实现双向绑定
响应式的三要素:模版、观察者、事件中心。
以下是使用的例子:
我们 M 类的实现:
简单的观察者与事件中心:
问题
Proxy 本身的特性所带来的问题
1. 对于嵌套的对象,只会代理第一层
所以如果我们要实现代理嵌套对象需要做递归处理。
2. 由于数组本身操作的特点,proxy 的 get 和 set 会被多次触发。
对于 push 操作,会先去get push 方法,再 set 数组下标 1 的值为 2,然后修改数组 length 属性值为 2,最后返回数组的长度 length。
也就是说,对于代理数组对象去触发回调需要考虑到触发的类型。
遍历对象属性的方法有哪些?以及它们的区别
在 es5 中,我们常用的获取对象属性的方式:
在熟悉了 Reflect 之后,我们要使用新的方式
Reflect.ownKeys()
。以上提到的 es5 中的4种方法中,只有
in
操作符不区分属性所在的位置,其它都要求对象实例本身拥有该属性。同时,该对象的属性描述符
enumerable
为 true 才能被遍历到。后续会学习 Vue 3 源码,敬请关注。
The text was updated successfully, but these errors were encountered: