# vue-observe **Repository Path**: gaotengjun/vue-observe ## Basic Information - **Project Name**: vue-observe - **Description**: vue2数据拦截简单实现 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-05-01 - **Last Updated**: 2024-11-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: Vue, JavaScript ## README ## 1. 初始化 ```json { "name": "vue", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "webpack-dev-server", "build": "webpack" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "html-webpack-plugin": "^4.5.0", "webpack": "^4.30.0", "webpack-cli": "^3.3.0", "webpack-dev-server": "^3.7.2" } } ``` ## 2. webpack配置 - resolve中的modules可以配置引入文件查找的顺序 - 先找根目录 - 再找node_modules ```json const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: resolve(__dirname, 'dist') }, devtool: 'source-map', // 源码输出利于调试 resolve: { // 配置模块查找循序,先找根目录,再找node_modules modules: [resolve(__dirname, ''), resolve(__dirname, 'node_modules')] }, plugins: [ new HtmlWebpackPlugin({ template: resolve(__dirname, 'public/index.html') }) ] } ``` ## 3. 目录结构 ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22065308/1651196766820-084746bb-33d9-49c6-9f8e-8c45b953ae0b.png#clientId=u13914140-ce95-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=672&id=uf70915c1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=672&originWidth=1107&originalType=binary&ratio=1&rotation=0&showTitle=false&size=90003&status=done&style=none&taskId=ud0512c5b-1cb6-42d6-908d-8cd12f235b1&title=&width=1107) ## 4. Vue正常使用方法 - 目录 ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22065308/1651198930371-ce8eaeb4-89a7-4537-b430-5a1194f106a0.png#clientId=u13914140-ce95-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=563&id=u8a31e603&margin=%5Bobject%20Object%5D&name=image.png&originHeight=563&originWidth=1105&originalType=binary&ratio=1&rotation=0&showTitle=false&size=65085&status=done&style=none&taskId=udc148baa-c9b4-4011-b68d-40a2df4ef30&title=&width=1105) - vue的使用 ```json import Vue from 'vue'; // options options API let vm = new Vue({ el: '#app', // data: null, data() { return { title: '学生列表', classNum: 1, total: 2, teacher: ['张三', '李四'], students: [ { id: 1, name: '小红' }, { id: 2, name: '小明' } ], } } }); console.log(vm); console.log(vm.title); ``` - vue/index.js - 使用ES5写Vue是一个构造函数 - 在构造函数中调用Vue上的prototype上的_init方法 - 保存this为vm利于分辨 - 将Vue构造函数的参数options,挂载到Vue构造函数的prototype上,调用初始化实例方法,将vm传入 ```json 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; ``` - vue/init.js - 初始化实例中先保存用户传入的参数 - 判断用户传入data时才进行数据的初始化,并传入实例 - 先获取用户传入的data - 在Vue的prototype上添加_data属性,判断用户传入的data是否为function - 是function则使用call调用,并将this指向Vue - 不是function直接赋值data - 遍历用户传入的数据进行代理数据 - 传入vm,_data, key - 目的是将获取数据的方式修改 - 原本方式为 vm._data.tilte - 修改为 vm.title 或 this.title ```json import proxyData from "./proxy"; function initState(vm) { var options = vm.$options; // 判断用户是否传入data if(options.data) { // 初始化数据 initData(vm); } } 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); } } export { initState } ``` - vue/proxy - 用户如果访问 vm.key - 则给他代理成vm._data.key - 用户赋值为 vm.key = val - 则给他代理为 - vm._data.key = val ```json function proxyData(vm, target, key) { Object.defineProperty(vm, key, { get() { return vm[target][key]; // return vm._data[key]; }, set(newValue) { vm[target][key] = newValue; // vm._data[key] = newValue; } }) } export default proxyData; ``` ## 5. 递归观察对象数据 - vue/init.js - 劫持完用户传入的数据之后 - 观察数据 ```javascript import proxyData from "./proxy"; import observe from './observe'; function initState(vm) { var options = vm.$options; // 判断用户是否传入data if(options.data) { // 初始化数据 initData(vm); } } 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 } ``` - vue/observe.js - 如果判断的对象不是object 或者是 null 则不需要观察 - 将数据交给观察构造函数观察 ```javascript import Observer from './observer'; function observe(data) { // 观察对象 不是对象不观察 if(typeof data !== 'object' || data == null) return; // 观察数据 return new Observer(data); } export default observe; ``` - vue/observer.js - 先处理是对象的情况 - 交给构造函数Observer上的walk方法观察数据 - 遍历数据调用defineReactiveData将操作数据变为响应式 - 传入data, key, value ```javascript import defineReactiveData from './reactive'; function Observer(data) { // data是数组 if(Array.isArray(data)) { } else { // 处理对象 this.walk(data); } } Observer.prototype.walk = function(data) { var keys = Object.keys(data); // 遍历keys 监听每个值 for(var i = 0; i < keys.length; i++) { var key = keys[i], value = data[key]; // 处理响应式的 defineReactiveData(data, key, value); } } // {} defineProperty // [] 自己去写方法 export default Observer; ``` - vue/reactive.js - 劫持用户对data中数据的操作 - 劫持之前需要先观察value是否为对象或数组 ```javascript import observe from "./observe"; function defineReactiveData(data, key, value) { // 先观察value 是否是对象或数组 observe(value) Object.defineProperty(data, key, { get() { console.log('响应式获取数据:', value); return value; }, set(newValue) { if(newValue == value) return console.log('响应式设置数据:' + newValue, value); value = newValue; } }) } export default defineReactiveData; ``` ## 6. 重写数组方法实现数组操作监听 ### 1. 需要重写的数组方法,修改原数组的方法 ```javascript // 更改原数组 var ARR_METHODS = [ 'push', 'unshift', 'pop', 'shift', 'splice', 'sort', 'reverse' ]; export { ARR_METHODS } ``` ### 2. 遍历数组名,重写方法 1. 首先需要一份新的Array.prototype, 在新的数组原型上重新定义数组操作方法 1. 此时在方法内部就可以监听数组的操作 2. 获取数组操作方法中的参数,并依然调用对应的原数组方法,接收返回值最后返回,目的是和原方法保持一致 2. 对于数组添加操作需要单独处理 1. 如果添加内容是对象或者数组,还需继续监听 ```javascript import { ARR_METHODS } from "./config"; import observeArr from './observeArr'; var originArrMethods = Array.prototype, arrMethods = Object.create(originArrMethods); ARR_METHODS.map(m => { // 重写ARR_METHODS里的方法 arrMethods[m] = function() { // 将传入的方法参数类数组变为数组 var args = Array.prototype.slice.call(arguments), rt = originArrMethods[m].apply(this, args); // 此处必须修改this指向,不然数组操作失败 console.log('数组新方法', args); // 现在就可以在数组操作方法中做其他事情 var newArr; // 还需判断用户新增数据是对象还是数组 switch (m) { case 'push': case 'unshift': newArr = args; break; case 'splice': newArr = args.slice(2); break; default: break; } // 如果newArr存在 就监听newArr newArr && observeArr(newArr); // 返回操作的值 return rt; } }) export { arrMethods } ``` ### 3. 监听数组 1. 监听数组就是遍历数组 1. 使用observe监听数组的每一项 1. observe中会监听数据是不是数组或者对象 ```javascript import observe from './observe'; // 监听数组 function observeArr(arr) { // 遍历数组的每一项进行监听 for (var i = 0; i < arr.length; i++) { observe(arr[i]); } } export default observeArr; ``` ### 4. 添加数组处理 1. 如果是数组需要对数组的__proto__上添加自定义的数组操作方法 1. 目的是调用数组方法时会调用自己自定义的方法 1. 从而实现了监听数组操作 2. 设置完后还需要再次监听数组中的每一项是否存在数组或者对象 1. 还行再次调用observeArr(data) 1. 遍历data查看每一项 ```javascript import defineReactiveData from './reactive'; import { arrMethods } from './array'; import observeArr from './observeArr'; function Observer(data) { // data是数组 if(Array.isArray(data)) { // data是个数组,就需要调用我自己封装的方法,所以在data数组的__proto__ 上添加数组方法 data.__proto__ = arrMethods; // 还需要判断数组中的元素有没有对象或者数组 observeArr(data); } else { // 处理对象 this.walk(data); } } Observer.prototype.walk = function(data) { var keys = Object.keys(data); // 遍历keys 监听每个值 for(var i = 0; i < keys.length; i++) { var key = keys[i], value = data[key]; // 处理响应式的 defineReactiveData(data, key, value); } } // {} defineProperty // [] 自己去写方法 export default Observer; ``` ### 5. 还需要对对象中的新值进行监听 1. newValue 可能也是对象或者数组还需监听 ```javascript import observe from "./observe"; function defineReactiveData(data, key, value) { // 先观察value 是否是对象或数组 observe(value) Object.defineProperty(data, key, { get() { console.log('响应式获取数据:', value); return value; }, set(newValue) { if(newValue == value) return console.log('响应式设置数据:', newValue, value); // newValue 还有可能是对象,也需要观察 observe(newValue); value = newValue; } }) } export default defineReactiveData; ```