今天開始系統學習vue前端框架. 我是有前端基礎的, 剛工作那會, 哪里分那么清楚啊, 前后端我都得做, 所以, css, js, jquery, bootstrap都會點, 還系統學過ext, 哈哈,是不是都不知道是啥, 沒事, 都過時了. 現在開始, 學習最流行的Vue, 后端不會頁面, 說不過去呀.....
言歸正傳, Ready, Go!
目錄
1. 認識Vuejs
2. Vuejs的安裝方式
3. Vuejs的初體驗-三個案例
4. MVVM模型
5. Vue對象的生命周期
6. Vue源碼
一. 認識Vuejs
1. 為什么學習Vuejs
-
- 這幾年Vue.js成為前端框架中最火的一個。越來越多的網站前端開始采用Vue.js開發。前端開發必備技能.
- Vuejs是開源世界華人的驕傲,其作者是我國尤雨溪。學習門檻低,成本低,可跨設備和多平台開發Vue.js.
- 前端換工作, 面試必問的框架.
2. 簡單認識一下Vuejs
-
- 官網地址: https://cn.vuejs.org/
- 是一套用於構建用戶界面的漸進式框架, 什么是漸進式框架呢?
漸進式框架是說, vue可以作為應用的一部分嵌入.
比如:之前項目使用的是jquery開發的, 項目體量比較大, 現在知道vue使用上,效果上都更方便, 想要替換為vue, 可問題是之前的頁面特別多,如果全部替換,工作量太大,那么沒關系, vue允許你部分嵌入, 也就是說原來的頁面依然使用jquery, 而后開發的頁面使用Vuejs. vue可以作為一部分嵌入到項目中. 后面再逐漸替換.
-
- 如果是使用vue開發新項目, 那么可以使用vue的全家桶. 包括核心庫和和生態系統. 比如: Core+Vue Router + Vuex.
3. Vuejs的核心功能
-
- 解耦視圖和數據
- 可復用的組件
- 前端路由技術
- 狀態管理
- 虛擬DOM
二. Vuejs安裝方式
vuejs的安裝有三種方式,
1. CDN引入
-
- CDN引入有兩個版本: 開發環境和生產環境. 也就是說, 不用本地安裝vue, 而是引入CDN中vue的包
<!-- 開發環境 --> <script src= "https://cdn.jsdelivr.net/npm/vue/dist/vue.js></script> <!-- 生產環境 --> <script src= "https://cdn.jsdelivr.net/npm/vue/vue.js></script>
生產環境建議帶上版本號, 避免因版本問題產生異常
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>使用這個版本的優點是: 速度快. 缺點也很明顯: 那就是每次打開都要去cdn上下載, 浪費時間. 學習不建議使用這種方式
- CDN引入有兩個版本: 開發環境和生產環境. 也就是說, 不用本地安裝vue, 而是引入CDN中vue的包
2. 下載和引入
-
- 這里也有兩個版本, 開發環境和生產環境, 在CDN上下載很慢, 那么我們可以將vue.js下載到本地, 引入到項目中
開發環境 https://vuejs.org/js/vue.js 生產環境 https://vuejs.org/js/vue.min.js
開發時可以使用開發包, 可以看到源碼. 生產環境的包更穩定, 體積也會更小
- 這里也有兩個版本, 開發環境和生產環境, 在CDN上下載很慢, 那么我們可以將vue.js下載到本地, 引入到項目中
3. NPM安裝管理
-
- 在用 Vue 構建大型應用時推薦使用 NPM 安裝
- vuejs可以和webpack和CLI等模塊配合使用
- 后續學習都是用這種方式操作的.
三. Vuejs初體驗
1. Hello Vuejs
我們學習程序, 經典代碼helloworld. 這里說一下開發工具, 開發工具建議使用vscode, 因為里面有很多插件, 但是其他也不是不可以哈
我們在感受vue的時候, 為了簡單, 方便的體驗vue, 我們使用第二種方式(注: 后面詳細研究還是會使用npm編譯的方式), 下載vue.js, 並引入到項目中. 接下來開始操作.
-
-
第一步: 先搭建一個簡單的項目. 我的項目名稱就叫vue-study. 在里面創建一個文件夾js, 項目結構如下:
-
-
-
第二步: 然后下載vue.js, 將其放入到js文件夾中
-
第三步: 寫一個html頁面, 並引入vue.js.
<html> <head> <title>第一個vue程序</title> <script src="../js/vue.js"></script> </head> <body> </body> </html>
我們看到, 第一步引入了vue.js. 其實這里有個簡單的辦法, 只需要把項目中js拖進來, 就可以了.引入了vue.js, 那么要如何使用呢? vue.js我們可以理解為對象. 使用使用new Vue()的方式.
<html> <head> <title>第一個vue程序</title> <script src="../js/vue.js"></script> </head> <body> <div id="app">{{message}}</div> <script> const app = new Vue({ el: "#app", data: { message: "hello, 盛開的太陽!" } }); </script> </body> </html>
如上, 我們看到了new Vue(), 相當於把這個對象構建了一份. 然后賦值給了一個常量const app. 這里需要說一下, 以前,我們都是直接使用var, 既可以設置變量也可以設置常量, 但在vue中, 我們的變量和常量都有自己的聲明方式
聲明方式: 常量使用const, 變量使用let.
創建vue對象的時候, 傳入了一個option, option中有兩個元素
el:全稱element, 表示指向的元素.其值使用的是jquery表達式. 該屬性決定了這個vue對象掛載到那個元素上, 可以看出, 我們這里是掛載到了id="app"的元素上 data: 這個屬性用來存儲數據, 這些數據可以試試手動寫的, 也可以是動態從服務端取的
data定義數據. 這里需要重點說一下了. vue采用的是VMMV的設計模式, 也就是數據和試圖分離. 這里的data指的就是數據. 而id="app"的div是視圖. 當頁面解析的時候, 解析到script腳本時發現, 我們已經將div交給vue容器了, 那么, 這時候, vue就會去尋找目標元素是否有待填補的變量. 這里我們看到<div id="app">{{message}}</div>里面定義了一個變量message, 而這個變量在vue容器中進行了聲明, 因此可以進行對應的自動填充.
這里如果暫時不理解, 也沒關系, 先混個眼熟, 后面還有詳細講解
-
第四步: 分析瀏覽器執行代碼的流程
1 <html> 2 3 <head> 4 <title>第一個vue程序</title> 5 <script src="../js/vue.js"></script> 6 </head> 7 8 <body> 9 <div id="app">{{message}}</div> 10 <script> 11 const app = new Vue({ 12 el: "#app", 13 data: { 14 message: "hello, 盛開的太陽!" 15 } 16 }); 17 </script> 18 </body> 19 </html>
頁面渲染, 首先加載1-10行, 顯示出對應的html. 執行到第11行的時候, 創建了vue實例, 並且對照html進行解析和修改.
-
2. Vue列表展示
下面來看一個稍微復雜一點的例子---列表展示
先來看看效果
下面思考, 如果我們使用jquery會如何實現呢? 需要些一個for循環, 然后在里面定義n個li, 然后拼裝數據. 很復雜. 然而, 使用vue完全不需要在js代碼中拼裝html元素的數據, 下面來看看怎么做
-
-
第一步: 新建一個html頁面, 命名為02-list.html, 然后引入vue.js
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>列表頁面</title> <script src="../js/vue.js"></script> </head> <body> </body> </html>
-
第二步構建vue對象
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="../js/vue.js"></script> </head> <body> <div id="app"> <h1>{{title}}</h1> <ul> <li v-for = "item in languages">{{item}}</li> </ul> </div> <script> const app = new Vue({ el: "#app", data:{ title: "常見的后端編程語言有哪些?", languages: ["python", "go", "java", "net", "php", "c++"] } }); </script> </body> </html>
這里指定了當前構建的vue對象掛載在id="app"的元素上. 並填充值title和languages. 和上一個案例不同, 這里有一個數組元素languages. 那么數組元素應該如何取值呢?
<ul> <li v-for = "item in languages">{{item}}</li> </ul>
注意紅色粗體部分. 使用了一個vue的指令v-for, 這是表示for循環, 這個第一次見到, 先熟悉一下. 后面還會具體講. 我們以前使用jquery會怎么寫呢?
<ul> <li >python</li> <li >go</li> <li >java</li> <li >php</li> <li >.net</li> <li >...</li> </ul>
以前我們要這么寫一大堆, 如果是動態從服務端取數據, 那么還要拼li代碼, 很容易出錯, 還很費勁. 但使用了vue指令, 我們發現一句話就搞定了, 這里是不是可以傲嬌一下. 怪不得vue能這么流行.
-
3. 案例:計數器
計數器是一個小的綜合案例, 通過這個案例來再次感受一下vue的強大. 我們先來看一下效果
分析: 這里有一個變量, 兩個按鈕. 點擊+, 數字加1, 點擊-, 數字減1. 下面我們就來實現這個功能
-
- 第一步: 創建一個html文件03-計數器.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="../js/vue.js"></script> </head> <body> <div id="app"> 當前數字: {{counter}} <br> <button v-on:click="counter++"> + </button> <button v-on:click="counter--"> - </button> </div> <script> const app = new Vue({ el: "#app", data:{ counter: 0 } }); </script> </body> </html>
引入vue.js, 並創建一個Vue對象. 這些之前都說過, 就不多說了. 接下來看看
<button v-on:click="counter++"> + </button>
這是什么意思呢? 這是vue的寫法. v-on是vue的指令, 這里先有個印象, 后面會詳細講解. v-on表示要執行一個事件, :click就是具體的事件, 這里是點擊事件, 點擊后執行什么邏輯呢? 執行counter ++. 是不是很神奇? 也許還沒有感覺, 那么我們來看看, 如果是jQuery, 要怎么做吧?
1. 給+按鈕添加一個點擊事件 2. 獲取counter計數器對象的值 3. 對counter進行++ 4. 再講counter計算后的結果賦值給計數器對象.
現在感受到了吧, jquery是命令式編程, 一行命令執行一個語句. 這里要執行好幾句話, 而vue一句話就搞定了.
- 第二步: 這里click事件中就有一句話, counter++, 那么要是有好多邏輯怎么辦呢? 那就需要提出來單獨處理了.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="../js/vue.js"></script> </head> <body> <div id="app"> 當前數字: {{counter}} <br> <button v-on:click="add"> + </button> <button v-on:click="sub"> - </button> </div> <script> const app = new Vue({ el: "#app", data:{ counter: 0 }, methods: { add: function() { console.info("add方法被執行") this.counter ++; }, sub: function () { console.info("sub方法被執行") this.counter --; } } }); </script> </body> </html>
在vue里面,要想增加一個事件, 那就放在methods屬性里就可以了. 這里有一點需要注意. 在方法里要對data中的變量執行counter ++, 直接這么寫是不行的, 需要加上this.counter++. this表示的是new出來的Vue對象. 有朋友可能就要說了, this在方法里面, 不應該是表示當前方法么?vue做了一層代理, 所以, 這里的this指的是new Vue()對象.
- 第一步: 創建一個html文件03-計數器.html
四. Vuejs的MVVM
1. 什么是MVVM
MVVM是Model-View-ViewModel的簡寫。它本質上就是MVC 的改進版。MVVM 就是將其中的View 的狀態和行為抽象化,讓我們將視圖 UI 和業務邏輯分開。當然這些事 ViewModel 已經幫我們做了,它可以取出 Model 的數據同時幫忙處理 View 中由於需要展示內容而涉及的業務邏輯。View綁定到ViewModel,然后執行一些命令在向它請求一個動作。而反過來,ViewModel跟Model通訊,告訴它更新來響應UI。這樣便使得為應用構建UI非常的容易。
MVVM有助於將圖形用戶界面的開發與業務邏輯或后端邏輯(數據模型)的開發分離開來,這是通過置標語言或GUI代碼實現的。MVVM的視圖模型是一個值轉換器,這意味着視圖模型負責從模型中暴露(轉換)數據對象,以便輕松管理和呈現對象。在這方面,視圖模型比視圖做得更多,並且處理大部分視圖的顯示邏輯。 視圖模型可以實現中介者模式,組織對視圖所支持的用例集的后端邏輯的訪問。
2. MVVM的優點
MVVM模式和MVC模式一樣,主要目的是分離視圖(View)和模型(Model),有幾大優點
-
- 低耦合。視圖(View)可以獨立於Model變化和修改,一個ViewModel可以綁定到不同的"View"上,當View變化的時候Model可以不變,當Model變化的時候View也可以不變。
- 可重用性。你可以把一些視圖邏輯放在一個ViewModel里面,讓很多view重用這段視圖邏輯。
- 獨立開發。開發人員可以專注於業務邏輯和數據的開發(ViewModel),設計人員可以專注於頁面設計,使用Expression Blend可以很容易設計界面並生成xaml代碼。
- 可測試。界面素來是比較難於測試的,測試可以針對ViewModel來寫。
3. MVVM模式的組成部分
-
-
模型
-
-
-
視圖
-
-
-
視圖模型
-
-
-
綁定器
聲明性數據和命令綁定隱含在MVVM模式中。綁定器使開發人員免於被迫編寫樣板邏輯來同步視圖模型和視圖。在微軟的堆之外實現時,聲明性數據綁定技術的出現是實現該模式的一個關鍵因素
-
4. Vue中的VMMV
下圖不僅概括了MVVM模式(Model-View-ViewModel),還描述了在Vue.js中ViewModel是如何和View以及Model進行交互的。
ViewModel是Vue.js的核心,它是一個Vue實例。Vue實例是作用於某一個HTML元素上的,這個元素可以是HTML的body元素,也可以是指定了id的某個元素。
當創建了ViewModel后,雙向綁定是如何達成的呢?
首先,我們將上圖中的DOM Listeners和Data Bindings看作兩個工具,它們是實現雙向綁定的關鍵。
從View側看,ViewModel中的DOM Listeners工具會幫我們監測頁面上DOM元素的變化,如果有變化,則更改Model中的數據;
從Model側看,當我們更新Model中的數據時,Data Bindings工具會幫我們更新頁面中的DOM元素。
拿第一個案例來說
<html> <head> <title>第一個vue程序</title> <script src="../js/vue.js"></script> </head> <body> <div id="app">{{message}}</div> <script> const app = new Vue({ el: "#app", data: { message: "hello, 盛開的太陽!" } }); </script> </body> </html>
在這里, 定義了一個View, 定義了model, 創建了一個Vue實例(view-model), 它用於連接view和model
在創建Vue實例時,需要傳入一個選項對象,選項對象可以包含數據、掛載元素、方法、模生命周期鈎子等等。
在這個示例中,選項對象的el屬性指向View,el: ‘#app’表示該Vue實例將掛載到<div id="app">...</div>
這個元素;data屬性指向Model,data: { message: "hello, 盛開的太陽" 表示我們的Model是一個對象。
Vue.js有多種數據綁定的語法,最基礎的形式是文本插值,使用一對大括號語法,在運行時{{ message }}會被數據對象的message屬性替換,所以頁面上會輸出”hello, 盛開的太陽!”。
五. Vue實例的生命周期
每個 Vue 實例在被創建時都要經過一系列的初始化過程——例如,需要設置數據監聽、編譯模板、將實例掛載到 DOM 並在數據變化時更新 DOM 等。同時在這個過程中也會運行一些叫做生命周期鈎子的函數,這給了用戶在不同階段添加自己的代碼的機會。
比如 created
鈎子可以用來在一個實例被創建之后執行代碼:
new Vue({ data: { a: 1 }, created: function () { // `this` 指向 vm 實例 console.log('a is: ' + this.a) } }) // => "a is: 1"
也有一些其它的鈎子,在實例生命周期的不同階段被調用,如 mounted
、updated
和 destroyed
。生命周期鈎子的 this
上下文指向調用它的 Vue 實例。
注意:
不要在選項 property 或回調上使用箭頭函數,比如
created: () => console.log(this.a) 或 vm.$watch('a', newValue => this.myMethod())。
因為箭頭函數並沒有 this,this 會作為變量一直向上級詞法作用域查找,直至找到為止,經常導致
Uncaught TypeError: Cannot read property of undefined 或
Uncaught TypeError: this.myMethod is not a function 之類的錯誤。
1. 生命周期圖示
下圖展示了實例的生命周期。你不需要立馬弄明白所有的東西,不過隨着你的不斷學習和使用,它的參考價值會越來越高。
2. Vue生命周期函數
如上圖, 常用的生命周期函數有: beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, beforeDestory, destoryed, 這些鈎子函數都是回調函數, 在vue生命周期執行過重,方便用戶操作控制的入口
六. Vue源碼
我們知道了vue的生命周期了, 接下來看看vue的源碼, 對vue的生命周期加深理解
源碼下載地址: https://github.com/vuejs/vue
我們選擇一個release版本. 下載代碼到本地
下載好以后, 打開項目, 我們來看看項目結構.
剛開始, 我們不熟悉, 那么先猜測一下, 哪個是主要文件, 經驗告訴我們, src里面的才是主目錄, 在src中和核心目錄是core.
我們看到了index.js, 通常一個網站的入口是index.html, 而對應的js腳本就是index.js. 打開index.js
import Vue from './instance/index' import { initGlobalAPI } from './global-api/index' import { isServerRendering } from 'core/util/env' import { FunctionalRenderContext } from 'core/vdom/create-functional-component' initGlobalAPI(Vue) Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering }) Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext } }) // expose FunctionalRenderContext for ssr runtime helper installation Object.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext }) Vue.version = '__VERSION__' export default Vue
這里面有兩句非常重要的話, 第一句
export default Vue
這句話表示export導出Vue, 我們new的就是這里導出的Vue. 我們看到index.js中沒有主邏輯, 那主邏輯在哪里呢? 在第二句話里面:
import Vue from './instance/index'
導入了./instance/index中的文件. 我們來看看這個文件
import { initMixin } from './init' import { stateMixin } from './state' import { renderMixin } from './render' import { eventsMixin } from './events' import { lifecycleMixin } from './lifecycle' import { warn } from '../util/index' function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default Vue
首先, 我們看到定義了一個Vue對象, 在對象里面執行了很多操作, 初始化, 事件監聽, 生命周期處理, 渲染等等. 這就是vue的整個流程. 我們進入到initMixin(Vue)初始化方法里面看一下
/* @flow */ import config from '../config' import { initProxy } from './proxy' import { initState } from './state' import { initRender } from './render' import { initEvents } from './events' import { mark, measure } from '../util/perf' import { initLifecycle, callHook } from './lifecycle' import { initProvide, initInjections } from './inject' import { extend, mergeOptions, formatComponentName } from '../util/index' let uid = 0 export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } // a flag to avoid this being observed vm._isVue = true // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') ......
初始化的時候又做了一系列的操作. 注意在方法創建之前有一個鈎子函數callHook(vm, 'beforeCreate'), 方法創建之后, 有一個callHook(vm, 'created')函數, 這里可以和上面的生命周期圖對比研究, 就能更加熟悉Vue的聲明周期了