Vue2 中的數(shù)據(jù)劫持簡(jiǎn)寫示例
目錄
- package.json 相關(guān)依賴
- Webpack.config.js 配置文件
- public/index.html 文件內(nèi)容
- 全部文件目錄結(jié)構(gòu)
- 實(shí)例一個(gè)模擬的 Vue 應(yīng)用
- vue/index.js 文件主要是負(fù)責(zé)初始化內(nèi)容
- initState方法
- 核心文件vue/observer.js
- vue/observer.js文件對(duì)數(shù)組進(jìn)行處理
package.json 相關(guān)依賴
我們今天要編寫的項(xiàng)目通過(guò)需要使用 Webpack 進(jìn)行編譯,package.json 相關(guān)依賴如下:
{ "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: { // 表示解析模塊引入的時(shí)候先從當(dāng)前文件夾尋找模塊,再去 node_modules 找模塊 modules: [ path.resolve(__dirname, ""), path.resolve(__dirname, "node_modules") ] }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, "public/index.html") }) ]};
public/index.html 文件內(nèi)容
<!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>
全部文件目錄結(jié)構(gòu)
好了,接下來(lái)我們就開始發(fā)車!
實(shí)例一個(gè)模擬的 Vue 應(yīng)用
首先,我們需要編寫我們的入口文件 index.js,該文件很普通主要就是實(shí)例一個(gè)模擬的 Vue 應(yīng)用:
// index.js// 我們?cè)?webpack.config.js 中進(jìn)行了配置,所以這里優(yōu)先在當(dāng)前目錄下尋找 vue 文件,也就是我們的 vue/index.js 文件import Vue from "vue"; let vm = new Vue({ el: "#app", data() { return { title: "學(xué)生列表", classNum: 1, teacher: ["張三", "李四"], info: {a: { b: 1} }, students: [{ id: 1, name: "小紅"},{ id: 2, name: "小明"} ] }; }});console.log(vm);
vue/index.js 文件主要是負(fù)責(zé)初始化內(nèi)容
// src/sindex.jsimport { initState } from "./init";function Vue(options) { this._init(options);}Vue.prototype._init = function (options) { // this 指向當(dāng)前實(shí)例對(duì)象 var vm = this; // 我們把 new Vue() 時(shí)候傳遞的數(shù)據(jù)統(tǒng)稱為 options // 并且掛載到 Vue 的實(shí)例對(duì)象上 vm.$options = options; // 調(diào)用 initState 初始化 data 數(shù)據(jù) initState(vm);};export default Vue;
initState方法
vue/init.js 文件暴露出一個(gè)initState
方法,該方法主要是處理初始化的數(shù)據(jù):
// vue/init.jsimport proxyData from "./proxy";import observer from "./observe"function initState(vm) { var options = vm.$options; // 如果 options 中存在 data 屬性,我們才會(huì)繼續(xù)處理 if (options.data) { initData(vm); }}function initData(vm) { var data = vm.$options.data; // 把 data 數(shù)據(jù)單獨(dú)保存到 Vue 的實(shí)例化對(duì)象上,方便我們獲取 // 如果 data 是一個(gè)函數(shù),我們需要執(zhí)行返回得到返回的對(duì)象 data = vm._data = typeof data === "function" ? data.call(vm) : data || {}; // 遍歷 data 對(duì)象,通過(guò) proxyData 對(duì)數(shù)據(jù)進(jìn)行攔截 for (const key in data) { // 傳入的參數(shù)分別是:當(dāng)前實(shí)例、key值(也就是 vm._data)、data 中的 key 值(例如 vm._data.title) proxyData(vm, "_data", key); } // 調(diào)用觀察者模式 observer(vm._data)}export { initState};
以上代碼,我們通過(guò)proxyData
對(duì)data
中的數(shù)據(jù)進(jìn)行攔截,詳情如下:
// vue/proxy.jsfunction proxyData(vm, target, key) { // 當(dāng)訪問(wèn) vm.title 的時(shí)候轉(zhuǎn)換為 vm._data.title //(請(qǐng)記住這句話!!!) Object.defineProperty(vm, key, { get: function () { return vm[target][key]; }, set: function (newVal) { vm[target][key] = newVal; } });}export default proxyData;
我們還調(diào)用了observer
方法進(jìn)行事件訂閱,詳細(xì)如下:
// vue/observe.jsimport Observer from "./observer"function observe(data) { // 判斷只處理對(duì)象,如果不是對(duì)象直接返回 if (typeof data !== "object" || data === null) { return false; } // 觀察數(shù)據(jù) return new Observer(data)}export default observe;
核心文件vue/observer.js
接下來(lái)就是我們的核心文件vue/observer.js
,該文件主要負(fù)責(zé)對(duì)數(shù)據(jù)類型進(jìn)行判斷,如果是數(shù)組就需要單獨(dú)處理數(shù)組,這個(gè)我們后面再說(shuō):
// vue/observer.jsimport defineReactiveData from "./reactive";import { arrMethods } from "./array";import observeArr from "./observeArr";// 這個(gè)方法會(huì)在多個(gè)地方調(diào)用,請(qǐng)記住這個(gè)方法以它的作用function Observer(data) { // 如果 data 是一個(gè)數(shù)組,那面需要單獨(dú)處理 if (Array.isArray(data)) { // 給數(shù)組新增一層原型 data._proto__ = arrMethods; // 循環(huán)數(shù)組的每一項(xiàng),然后讓每一項(xiàng)都調(diào)用 Observer 方法進(jìn)行訂閱 observeArr(data) } else { // 處理對(duì)象 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 數(shù)據(jù) // 分別傳入?yún)?shù)為:vm._data、data 中的 key、data 中 key 對(duì)應(yīng)的 value defineReactiveData(data, key, value); }};export default Observer;
以上代碼,我們分別對(duì)數(shù)組和對(duì)象執(zhí)行不同的操作,我們先來(lái)看對(duì)象的操作:
在Observer
構(gòu)造函數(shù)中我們新增了一個(gè)walk
方法,該方法獲取到了所有的key
值,然后調(diào)用了defineReactiveData
進(jìn)行處理。
// vue/reactive.jsimport observe from "./observe";function defineReactiveData(data, key, value) { // 例如 info.a 還是個(gè)對(duì)象,那么就遞歸觀察 observe(value); // 這里的 data 是 vm._data,所以這里攔截的也是 vm._data Object.defineProperty(data, key, { get() { console.log(`?? 響應(yīng)式獲取:data.${key},`, value); return value; }, set(newVal) { console.log(`
