实现一个简单版本的Vue及源码解析(一)

项目搭建

Vue概述

Vue.js是一套构建用户界面的渐进式框架。与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计。Vue 的核心库只关注视图层,它不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与单文件组件和 Vue 生态系统支持的库结合使用时,Vue 也完全能够为复杂的单页应用程序提供驱动。 由于上述优点,Vue的使用在业界越来越广泛,截至本文写作时Vue在github上的star已经高达113322。

Vue的中间件

生命周期

一个Vue实例具有许多阶段,例如observing data、initializing events、compiling template 及render,在这些阶段我们可以注册相应的钩子函数。

  /*初始化生命周期*/
    initLifecycle(vm)
    /*初始化事件*/
    initEvents(vm)
    /*初始化render*/
    initRender(vm)
    /*调用beforeCreate钩子函数并且触发beforeCreate钩子事件*/
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    /*初始化props、methods、data、computed与watch*/
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    /*调用created钩子函数并且触发created钩子事件*/
    callHook(vm, 'created')
复制代码

响应式

之所以称为响应式,是因为Vue.js实现了数据和视图的双向绑定,即数据发生变化后视图也随之变化。 其中Vue实现响应式的核心函数是Object.defineProperty,而且因为Object.defineProperty最低支持IE8,所以导致Vue只兼容到IE8。Vue使用Object.defineProperty来实现响应式的具体原理会在接下来的内容中进行详细讲解。

虚拟DOM

虚拟DOM是真实DOM的抽象,Vue的主要操作先是在虚拟DOM上进行的,最后再根据不同的平台来映射到真实DOM上,其中的核心是diff算法.

部署开发环境

本项目使用npm作为包管理器,因此构建myVue项目的第一件事,便是在空文件夹下使用npm init命令初始化项目。 项目初始化之后,在正式编写项目前,我们需还要下载和配置第三方工具,其中包括模块构建工具和测试工具。

设置Rollup

在该项目中我们使用Rollup作为打包工具。Rollup作为一款javascript打包工具,其支持ES2015语法,同时它也是Vue的打包工具。

使用Rollup前,首先是使用npm install rollup rollup-watch --save-dev命令安装Rollup相关包,安装完成后在根目录下创建rollup.config.js文件并进行一些基础配置。关于Rollup的进一步使用,请自行前往官网。

export default {
  input: 'src/instance/index.js',
  output: {
    name: 'Vue',
    file: 'dist/vue.js',
    format: 'iife'
  },
};
复制代码

配置测试工具Karma和jasmine

本项目采用karma作为自动化测试工具,jasmine作为单元测试工具,同样要想使用这两个工具,使用前需下载相关包并进行配置。

npm install karma jasmine karma-jasmine karma-chrome-launcher buble --save-dev
npm install karma-rollup-plugin karma-rollup-preprocessor rollup-plugin-buble --save-dev
复制代码

下载相关包后在根目录下创建karma.conf.js并配置。

module.exports = function(config) {
  config.set({
    files: [{ pattern: 'test/**/*.spec.js', watched: false }],
    frameworks: ['jasmine'],
    browsers: ['Chrome'],
    preprocessors: {
      './test/**/*.js': ['rollup']
    },
    rollupPreprocessor: {
      plugins: [
        require('rollup-plugin-buble')(),
      ],
      output: {
        format: 'iife',
        name: 'Vue',
        sourcemap: 'inline'
      }
    }
  })
}
复制代码

项目结构

项目搭建好的结构如下。

- package.json
- rollup.config.js
- node_modules
- dist
- test
- src
	- observer
	- instance
	- util
	- vdom
复制代码

配置启动命令

项目搭建好后,要想启动项目,我们可以在npm的配置文件中进行配置。 package.json

"scripts": {
   "build": "rollup -c",
   "watch": "rollup -c -w",
   "test": "karma start"
}
复制代码

起步

要想编写出高质量的代码,正式编程前我们最好养成编写代码前先编写测试用例进行单元测试的好习惯,下面我们就开始编写测试用例吧。

test/options/options.spec.js

import Vue from "../src/instance/index";

describe('Proxy test', function() {
  it('should proxy vm._data.a = vm.a', function() {
  	var vm = new Vue({
  		data:{
  			a:2
  		}
  	})
    expect(vm.a).toEqual(2);
  });
});
复制代码

编写测试用例后,我们的准备工作就全部完成了,我们就可以开始编写真正的代码了,此时是不是感到很激动。(毕竟特烦配置什么的了,特别是javaweb项目,想到当年被javaweb三大框架配置支配的恐惧就不禁内牛满面啊...)

要想实现Vue,自然是先编写Vue的构造函数,然后再一步一步的实现其他功能。

src/instance/index.js

import { initMixin } from './init'

function Vue (options) {
  this._init(options)
}

initMixin(Vue)

export default Vue
复制代码

上面只是Vue的构造器调用了this._init方法来初始化一个Vue的实例,而initMixin 的作用便是给Vue定义原型方法,具体代码如下。

src/instance/init.js

import { initState } from './state'

export function initMixin (Vue) {
  Vue.prototype._init = function (options) {
  	var vm = this
  	vm.$options = options
  	initState(vm)
  }
}
复制代码

initMixin方法只是在Vue的原型上定义了_init方法,而_init方法则是调用了initState方法来初始化data。

src/instance/state.js

export function initState(vm) {
  initData(vm)
}

function initData(vm) {
  var data = vm.$options.data
  vm._data = data
  // proxy data on instance
  var keys = Object.keys(data)

  var i = keys.length
  while (i--) {
    proxy(vm, keys[i])
  }
}

function proxy(vm, key) {
    Object.defineProperty(vm, key, {
      configurable: true,
      enumerable: true,
      get: function proxyGetter() {
        return vm._data[key]
      },
      set: function proxySetter(val) {
        vm._data[key] = val
      }
    })
}
复制代码

如上initState方法只是调用了initData方法,而initData方法则是初始化了data并使用proxy方法将vm._data.a代理成vm.a

proxyvm上使用同样的key定义了一个属性,并通过 get/set 从vm._data上存取data。

最后使用npm run test命令启动项目。

转载于:https://juejin.im/post/5b9b14bee51d450e7a24bb27

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值