手摸手從0實現簡版Vue --- (對象劫持)

1. 工欲善其事,必先利其器,首先搭建咱們的開發環境

首先使用npm init -y 建立初始化的配置文件,而後下載一下咱們後面須要的開發依賴:javascript

npm i webpack webpack-cli webpack-dev-server html-webpack-plugin --save-devhtml

新建 webpack.config.js 用來編寫webpack配置, 新建src/index.js作爲項目入口文件,而後新建public/index.html作爲模板文件。webpack 基本配置內容以下:vue

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修改成:java

"scripts": {
  "start": "webpack-dev-server",
  "build": "webpack"
}
複製代碼

而後執行npm start可啓動咱們的項目了。node

2. 建立咱們的基本框架

上面修改了咱們的默認引入文件路徑,此時咱們在source 文件夾下新建vue/index.js,而後在咱們的src/index.js中引入webpack

import Vue from 'vue'; // 會默認查找 source 目錄下的 vue 文件夾
複製代碼

此時在source/vue/index.jsjs就能夠正常執行了。git

下面繼續編寫咱們的src/index.js,前面已經引入了咱們本身編寫的Vue,後面咱們模擬Vue的語法去編寫:github

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.jsweb

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去實現用戶傳入數據的初始化:npm

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');
複製代碼

image-20200307144823192

到如今爲止咱們就實現了第一部分,對用戶數據傳入的data進行了數據劫持,可是若是咱們使用vm.arr.push(123),會發現只有獲取數據而沒有設置數據,這也就是Object.defineProperty的弊端,沒有實現對數組的劫持,下一部分去實現一下數組的數據劫持。

代碼部分可看本次提交commit

但願各位大佬點個star,小弟跪謝~

相關文章
相關標籤/搜索