vue 响应式原理

一:vue2的响应式原理 (数据双向绑定原理)

Object.defineProperty()

Object.defineProperty() 其实核心的并不是为对象做数据双向绑定,而是去给对象做属性标签,只不过属性里的 get 和 set 实现了响应式。

1. Object.defineproperty()

Object.defineProperty()
属性名默认值
valueundefined
setundefined
getundefined
writable(可写性)true
enumerable (可枚举性)for/in 是否可循环true
configurable (可配置性)true

例子:

var o = {}
    Object.defineProperty(o, 'name', {
      value: 'zhoufangbing',
      configurable: false,
      enumerable: false,
      writable: false
    })
    console.log(o) // { name: "zhoufangbing" }

    console.log(o.name) // zhoufangbing
    o.name = 'xx'
    console.log(o.name) // zhoufangbing 
    // 因为 writable (可写性)为false,所以值不可更改,仍为zhoufangbing
    // 若值为 true , 则会输出 xx
    
    // Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 。
    console.log(Object.keys(o)) // []
    // 因为 enumerable (可枚举性)false 所以返回 [], 如果enumerable 为true
    console.log(Object.keys(o)) // ['name']

    // 访问器属性 get set
    var person = {
      _age: 22, // 属性前面加_,代表属性只能通过对象方法访问 不然浏览器报错
      isGrowUp: false
    }
    Object.defineProperty(person, 'age', {
      get: function() {
        return this._age // this 指向 person
      },
      set: function(value) {
        this._age = value // set 重新赋值
        if (value >= 18) {
          this.isGrowUp = true
        } else {
          this.isGrowUp = false
        }
      }
    })

    console.log(person.age) // 22
    person.age = 19
    console.log(person.age) // 19
    console.log(person.isGrowUp) // true
    person.age = 12
    console.log(person.age) // 12
    console.log(person.isGrowUp) // false

2. Object.defineProperties()

Object.defineProperty(object, propertyName, descriptor) 定义新属性时,descriptor 中不能同时有 访问器(getter/setter) 与 value/writable 属性

var person = {}
    Object.defineProperties(person, {
      age: {
        value: 18,
        writable: true,
        enumerable: true
      },
      isGroup: {
        get: function() {
          if (this.age >= 18) {
            return true
          } else {
            return false
          }
        }
      }
    })

笔记:

1. vue2 中的数据双向绑定原理:使用Object.defineProperty() 中的 get 和 set

2. 当从对象中取值时,会触发 get 函数,当重新赋值时,会触发 set 函数~

如果定义了get 函数,get函数必须要有 return ,否则 【对象.属性】 取值 会是undefined~ return 的值 就是 【对象.属性】

二: vue 从改变一个数据到发生改变的全过程

1. 数据更改触发 Object.defineProperty() 中的set函数

2. Set 函数触发更新

3. 更改对应的虚拟dom

4. 更新render

三:数据双向绑定简易版代码

<body>
  <div id="app"></div>
  <script>
    function Vue(dataObj) {
      this.$data = dataObj
      this.virtualDom = ''
      this.el = document.getElementById('app')
      this.observer(dataObj)
      this.render()
    }
    // 注册 set 和 get
    Vue.prototype.observer = function(obj) {
      var _this = this
      // 递归一下 防止对象里面套对象
      for(var key in obj) {
        var value = obj[key]
        if (typeof value === 'object') {
          this.observer(value)
        } else {
          Object.defineProperty(obj, key, { // TODO
            get: function() {
              // 进行依赖收集
              // 1. data 中的数据并不是所有地方都能用到的
              // 2. 如果直接更新整个视图,有点亏
              // 3. 先收集来依赖 改变变量 的视图(组件
              // 4. 把依赖的组件进行视图更新,其他不变视图
              return value
            },
            set: function(newValue) {
              value = newValue
              _this.render()
            }
          })
        }
      }
    }
    // 渲染页面
    Vue.prototype.render = function() {
      this.virtualDom = 'i am ' + this.$data.a
      this.el.innerHTML = this.virtualDom
    }

   // 创建实例
    var vm = new Vue({
      a: 1
    })
    setTimeout(() => {
      vm.$data.a = 2 // 值改变,执行set函数
    }, 1000)
  </script>
</body>

四: Object.defineProperty() 定义的get 和 set 是对象的属性,那么数组怎么办?

在 Vue 中,通过数组下标更改数组内容,是不会触发视图更新的

var arr = [1,2,3,4]
arr[3] = 5

只有使用数组的方法,视图才能更新

arr.push()
arr.unshift()
arr.shift()
arr.pop()
arr.reserve()

先学透知识、会用,再去看原理、源码~

五:Vue3 的响应式原理

Proxy
// Proxy 对象用于定义基本操作的自定义行为
// 和 Object.defineProperty() 功能基本一样,但是用法不同

Proxy: 代理

作用:扩展(增强)对象的一些功能,比如预警、上报、扩展功能、统计等

语法: new Proxy(被代理的对象,对代理的对象做什么操作)

new Proxy : 优点不会污染原对象,重新代理 objProxy

比如: 

// 原始对象
    let obj = { name: 'zhouzhou' }
    // 代理对象
    let objProxy = new Proxy(obj, {
      // get 方法两个参数,第一个是对象,第二个是你访问的是谁就是谁 
      // objProxy.name 则 property 就是 name
      get: function(target, property) {
        console.log(`您访问了${property}属性`)
        return target[property]
      }
    })
    console.log(objProxy.name) // get 函数的return值

例子1:实现一个访问一个对象的属性,正常情况下是返回 undefined,希望如果不存在返回 warning 警告信息

  <script>
    let obj = {
      name: 'zhouzhou',
      age: 24,
      sex: '女'
    }
    let objProxy = new Proxy(obj, {
      get: function(target, property) {
        if (obj[property]) {
          return target[property]
        } else {
          throw new Error(`您访问的属性${property}不存在`)
        }
      }
    })

    console.log(objProxy.sex)
    console.log(objProxy.money)
  </script>

例子2:定义一个Proxy,DOM.div() 则生成一个div,DOM.a() 生成一个 a 标签,DOM.span 生成一个 span

<div id="app"></div>
  <script>
    let DOM = new Proxy({}, {
      get: function(target, peoperty) {
        return function(attr = {}, ...argument) {
          // 创建对象
          const el = document.createElement(peoperty)
          // 设定属性
          for(keys of Object.keys(attr)) {
            el.setAttribute(keys, attr[keys])
          }
          // 设定内容
          for(let elem of argument) {
            if (typeof elem === 'string') {
              child = document.createTextNode(elem)
            } else {
              child = elem
            }
            el.appendChild(child)
          }
          return el
        }
      }
    })

    const node = DOM.div(
      { class: 'wrapper'},
      'hhhhh',
      DOM.p({}, 'aaa'),
      DOM.a({href: 'https:www.baidu.com'}, '点击跳转')
    )
    console.log(node)
    const app = document.getElementById('app')
    app.appendChild(node)
  </script>

Proxy 的其他属性~~

1. set : 设置值 拦截

var obj = new Proxy({}, {
      // set 三个参数:目标对象、属性、属性值
      set: function(target, property, value) {
        // 可以进行值拦截校验
        if (property === 'age') {
          if (!Number.isInteger(value)) {
            throw TypeError('年龄必须为整数')
            return
          }
          if (value < 18 || value > 80) {
            throw RangeError('年龄不在合法范围内')
            return
          }
        }
        // 设置属性值
        target[property] = value
      }
    })

    obj.name = 'zhouzhou'
    obj.age = 19
    console.log(obj) //  {name: "zhouzhou", age: 19}

2. deleteProperty() : 删除属性

let json = {
      a: 1,
      b: 2
    }
    var obj = new Proxy(json, {
      deleteProperty: function(target, property) {
        console.log(`you will delete ${property}`) // 删除之前提示
        delete target[property]
      }
    })
    delete obj.a
    console.log(obj)

3. has():判断属性是否在 搭配关键字 in

let json = {
      a: 1,
      b: 2
    }
    var obj = new Proxy(json, {
      has: function(target, property) {
        console.log(`判断是否存在${property}属性`)
        return property in target
      }
    })
    
    console.log('a' in obj) // true
    console.log('c' in obj) // false

4. apply() 拦截方法

function sum (a, b) {
    return a + b
   }

  let newSum = new Proxy(sum, {
    // 目标对象、this指向、函数参数(数组形式)
    apply: function(target, context, args) {
      
      return Reflect.apply(...arguments) // 执行目标函数 sum 返回 5
    }
  })

  const result = newSum(2,3)
  console.log(result)

所以 Vue3 的响应式原理:Proxy 的 set 和 get

重写:

function Vue(dataObj) {
      this.$data = dataObj
      this.virtualDom = ''
      this.el = document.getElementById('app')
      this.observer(dataObj)
      this.render()
    }
    // 注册 set 和 get
    Vue.prototype.observer = function(obj) {
      var _this = this
      // 递归一下 防止对象里面套对象
      this.$data = new Proxy(this.$data, {
        set: function(target, property, value) {
          target[property] = value
          _this.render()
        },
        get: function(target, property) {
          return target[property]
        }
      })
    }
    // 渲染页面
    Vue.prototype.render = function() {
      this.virtualDom = 'i am ' + this.$data.a
      this.el.innerHTML = this.virtualDom
    }

   // 创建实例
    var vm = new Vue({
      a: 1
    })
    setTimeout(() => {
      vm.$data.a = 2 // 值改变,执行set函数
    }, 1000)

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值