VUE响应式

一、vue2.0 响应式

1. 对象的响应式

1.1 Object.defineProperty

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

obj——要定义属性的对象
prop——要定义或修改的属性的名称或Symbol
descriptor——对象,要定义或修改的属性描述符

// descriptor
{
  value: undefined, // 属性的值
  get: undefined,   // 获取属性值时触发的方法
  set: undefined,   // 设置属性值时触发的方法
  writable: false,  // 属性值是否可修改,false不可改
  enumerable: false, // 属性是否可以用for...in 和 Object.keys()枚举
  configurable: false  // 该属性是否可以用delete删除,false不可删除,为false时也不能再修改该参数
}

通过赋值操作添加的普通属性是可枚举的,在枚举对象属性时会被枚举到(for…in 或 Object.keys 方法),可以改变这些属性的值,也可以删除这些属性。这个方法允许修改默认的额外选项(或配置)。
而默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改(immutable)的。
示例:

const a = {b : 1}
console.log(Object.getOwnPropertyDescriptor(a, 'b'))
// {value: 1, writable: true, enumerable: true, configurable: true}

Object.defineProperty(a, 'c', {value: '2'})
console.log(Object.getOwnPropertyDescriptor(a, 'c'))
// {value: '2', writable: false, enumerable: false, configurable: false}

a.c = 3
console.log(a.c)
// 2

Object.defineProperty(a, 'c', {value: '4'})
console.log(a.c)
// error: Uncaught TypeError: Cannot redefine property: c

1.2 set和get
// 模拟vue响应式过程
const app = document.getElementById('app')
const data = {
	a: {
	  b: {
	    c: 1
	  }
	}
}
function render () {
  const virtualDom = `这是我的内容${data.a.b.c}`		
  app.innerHTML = virtualDom
}
function observer (obj) {
  let value
  for (const key in obj) {  // 递归设置set和get
    value = obj[key]
    if (typeof value === 'object'){
      arguments.callee(value)
    } else {
      Object.defineProperty(obj, key, {
        get: function(){
          return value
        },
        set: function(newValue){
          value = newValue
          render()
        }
      })
    }
  }
}

render()
observer(data)

setTimeout(() => {
  data.a.b.c = 22
}, 2000)

setTimeout(() => {
  data.a.b.c = 88
}, 5000)

上述方法实现了数据的响应,但存在很大的问题,我们触发一次set,就需要整个页面重新渲染,然而这个值可能只在某一个组件中使用了。
在这里插入图片描述

所以将get和set优化:

Object.defineProperty(data, key, {
  get: function(){
    dep.depend() // 这里进行依赖收集
    return value
  },
  set: function(newValue){
    value = newValue
    // render()
    dep.notify()  // 这里进行virtualDom更新,通知需要更新的组件render
  }
});

dep是Vue负责管理依赖的一个类

补充: Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的

const vm = new Vue({
	data: {
		a: 1
	}
})
// vm.a是响应式的

vm.b = 2
// vm.b是非响应式的

2. 数组的响应式

vue 中处理数组的变化,直接通过下标触发视图的更改,只能使用push、shift等方法,而数组不能使用Object.defineProperty()
其实 Vue用装饰者模式来重写了数组这些方法

Object.create(proto,[propertiesObject]) 方法是创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

proto——新创建对象的原型对象;
propertiesObject ——选填,类型是对象,如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符

const a = {}
// 相当于
const a = Object.create(Object.prototype)

const person = {
  isHuman: false,
  printIntroduction: function () {
    console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  }
};

const me = Object.create(person);

me.name = 'Matthew'; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten

me.printIntroduction();
// expected output: "My name is Matthew. Am I human? true"
const o = Object.create(Object.prototype, {
  foo: { // foo会成为所创建对象的数据属性
    writable:true,
    configurable:true,
    value: "hello"
  },
  bar: { // bar会成为所创建对象的访问器属性
    configurable: false,
    get: function() { return 10 },
    set: function(value) {
      console.log("Setting `o.bar` to", value);
    }
  }
});
console.log(o) // {foo: 'hello'}

vue中的装饰者模式

const arraypro = Array.prototype    // 获取Array的原型
const arrob = Object.create(arraypro) // 用Array的原型创建一个新对象,arrob.__proto__ === arraypro,免得污染原生Array;
const arr=['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']   // 需要重写的方法

arr.forEach(function(method) {
  arrob[method] = function () {
    arraypro[method].apply(this, arguments) // 重写时先调用原生方法
    dep.notify() // 并且同时更新
  }
})

// 对于用户定义的数组,手动将数组的__proto__指向我们修改过的原型
const a = [1, 2, 3]
a.__proto__ = arrob

上面对于新对象arrob的方法,我们是直接赋值的,这样会有一个问题,就是用户可能会不小心改掉我们的对象,所以我们可以用到我们前面讲到的Object.defineProperty来规避这个问题,我们创建一个公用方法def专门来设置不能修改值的属性

function def (obj, key, value) {
  Object.defineProperty(obj, key, {
    // 这里我们没有指定writeable,默认为false,即不可修改
    enumerable: true,
    configurable: true,
    value: value,
  });
}

// 数组方法重写改为
arr.forEach(function(method){
  def(arrob, method, function () {
    arraypro[method].apply(this, arguments)  // 重写时先调用原生方法
    dep.notify()// 并且同时更新
  })
})

3. 补充:对象的数据属性和访问器属性

数据属性: 它包含的是一个数据值的位置,在这可以对数据值进行读写

数据属性的四个描述符:

含义
configurable表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或能否把属性修改为访问器属性,默认为true
enumerable表示能否通过for-in循环返回属性,默认为true
writable表示能否修改属性的值,默认为true
value包含该属性的数据值,默认为undefined

访问器属性: 这个属性不包含数据值,包含的是一对get和set方法,在读写访问器属性时,就是通过这两个方法来进行操作处理的。

访问器属性的四个描述符:

含义
configurable表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或能否把属性修改为访问器属性,默认为false
enumerable表示能否通过for-in循环返回属性,默认为false
get在读取属性时调用的函数,默认值为undefined
set在写入属性时调用的函数,默认值为undefined

二、vue3.0数据响应

vue3.0的响应式和vue2.0响应式原理类似,都是在get中收集依赖,在set中通知依赖更新视图,但vue3.0使用了es6新增的proxy来代替Object.defineProperty()

proxy相对于Object.defineProperty()的好处:

  1. Object.defineProperty需要指定对象和属性,对于多层嵌套的对象需要递归监听,Proxy可以直接监听整个对象,不需要递归;
  2. Object.defineProperty的get方法没有传入参数,如果我们需要返回原值,需要在外部缓存一遍之前的值,Proxy的get方法会传入对象和属性,可以直接在函数内部操作,不需要外部变量;
  3. set方法也有类似的问题,Object.defineProperty的set方法传入参数只有newValue,也需要手动将newValue赋给外部变量,Proxy的set也会传入对象和属性,可以直接在函数内部操作;
  4. new Proxy()会返回一个新对象,不会污染源原对象
  5. Proxy可以监听数组,不用单独处理数组

proxy劣势: vue3.0将放弃对低版本浏览器的兼容(兼容版本ie11以上)

这样上边的observe方法就可以优化成:

function observer () {
  var self = this;
  data = new Proxy(data, {
    get: function(target, key){
      dep.depend() // 这里进行依赖收集
      return target[key]
    },
    set: function(target, key, newValue){
      target[key] = newValue;
      // render()
	  dep.notify()  // 这里进行virtualDom更新,通知需要更新的组件render
    }
  });
}

补充:

  1. new proxy(target, handler)中的handler不仅可以在get和set时触发,还可以在下列方法时触发
    getPrototypeOf()
    setPrototypeOf()
    isExtensible()
    preventExtensions()
    getOwnPropertyDescriptor()
    defineProperty()
    has()
    get()
    set()
    deleteProperty()
    ownKeys()
    apply()
    construct()
  2. 可以利用proxy的特性,优化表单的校验
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值