手写Vue2.0 数据劫持的示例

一:搭建webpack

简单的搭建一下webpack的配置。新建一个文件夹,然后init一下。之后新建一个webpack.config.js文件,这是webpack的配置文件。安装一下简单的依赖。

npm install webpack webpack-cli webpack-dev-server -D

在同级目录下新建一个public/index.html和src/index.js,作为出口文件和入口文件。

j简单配置一下webpack, 在webpack.config.js文件中:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
 entry: './src/index.js',
 output: {
  filename: 'bundle.js',
  path: path.resolve(__dirname, 'dist')
 },
 resolve: {
  modules: [
  path.resolve(__dirname, ''), path.resolve(__dirname, 'node_modules')
  ]
 },
 plugins: [
  new HtmlWebpackPlugin({
   template: path.resolve(__dirname, 'public/index.html')
  })
 ]
}

ok,基本配置好webpack就可以开始正题了。

二:数据劫持

在v2中,通过new Vue(el, options)的方式,完成vue的实例化。我们需要新建一下vue文件,把数据劫持的方法统一到vue中。

新建一个vue/index.js,作为数据劫持的入口文件。

import {initState} from './init.js';

function Vue (options) {
 this._init(options);  // 数据初始化
}
Vue.prototype._init = function (options) {
 var vm = options; // 保存一下实例
 vm.$options = options; // 实例挂载
 initState(vm);   // 实例初始化
}

新建一个init.js文件初始化实例:

初始化的时候注意几个问题:

1.  需要分别对computed,watch, data进行处理。

2. 不要在用户定义的data上直接修改。

3. 官方指定data为函数,是为了保证组件内部有自己的作用域不会有污染,直接访问data函数是不行的,需要自动执行。data也可以是对象(需要考虑到这个情况)

4. 这种方式获取data是通过vm._data.xxx 但是在vue中不需要data来获取,所以这里需要拦截重写。

5. 内部的引用类型需要递归

function initState (vm) {
 var options = vm.$options; // 获取options
 if (options.data) {
  initData(vm); // 因为computed,watch都需要在这里初始化,所以针对data初始化
};

function initData (vm) {
 var data = vm.$options.data; // 对data重新赋值,不要改变用户定义的data
 data = vm._data = typeof data === 'function' ? data.call(vm) : data || {};
 for (var key in data) {
  proxyData(vm, '_data', key);   // 对data的取值重新赋值
 };
 observe(vm._data); // 对data内部进行观察
}

新建一个proxy.js作为代理层:

function proxyData(vm, target, key) {
 Object.defineProperty(vm, key, {
   get () {
   // 这里做了拦截: vm.xxx => vm._data.xxx
   return vm[target][key];
  },
  set(newValue) {
   // vm.xxx = yyy ===> vm._data.title = yyy
   vm[target][key] = newValue;
  }
 })
}
export default proxyData;

处理好了访问问题,现在需要递归一下data内部元素。obseve(vm._data);

新建一个observe.js:

function observe (data) {
 if (typeof data !== 'object' || data = null) return;
 return new Observer(data); // 如果是应用类型,直接添加一个观察者
}

新建一个观察者:observer.js

function Observer(data) {
 if (Array.isArray(data)) {
  // 处理数组
   data._proto_ = arrMethods;
 }
 else {
  // 处理对象
  this.walks(data);
 }
}
Observer.prototype.walks = function (data) {
 let keys = Object.keys(data); // 拿到data下面所有的key,并且还是一个数组
 for (var i = 0 ; i < keys.length ; i++) {
  var key = keys[i];
  var value = data[key];
  defineReactiveData(data, key, value); // 每个重新生成响应式数据
 }}

新建一个reactive.js 处理对象等响应式

function defineReactiveData (data, key, value) {
 observe(value);  // 对子元素接着递归。
 Object.defineProperty(data, key, {
  get() {
   return value;
  },
  set (newValue) {
   if (newValue === value) return;
   value = newValue;  // 触发更改
  }
 }
 )
};

ok,这里处理好了对象的数据劫持,剩余的需要处理数组了

在V2中采用重写原型上的7种方法,做到数据劫持。

劫持数组:

新建一个Array.js文件:

import {ARR_METHODS} from './config.js';
 // 7个数组方法的合集
import observeArr from './observeArr.js';
var originArrMethods = Array.prototype,
arrMethods = Object.create(originArrMethods);
ARR_METHODS.map(function (m) {
 arrMethods[m] = function () {
  var args = Array.prototype.slice.call(arguments); // 类数组转为数组
  var rt = originArrMethods[m].apply(this, args);
  var newArr;
  switch (m) {
   case 'push':
   case 'ushift':
    newArr = args;
   case 'splice':
    newArr = args.slice(2);
    break;
   default:
    break;  };
  newArr && observeArr(newArr);
  return rt;
  }
});
 export { arrMethods }

observeArr(newArr): 数组也可能有嵌套,所以需要对数据进行观察。

import observe from "./observe";
function observeArr (arr) {
 for (var i = 0 ; i < arr.length ; i++) {
  observe(arr[i]); // 重新走到了observe上。
 }
}
export default observeArr;

三:总结

基本流程就是这样的,不仅仅是object.defineProperty对数据进行get和set这么简单。总结一下主要流程:

(1): 在初始化的时候:保存一下实例,挂载实例。通过initState方法来初始化数据,这里主要是data数据,也有computed和watch需要处理。

(2): 调用initData(); 重新赋值data,然后执行data,修改用户获取data属性的写法统一为this.xxx同时observe(data)

(3):在observe(data)的时候需要对data进行判断,如果是引用类型需要加上一个观察者observer,同时在观察者终判断data是为数组还是对象,对象直接重新触发object.defineProperty,同时对内部重新observe。如果是数组直接重新7种数组方法,然后对数组内部接着observe。

以上就是手写Vue2.0 数据劫持的示例的详细内容,更多关于手写vue 数据劫持的资料请关注我们其它相关文章!

(0)

相关推荐

  • Vue 重置data的数据为初始状态操作

    在某些情况下,需要重新使用data中的数据,但是data中的数据已经被各种表单.变量等赋值,那么怎么重置data的值呢? 1. 逐个赋值 ... data() { return { name: '', sex: '', desc: '' } } ... // 逐个赋值 this.name = '' this.sex = '' this.desc = '' 这个方法比较笨,当然也可以实现效果,但是一个一个去重新赋值比较麻烦而且代码看起来也会比较乱. 下面这个方法肯定是你喜欢的,一行代码搞定- 2.

  • 用vue设计一个数据采集器

    场景 在业务上现在有一个场景,当发生业务行为变化时,需要对各个模块的行为进行数据收集,数据用途可以用作回顾,也可以是例如监控这样的场景. 核心问题 说白了这个需求就是需要对各个模块状态变更进行记录,然后再格式化上传到服务端. 解题思路有两种一种是状态监听,第二主动收集. 状态监听 状态监听优势 快速实现利用状态管理和wacth的机制很快就知道不同模块的状态变更,然后就可以获取数据,再格式化数据,发送给服务端 状态监听劣势 wacth的重复监听,只要使用了wacth,不管是不是你所需要的数据,只要

  • vue+canvas实现数据实时从上到下刷新瀑布图效果(类似QT的)

    话不多说了,先上一张Demo图,实现的功能有:左侧图例.右侧瀑布图.鼠标移入弹出当前坐标对应的数据信息(有优化的空间,大家自由发挥). 图例使用到的插件 这里推荐使用安装npm插件colormap 瀑布图主体内容 这里不多做解释了,都是一些原生标签还有vue绑定的事件,可以根据实际项目情况自己封装成组件,我这里是写在一起的. <template> <div> <div class="content"> <div class="neir

  • Vue组件传值过程中丢失数据的分析与解决方案

    前言 在上一篇文章 JavaScript 中的两种数据类型中,分别介绍了基本类型和引用类型,以及引用类型的浅拷贝与深拷贝.这里需要注意的是,该文章中深拷贝引用类型值的方法,并不是完美的,引用类型值中的某些属性值,仍不能完整地复制到新的变量中.比如函数值,在深拷贝过程中,就会丢失. 问题 在实际项目中,假如使用了二次封装的组件,并且封装的组件内部做了一些属性值的深拷贝操作,就有极有可能因为传入的属性值是引用类型的值,导致丢失部分数据. 举例 以基于 el-table 封装的 ak-table 组件

  • 关于vuex强刷数据丢失问题解析

    vuex-persistedstate 核心原理:在本地存储中存入所有的vuex数据,页面刷新时到缓存中取数据,放到vuex中 下载:$ npm install vuex-persistedstate -S 在store中引入插件 import persistedState from 'vuex-persistedstate' const store = new Vuex.Store({ // ... plugins: [persistedState()] }) vuex-persistedst

  • vue 数据(data)赋值问题的解决方案

    总结一下我遇到的一个纠结很久的问题. 在项目中需要用到后台的数据对前端渲染,使用到了vue整合的axios,使用vue中的钩子函数在页面组件挂载完成之后向后台发送一个get请求然后将返回后的数据赋值data()中定义的属性: 执行后前端报错: 原因: 在请求执行成功后执行回调函数中的内容,回调函数处于其它函数的内部this不会与任何对象绑定,为undefined. 解决方案: 一)将指向vue对象的this赋值给外部方法定义的属性,然后在内部方法中使用该属性 二)使用箭头函数 补充:解决 vue

  • Vue2.0/3.0双向数据绑定的实现原理详解

    Vue2.0/3.0 双向数据绑定的实现原理 双向数据绑定简意 即数据的改变能让页面重新渲染 Vue2.0 ES5的原理: Object.defineProperty 对数据进行拦截 简单小案例 <body> 姓名: <span id="name"></span> <br /> <input type="text" id="inputName" /> </body> 改变in

  • SpringBoot+Vue实现数据添加功能

    一.添加代码生成器 用来自动为数据库映射类建立:mapper.service.controller 注:代码生成器的写法,参考官方文档:https://mp.baomidou.com/ package com.hanmh.utils; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomido

  • Vue中避免滥用this去读取data中数据

    前言 在Vue中,data选项是个好东西,把数据往里一丢,在一个Vue组件中任何一个地方都可以通过this来读取data中数据.但是要避免滥用this去读取data中数据,至于在哪里要避免滥用,如果滥用会导致什么后果,本专栏将会一一揭晓. 一.用this读取data中数据的过程 在Vue源码中会把data中数据添加getter函数和setter函数,将其转成响应式的.getter函数代码如下所示: function reactiveGetter() { var value = getter ?

  • Vue 如何追踪数据变化

    背景 工作中时不时就会遇到这种情况:页面上显示的数据不对,需要前端同事进行定位. 在一个使用 Vue 搭建的 SPA 应用上,页面最终展示的数据,从初始化,到最后的展示,过程可能很简单,也可能很复杂.遇到复杂的数据流,没有合适的方法,排查起来会很头疼. 如果能够看到数据发生变化时的调用栈,就可以知道在错误的数据生成前,发生了什么,是哪一步的错误,导致了最终的错误.顺着调用栈给出的线索找下去,就能够快速定位到问题. 例子 <template> <div> <!-- 预期输出:h

  • vue 数据双向绑定的实现方法

    1. 前言 本文适合于学习Vue源码的初级学者,阅读后,你将对Vue的数据双向绑定原理有一个大致的了解,认识Observer.Compile.Wathcer三大角色(如下图所示)以及它们所发挥的功能. 本文将一步步带你实现简易版的数据双向绑定,每一步都会详细分析这一步要解决的问题以及代码为何如此写,因此,在阅读完本文后,希望你能自己动手实现一个简易版数据双向绑定. 2. 代码实现 2.1 目的分析 本文要实现的效果如下图所示: 本文用到的HTML和JS主体代码如下: <div id="ap

随机推荐