實現一個簡單版本的Vue及源碼解析(一)

項目搭建

Vue概述

Vue.js是一套構建用戶界面的漸進式框架。與其餘重量級框架不一樣的是,Vue 採用自底向上增量開發的設計。Vue 的核心庫只關注視圖層,它不只易於上手,還便於與第三方庫或既有項目整合。另外一方面,當與單文件組件和 Vue 生態系統支持的庫結合使用時,Vue 也徹底可以爲複雜的單頁應用程序提供驅動。 因爲上述優勢,Vue的使用在業界愈來愈普遍,截至本文寫做時Vue在github上的star已經高達113322。javascript

Vue的中間件

生命週期

一個Vue實例具備許多階段,例如observing data、initializing events、compiling template 及render,在這些階段咱們能夠註冊相應的鉤子函數。vue

/*初始化生命週期*/
    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來實現響應式的具體原理會在接下來的內容中進行詳細講解。java

虛擬DOM

虛擬DOM是真實DOM的抽象,Vue的主要操做先是在虛擬DOM上進行的,最後再根據不一樣的平臺來映射到真實DOM上,其中的核心是diff算法.node

部署開發環境

本項目使用npm做爲包管理器,所以構建myVue項目的第一件事,即是在空文件夾下使用npm init命令初始化項目。 項目初始化以後,在正式編寫項目前,咱們需還要下載和配置第三方工具,其中包括模塊構建工具和測試工具。git

設置Rollup

在該項目中咱們使用Rollup做爲打包工具。Rollup做爲一款javascript打包工具,其支持ES2015語法,同時它也是Vue的打包工具。github

使用Rollup前,首先是使用npm install rollup rollup-watch --save-dev命令安裝Rollup相關包,安裝完成後在根目錄下建立rollup.config.js文件並進行一些基礎配置。關於Rollup的進一步使用,請自行前往官網。web

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並配置。chrome

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

項目結構

項目搭建好的結構以下。npm

- 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命令啓動項目。

相關文章
相關標籤/搜索