实现vue2数据劫持

·  阅读 48

对于vue框架的使用,或许大家都不陌生,对于vue2数据劫持的大体实现是如何做到的。下面写点代码简单实现功能。

环境准备

环境是基于webpack搭建的,新建一个文件夹,进入目录终端,执行npm init -y命令创建package.json文件,安装相关指定版本依赖如下

截屏2022-02-22 下午3.55.11.png 在根目录下创建src文件夹,在其文件夹下创建index.js文件,为入口文件

在根目录创建public文件夹,在其文件夹下创建index.html文件,

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>
复制代码

在根目录创建webpack.config.js文件来编写相关配置文件。

webpack.config.js代码如下:

const path = require('path'),
      HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'js/bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  devtool: 'source-map',
  resolve: {
    modules: [path.resolve(__dirname, 'vue'), path.resolve(__dirname, 'node_modules')]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'public/index.html')
    })
  ]
}
复制代码

更改package.json脚本命令

  "scripts": {
    "dev": "webpack-dev-server",
    "build": "webpack"
  }
复制代码

数据劫持

进入src文件夹下的index.js文件,创建一个vue实例对象,通过自己实现Vue构造函数创建出来。

import Vue from 'vue'

let vm = new Vue({
  el: '#app',
  data () {
    return {
      title: '数据劫持',
      teacher: ['张三', '李四'],
      info: {
        a: {
          b: 1
        }
      },
      students: [
        {
          id: 1,
          name: '小红'
        },
        {
          id: 2,
          name: '小明'
        }
      ]
    }
  }
})
复制代码

来到vue文件夹,创建index.js入口。上面Vue构造函数的参数为一个options对象。在Vue的prototype上新增一个_init方法,初始化的时候执行它,并把options对象作为参数。

import { initState } from './init'

function Vue (options) {
  this._init(options);
}

Vue.prototype._init = function (options) {
  var vm = this;
  vm.$options = options;

  initState(vm);
}

export default Vue

复制代码

为了方便创建一个遍量vm来接收this,并把options赋值给vm.$options。在_init方法中实现initState方法,传入vm作为形参。

在vue文件夹下创建init.js文件。

function initState (vm) {
  var options = vm.$options;

  if (options.data) {
    initData(vm);
  }
}
复制代码

传入的options上带有data属性时,需要initData方法处理,

import proxyData from './proxy'
import observe from './observe'

function initData (vm) {
  var data = vm.$options.data;

  data = vm._data = typeof data === 'function' ? data.call(vm) : data || {}
  
  for (var key in data) {
    proxyData(vm, '_data', key)
  }

  observe(vm._data)
}

export {
  initState
}
复制代码

首先需要对传入options的data属性进行处理,如果传入的data为函数必须先执行后获取执行后的结果,确保后面取到的值为对象。 这里需要对获取到的值进行代理操作,主要为了方便取值,例如,当要获取options中data的title,只需要vm.title,不需要vm.data.title。

在vue文件夹下创建一个proxy.js文件,主要配合defineProperty实现

function proxyData (vm, target, key) {
  Object.defineProperty(vm, key, {
    get () {
      return vm[target][key];
    },
    set (newValue) {
      vm[target][key] = newValue;
    }
  })
}

export default proxyData
复制代码

observe方法将传入的options的data进行观察,在vue文件夹下创建observe.js文件

import Observer from './observer'

function observe (data) {
  if (typeof data !== 'object' || data === null) return
  return new Observer(data);
}

export default observe
复制代码

传入的data如果不是object类型不做处理,并且将data作为参数传递给Observer观察者处理。在vue文件夹下创建observer.js文件

function Observer (data) {
  if (Array.isArray(data)) {
    // todo
  } else {
    this.walk(data);
  }
}
复制代码

传入的data需要进行判断是否为数组,这里需要走不同的分支。处理对象类型的方法,在Observer的原型上定义walk方法

Observer.prototype.walk = function (data) {
  var keys = Object.keys(data);
   
  for (var i = 0; i < keys.length; i ++) {
    var key = keys[i],
        value = data[key];
    
    defineReactiveData(data, key, value);
  }
}
复制代码

defineReactiveData方法是将数据进行相应式操作的,在vue文件夹下创建reactive.js文件。主要用到Object.defineProperty,这里用到observe方法递归操作的原因是传入的值和设置的值的类型有可能是object类型。

import observe from "./observe"

function defineReactiveData (data, key, value) {
  observe(value);
  Object.defineProperty(data, key, {
    get () {
      console.log('响应式数据:获取', value)
      return value
    },
    set (newValue) {
      console.log('响应式数据:设置', newValue)
      if (newValue === value) return
      observe(value)
      value = newValue
    }
  })
}

export default defineReactiveData
复制代码

在vue文件夹下创建一个config.js文件夹存放改变数组方法的数据

var ARR_METHODS = [  'push',  'pop',  'shift',  'unshift',  'splice',  'sort',  'reverse']

export {
  ARR_METHODS
}
复制代码

在vue文件夹下创建一个observeArr.js文件,定义observeArr方法观察数组

import observe from './observe'

function observeArr (arr) {
  for (var i = 0; i < arr.length; i ++) {
    observe(arr[i])
  }
}

export default observeArr
复制代码

在vue文件夹下创建一个arrry.js文件,重写数组的相关方法,在数组的原型上进行扩展,Object.create创建以原数组方法为原型的对象,并且对新增的数组方法的参数进行数组的观察

import { ARR_METHODS } from './config'
import observeArr from './observeArr'

var originArrMethods = Array.prototype,
    arrMethods = Object.create(originArrMethods)

ARR_METHODS.map(function (m) {
  arrMethods[m] = function () {
    
    var args = Array.prototype.slice.call(arguments),
        rt = originArrMethods[m].apply(this, args)
 
    var newArr

    switch (m) {
      case 'push':
      case 'unshift':
        newArr = args
        break
      case 'splice'
        newArr = args.slice(2)
        break
      default:
        break
    }

    newArr && observeArr(newArr)

    return rt
  }
})

export {
  arrMethods
}
复制代码

回到observer.js文件处理数组情况的分支,将新增的数组方法赋值给data的原型,并对数组进行观察

import { arrMethods } from './array'
import observeArr from './observeArr'

function Observer (data) {
  if (Array.isArray(data)) {
    data.__proto__ = arrMethods
    observeArr(data)
  } else {
    this.walk(data)
  }
}
复制代码
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改