Vue2 中的数据劫持简写示例

 更新时间:2023年02月23日 15:35:42   作者:Serein_  
这篇文章主要为大家介绍了Vue2 中的数据劫持简写示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

package.json 相关依赖

我们今天要编写的项目通过需要使用 Webpack 进行编译,package.json 相关依赖如下:

{
  "scripts": {
    "dev": "webpack-dev-server",
    "build:": "webpack"
  },
  "devDependencies": {
    "html-webpack-plugin": "^4.5.2",
    "webpack": "^4.46.0",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.3"
  }
}

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")
  },
  devtool: "source-map",
  resolve: {
    // 表示解析模块引入的时候先从当前文件夹寻找模块,再去 node_modules 找模块
    modules: [
      path.resolve(__dirname, ""), 
      path.resolve(__dirname, "node_modules")
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public/index.html")
    })
  ]
};

public/index.html 文件内容

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title></title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

全部文件目录结构

好了,接下来我们就开始发车!

实例一个模拟的 Vue 应用

首先,我们需要编写我们的入口文件 index.js,该文件很普通主要就是实例一个模拟的 Vue 应用:

// index.js
// 我们在 webpack.config.js 中进行了配置,所以这里优先在当前目录下寻找 vue 文件,也就是我们的 vue/index.js 文件
import Vue from "vue"; 
let vm = new Vue({
  el: "#app",
  data() {
    return {
      title: "学生列表",
      classNum: 1,
      teacher: ["张三", "李四"],
      info: {
        a: {
          b: 1
        }
      },
      students: [
        {
          id: 1,
          name: "小红"
        },
        {
          id: 2,
          name: "小明"
        }
      ]
    };
  }
});
console.log(vm);

vue/index.js 文件主要是负责初始化内容

// src/sindex.js
import { initState } from "./init";
function Vue(options) {
  this._init(options);
}
Vue.prototype._init = function (options) {
  // this 指向当前实例对象
  var vm = this;
  // 我们把 new Vue() 时候传递的数据统称为 options
  // 并且挂载到 Vue 的实例对象上
  vm.$options = options;
  // 调用 initState 初始化 data 数据
  initState(vm);
};
export default Vue;

initState方法

vue/init.js 文件暴露出一个initState方法,该方法主要是处理初始化的数据:

// vue/init.js
import proxyData from "./proxy";
import observer from "./observe"
function initState(vm) {
  var options = vm.$options;
  // 如果 options 中存在 data 属性,我们才会继续处理
  if (options.data) {
    initData(vm);
  }
}
function initData(vm) {
  var data = vm.$options.data;
  // 把 data 数据单独保存到 Vue 的实例化对象上,方便我们获取
  // 如果 data 是一个函数,我们需要执行返回得到返回的对象
  data = vm._data = typeof data === "function" ? data.call(vm) : data || {};
  // 遍历 data 对象,通过 proxyData 对数据进行拦截
  for (const key in data) {
    // 传入的参数分别是:当前实例、key值(也就是 vm._data)、data 中的 key 值(例如 vm._data.title)
    proxyData(vm, "_data", key);
  }
  // 调用观察者模式
  observer(vm._data)
}
export { 
  initState
};

以上代码,我们通过proxyDatadata中的数据进行拦截,详情如下:

// vue/proxy.js
function proxyData(vm, target, key) {
  // 当访问 vm.title 的时候转换为 vm._data.title
  //(请记住这句话!!!)
  Object.defineProperty(vm, key, {
    get: function () {
      return vm[target][key];
    },
    set: function (newVal) {
      vm[target][key] = newVal;
    }
  });
}
export default proxyData;

我们还调用了observer方法进行事件订阅,详细如下:

// vue/observe.js
import Observer from "./observer"
function observe(data) {
  // 判断只处理对象,如果不是对象直接返回
  if (typeof data !== "object" || data === null) {
    return false;
  }
  // 观察数据
  return new Observer(data)
}
export default observe;

核心文件vue/observer.js

接下来就是我们的核心文件vue/observer.js,该文件主要负责对数据类型进行判断,如果是数组就需要单独处理数组,这个我们后面再说:

// vue/observer.js
import defineReactiveData from "./reactive";
import { arrMethods } from "./array";
import observeArr from "./observeArr";
// 这个方法会在多个地方调用,请记住这个方法以它的作用
function Observer(data) {
  // 如果 data 是一个数组,那面需要单独处理
  if (Array.isArray(data)) {
    // 给数组新增一层原型
    data._proto__ = arrMethods;
    // 循环数组的每一项,然后让每一项都调用 Observer 方法进行订阅
    observeArr(data)
  } else {
    // 处理对象
    this.walk(data);
  }
}
Observer.prototype.walk = function (data) {
  // 获取到 data 全部的 key
  // 也就是我们定义的 ['title', 'classNum', 'teacher', 'info', 'students']
  let keys = Object.keys(data);
  for (var i = 0; i < keys.length; i++) {
    let key = keys[i];
    let value = data[key];
    // 拦截 data 数据
    // 分别传入参数为:vm._data、data 中的 key、data 中 key 对应的 value
    defineReactiveData(data, key, value);
  }
};
export default Observer;

以上代码,我们分别对数组和对象执行不同的操作,我们先来看对象的操作:

Observer构造函数中我们新增了一个walk方法,该方法获取到了所有的key值,然后调用了defineReactiveData进行处理。

// vue/reactive.js
import observe from "./observe";
function defineReactiveData(data, key, value) {
  // 例如 info.a 还是个对象,那么就递归观察
  observe(value);
  // 这里的 data 是 vm._data,所以这里拦截的也是 vm._data
  Object.defineProperty(data, key, {
    get() {
      console.log(`⤴️ 响应式获取:data.${key},`, value);
      return value;
    },
    set(newVal) {
      console.log(`🔁 响应式设置:data.${key},`, newVal);
      if (newVal === value) {
        return false;
      }
      // 如果新值还是对象,那么接着进行观察
      observe(newVal);
      value = newVal;
    }
  });
}
export default defineReactiveData;

以上代码,我们是对vm._data进行拦截的,这是因为我们前面说的proxyData拦截的是vm对象,当访问vm.title的时候,proxyData的拦截就会生效,而proxyData内部是通过vm._data来获取的,这样又会触发defineReactiveData的拦截!

vue/observer.js文件对数组进行处理

回到vue/observer.js文件,我们还需要对数组进行处理:

import defineReactiveData from "./reactive";
import { arrMethods } from "./array";
import observeArr from "./observeArr";
// 这个方法会在多个地方调用,请记住这个方法以它的作用
function Observer(data) {
  // 如果 data 是一个数组,那面需要单独处理
  if (Array.isArray(data)) {
    // 为数组更改原型
    data._proto__ = arrMethods;
    // 循环数组的每一项,然后让每一项都调用 Observer 方法进行订阅
    observeArr(data)
  } else {
   // ...
  }
}
Observer.prototype.walk = function (data) {
  // ...
};
export default Observer;

以上代码我们对数组更改一个原型arrMethods,那看看它到底做了什么事情:

// vue/array.js
// ARR_METHODS 是一些可以更改数组本身的方法,里面包括以下内容,我们就不展开看了
// ["push", "pop", "shift", "unshift", "splic", "sort", "reverse"]
import { ARR_METHODS } from "./config";
import observeArr from "./observeArr";
// 把数组本身的元素进行拷贝
var originArrayMethods = Array.prototype;
// 创建一个空对象,该空对象的原型就是数组的原型
var arrMethods = Object.create(originArrayMethods);
// 遍历这些数组的方法名称
ARR_METHODS.forEach(function (m) {
  // 在新对象上重写数组的方法
  arrMethods[m] = function () {
    // 把数组接到的参数转换为一个数组
    var args = Array.prototype.slice.call(arguments);
    // 执行数组原本的方法
    var rt = originArrayMethods[m].apply(this, args);
    var newArr;
    switch (m) {
      case "push":
      case "unshift":
        // 例如 arr.push({a: 1})
        // args 就是 [{a: 1}]
        newArr = args;
        break;
      case "splice":
        // 例如 arr.splice(1, 0, {a: 1}, {b: 2})
        // args 就是 [{a: 1}, {b: 2}]
        newArr = args.slice(2);
        break;
      default:
        break;
    }
    // 如果有值那面就调用 observeArr 方法
    // observeArr 方法就是循环数组的每一项,然后让每一项都调用 Observer 方法进行订阅
    newArr && observeArr(newArr);
    return rt;
  };
});
export { arrMethods };

以上代码我们重写了数组相关的方法,这是因为这些方法被并不能被Object.defineProperty拦截到。

详细请看:v-for 列表循环

所以我们通过重写方法的方式,让数组可以正常的执行方法,同时也能被我们的observeArr方法拦截到,所以数组现在就是这样多了一层我们写的原型,但最终它还是继承于Array构造函数的:

而我们的observeArr只是遍历了数组的每一项,让每一项都进行了拦截:

// vue/observeArr.js
import observe from "./observe";
function observeArr(arr) {
  for (let i = 0; i < arr.length; i++) {
    // 又回到了起点,进行更新订阅
    observe(arr[i]);
  }
}
export default observeArr;

然后我们去index.js文件获取属性,看看结果:

import Vue from "vue";
let vm = new Vue({
  el: "#app",
  data() {
    return {
      title: "学生列表",
      classNum: 1,
      teacher: ["张三", "李四"],
      info: {
        a: {
          b: 1
        }
      },
      students: [
        {
          id: 1,
          name: "小红"
        },
        {
          id: 2,
          name: "小明"
        }
      ]
    };
  }
});
console.log(vm);
console.log(vm.title);
console.log(vm.teacher);
console.log(vm.info.a);

以上就是Vue2 中的数据劫持简写示例的详细内容,更多关于Vue2 数据劫持的资料请关注脚本之家其它相关文章!

相关文章

  • Vue实现图片预览功能的详细指南

    Vue实现图片预览功能的详细指南

    在现代 web 应用程序中,图片预览功能提升了用户体验,使用户可以在上传图片之前查看图片内容,本文将详细介绍如何在 Vue.js 应用中实现图片预览功能,包括基本实现、进阶功能、与 Element UI 的集成、常见优化技巧以及与其他库的结合使用,需要的朋友可以参考下
    2024-09-09
  • 解决el-select数据量过大的3种方案

    解决el-select数据量过大的3种方案

    最近做完一个小的后台管理系统,快上线了,发现一个问题,有2个select的选项框线上的数据量是1w+,而测试环境都是几百的,所以导致页面直接卡住了,本文给大家总结了3种方法,需要的朋友可以参考下
    2023-09-09
  • vue3 axios 实现自动化api配置详解

    vue3 axios 实现自动化api配置详解

    这篇文章主要为大家介绍了vue3 axios 实现自动化api配置详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • vue+layui实现select动态加载后台数据的例子

    vue+layui实现select动态加载后台数据的例子

    今天小编就为大家分享一篇vue+layui实现select动态加载后台数据的例子,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-09-09
  • 详解Vue3 父组件调用子组件方法($refs 在setup()、<script setup> 中使用)

    详解Vue3 父组件调用子组件方法($refs 在setup()、<script setup> 中使用)

    这篇文章主要介绍了Vue3 父组件调用子组件方法($refs 在setup()、<script setup> 中使用),在 vue2 中 ref 被用来获取对应的子元素,然后调用子元素内部的方法,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-08-08
  • vue.js实现简单计时器功能

    vue.js实现简单计时器功能

    这篇文章主要为大家详细介绍了vue.js实现简单计时器功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • uni-app自定义组件详细代码示例

    uni-app自定义组件详细代码示例

    组件是vue技术中非常重要的部分,组件使得与ui相关的轮子可以方便的制造和共享,进而使得vue使用者的开发效率大幅提升,这篇文章主要给大家介绍了关于uni-app自定义组件的相关资料,需要的朋友可以参考下
    2024-02-02
  • 详解element ui 添加自定义方法

    详解element ui 添加自定义方法

    今天在修改 el-table 源码过程中遇到一个头大的问题,原本修改编译后,将 element的子目录lib下的文件复制到项目的响应目录里就可以了,但是这次总出问题,下面小编给大家分享element ui 添加自定义方法,感兴趣的朋友一起看看吧
    2024-02-02
  • vue中$nexttick,$set,$forceupdate的区别

    vue中$nexttick,$set,$forceupdate的区别

    本文主要介绍了vue中$nexttick,$set,$forceupdate的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • elementplus实现多级表格(最后一级展示图片)

    elementplus实现多级表格(最后一级展示图片)

    本文主要介绍了elementplus实现多级表格(最后一级展示图片),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05

最新评论