1. 工欲善其事,必先利其器,首先搭建我們的開發環境
首先使用npm init -y
創建初始化的配置文件,然后下載一下我們后面需要的開發依賴:
npm i webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev
新建 webpack.config.js
用來編寫webpack
配置, 新建src/index.js
做為項目入口文件,然后新建public/index.html
做為模板文件。webpack
基本配置內容如下:
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js', // 以我們src 的index.js為入口進行打包
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
devtool: 'source-map',
resolve: {
// 默認讓import去我們的source文件夾去查找模塊,其次去node_modules查找
modules: [path.resolve(__dirname, 'source'), path.resolve('node_modules')]
},
plugins: [
new htmlWebpackPlugin({
template: path.resolve(__dirname, 'public/index.html')
})
]
}
使用npm scripts
啟動我們的項目, 將 package.json
里面的scripts
修改為:
"scripts": {
"start": "webpack-dev-server",
"build": "webpack"
}
然后執行npm start
可啟動我們的項目了。
2. 創建我們的基本框架
上面修改了我們的默認引入文件路徑,此時我們在source
文件夾下新建vue/index.js
,然后在我們的src/index.js
中引入
import Vue from 'vue'; // 會默認查找 source 目錄下的 vue 文件夾
此時在source/vue/index.js
的js
就可以正常執行了。
下面繼續編寫我們的src/index.js
,前面已經引入了我們自己編寫的Vue
,后面我們模擬Vue
的語法去編寫:
import Vue from 'vue'; // 會默認查找 source 目錄下的 vue 文件夾
let vm = new Vue({
el: '#app', // 表示要渲染的元素是#app
data() {
return {
msg: 'hello',
school: {
name: 'black',
age: 18
},
arr: [1, 2, 3],
}
},
computed: {
},
watch: {
}
});
接著我們就要去編寫實現我們 Vue
代碼了, vue/index.js
:
import {initState} from './observe';
function Vue(options) { // Vue 中原始用戶傳入的數據
this._init(options); // 初始化 Vue, 并且將用戶選項傳入
}
Vue.prototype._init = function(options) {
// vue 中的初始化 this.$options 表示 Vue 中的參數
let vm = this;
vm.$options = options;
// MVVM 原理, 需要數據重新初始化
initState(vm);
}
export default Vue; // 首先默認導出一個Vue
首先我們需要去初始化Vue
,將用戶傳入的數據進行初始化處理,新建observe/index.js
去實現用戶傳入數據的初始化:
export function initState(vm) {
let opts = vm.$options;
if (opts.data) {
initData(vm); // 初始化數據
}
if (opts.computed) {
initComputed(); // 初始化計算屬性
}
if (opts.watch) {
initWatch(); // 初始化 watch
}
}
/**
* 初始化數據
* 將用戶傳入的數據 通過Object.defineProperty重新定義
*/
function initData(vm) {
}
/**
* 初始化計算屬性
*/
function initComputed() {
}
/**
* 初始化watch
*/
function initWatch() {
}
3. 數據的初始化
首先我們去實現用戶傳入的data
的初始化,也就是經常提到的使用Object.defineProperty
進行數據劫持,重寫getter
和setter
,下面去實現 initData
的具體內容:
/**
* 將對vm上的取值、賦值操作代理到 vm._data 屬性上
* 代理數據 實現 例如:vm.msg = vm._data.msg
*/
function proxy(vm, source, key) {
Object.defineProperty(vm, key, {
get() {
return vm[source][key];
},
set(newValue) {
vm[source][key] = newValue;
}
})
}
/**
* 初始化數據
* 將用戶傳入的數據 通過Object.defineProperty重新定義
*/
function initData(vm) {
let data = vm.$options.data; // 用戶傳入的data
data = vm._data = typeof data === 'function' ? data.call(vm) : data || {};
for(let key in data) {
proxy(vm, '_data', key); // 將對vm上的取值、賦值操作代理到 vm._data 屬性上,便于我們直接使用vm取值
}
observe(vm._data); // 觀察數據
}
export function observe(data) {
if(typeof data !== 'object' || data == null) {
return; // 不是對象或為null 不執行后續邏輯
}
return new Observer(data);
}
4. 數據劫持核心方法實現
我們去該目錄下新建observer.js
去實現我們的Observer
:
import {observe} from './index';
/**
* 定義響應式的數據變化
* @param {Object} data 用戶傳入的data
* @param {string} key data的key
* @param {*} value data對應key的value
*/
export function defineReactive(data, key, value) {
observe(value); // 如果value依舊是一個對象,需要深度劫持
Object.defineProperty(data, key, {
get() {
console.log('獲取數據');
return value;
},
set(newValue) {
console.log('設置數據');
if (newValue === value) return;
value = newValue;
}
});
}
class Observer {
constructor(data) { // data === vm._data
// 將用戶的數據使用 Object.defineProperty重新定義
this.walk(data);
}
/**
* 循環數據遍歷
*/
walk(data) {
let keys = Object.keys(data);
for(let i = 0; i < keys.length; i++) {
let key = keys[i];
let value = data[key];
defineReactive(data, key, value);
}
}
}
export default Observer;
5. 結果
到這我們就初步實現了對用戶傳入data
的數據劫持,看一下效果。
此時如果我們在src/index.js
去取和修改data
中的值:
console.log(vm.msg);
console.log(vm.msg = 'world');
到現在為止我們就實現了第一部分,對用戶數據傳入的data
進行了數據劫持,但是如果我們使用vm.arr.push(123)
,會發現只有獲取數據而沒有設置數據,這也就是Object.defineProperty
的弊端,沒有實現對數組的劫持,下一部分去實現一下數組的數據劫持。
代碼部分可看本次提交commit
希望各位大佬點個star,小弟跪謝~