vue2.x 對象劫持的原理實(shí)現(xiàn)
目標(biāo):手寫迷你版Vue
一:使用rollup打包,打包后的代碼體積更小,更適合寫框架源碼的打包
npm i rollup -D
二:安裝babel相關(guān)的包,以及實(shí)現(xiàn)靜態(tài)服務(wù),設(shè)置環(huán)境變量的包
npm i @babel/core @babel/preset-env rollup-plugin-babel roullup-plugin-serve cross-env -D
三:包的相關(guān)介紹
- rollup (打包工具)
- @babel/core(用babel核心模塊)
- @babel/preset-env(babel將高級(jí)語法轉(zhuǎn)成低級(jí)語法)
- rollup-plugin-serve(實(shí)現(xiàn)靜態(tài)服務(wù))
- cross-env(設(shè)置環(huán)境變量)
- rollup-plugin-babel(橋梁)
四:根目錄書寫rollup.config.js
import babel from 'rollup-plugin-babel'; import serve from 'rollup-plugin-serve'; export default { input:'./src/index.js', // 以哪個(gè)文件作為打包的入口 output:{ file:'dist/umd/vue.js', // 出口路徑 name:'Vue', // 指定打包后全局變量的名字 format: 'umd', // 統(tǒng)一模塊規(guī)范 sourcemap:true, // es6-> es5 開啟源碼調(diào)試 可以找到源代碼的報(bào)錯(cuò)位置 }, plugins:[ // 使用的插件 babel({ exclude:"node_modules/**" }), process.env.ENV === 'development'?serve({ open:true, openPage:'/public/index.html', // 默認(rèn)打開html的路徑 port:3000, contentBase:'' }):null ] }
配置package.josn
{ "name": "vue_souce", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build:dev": "rollup -c", "serve": "cross-env ENV=development rollup -c -w" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@babel/core": "^7.9.0", "@babel/preset-env": "^7.9.5", "cross-env": "^7.0.2", "rollup": "^2.6.1", "rollup-plugin-babel": "^4.4.0", "rollup-plugin-serve": "^1.0.1" } }
五:新建index.html(public/index.html)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script src="/dist/umd/vue.js"></script> <script> let vm = new Vue({ el:'#app', // 隨便給些數(shù)據(jù) data(){ return { name:'張三', age:11, address:{ number:0, name:'李四' }} }, }) vm._data.address = {a:1}; console.log(vm._data) </script> </body> </html>
六:編寫Vue入口:index.js
// Vue的核心代碼 只是Vue的一個(gè)聲明 import {initMixin} from './init'; function Vue(options){ // 進(jìn)行Vue的初始化操作 this._init(options); } // 通過引入文件的方式 給Vue原型上添加方法 initMixin(Vue); // 給Vue原型上添加一個(gè)_init方法 export default Vue
七:編寫初始化操作 init.js
import {initState} from './state' // 在原型上添加一個(gè)init方法 export function initMixin(Vue){ // 初始化流程 Vue.prototype._init = function (options) { // 數(shù)據(jù)的劫持 const vm = this; // vue中使用 this.$options 指代的就是用戶傳遞的屬性 vm.$options = options; // 初始化狀態(tài) initState(vm); // 分割代碼 } }
八:初始化數(shù)據(jù)
import {observe} from './observer/index.js' export function initState(vm){ const opts = vm.$options; // vue的數(shù)據(jù)來源 屬性 方法 數(shù)據(jù) 計(jì)算屬性 watch if(opts.props){ initProps(vm); } if(opts.methods){ initMethod(vm); } if(opts.data){ initData(vm); } if(opts.computed){ initComputed(vm); } if(opts.watch){ initWatch(vm); } } function initProps(){} function initMethod() {} function initData(vm){ // 數(shù)據(jù)初始化工作 let data = vm.$options.data; // 用戶傳遞的data data = vm._data = typeof data === 'function'?data.call(vm):data; // 對象劫持 用戶改變了數(shù)據(jù) 我希望可以得到通知 =》 刷新頁面 // MVVM模式 數(shù)據(jù)變化可以驅(qū)動(dòng)視圖變化 // Object.defineProperty () 給屬性增加get方法和set方法 observe(data); // 響應(yīng)式原理 } function initComputed(){} function initWatch(){}
九:書寫核心監(jiān)聽功能
// 把data中的數(shù)據(jù) 都使用Object.defineProperty重新定義 es5 // Object.defineProperty 不能兼容ie8 及以下 vue2 無法兼容ie8版本 import { isObject } from '../util/index' // 后續(xù)我可以知道它是不是一個(gè)已經(jīng)觀察了的數(shù)據(jù) __ob__ class Observer{ constructor(value){ // 僅僅是初始化的操作 // vue如果數(shù)據(jù)的層次過多 需要遞歸的去解析對象中的屬性,依次增加set和get方法 // 對數(shù)組監(jiān)控 this.walk(value); // 對對象進(jìn)行觀測 } walk(data){ let keys = Object.keys(data); // [name,age,address] // 如果這個(gè)data 不可配置 直接return keys.forEach((key)=>{ defineReactive(data,key,data[key]); }) } } function defineReactive(data,key,value){ observe(value); // 遞歸實(shí)現(xiàn)深度檢測 Object.defineProperty(data,key,{ configurable:true, enumerable:false, get(){ // 獲取值的時(shí)候做一些操作 return value; }, set(newValue){ // 也可以做一些操作 if(newValue === value) return; observe(newValue); // 繼續(xù)劫持用戶設(shè)置的值,因?yàn)橛锌赡苡脩粼O(shè)置的值是一個(gè)對象 value = newValue; } }); } export function observe(data) { let isObj = isObject(data); if (!isObj) { return } return new Observer(data); // 用來觀測數(shù)據(jù) }
十:編寫工具類文件,存放校驗(yàn)對象
/** * * @param {*} data 當(dāng)前數(shù)據(jù)是不是對象 */ export function isObject(data) { return typeof data === 'object' && data !== null; }
總結(jié):
1 創(chuàng)建Vue構(gòu)造函數(shù),接收所有所有參數(shù)options
2 分類初始化options,本章主要處理data,讓data上的引用類型的數(shù)據(jù)通過Object.definePrototy 變成響應(yīng)式的,初始化是有循序的,先初始化props 然后初始化method 然后初始化data computed watch
3 核心如下
function defineReactive(data,key,value){ observe(value); // 遞歸實(shí)現(xiàn)深度檢測 Object.defineProperty(data,key,{ configurable:true, enumerable:false, get(){ // 獲取值的時(shí)候做一些操作 return value; }, set(newValue){ // 也可以做一些操作 if(newValue === value) return; observe(newValue); // 繼續(xù)劫持用戶設(shè)置的值,因?yàn)橛锌赡苡脩粼O(shè)置的值是一個(gè)對象 value = newValue; } }); }
到此這篇關(guān)于vue2.x 對象劫持的原理實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)vue2.x 對象劫持內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VUE+Element-ui實(shí)戰(zhàn)之使用el-calendar日歷自定義顯示內(nèi)容
這篇文章主要介紹了VUE+Element-ui實(shí)戰(zhàn)之使用el-calendar日歷自定義顯示內(nèi)容方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03vue.js高德地圖實(shí)現(xiàn)熱點(diǎn)圖代碼實(shí)例
這篇文章主要介紹了vue.js高德地圖實(shí)現(xiàn)熱點(diǎn)圖,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04詳解從vue-loader源碼分析CSS Scoped的實(shí)現(xiàn)
這篇文章主要介紹了詳解從vue-loader源碼分析CSS Scoped的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09