likes
comments
collection
share

vue响应式原理(一)

作者站长头像
站长
· 阅读数 43

🤔什么是响应式?

通俗来说,在使用 vue 脚手架开发项目的时候,当我们修改 data 中的数据,视图会自动更新,这种我们称为“响应式”。那为什么修改 data 中的数据视图会自动更新? vue 又是如何实现响应式的呢?这里我们就不得不提一个名词叫“数据代理”。

数据代理

什么是数据代理呢?其实很好理解,就是当我修改或者访问 data 中的属性时候是 vue 帮我们去修改和访问的,这就相当于 vue 中指定的行为代替了我们的默认行为。举个例子:比如小王让小李去咖啡店买杯咖啡,但是小李不想自己去,于是委托小明去,咖啡买回来后小李再拿给小王。这里的小明就是“代理”。同样的对于 vue 中的属性也可以设置代理。

Vue2.x 中,使用Object.defineProperty()方法来进行数据代理,但是这种方法无法代理数组类型的数据属性,Vue2.x 中通过改写数组方法的方式来监听数组的改变。在 Vue3.x 时候改用 ES6 的新特性Proxy来进行数据代理,就可以方便地监听数组变化了。

如何实现数据代理?

如果想要实现数据代理就需要对数据进行劫持,通俗来说就是当 data 中的数据发生变化的时候会被捕获到,而在 vue 中是通过 Object.defineProperty() 对 data中的每个属性添加 gettersetter 方法进行数据的劫持。如果仔细的观察过 data 中的属性都会有 gettersetter 方法。

vue响应式原理(一)

Object.defineProperty()

上面我们提到了要实现数据代理就必须搞明白 Object.defineProperty() 是干嘛用的?

定义:

Object.defineProperty()  方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

语法:

Object.defineProperty(obj, prop, descriptor)

// `obj`: 要定义属性的对象。
//  `prop`:要定义或修改的属性的名称或 [`Symbol`]
//  `descriptor`:要定义或修改的属性描述符。

🪖案例展示

const person = {
  age: 18,
  name: '小明'
}

let nowAge

Object.defineProperty(person, 'age', {

  // 读取数据的时候会触发getter
  get() {
    console.log(`有人访问了小明信息—————${nowAge}`); // 有人访问了小明信息————————undefined   有人访问了小明信息————————22
    return nowAge
  },

  // 修改数据的时候会触发setter
  set(value) {
    console.log(`小明的信息被修改了,赶紧去更新视图吧=====${value}`); //  小明的信息被修改了,赶紧去更新视图吧=====22
    nowAge = value
  }
})

console.log(person.age); // undefined
person.age = 22
console.log(person.age); // 22

运行代码后:

vue响应式原理(一)

分析:Object.defineProperty 中的描述符为我们提供了两个方法。分别是 getter ,当用户访问当前数据时触发,getter 是当用户修改数据的时候触发。所以当我们每次去访问 age 的时候都会触发 get 方法 ,而当我们去修改 age 的时候都会触发 set 方法,这里我们用一个全局临时变量 nowAge 去周转修改后 age

📦封装defineProperty

const person = {
  age: 18,
  name: '小明'
}

let nowAge

Object.defineProperty(person, 'age', {

  // 读取数据的时候会触发getter
  get() {
    console.log(`有人访问了小明信息—————${nowAge}`); // 有人访问了小明信息————————undefined   有人访问了小明信息————————22
    return nowAge
  },

  // 修改数据的时候会触发setter
  set(value) {
    console.log(`小明的信息被修改了,赶紧去更新视图吧=====${value}`); //  小明的信息被修改了,赶紧去更新视图吧=====22
    nowAge = value
  }
})

console.log(person.age); // undefined
person.age = 22
console.log(person.age); // 22

运行代码后:

vue响应式原理(一)

分析:这里把 defineProperty 封装在 defineProperty 方法中,这样就不需要设置临时变量,利用闭包保证了数据的安全。

递归监听对象全部属性

在上面的案例中,我们实现了对于对象的第一层属性进行监听,但是如果出来了对象嵌套对象的情况,则无法进行监听,这个时候就需要使用递归监听对象的每一层属性。

const person = {
  age: 18,
  name: '小明',
  hobby: {
    ball: '篮球',
  }
}

observer(person)  // 第一次监听,这里只会监听 person 对象的第一层属性 

function observer(data) {
  if (typeof data === 'object') {
    for (let key in data) {
      defineReactive(data, key, data[key])
    }
  } else {
    return data
  }
}

function defineReactive(obj, key, nowAge) {
  // 深度监听对象中每一层
  observer(nowAge)

  Object.defineProperty(obj, key, {

    // 读取数据的时候会触发getter
    get() {
      console.log(`有人访问了小明信息————${nowAge}`); // 有人访问了小明信息————[object Object],篮球,[object Object],[object Object],足球
      return nowAge
    },

    // 修改数据的时候会触发setter
    set(value) {
      console.log(`小明的信息被修改了,赶紧去更新视图吧=====${value}`); //  小明的信息被修改了,赶紧去更新视图吧=====足球
      nowAge = value
    }
  })
}

console.log(person.hobby.ball); // 篮球
person.hobby.ball = '足球'
console.log(person.hobby.ball); // 足球

运行代码后:

vue响应式原理(一)

分析:这里的 observer 函数是为了实现对于对象中嵌套的每一层属性都可以实时监听。可以看出对于 ball 修改的时候触发了 setter 方法去更新视图。值得注意的是这里的 getter 方法被触发了5次。

🤔️是否对于现有属性的修改都会被监听?

const person = {
  age: 18,
  name: '小明',
  hobby: {
    ball: '篮球',
  }
}

observer(person)  // 第一次监听,这里只会监听 person 对象的第一层属性 

function observer(data) {
  if (typeof data === 'object') {
    for (let key in data) {
      defineReactive(data, key, data[key])
    }
  } else {
    return data
  }
}

function defineReactive(obj, key, nowAge) {
  // 深度监听对象中每一层
  observer(nowAge)

  Object.defineProperty(obj, key, {

    // 读取数据的时候会触发getter
    get() {
      console.log(`有人访问了小明信息————${nowAge}`); // 有人访问了小明信息————[object Object],篮球,[object Object],[object Object],足球
      return nowAge
    },

    // 修改数据的时候会触发setter
    set(value) {
      // observer(value)  
      console.log(`小明的信息被修改了,赶紧去更新视图吧=====${value}`); //  小明的信息被修改了,赶紧去更新视图吧=====足球
      nowAge = value
    }
  })
}


person.hobby = {
  reading: '鲁滨逊漂流记'
}
person.hobby.reading = '上下五千年'

运行代码后: vue响应式原理(一)

分析:上面的 hobby 被修改了两次,但是页面只更新了一次,这是由于在第一次修改 hobby 的时候我们并没有对 hobby 中的 reading 进行监听,这样第二次修改 reading 也没并不会更新,这个时候就需要在设置的时候也去监听,代码中就是在 set 方法中添加 observer(value)

🤔️删除或者添加新的属性会不会被监听?

实际上使用 Object.defineProperty 方法只能监听和修改已有属性值。如果添加和删除新的属性值是不会被 Object.defineProperty 监听到的, vue2.x 官方提供了两个方法用于处理删除和添加新的属性页面不更新的问题:Vue.set(target, propertyName/index, value)Vue.delete(target, propertyName/index, value). 当然在 vue3.x 中不会出现这样的问题,vue3.x 中对于属性的监听使用的 Proxy 。。。

转载自:https://juejin.cn/post/7225820682432823353
评论
请登录