Proxy 方式实现数据绑定中涉及到 Proxy、Reflect、Set、Map 和 WeakMap,这些都是 ES6 的新特性。javascript
Proxy 对象代理,在目标对象以前架设一层拦截,外部对目标对象的操做,都会经过这层拦截,咱们能够定制拦截行为,每个被代理的拦截行为都对应一个处理函数。html
1java |
let p = new Proxy(target, handler);git |
1es6 2github 3数组 4app 5异步 6函数 7 8 9 |
var handler = { get: (target, name, recevier) => { return 'proxy' } } var p = new Proxy({}, handler) p.a = 1 console.log(p.a, p.c) // -> proxy proxy |
Proxy 构造函数接收两个参数:
在这个例子中,目标对象是一个空对象,配置对象中有一个 get
函数,用来拦截外部对目标对象属性的访问,能够看到,get
函数始终返回 proxy
。
Proxy 支持拦截的操做一共有13种:
Reflect 对象同 Proxy 对象同样,也是 ES6 为了操做对象而提供的新特性。
Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的静态方法(Reflect 对象没有构造函数,不能使用 new 建立实例)。这就让 Proxy 对象能够方便地调用对应的 Reflect 方法,完成默认行为,做为修改行为的基础。也就是说,无论 Proxy 怎么修改默认行为,你总能够在 Reflect 上获取默认行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var handler = { get: (target, name, recevier) => { console.log('get: ', name) Reflect.get(target, name) }, set: (target, name, value, recevier) => { console.log('set: ', name) Reflect.get(target, name, value) } } var p = new Proxy({}, handler) p.a = 1 console.log(p.a, p.c) |
代码执行结果,输出:
1 2 3 |
set: a get: a get: c |
上面代码中,Proxy 拦截目标对象的 get
和 set
方法,在其中定制拦截行为,最后采用 Reflect.get
和 Reflect.set
分别完成目标对象默认的属性获取和设置行为。
👉 更详细介绍参考:
MDN·Reflect
Reflect
Set
WeakSet
Map
WeakMap
先上完整代码 👉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
// 监听对象集合 var observers = new WeakMap() // 待执行监听函数集合,Set 能够避免重复 var queuedObservers = new Set() // 当前监听函数 var currentObserver function observable(obj) { observers.set(obj, new Map()) return new Proxy(obj, { get, set }) } function get(target, key, receiver) { // get 方法默认行为 const result = Reflect.get(target, key, receiver) // 当前监听函数中,监听使用了该属性, // 那么把该 监听函数 存放到该属性对应的 对象属性监听函数集合 Set if (currentObserver) { registerObserver(target, key, currentObserver) } return result } function registerObserver(target, key, observer) { let observersForKey = observers.get(target).get(key) // 为每个对象属性都建立一个 Set 集合,存放监听了该属性的监听函数 if (!observersForKey) { observersForKey = new Set() observers.get(target).set(key, observersForKey) } observersForKey.add(observer) } function set(target, key, value, receiver) { const observersForKey = observers.get(target).get(key) // 修改对象属性,即对象属性值发生变动时, // 判断 对象属性监听函数集合 Set 是否存在,将其中的全部监听函数都添加到 待执行监听函数集合 if (observersForKey) { observersForKey.forEach(queueObserver) } // set 方法默认行为 return Reflect.set(target, key, value, receiver) } function observe(fn) { queueObserver(fn) } // 将监听函数添加到 待执行监听函数集合 Set 中 // 若是 待执行监听函数集合 Set 为空,那么在添加后当即执行 function queueObserver(observer) { if (queuedObservers.size === 0) { // 异步执行 Promise.resolve().then(runObservers) } queuedObservers.add(observer) } // 执行 待执行监听函数集合 Set 中的监听函数 // 执行完毕后,进行清理工做 function runObservers() { try { queuedObservers.forEach((observer) => { currentObserver = observer observer() }) } finally { currentObserver = undefined queuedObservers.clear() } } |
对外暴露的 observable(obj)
和 observe(fn)
方法两者分别用于建立 observable 监听对象和 observer 监听回调函数。当 observable 监听对象发生属性变化时,observer 函数将自动执行。
测试用例:
1 2 3 4 5 6 7 8 9 |
var obj = {name: 'John', age: 20} // observable object var person = observable(obj) function print () { console.log(`监听属性发生变化:${person.name}, ${person.age}`) } // observer function observe(print) |
关于 observable(obj)
和 observe(fn)
:observable(obj)
方法中,经过 ES6 Proxy 为目标对象 obj
建立代理,拦截 get
和 set
操做
currentObserver
var queuedObservers = new Set()
var observers = new WeakMap()
键值为监听对象get
:使用 obj.property
获取对象属性,即会被拦截方法 get
拦截get
中的注释set
:使用 obj.property = value
设置对象属性,即会被拦截方法 set
拦截set
中的注释observe(fn)
方法中,添加对象属性监听函数
监听函数中使用 obj.property
获取对象属性,即代表监听函数监听了该属性,那么就会触发拦截方法 get
中对监听属性的逻辑处理,为其建立对象属性监听函数集合 Set,并将当前的监听函数添加进其中
observable
方法建立代理对象 person
observe
方法设置监听函数,此时待执行监听函数集合 Set 为空,监听函数添加到 Set 中后执行待执行监听函数集合 Set 中的监听函数runObservers
方法中当前监听函数 currentObserver
被设为 print
print
开始执行print
内部检索到 person.name
person
上触发拦截方法 get
observers.get(person).get('name')
检索到 (person, name)
组合的对象属性监听函数集 Setprint
被添加到对象属性监听函数集 Set 中person.age
,同理,执行前面在 print
内部检索到 person.name
的流程${person.name}, ${person.age}
打印出来;print
函数执行结束;currentObserver
变为 undefined
当调用 person.age = 22
修改对象属性时:
person
上触发拦截方法 set
observers.get(person).get('age')
检索到 (person, age)
组合的对象属性监听函数集 Setprint
)入待执行监听函数集合,准备执行print
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var obj = { name: 'John', age: 20, teacher: { name: 'Tom', age: 30 }} // observable object var person = observable(obj) function print () { console.log(`监听属性发生变化:${person.teacher.name}, ${person.teacher.age}`) } // observer function observe(print) setTimeout(() => {person.teacher.name = 'Jack'}) |
到目前为止,单层对象的数据绑定监听是正常工做的。可是在这个例子中,咱们监听的对象值又是对象,这个时候监听就失效了,咱们须要将:
1 |
observable({data: {name: 'John'}}) |
替换成
1 |
observable({data: observable({name: 'John'})}) |
这样就能正常运行了 😋
显然,这样使用不方便,能够作拦截方法 get
中修改一下,在返回值是对象时,对返回值对象也调用 observable(obj)
为其建立监听对象。
1 2 3 4 5 6 7 8 9 10 11 12 |
function get(target, key, receiver) { const result = Reflect.get(target, key, receiver) if (currentObserver) { registerObserver(target, key, currentObserver) if (typeof result === 'object') { const observableResult = observable(result) Reflect.set(target, key, observableResult, receiver) return observableResult } } return result } |
对于 Proxy 拦截操做也能够在原型链中被继承,例如:
1 2 3 4 5 6 7 8 9 |
let proto = new Proxy({}, { get(target, propertyKey, receiver) { console.log('GET ' + propertyKey); return Reflect.get(target, propertyKey, receiver); } }); let obj = Object.create(proto); obj.foo // "GET foo" |
上面代码中,拦截操做 get
定义在原型对象上面,因此若是读取 obj
对象属性时,拦截会生效。
同理,经过 Proxy 实现的数据绑定也能与原型继承搭配工做,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const parent = observable({greeting: 'Hello'}) const child = observable({subject: 'World!'}) Object.setPrototypeOf(child, parent) function print () { console.log(`${child.greeting} ${child.subject}`) } // 控制台打印出 'Hello World!' observe(print) // 控制台打印出 'Hello There!' setTimeout(() => child.subject = 'There!') // 控制台打印出 'Hey There!' setTimeout(() => parent.greeting = 'Hey', 100) // 控制台打印出 'Look There!' setTimeout(() => child.greeting = 'Look', 200) |
本文中经过简单的代码展现了 Proxy 实现数据绑定,更加完整的实现,参考:nx-js/observer-util
Writing a JavaScript Framework - Data Binding with ES6 Proxies
使用 ES6 Proxy 实现数据绑定