经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » Vue.js » 查看文章
Vue2?中的数据劫持简写示例
来源:jb51  时间:2023/2/24 9:06:47  对本文有异议

package.json 相关依赖

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

  1. {
  2. "scripts": {
  3. "dev": "webpack-dev-server",
  4. "build:": "webpack"
  5. },
  6. "devDependencies": {
  7. "html-webpack-plugin": "^4.5.2",
  8. "webpack": "^4.46.0",
  9. "webpack-cli": "^3.3.12",
  10. "webpack-dev-server": "^3.11.3"
  11. }
  12. }

Webpack.config.js 配置文件

  1. const path = require("path");
  2. const HtmlWebpackPlugin = require("html-webpack-plugin");
  3. module.exports = {
  4. entry: "./src/index.js",
  5. output: {
  6. filename: "bundle.js",
  7. path: path.resolve(__dirname, "dist")
  8. },
  9. devtool: "source-map",
  10. resolve: {
  11. // 表示解析模块引入的时候先从当前文件夹寻找模块,再去 node_modules 找模块
  12. modules: [
  13. path.resolve(__dirname, ""),
  14. path.resolve(__dirname, "node_modules")
  15. ]
  16. },
  17. plugins: [
  18. new HtmlWebpackPlugin({
  19. template: path.resolve(__dirname, "public/index.html")
  20. })
  21. ]
  22. };

public/index.html 文件内容

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7. <title></title>
  8. </head>
  9. <body>
  10. <div id="app"></div>
  11. </body>
  12. </html>

全部文件目录结构

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

实例一个模拟的 Vue 应用

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

  1. // index.js
  2. // 我们在 webpack.config.js 中进行了配置,所以这里优先在当前目录下寻找 vue 文件,也就是我们的 vue/index.js 文件
  3. import Vue from "vue";
  4. let vm = new Vue({
  5. el: "#app",
  6. data() {
  7. return {
  8. title: "学生列表",
  9. classNum: 1,
  10. teacher: ["张三", "李四"],
  11. info: {
  12. a: {
  13. b: 1
  14. }
  15. },
  16. students: [
  17. {
  18. id: 1,
  19. name: "小红"
  20. },
  21. {
  22. id: 2,
  23. name: "小明"
  24. }
  25. ]
  26. };
  27. }
  28. });
  29. console.log(vm);

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

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

initState方法

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

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

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

  1. // vue/proxy.js
  2. function proxyData(vm, target, key) {
  3. // 当访问 vm.title 的时候转换为 vm._data.title
  4. //(请记住这句话!!!)
  5. Object.defineProperty(vm, key, {
  6. get: function () {
  7. return vm[target][key];
  8. },
  9. set: function (newVal) {
  10. vm[target][key] = newVal;
  11. }
  12. });
  13. }
  14. export default proxyData;

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

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

核心文件vue/observer.js

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

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

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

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

  1. // vue/reactive.js
  2. import observe from "./observe";
  3. function defineReactiveData(data, key, value) {
  4. // 例如 info.a 还是个对象,那么就递归观察
  5. observe(value);
  6. // 这里的 data 是 vm._data,所以这里拦截的也是 vm._data
  7. Object.defineProperty(data, key, {
  8. get() {
  9. console.log(`?? 响应式获取:data.${key},`, value);
  10. return value;
  11. },
  12. set(newVal) {
  13. console.log(`?? 响应式设置:data.${key},`, newVal);
  14. if (newVal === value) {
  15. return false;
  16. }
  17. // 如果新值还是对象,那么接着进行观察
  18. observe(newVal);
  19. value = newVal;
  20. }
  21. });
  22. }
  23. export default defineReactiveData;

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

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

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

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

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

  1. // vue/array.js
  2. // ARR_METHODS 是一些可以更改数组本身的方法,里面包括以下内容,我们就不展开看了
  3. // ["push", "pop", "shift", "unshift", "splic", "sort", "reverse"]
  4. import { ARR_METHODS } from "./config";
  5. import observeArr from "./observeArr";
  6. // 把数组本身的元素进行拷贝
  7. var originArrayMethods = Array.prototype;
  8. // 创建一个空对象,该空对象的原型就是数组的原型
  9. var arrMethods = Object.create(originArrayMethods);
  10. // 遍历这些数组的方法名称
  11. ARR_METHODS.forEach(function (m) {
  12. // 在新对象上重写数组的方法
  13. arrMethods[m] = function () {
  14. // 把数组接到的参数转换为一个数组
  15. var args = Array.prototype.slice.call(arguments);
  16. // 执行数组原本的方法
  17. var rt = originArrayMethods[m].apply(this, args);
  18. var newArr;
  19. switch (m) {
  20. case "push":
  21. case "unshift":
  22. // 例如 arr.push({a: 1})
  23. // args 就是 [{a: 1}]
  24. newArr = args;
  25. break;
  26. case "splice":
  27. // 例如 arr.splice(1, 0, {a: 1}, {b: 2})
  28. // args 就是 [{a: 1}, {b: 2}]
  29. newArr = args.slice(2);
  30. break;
  31. default:
  32. break;
  33. }
  34. // 如果有值那面就调用 observeArr 方法
  35. // observeArr 方法就是循环数组的每一项,然后让每一项都调用 Observer 方法进行订阅
  36. newArr && observeArr(newArr);
  37. return rt;
  38. };
  39. });
  40. export { arrMethods };

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

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

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

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

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

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

  1. import Vue from "vue";
  2. let vm = new Vue({
  3. el: "#app",
  4. data() {
  5. return {
  6. title: "学生列表",
  7. classNum: 1,
  8. teacher: ["张三", "李四"],
  9. info: {
  10. a: {
  11. b: 1
  12. }
  13. },
  14. students: [
  15. {
  16. id: 1,
  17. name: "小红"
  18. },
  19. {
  20. id: 2,
  21. name: "小明"
  22. }
  23. ]
  24. };
  25. }
  26. });
  27. console.log(vm);
  28. console.log(vm.title);
  29. console.log(vm.teacher);
  30. console.log(vm.info.a);

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号