package.json 相关依赖
我们今天要编写的项目通过需要使用 Webpack 进行编译,package.json 相关依赖如下:
1 2 3 4 5 6 7 8 9 10 11 12 | { "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 配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 文件内容
1 2 3 | < title ></ title >< div id = "app" ></ div > |
全部文件目录结构
好了,接下来我们就开始发车!
实例一个模拟的 Vue 应用
首先,我们需要编写我们的入口文件 index.js,该文件很普通主要就是实例一个模拟的 Vue 应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | // 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 文件主要是负责初始化内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // 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
方法,该方法主要是处理初始化的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // 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 }; |
以上代码,我们通过proxyData
对data
中的数据进行拦截,详情如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 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
方法进行事件订阅,详细如下:
1 2 3 4 5 6 7 8 9 10 11 | // 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
,该文件主要负责对数据类型进行判断,如果是数组就需要单独处理数组,这个我们后面再说:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // 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 |
以上代码,我们分别对数组和对象执行不同的操作,我们先来看对象的操作:
在Observer
构造函数中我们新增了一个walk
方法,该方法获取到了所有的key
值,然后调用了defineReactiveData
进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // 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(`<img draggable= "false" role= "img" class = "emoji" alt= " src= "https://s.w.org/images/core/emoji/15.1.0/svg/2934.svg" > 响应式获取:data.${key},`, value); return value; }, set(newVal) { console.log(`<img draggable= "false" role= "img" class = "emoji" alt= " src= "https://s.w.org/images/core/emoji/15.1.0/svg/1f501.svg" > 响应式设置: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
文件,我们还需要对数组进行处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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
,那看看它到底做了什么事情:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | // 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
只是遍历了数组的每一项,让每一项都进行了拦截:
1 2 3 4 | // vue/observeArr.js import observe from "./observe" ; function observeArr(arr) { for ( let i = 0; i |
然后我们去index.js
文件获取属性,看看结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 数据劫持的资料请关注IT俱乐部其它相关文章!