新用户注册入口 老用户登录入口

[转载]Proxy 、Relect、响应式

文章作者:转载 更新时间:2023-01-11 12:37:47 阅读数量:678
文章标签:ProxyReflect响应式编程监听对象操作Vuejs依赖收集
本文摘要:本文探讨了ES6中的Proxy和Reflect特性在实现响应式编程中的应用。通过Proxy的set和get捕获器,可以监听对象属性的读取与设置操作,并能进一步扩展至新增、删除等更丰富的操作场景。Reflect作为与Proxy配套使用的工具,提供了标准化的对象操作方法,有助于实现数据变化时的行为一致性。在Vue.js框架中,响应式原理借助Proxy实现了对数据变化的全面监听,配合依赖收集机制,确保视图能够实时准确地反映数据状态。同时,文章也提及了Object.defineProperty在Vue2中的使用及限制,突显出Proxy相比而言在实现响应式上的优势。
转载文章

本篇文章为转载内容。原文链接:https://blog.csdn.net/wanghuan1020/article/details/126774033。

该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。

作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。

如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。

Proxy 、Relect、响应式

  • Proxy 、Relect、响应式
    • 1. 监听对象的操作
    • 2. Proxy基本使用
      • 2.1 Proxy 的 set 和 get 捕获器
      • 2.2 Proxy 所有捕获器 (13个)
      • 2.3 Proxy 的 construct 和 apply
    • 3. Reflect
      • 3.1 Reflect 的作用
      • 3.2 Reflect 的常见方法
      • 3.3 Reflect 的使用
      • 3.4 Receiver的作用
      • 3.5 Reflect 的 construct
    • 4. 响应式
      • 4.1 什么是响应式?
      • 4.2 响应式函数设计
      • 4.3 响应式依赖的收集
      • 4.4 监听对象的变化
      • 4.5 对象的依赖管理
      • 4.6 对 Depend 重构
      • 4.7 创建响应式对象
      • 4.8 Vue2 响应式原理

Proxy 、Relect、响应式

1. 监听对象的操作

  • 需求:有一个对象,我们希望监听这个对象中的属性被设置或获取的过程

    • 可以通过属性描述符中的存储属性描述符来做到
  • 这段代码就利用了 Object.defineProperty 的存储属性描述符来对属性的操作进行监听

    const obj = {name: 'why',age: 18
    }Object.keys(obj).forEach((key) => {let value = obj[key]Object.defineProperty(obj, key, {get: function () {console.log(`监听到obj对象的${key}属性被访问了`)return value},set: function (newValue) {console.log(`监听到obj对象的${key}属性被设置值`)value = newValue}})
    })obj.name = 'kobe'
    obj.age = 30
    console.log(obj.name)
    console.log(obj.age)
    /* 
    监听到obj对象的name属性被设置值
    监听到obj对象的age属性被设置值
    监听到obj对象的name属性被访问了
    kobe
    监听到obj对象的age属性被访问了
    30
    */
    
  • 属性描述符监听对象的缺点:

    • 首先,Object.defineProperty 设计的初衷,不是为了去监听截止一个对象中所有的属性的
      • 我们在定义某些属性的时候,初衷其实是定义普通的属性,但是后面我们强行将它变成了数据属性描述符
    • 其次,如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么 Object.defineProperty 是无能为力的
    • 所以我们要知道,存储数据描述符设计的初衷并不是为了去监听一个完整的对象
    • Ps: 原来的对象是 数据属性描述符,通过 Object.defineProperty 变成了 访问属性描述符

2. Proxy基本使用

  • 在ES6中,新增了一个Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的:

    • 也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象)
    • 之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作
  • 将上面的案例用 Proxy 来实现一次:

    • 首先,我们需要 new Proxy 对象,并且传入需要侦听的对象以及一个处理对象,可以称之为 handler
  • const p = new Proxy(target, handler)

  • 其次,我们之后的操作都是直接对 Proxy 的操作,而不是原有的对象,因为我们需要在 handler 里面进行侦听

    const obj = {name: 'why',age: 18
    }const objProxy = new Proxy(obj, {// 获取值时的捕获器get: function (target, key) {console.log(`监听到obj对象的${key}属性被访问了`)return target[key]},// 设置值时的捕获器set: function (target, key, newValue) {console.log(`监听到obj对象的${key}属性被设置值`)target[key] = newValue}
    })console.log(objProxy.name)
    console.log(objProxy.age)objProxy.name = 'kobe'
    objProxy.age = 30console.log(obj.name)
    console.log(obj.age)/* 
    监听到obj对象的name属性被访问了
    why
    监听到obj对象的age属性被访问了
    18
    监听到obj对象的name属性被设置值
    监听到obj对象的age属性被设置值
    kobe
    30
    */
    

2.1 Proxy 的 set 和 get 捕获器

  • 如果我们想要侦听某些具体的操作,那么就可以在 handler 中添加对应的捕捉器(Trap)
  • setget 分别对应的是函数类型
    • set 函数有四个参数:
      • target:目标对象(侦听的对象)
      • property:将被设置的属性 key
      • value:新属性值
      • receiver:调用的代理对象
    • get 函数有三个参数
      • target:目标对象(侦听的对象)
      • property:被获取的属性 key
      • receiver:调用的代理对象

2.2 Proxy 所有捕获器 (13个)

  • handler.getPrototypeOf()

    • Object.getPrototypeOf 方法的捕捉器
  • handler.setPrototypeOf()

    • Object.setPrototypeOf 方法的捕捉器
  • handler.isExtensible()

    • Object.isExtensible 方法的捕捉器
  • handler.preventExtensions()

    • Object.preventExtensions 方法的捕捉器
  • handler.getOwnPropertyDescriptor()

    • Object.getOwnPropertyDescriptor 方法的捕捉器
  • handler.defineProperty()

    • Object.defineProperty 方法的捕捉器
  • handler.ownKeys()

    • Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器
  • handler.has()

    • in 操作符的捕捉器
  • handler.get()

    • 属性读取操作的捕捉器
  • handler.set()

    • 属性设置操作的捕捉器
  • handler.deleteProperty()

    • delete 操作符的捕捉器
  • handler.apply()

    • 函数调用操作的捕捉器
  • handler.construct()

    • new 操作符的捕捉器
const obj = {name: 'why',age: 18
}const objProxy = new Proxy(obj, {// 获取值时的捕获器get: function (target, key) {console.log(`监听到obj对象的${key}属性被访问了`)return target[key]},// 设置值时的捕获器set: function (target, key, newValue) {console.log(`监听到obj对象的${key}属性被设置值`)target[key] = newValue},// 监听 in 的捕获器has: function (target, key) {console.log(`监听到obj对象的${key}属性的in操作`)return key in target},// 监听 delete 的捕获器deleteProperty: function (target, key) {console.log(`监听到obj对象的${key}属性的delete操作`)delete target[key]}
})// in 操作符
console.log('name' in objProxy)// delete 操作
delete objProxy.name/* 
监听到obj对象的name属性的in操作
true
监听到obj对象的name属性的delete操作
*/

2.3 Proxy 的 construct 和 apply

  • 到捕捉器中还有 constructapply,它们是应用于函数对象的
function foo() {console.log('调用了 foo')
}const fooProxy = new Proxy(foo, {apply: function (target, thisArg, argArray) {console.log(`对 foo 函数进行了 apply 调用`)target.apply(thisArg, argArray)},construct: function (target, argArray, newTarget) {console.log(`对 foo 函数进行了 new 调用`)return new target(...argArray)}
})fooProxy.apply({}, ['abc', 'cba'])
new fooProxy('abc', 'cba')/* 
对 foo 函数进行了 apply 调用
调用了 foo
对 foo 函数进行了 new 调用
调用了 foo
*/

3. Reflect

3.1 Reflect 的作用

  • Reflect 也是 ES6 新增的一个 API,它是一个对象,字面的意思是反射
  • Reflect 的作用:
    • 它主要提供了很多操作 JavaScript 对象的方法,有点像 Object 中操作对象的方法
      • 比如 Reflect.getPrototypeOf(target) 类似于 Object.getPrototypeOf()
      • 比如 Reflect.defineProperty(target, propertyKey, attributes) 类似于 Object.defineProperty()
    • 如果我们有 Object 可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?
      • 这是因为在早期的 ECMA 规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些 API 放到了 Object上面
      • 但是 Object 作为一个构造函数,这些操作实际上放到它身上并不合适
      • 另外还包含一些类似于 indelete 操作符,让 JS 看起来是会有一些奇怪的
      • 所以在 ES6 中新增了 Reflect,让我们这些操作都集中到了 Reflect 对象上
    • 那么 ObjectReflect 对象之间的 API 关系,可以参考 MDN 文档:
      • 比较 Reflect 和 Object 方法

3.2 Reflect 的常见方法

Reflect中有哪些常见的方法呢?它和Proxy是一一对应的,也是13个

  • Reflect.getPrototypeOf(target)
    • 类似于 Object.getPrototypeOf()
  • Reflect.setPrototypeOf(target, prototype)
    • 设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回 true
  • Reflect.isExtensible(target)
    • 类似于 Object.isExtensible()
  • Reflect.preventExtensions(target)
    • 类似于 Object.preventExtensions() , 返回一个 Boolean
  • Reflect.getOwnPropertyDescriptor(target, propertyKey)
    • 类似于 Object.getOwnPropertyDescriptor() , 如果对象中存在该属性,则返回对应的属性描述符, 否则返回 undefined
  • Reflect.defineProperty(target, propertyKey, attributes)
    • Object.defineProperty() 类似, 如果设置成功就会返回 true
  • Reflect.ownKeys(target)
    • 返回一个包含所有自身属性(不包含继承属性)的数组 (类似于 Object.keys(), 但不会受 enumerable 影响)
  • Reflect.has(target, propertyKey)
    • 判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同
  • Reflect.get(target, propertyKey[, receiver])
    • 获取对象身上某个属性的值,类似于 target[name]
  • Reflect.set(target, propertyKey, value[, receiver])
    • 将值分配给属性的函数,返回一个 Boolean,如果更新成功,则返回 true
  • Reflect.deleteProperty(target, propertyKey)
    • 作为函数的 delete 操作符,相当于执行 delete target[name]
  • Reflect.apply(target, thisArgument, argumentsList)
    • 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似
  • Reflect.construct(target, argumentsList[, newTarget])
    • 对构造函数进行 new 操作,相当于执行 new target(...args)

3.3 Reflect 的使用

那么我们可以将之前Proxy案例中对原对象的操作,都修改为Reflect来操作

const obj = {name: 'why',age: 18
}const objProxy = new Proxy(obj, {get: function (target, key) {console.log(`监听到obj对象的${key}属性被访问了`)return Reflect.get(target, key)// return target[key] // 对原来对象进行了直接操作},set: function (target, key, newValue) {console.log(`监听到obj对象的${key}属性被设置值`)Reflect.set(target, key, newValue)// target[key] = newValue // 对原来对象进行了直接操作}
})objProxy.name = 'kobe'
console.log(objProxy.name)
/* 
监听到obj对象的name属性被设置值
监听到obj对象的name属性被访问了
kobe
*/

3.4 Receiver的作用

我们发现在使用getter、setter的时候有一个receiver的参数,它的作用是什么呢?

  • 如果我们的源对象(obj)有 settergetter 的访问器属性,那么可以通过 receiver 来改变里面的 this
const obj = {_name: 'why',get name() {return this._name // 不使用receiver, _name属性的操作不会被objProxy代理,因为this指向obj},set name(newValue) {this._name = newValue}
}const objProxy = new Proxy(obj, {get: function (target, key, receiver) {// receiver 是创建出来的代理对象console.log('get 方法被访问-------', key, receiver)console.log(objProxy === receiver) // truereturn Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)}
})objProxy.name = 'kobe'
console.log(objProxy.name) // kobe/* 
get 方法被访问------- name { _name: 'kobe', name: [Getter/Setter] }
true
get 方法被访问------- _name { _name: 'kobe', name: [Getter/Setter] }
true
kobe
*/

3.5 Reflect 的 construct

function Student(name, age) {this.name = namethis.age = age
}function Teacher() {}const stu = new Student('why', 18)
console.log(stu)
console.log(stu.__proto__ === Student.prototype)/* 
Student { name: 'why', age: 18 }
true
*/// 执行 Student 函数中的内容,但是创建出来的对象是 Teacher 对象
const teacher = Reflect.construct(Student, ['why', 18], Teacher)
console.log(teacher)
console.log(teacher.__proto__ === Teacher.prototype)
/* 
Teacher { name: 'why', age: 18 }
true
*/

4. 响应式

4.1 什么是响应式?

  • 先来看一下响应式意味着什么?我们来看一段代码:

    • m 有一个初始化的值,有一段代码使用了这个值;

    • 那么在 m 有一个新的值时,这段代码可以自动重新执行

      let m = 0// 一段代码
      console.log(m)
      console.log(m * 2)
      console.log(m ** 2)m = 200
      
  • 上面的这样一种可以自动响应数据变量的代码机制,我们就称之为是响应式的

    • 对象的响应式

在这里插入图片描述

4.2 响应式函数设计

  • 首先,执行的代码中可能不止一行代码,所以我们可以将这些代码放到一个函数中:

    • 那么问题就变成了,当数据发生变化时,自动去执行某一个函数;
      在这里插入图片描述
  • 但是有一个问题:在开发中是有很多的函数的,如何区分一个函数需要响应式,还是不需要响应式呢?

    • 很明显,下面的函数中 foo 需要在 objname 发生变化时,重新执行,做出相应;

    • bar 函数是一个完全独立于 obj 的函数,它不需要执行任何响应式的操作;

    // 对象的响应式
    const obj = {name: 'why',age: 18
    }function foo() {const newName = obj.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(obj.name)
    }function bar() {console.log('普通的其他函数')console.log('这个函数不需要有任何的响应式')
    }obj.name = 'kobe' // name 发生改变时候 foo 函数执行
    
  • 响应式函数的实现 watchFn

    • 如何区分响应式函数?
      • 这个时候我们封装一个新的函数 watchFn
      • 凡是传入到 watchFn 的函数,就是需要响应式的
      • 其他默认定义的函数都是不需要响应式的
    /* 封装一个响应式的函数 */
    let reactiveFns = []
    function watchFn(fn) {reactiveFns.push(fn)
    }// 对象的响应式
    const obj = {name: 'why',age: 18
    }watchFn(function foo() {const newName = obj.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(obj.name)
    })watchFn(function demo() {console.log(obj.name, 'demo function ---------')
    })function bar() {console.log('普通的其他函数')console.log('这个函数不需要有任何的响应式')
    }obj.name = 'kobe' // name 发生改变时候 foo 函数执行reactiveFns.forEach((fn) => {fn()
    })
    

4.3 响应式依赖的收集

  • 目前收集的依赖是放到一个数组中来保存的,但是这里会存在数据管理的问题:

    • 在实际开发中需要监听很多对象的响应式
    • 这些对象需要监听的不只是一个属性,它们很多属性的变化,都会有对应的响应式函数
    • 不可能在全局维护一大堆的数组来保存这些响应函数
  • 所以要设计一个类,这个类用于管理某一个对象的某一个属性的所有响应式函数

    • 相当于替代了原来的简单 reactiveFns 的数组;

      class Depend {constructor() {this.reactiveFns = []}addDepend(reactiveFn) {this.reactiveFns.push(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})}
      }const depend = new Depend()
      function watchFn(fn) {depend.addDepend(fn)
      }// 对象的响应式
      const obj = {name: 'why', // depend 对象age: 18 // depend 对象
      }watchFn(function foo() {const newName = obj.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(obj.name)
      })watchFn(function demo() {console.log(obj.name, 'demo function ---------')
      })function bar() {console.log('普通的其他函数')console.log('这个函数不需要有任何的响应式')
      }obj.name = 'kobe'
      depend.notify()
      

4.4 监听对象的变化

  • 那么接下来就可以通过之前的方式来监听对象的变化:

    • 方式一:通过 Object.defineProperty 的方式(vue2采用的方式);

    • 方式二:通过 new Proxy 的方式(vue3采用的方式);

  • 我们这里先以Proxy的方式来监听

    class Depend {constructor() {this.reactiveFns = []}addDepend(reactiveFn) {this.reactiveFns.push(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})}
    }const depend = new Depend()
    function watchFn(fn) {depend.addDepend(fn)
    }// 对象的响应式
    const obj = {name: 'why', // depend 对象age: 18 // depend 对象
    }// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)
    const objProxy = new Proxy(obj, {get: function (target, key, receiver) {return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)depend.notify()}
    })watchFn(function foo() {const newName = objProxy.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(objProxy.name)
    })watchFn(function demo() {console.log(objProxy.name, 'demo function ---------')
    })objProxy.name = 'kobe'
    objProxy.name = 'james'/* 
    你好啊,李银河
    Hello World
    kobe
    kobe demo function ---------
    你好啊,李银河
    Hello World
    james
    james demo function ---------
    */
    

4.5 对象的依赖管理

  • 目前是创建了一个 Depend 对象,用来管理对于 name 变化需要监听的响应函数:

    • 但是实际开发中我们会有不同的对象,另外会有不同的属性需要管理;
    • 如何可以使用一种数据结构来管理不同对象的不同依赖关系呢?
  • 在前面我们刚刚学习过 WeakMap,并且在学习 WeakMap 的时候我讲到了后面通过 WeakMap 如何管理这种响应式的数据依赖:

在这里插入图片描述

  • 实现

    • 可以写一个 getDepend 函数专门来管理这种依赖关系
    /* 封装一个获取depend的函数 */
    const taregtMap = new WeakMap()
    function getDepend(target, key) {// 根据target对象获取mapconst map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象const depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend
    }// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)
    const objProxy = new Proxy(obj, {get: function (target, key, receiver) {return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()}
    })
    
  • 正确的依赖收集

    • 我们之前收集依赖的地方是在 watchFn 中:
      • 但是这种收集依赖的方式我们根本不知道是哪一个 key 的哪一个 depend 需要收集依赖;
      • 只能针对一个单独的 depend 对象来添加你的依赖对象;
    • 那么正确的应该是在哪里收集呢?应该在我们调用了 Proxyget 捕获器时
      • 因为如果一个函数中使用了某个对象的 key,那么它应该被收集依赖
    /* 封装一个响应式函数 */
    let activeReactviceFn = null
    function watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null
    }/* 封装一个获取depend的函数 */
    const taregtMap = new WeakMap()
    function getDepend(target, key) {// 根据target对象获取maplet map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend
    }// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)
    const objProxy = new Proxy(obj, {get: function (target, key, receiver) {// 根据 target key 获取对应的 depnedconst depend = getDepend(target, key)// 给 depend 对象中添加响应式函数activeReactviceFn && depend.addDepend(activeReactviceFn)return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()}
    })
    

4.6 对 Depend 重构

  • 两个问题:
    • 问题一:如果函数中有用到两次 key,比如 name,那么这个函数会被收集两次
    • 问题二:我们并不希望将添加 reactiveFn 放到 get 中,因为它是属于 Depend 的行为
  • 所以我们需要对 Depend 类进行重构:
    • 解决问题一的方法:不使用数组,而是使用 Set
    • 解决问题二的方法:添加一个新的方法,用于收集依赖
// 保存当前需要收集的响应式函数
let activeReactviceFn = null
class Depend {constructor() {this.reactiveFns = new Set()}depend() {if (activeReactviceFn) {this.reactiveFns.add(activeReactviceFn)}}addDepend(reactiveFn) {this.reactiveFns.add(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})}
}// 对象的响应式
const obj = {name: 'why', // depend 对象age: 18 // depend 对象
}/* 封装一个响应式函数 */function watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null
}/* 封装一个获取depend的函数 */
const taregtMap = new WeakMap()
function getDepend(target, key) {// 根据target对象获取maplet map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend
}// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = new Proxy(obj, {get: function (target, key, receiver) {// 根据 target key 获取对应的 depnedconst depend = getDepend(target, key)// 给 depend 对象中添加响应式函数depend.depend()return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()}
})watchFn(function () {console.log(objProxy.name, '--------------')console.log(objProxy.name, '++++++++++++++')
})objProxy.name = 'kobe'/* 
why --------------
why ++++++++++++++
kobe --------------
kobe ++++++++++++++
*/

4.7 创建响应式对象

  • 目前的响应式是针对于obj一个对象的,我们可以创建出来一个函数,针对所有的对象都可以变成响应式对象

    /* 保存当前需要收集的响应式函数 */
    let activeReactviceFn = null
    /* 依赖收集类 */
    class Depend {constructor() {this.reactiveFns = new Set()}depend() {if (activeReactviceFn) {this.reactiveFns.add(activeReactviceFn)}}addDepend(reactiveFn) {this.reactiveFns.add(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})}
    }/* 封装一个响应式函数 */
    function watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null
    }/* 封装一个获取depend的函数 */
    const taregtMap = new WeakMap()
    function getDepend(target, key) {// 根据target对象获取maplet map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend
    }/* 创建响应式对象函数 */
    function reactive(obj) {// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)return new Proxy(obj, {get: function (target, key, receiver) {// 根据 target key 获取对应的 depnedconst depend = getDepend(target, key)// 给 depend 对象中添加响应式函数depend.depend()return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()}})
    }const info = reactive({address: '广州市',height: 1.88
    })watchFn(() => {console.log(info.address, '---')
    })info.address = '北京市'
    

4.8 Vue2 响应式原理

  • 前面所实现的响应式的代码,其实就是 Vue3 中的响应式原理:
    • Vue3 主要是通过 Proxy 来监听数据的变化以及收集相关的依赖的
    • Vue2 中通过 Object.defineProerty的方式来实现对象属性的监听
  • 可以将 reactive 函数进行如下的重构:
  • 在传入对象时,我们可以遍历所有的 key,并且通过属性存储描述符来监听属性的获取和修改
  • settergetter 方法中的逻辑和前面的 Proxy 是一致的
/* 保存当前需要收集的响应式函数 */
let activeReactviceFn = null
/* 依赖收集类 */
class Depend {constructor() {this.reactiveFns = new Set()}depend() {if (activeReactviceFn) {this.reactiveFns.add(activeReactviceFn)}}addDepend(reactiveFn) {this.reactiveFns.add(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})}
}/* 封装一个响应式函数 */
function watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null
}/* 封装一个获取depend的函数 */
const taregtMap = new WeakMap()
function getDepend(target, key) {// 根据target对象获取maplet map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend
}/* 创建响应式对象函数 */
function reactive(obj) {Object.keys(obj).forEach((key) => {let value = obj[key]Object.defineProperty(obj, key, {get: function () {const dep = getDepend(obj, key)dep.depend()return value},set: function (newValue) {value = newValueconst dep = getDepend(obj, key)dep.notify()}})})return obj
}const info = reactive({address: '广州市',height: 1.88
})watchFn(() => {console.log(info.address, '---')
})info.address = '北京市'

本篇文章为转载内容。原文链接:https://blog.csdn.net/wanghuan1020/article/details/126774033。

该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。

作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。

如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。

相关阅读
文章标题:[转载][洛谷P1082]同余方程

更新时间:2023-02-18
[转载][洛谷P1082]同余方程
文章标题:[转载]webpack优化之HappyPack实战

更新时间:2023-08-07
[转载]webpack优化之HappyPack实战
文章标题:[转载]oracle 同时更新多表,在Oracle数据库中同时更新两张表的简单方法

更新时间:2023-09-10
[转载]oracle 同时更新多表,在Oracle数据库中同时更新两张表的简单方法
文章标题:[转载][Unity] 包括场景互动与射击要素的俯视角闯关游戏Demo

更新时间:2024-03-11
[转载][Unity] 包括场景互动与射击要素的俯视角闯关游戏Demo
文章标题:[转载]程序员也分三六九等?等级差异,一个看不起一个!

更新时间:2024-05-10
[转载]程序员也分三六九等?等级差异,一个看不起一个!
文章标题:[转载]海贼王 动漫 全集目录 分章节 精彩打斗剧集

更新时间:2024-01-12
[转载]海贼王 动漫 全集目录 分章节 精彩打斗剧集
名词解释
作为当前文章的名词解释,仅对当前文章有效。
ProxyProxy 是 JavaScript ES6 中引入的一种新特性,它用于创建一个对象的代理,可以拦截和自定义基本操作,如属性访问、赋值、函数调用等。在本文语境中,Proxy 被用来实现对目标对象(例如 Vue.js 中的数据对象)的所有操作的监听,通过设置 Proxy 的 handler(处理器对象)中的各种捕获器(trap),开发者能够定制化地控制这些操作的行为,从而实现响应式数据绑定。
ReflectReflect 是 ES6 中新增的一个内置对象,提供了与 Proxy 相对应的方法,用于执行默认的 JavaScript 操作,并且其行为与语言规范更为一致。在文中,Reflect 与 Proxy 结合使用,使得开发者能够在 Proxy 的 handler 中调用 Reflect 方法来执行原生操作,同时确保这些操作具有明确的返回结果,增强了代码的可读性和一致性。例如,在实现响应式编程时,可以通过 Reflect.defineProperty 或 Reflect.get 等方法配合 Proxy 进行对象属性的设置和获取。
响应式编程(Reactive Programming)响应式编程是一种编程范式,它的核心思想是数据变化驱动程序状态的自动更新。在本文中,响应式编程被应用于前端框架 Vue.js 的数据绑定机制中,当数据发生变化时,依赖该数据的视图会自动得到更新。通过 Proxy 和 Reflect 等技术手段,Vue.js 可以监听并追踪数据的变化,并触发相应的回调函数来更新视图,从而实现了数据和视图之间的联动关系,简化了用户界面开发过程中手动管理数据同步的工作量。
延伸阅读
作为当前文章的延伸阅读,仅对当前文章有效。
在进一步了解Proxy、Reflect以及响应式编程的应用后,我们可以关注近期JavaScript社区的一些最新进展和深入讨论。例如,随着Vue3的发布,其对Proxy的充分利用使得响应式系统更加高效且全面,开发者可以通过阅读Vue.js官方文档和相关技术博客文章来深入了解如何在实际项目中运用Proxy实现复杂的数据绑定与更新逻辑。
此外,浏览器对ES6新特性的支持也在不断推进,当前所有现代浏览器均支持Proxy和Reflect。Mozilla开发者网络(MDN)提供了详尽的API文档和技术指南,帮助开发者更好地掌握这两个特性,并应用于日常开发工作中。
同时,在前端框架领域,除了Vue之外,React Hooks的useState和useEffect也从另一个角度实现了数据响应式,它们通过函数组件状态管理和副作用钩子机制,间接实现了对数据变化的监听。读者可以对比研究两种不同的响应式实现方式,理解它们各自的优势与应用场景。
最近,一些前沿的JavaScript库如MobX、RxJS等也在响应式编程上做出了新的探索,通过更高级的抽象和流处理思想,将响应式理念扩展到了异步编程和大规模应用架构层面。深入学习这些库的设计原理和实践案例,有助于我们拓宽视野,更好地适应未来JavaScript生态的发展趋势。
综上所述,无论是紧跟最新的JavaScript语言特性发展动态,还是深入探究各类前端框架的响应式实现原理,都有助于我们提升代码质量和开发效率,为构建高性能、易于维护的现代Web应用奠定坚实基础。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
nc -l 8080 - 开启一个监听8080端口的简单网络服务器。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
[转载]关于SysinternalsSuite全部工具详解 01-22 [转载]18.准入控制器 12-25 css每个数字添加背景 12-24 [转载]程序员入门编程,看这10本书,少走10年弯路,java二级教学视频 12-11 绿色塑料机械制造类前端企业模板下载 12-06 响应式大气长途搬家物流公司网站模板 11-02 数字代理商业公司模板下载 10-16 Vue打包后404错误排查:路由配置、静态资源路径与服务器部署详解 10-10 CSS3响应式酒店HTML5网页模板下载 09-19 本次刷新还10个文章未展示,点击 更多查看。
响应式茶叶产品展示销售类企业前端CMS网站下载 08-12 专业咨询服务展示HTML网页模板下载 08-06 jQuery实用表单文件域美化插件 07-03 [转载]【angularJS】前后台分离,angularJS使用Token认证 06-14 [转载]云计算与虚拟化工具之KVM,KVM和VMware的区别 04-06 Maven中Resource Filtering的错误类型与解决:变量未定义、过滤规则冲突及特殊字符处理在`pom.xml`构建配置中的应用 03-30 红色大气平行进口车网站HTML5模板 03-25 黑色机械设备科研公司网页模板下载 03-22 CSS模块化配置实战:提升项目可维护性与可扩展性的模块划分与导入实践 02-21 jQuery高仿真移动手机滑动侧边栏布局插件 01-21 [转载]L2-007 家庭房产 (25 分) 01-09
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"