Vue2响应式原理一:数据劫持

文章详细介绍了Vue实例的构造函数,如何将_data和$options挂载到Vue实例上,并通过Observer类创建响应式数据。Observer类针对数据是对象或数组的情况,进行不同处理,如对象的递归遍历和属性劫持,以及数组的原型链修改和方法重写,确保数据变化时能触发视图更新。
摘要由CSDN通过智能技术生成

构造函数

将 _data和 $options挂载到 Vue实例上

class Vue {
  constructor(options) {
    const data = options.data
    this.$options = options
    this._data = typeof data === 'function' ? data() : data

    this.initData()
  }

  // 对 data中的数据进行处理
  initData() {
    // ...
  }
}

Observer

创建 Observer类,分别对 data中的数组和对象做不同的处理。

class Observer {
  constructor(data) {
    if (Array.isArray(data)) {
      this.observeArray(data)
    } else {
      this.walk(data)
    }
  }

  // 处理对象
  walk(data) {

  }

  // 处理数组
  observeArray(arr) {
    
  }
}

观察数据类型

如果是数组或对象,则调用 Observer类创建实例。

function observer(data) {
  const dataType = Object.prototype.toString.call(data)

  if (dataType !== '[object Object]' && dataType !== '[object Array]') {
    return
  }

  // 对象、数组处理...
  new Observer(data)
}

对象

如果是对象,则对其·进行递归遍历,并通过 Object.defineProperty对其属性进行劫持。

walk(data) {
  const kyes = Object.keys(data)

  for (const key of kyes) {
    let value = data[key]
    observer(value) // 递归观察 value

    Object.defineProperty(data, key, {
      get() {
        return value
      },
      set(val) {
        if (val === value) return
        value = val
      }
    })
  }
}

由于设置响应式后续还会使用,先将其抽取成函数

walk(data) {
  const kyes = Object.keys(data)
  for (const key of kyes) {
    defindReactive(data, key, data[key])
  }
}

// ...

// 定义 defindReactive函数,对传入的属性进行递归遍历劫持
function defindReactive(target, key, value) {
  observer(value) // 递归观察 value

  Object.defineProperty(target, key, {
    get() {
      return value
    },
    set(val) {
      if (val === value) return
      value = val
    }
  })
}

数组

如果是数组,则修改其原型链,并重写7个改动数组的方法
重写方法

// 第一步:定义需要重写的方法明名和数组新的原型对象
const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sotr', 'splice']
const arrayMethods = {}

// 第二步:将 arrayMethods的原型对象指向 Array.prototype
arrayMethods.__proto__ = Array.prototype

// 第三步:在 arrayMethods中对方法进行重写
for (const method of methods) {
  arrayMethods[method] = function(...args) {
    const res = Array.prototype[method].apply(this, args)

    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
      default:
        break
    }
    inserted?.forEach(observer) // 观察数组新增元素

    return res
  }
}

此处三步可写成一步

const arrayMethods = methods.reduce((obj, method) => {
  obj[method] = function (...args) {
    const res = Array.prototype[method].apply(this, args)

    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
      default:
        break
    }
    inserted?.forEach(observer)

    return res
  }

  return obj
}, Object.create(Array.prototype))

处理数组

class Observer {
  constructor(data) {
    if (Array.isArray(data)) {
      data.__proto__ = arrayMethods // 修改数组原型链
      this.observeArray(data) // 对数组中的每一个元素进行观察
    } else {
      this.walk(data)
    }
  }

  // ...

  // 处理数组
  observeArray(arr) {
    arr.forEach(observer)
  }
}

挂载

对数组和对象的递归遍历劫持已完成,接下来将属性挂载到vue实例上方便使用。

initData() {
  const data = this._data
  observer(data)
  
  const keys = Object.keys(data)
  for (const key of keys) {
    Object.defineProperty(this, key, {
      get() {
        return data[key]
      },
      set(value) {
        data[key] = value
      }
    })
  }
}

完整代码

class Vue {
  constructor(options) {
    const data = options.data
    this.$options = options
    this._data = typeof data === 'function' ? data() : data

    this.initData()
  }

  initData() {
    const data = this._data
    observer(data)

    const keys = Object.keys(data)
    for (const key of keys) {
      Object.defineProperty(this, key, {
        get() {
          return data[key]
        },
        set(value) {
          data[key] = value
        }
      })
    }
  }
}

function observer(data) {
  const dataType = Object.prototype.toString.call(data)

  if (dataType !== '[object Object]' && dataType !== '[object Array]') {
    return
  }

  // 对象、数组处理...
  new Observer(data)
}

function defindReactive(target, key, value) {
  // 递归观察 value
  observer(value)

  Object.defineProperty(target, key, {
    get() {
      return value
    },
    set(val) {
      if (val === value) return
      value = val
    }
  })
}

class Observer {
  constructor(data) {
    if (Array.isArray(data)) {
      data.__proto__ = arrayMethods
      this.observeArray(data)
    } else {
      this.walk(data)
    }
  }

  // 处理对象
  walk(data) {
    const kyes = Object.keys(data)

    for (const key of kyes) {
      defindReactive(data, key, data[key])
    }
  }

  // 处理数组
  observeArray(arr) {
    arr.forEach(observer)
  }
}

const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sotr', 'splice']
const arrayMethods = methods.reduce((obj, method) => {
  obj[method] = function (...args) {
    const res = Array.prototype[method].apply(this, args)

    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
      default:
        break
    }
    inserted?.forEach(observer)

    return res
  }

  return obj
}, Object.create(Array.prototype))

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值