1. 程式人生 > >Vue 原始碼分析之proxy代理

Vue 原始碼分析之proxy代理

Vue 原始碼分析之proxy代理

當我們在使用Vue進行資料設定時,通常初始化格式為:

let data = {
    age: 12,
    name: 'yang'
}
// 例項化Vue物件
let vm = new Vue({
    data
})

// 輸出
vm.age // 12
vm.name // 'yang'
vm.data.age // throw error

我們發現,我們訪問vm例項的物件屬性時,是直接通過 vm.age 訪問,而通過vm.data.age訪問則會報錯。

處於好奇,初窺Vue的原始碼,才瞭解一二,這裡就與大家一同分享一下:

在這裡插入圖片描述

實現步驟

看完原始碼大致梳理一下流程,主要是下面三點:

  1. 例項化Vue
  2. 掛載data
  3. 代理data

那麼我們來簡單的例項一個Viwe(仿Vue)類,來實現文章開頭展示的功能。

1. 例項化Vue

即建立一個vm例項物件,給View類傳入data物件

// 建立一個View類
const View = function (options) {
    console.log(this, options)
}
// 例項化View
const vm = new View({
    data: {
        age: 12,
        name: 'yang'
    }
})

// 此時,無法訪問data中的屬性
vm // {}
vm.age // undefined
vm.name // undefined

2. 掛載data

緊接上一步,我們開始為vm掛載_data物件(私有物件以’_'起始),那麼,我們就要準備改造我們的View類了。


const vm = function (options) {
    // 取出data物件
    const data = options.data || {}
    // 將data 指向this._data
    this._data = data
}

// 例項化View
const vm = new View({
    data: {
        age: 12,
        name: 'yang'
    }
})

// 此時,無法訪問data中的屬性
vm // {_data:{...}}
vm._data // {age:12,name:'yang'}
vm._data.age // 12
vm._data.name // 'yang'
vm.age // undefined
vm.name // undefined

3. 代理data

在掛載data的時候,可以通過vm._data訪問data中的物件屬性了,但是這和我們的預期還是差一點。

這裡,我們使用Object.defineProperty來代理vm._data中的物件屬性。

關於Object.defineProperty使用教程,請參考MDN文件

下面我們繼續改造我們的View類


// 定義一個空函式
const noop = function () {}
// 定義一個預設屬性配置
const defineProperty = {
    enumerable: true,
    configurable: true,
    get: noop,
    set: noop
}

const View = function (options) {
    
    this._data = options.data || {}
    
    // 獲取this._data中的屬性名
    Object.keys(this._data).forEach(key => {
        // 代理
        defineProperty.get = function dataGetter() {
            return this._data[key]
        }
        defineProperty.set = function dataSetter (val) {
            this._data[key] = val
        }
        // 關鍵點,代理this._data中的屬性
        Object.defineProperty(this,key,defineProperty)
    
    })
}

const vm = new View({
    age: 12,
    name: 'yang'
})

vm // {_data:{...},age:12,name:'yang'}
vm._data // {age:12,name:'yang'}
vm.age // 12
vm.name // 'yang'

改到這裡,一個基本的代理實現就完成啦~,下面,我們看看是否能夠使用其他方式達到相同的改造效果呢?

二、使用ES6原生 Proxy實現

先來了解一下Proxy的簡單用法:

例子一:


/**
* 定義:
* target type: Object
* handler type: Object
 **/
let proxy = new Proxy(target,handler)

// 用法
let target = {}
let proxy = new Proxy(target,{
    get: function () {
        return 'yang'
    },
    set: function () {
        return 12
    }
})

proxy.a // 'yang'
proxy.xxx // 'yang'
proxy.a = 1 // 12

例子二:

const target = {
  data: {
    age: 12,
    name: 'yang'
  }
}
const proxy = new Proxy(target, {
  get: function dataGetter (target, key) {
    return target.data[key] || {}
  },
  set: function dataSetter (target, key, val) {
    target.data[key] = val
    return true
  }
})
proxy.age = 1
console.log('p', proxy.age)

好了,掌握了基本語法之後,那麼開始進行構建:

// 定義View類
const View = function (options) {
  // 掛載_data
  this._data = options.data || {}
  // 返回一個Proxy物件
  return new Proxy(this, {
    // get: function (target, key) {
    //   return target._data[key]
    // },
    // set: function (target, key, val) {
    //   target._data[key] = val
    //   return true
    // }
    // 簡化寫法
    get: (target, key) => this._data[key],
    set: (target, key, val) => this._data[key] = val // eslint-disable-line
  })
}

let vm = new View({
  data: {
    age: 12,
    name: 'yang'
  }
})
vm // Proxy {...}
vm.age += 1
console.log('vm', vm.age) // 13



好了,基本的內容講解完了。如果有興趣可以一起探討~多謝支援