对于vue框架的使用,或许大家都不陌生,对于vue2数据劫持的大体实现是如何做到的。下面写点代码简单实现功能。
环境准备
环境是基于webpack搭建的,新建一个文件夹,进入目录终端,执行npm init -y命令创建package.json文件,安装相关指定版本依赖如下
在根目录下创建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)
}
}
复制代码