手摸手从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,小弟跪谢~

相关文章
相关标签/搜索