您好,登錄后才能下訂單哦!
今天小編給大家分享一下Vue2中的數據劫持怎么實現的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
我們今天要編寫的項目通過需要使用 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>
全部文件目錄結構如圖:
首先,我們需要編寫我們的入口文件 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;
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 };
以上代碼,我們通過proxyData
對data
中的數據進行攔截,詳情如下:
// 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 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
文件,我們還需要對數組進行處理:
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中的數據劫持怎么實現”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。