經過前面的介紹,咱們對目前的項目工程化有了大致了了解,那麼其中,在第二階段的工程化演進中,有一個重要的工程設計理念誕生,他就是著名的 MVC 設計模式,簡單點,MVC 其實就是爲了項目工程化的一種分工模式;javascript
MVC 中的最大缺點就是單項輸入輸出,全部的 M 的變化及 V 層的變化,必須經過 C 層調用才能展現;php
爲了解決相應的問題,出現了 MVVM 的設計思想,簡單理解就是實想數據層與展現層的相互調用,下降業務層面的交互邏輯;後面再進行詳細介紹;html
Vue (讀音 /vjuː/,相似於 view) 是一套用於構建用戶界面的 漸進式框架。vue
注意:Vue是一個框架,相對於 jq 庫來講,是由本質區別的;java
https://cn.vuejs.org/git
Vue 不支持 IE8 及如下版本,由於 Vue 使用了 IE8 沒法模擬的 ECMAScript 5 特性。但它支持全部兼容 ECMAScript 5 的瀏覽器。es6
直接下載引入:https://cn.vuejs.org/v2/guide/installation.htmlgithub
CDN 引入:web
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
CDN 加速: https://www.bootcdn.cn/ajax
<body> <div id="div"> {{user_name}} </div> </body> // 兩種引入方式,任意選擇 <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script src="./vue.js"></script> <script> var app = new Vue({ el:'#div', // 設置要操做的元素 // 要替換的額數據 data:{ user_name:'我是一個div' } }) </script>
基礎知識 --> 項目 --> 構建工具 --> Vue其餘相關技術
每一個 Vue 應用都是經過用 Vue
函數建立一個新的 Vue 實例 開始的:
var vm = new Vue({ // 選項 })
<body> <div id="div"> {{user_name}} </div> </body> <script src="./vue.js"></script> <script> var app = new Vue({ el:'#div', // 設置要操做的元素 // 要替換的額數據 data:{ user_name:'我是一個div' } }) // 打印Vue實例對象 console.log(app); </script>
經過打印實例對象發現,其中 el 被Vue 放入了公有屬性中,而data 則被放入了 私有屬性中,而 data 中的數據,須要被外部使用,因而 Vue 直接將data 中的屬性及屬性值,直接掛載到 Vue 實例中,也就是說,data中的數據,咱們能夠直接使用 app.user_name
直接調用;
var app = new Vue({ el:'#div', // 設置要操做的元素 // 要替換的額數據 data:{ user_name:'我是一個div', user:222222 } }) console.log(app.user_name);
咱們在前面的代碼中,使用 {{}}
的形式在 html 中獲取實例對象對象中 data 的屬性值;
這種使用 {{}}
獲取值得方式,叫作 插值 或 插值表達式 ;
數據綁定最多見的形式就是使用「Mustache」語法 (雙大括號) 的文本插值:
<span>Message: {{ msg }}</span>
Mustache 標籤將會被替代爲對應數據對象上 msg
屬性的值。不管什麼時候,綁定的數據對象上 msg
屬性發生了改變,插值處的內容都會更新。即使數據內容爲一段 html 代碼,仍然以文本內容展現
<body> <div id="div"> 文本插值 {{html_str}} </div> </body> <script> var app = new Vue({ el:'#div', data:{ html_str:'<h2>Vue<h2>' } }) </script>
瀏覽器渲染結果:<div id="div">文本插值 <h2>Vue<h2></div>
打開瀏覽器的 REPL 環境 輸入 app.html_str = '<s>vue</s>'
隨機瀏覽器渲染結果就會改變: <div id="div">文本插值 <s>vue</s></div>
迄今爲止,在咱們的模板中,咱們一直都只綁定簡單的屬性鍵值。但實際上,對於全部的數據綁定,Vue.js 都提供了徹底的 JavaScript 表達式支持,可是不能使用 JS 語句;
(表達式是運算,有結果;語句就是代碼,能夠沒有結果)
<body> <div id="div" > {{ un > 3 ? '大' : '小'}} {{ fun() }} </div> </body> <script> var app = new Vue({ el:'#div', data:{ un:2, fun:()=> {return 1+2} } }) </script>
指令 (Directives) 是帶有 v-
前綴的特殊特性。指令特性的值預期是單個 JavaScript 表達式 (v-for
是例外狀況,稍後咱們再討論)。指令的職責是,當表達式的值改變時,將其產生的連帶影響,響應式地做用於 DOM;參考 手冊 、 API
<body> <div id="div" > <p v-if="seen">如今你看到我了</p> </div> </body> <script> var app = new Vue({ el:'#div', data:{ seen:false } }) </script>
這裏,v-if
指令將根據表達式 seen
的值的真假來插入/移除 <p>
元素。
https://cn.vuejs.org/v2/api/#v-text
https://cn.vuejs.org/v2/api/#v-html
//這個代碼是錯誤的, <body> <div id="div" {{class}}> <p v-text="seen"></p> <p v-html="str_html"></p> </div> </body> <script> var app = new Vue({ el:'#div', data:{ seen:'<h1>Vue</h1>', str_html:'<h1>Vue</h1>', class:'dd', } }) </script>
注意:
- v-text
- v-text和差值表達式的區別
- v-text 標籤的指令更新整個標籤中的內容(替換整個標籤包括標籤自身)
- 差值表達式,能夠更新標籤中局部的內容
- v-html
- 能夠渲染內容中的HTML標籤
- 儘可能避免使用,不然會帶來危險(XSS攻擊 跨站腳本攻擊)
HTML 屬性不能用 {{}}
語法
https://cn.vuejs.org/v2/api/#v-bind
能夠綁定標籤上的任何屬性。
動態綁定圖片的路徑
<img id=「app」 v-bind:src="src" /> <script> var vm = new Vue({ el: '#app', data: { src: '1.jpg' } }); </script>
綁定a標籤上的id
<a id="app" v-bind:href="'del.php?id=' + id">刪除</a> <script> var vm = new Vue({ el: '#app', data: { id: 11 } }); </script>
綁定class
對象語法和數組語法
對象語法
若是isActive爲true,則返回的結果爲 <div id="app" class="active"></div>
<div id="app" v-bind:class="{active: isActive}"> hei </div> <script> var vm = new Vue({ el: '#app', data: { isActive: true } }); </script>
數組語法
渲染的結果: <div id="app" class="active text-danger"></div>
<div id="app" v-bind:class="[activeClass, dangerClass]"> hei </div> <script> var vm = new Vue({ el: '#app', data: { activeClass: 'active', dangerClass: 'text-danger' } }); </script>
綁定style
對象語法和數組語法
對象語法
渲染的結果: <div id="app" style="color: red; font-size: 40px;">hei</div>
<div id="app" v-bind:style="{color: redColor, fontSize: font + 'px'}"> hei </div> <script> var vm = new Vue({ el: '#app', data: { redColor: 'red', font: 40 } }); </script>
數組語法
渲染結果:<div id="app" style="color: red; font-size: 18px;">abc</div>
<div id="app" v-bind:style="[color, fontSize]">abc</div> <script> var vm = new Vue({ el: '#app', data: { color: { color: 'red' }, fontSize: { 'font-size': '18px' } } }); </script>
簡化語法
<div id="app"> <img v-bind:src="imageSrc"> <!-- 縮寫 --> <img :src="imageSrc"> </div> <script> var vm = new Vue({ el: '#app', data: { imageSrc: '1.jpg', } }); </script>
https://cn.vuejs.org/v2/api/#v-model
單向數據綁定
<div id="div"> <input type="text" :value="input_val"> </div> <script> var app = new Vue({ el: '#div', data: { input_val: 'hello world ' } }) </script>
瀏覽器渲染結果: <div id="div"><input type="text" value="hello world"></div>
經過瀏覽器 REPL 環境能夠進行修改 app.input_val = 'Vue'
瀏覽器渲染結果: <div id="div"><input type="text" value="Vue"></div>
咱們經過 vue 對象修改數據能夠直接影響到 DOM 元素,可是,若是直接修改 DOM 元素,卻不會影響到 vue 對象的數據;咱們把這種現象稱爲 單向數據綁定 ;
雙向數據綁定
<div id="div"> <input type="text" v-model="input_val" > </div> <script> var app = new Vue({ el: '#div', data: { input_val: 'hello world ' } }) </script>
經過 v-model 指令展現表單數據,此時就完成了 雙向數據綁定 ;
無論 DOM 元素仍是 vue 對象,數據的改變都會影響到另外一個;
多行文本 / 文本域
<div id="div"> <textarea v-model="inp_val"></textarea> <div>{{ inp_val }}</div> </div> <script> var app = new Vue({ el: '#div', data: { inp_val: '' } }) </script>
綁定複選框
<div id="div"> 吃飯:<input type="checkbox" value="eat" v-model="checklist"><br> 睡覺:<input type="checkbox" value="sleep" v-model="checklist"><br> 打豆豆:<input type="checkbox" value="ddd" v-model="checklist"><br> {{ checklist }} </div> <script> var vm = new Vue({ el: '#div', data: { checklist: '' // checklist: [] } }); </script>
綁定單選框
<div id="app"> 男<input type="radio" name="sex" value="男" v-model="sex"> 女<input type="radio" name="sex" value="女" v-model="sex"> <br> {{sex}} </div> <script> var vm = new Vue({ el: '#app', data: { sex: '' } }); </script>
修飾符
.lazy
- 取代 input
監聽 change
事件
.number
- 輸入字符串轉爲有效的數字
.trim
- 輸入首尾空格過濾
<div id="div"> <input type="text" v-model.lazy="input_val"> {{input_val}} </div> <script> var app = new Vue({ el: '#div', data: { input_val: 'hello world ' } }) </script>
https://cn.vuejs.org/v2/api/#v-on
https://cn.vuejs.org/v2/guide/events.html
<div id="app"> <input type="button" value="按鈕" v-on:click="cli"> </div> <script> var vm = new Vue({ el: '#app', data: { cli:function(){ alert('123'); } } }); </script>
上面的代碼運行是沒有問題的,可是,咱們不建議這樣作,由於 data 是專門提供數據的對象,事件觸發須要執行的是一段代碼,須要的是一個方法 (事件處理程序) ;
修改代碼以下:
<div id="app"> <!-- 使用事件綁定的簡寫形式 --> <input type="button" value="按鈕" @click="cli"> </div> <script> var vm = new Vue({ el: '#app', data: {}, // 將事件處理程序寫入methods對象 methods: { cli: function () { alert('123'); } } }); </script>
向事件處理器中傳參
<div id="app"> <!-- 直接調用傳參便可 --> <input type="button" value="按鈕" @click="cli(1,3)"> </div> <script> var vm = new Vue({ el: '#app', data: {}, methods: { // 接受參數 cli: function (a,b) { alert(a+b); } } }); </script>
而此時,若是在處理器中須要使用事件對象,則沒法獲取,咱們能夠用特殊變量 $event
把它傳入方法
<input type="button" value="按鈕" @click="cli(1,3,$event)">
methods: { // 接受參數 cli: function (a,b,ev) { alert(a+b); console.log(ev); } }
原生 JS 代碼,想要阻止瀏覽器的默認行爲(a標籤跳轉、submit提交),咱們要使用事件對象的 preventDefault()
方法
<div id="app"> <a href="http://www.qq.com" id="a">騰百萬</a> </div> <script> document.getElementById('a').onclick = (ev)=>{ // 組織瀏覽器的默認行爲 ev.preventDefault(); } </script>
使用修飾符 阻止瀏覽器的默認行爲
<div id="app"> <a href="http://www.qq.com" @click.prevent="cli">騰百萬</a> </div> <script> var vm = new Vue({ el: '#app', data: {}, // 將事件處理程序寫入methods對象 methods: { cli: function () { alert('123'); } } }); </script>
使用修飾符綁定一次性事件
<div id="app"> <a href="http://www.qq.com" @click.once="cli($event)">騰百萬</a> </div> <script> var vm = new Vue({ el: '#app', data: {}, // 將事件處理程序寫入methods對象 methods: { cli: function (ev) { ev.preventDefault(); alert('123'); } } }); </script>
綁定鍵盤擡起事件,可是隻有enter
鍵能觸發此事件
<div id="app"> <input type="text" @keyup.enter="keyup"> </div> <script> var vm = new Vue({ el: '#app', data: {}, methods: { keyup:()=>{ console.log('111') } } }); </script>
按住 shift
後才能觸發點擊事件
<div id="app"> <input type="button" value="按鈕" @click.shift="cli"> </div> <script> var vm = new Vue({ el: '#app', data: {}, methods: { cli:()=>{ console.log('111') } } }); </script>
鼠標中鍵觸發事件
<div id="app"> <input type="button" value="按鈕" @click.middle="cli"> </div> <script> var vm = new Vue({ el: '#app', data: {}, methods: { cli:()=>{ console.log('111') } } }); </script>
你可能注意到這種事件監聽的方式違背了關注點分離 (separation of concern) 這個長期以來的優良傳統。但沒必要擔憂,由於全部的 Vue.js 事件處理方法和表達式都嚴格綁定在當前視圖的 ViewModel 上,它不會致使任何維護上的困難。實際上,使用
v-on
有幾個好處:
- 掃一眼 HTML 模板便能輕鬆定位在 JavaScript 代碼裏對應的方法。
- 由於你無須在 JavaScript 裏手動綁定事件,你的 ViewModel 代碼能夠是很是純粹的邏輯,和 DOM 徹底解耦,更易於測試。
- 當一個 ViewModel 被銷燬時,全部的事件處理器都會自動被刪除。你無須擔憂如何清理它們。
https://cn.vuejs.org/v2/api/#v-show
根據表達式之真假值,切換元素的 display
CSS 屬性。
<div id="app"> <p v-show="is_show">Vue</p> </div> <script> var vm = new Vue({ el:'#app', data:{ is_show:false }, methods:{}, }) </script>
案例:點擊按鈕切換隱藏顯示
<div id="app"> <input type="button" value="按鈕" @click="isshow"> <p v-show="is_show">Vue</p> </div> <script> var vm = new Vue({ el:'#app', data:{ is_show:false }, methods:{ isshow:function(){ this.is_show = !this.is_show; } }, }) </script>
https://cn.vuejs.org/v2/api/#v-if
<div id="app"> <div v-if="type === 'A'"> A </div> <div v-else-if="type === 'B'"> B </div> <div v-else-if="type === 'C'"> C </div> <div v-else> Not A/B/C </div> </div> <script> var vm = new Vue({ el: '#app', data: { type: 'F' }, }) </script>
https://cn.vuejs.org/v2/api/#v-for
<div id="app"> <ul> <li v-for="(val,key) in arr">{{val}}---{{key}}</li> </ul> <ul> <li v-for="(val,key) in obj">{{val}}---{{key}}</li> </ul> </div> <script> var vm = new Vue({ el: '#app', data: { arr: ['a', 'b', 'c'], obj: { id: 1, name: '李四' } }, }) </script>
https://cn.vuejs.org/v2/api/#v-cloak
和 CSS 規則如 [v-cloak] { display: none }
一塊兒用時,這個指令能夠隱藏未編譯的 Mustache 標籤直到實例準備完畢。
<div id="app"> <p>{{obj.id}}</p> </div> <script src="./vue.js"></script> <script> setTimeout(() => { var vm = new Vue({ el: '#app', data: { arr: ['a', 'b', 'c'], obj: { id: 1, name: '李四' } }, }) }, 2000); </script>
當咱們的網絡受阻時,或者頁面加載完畢而沒有初始化獲得 vue 實例時,DOM中的 {{}}
則會展現出來;
爲了防止現象,咱們可使用 CSS 配合 v-cloak 實現獲取 VUE 實例前的隱藏;
<style> [v-cloak] { display: none; } </style> <div id="app"> <p v-cloak>{{obj.id}}</p> </div> <script src="./vue.js"></script> <script> setTimeout(() => { var vm = new Vue({ el: '#app', data: { obj: { id: 1, name: '李四' } }, }) }, 2000); </script>
https://cn.vuejs.org/v2/api/#v-once
只渲染元素和組件一次。隨後的從新渲染,元素/組件及其全部的子節點將被視爲靜態內容並跳過
<div id="app"> <p v-once>{{msg}}</p> </div> <script> var vm = new Vue({ el: '#app', data: { msg:'kkk' }, }) </script>
爲何選擇這樣的案例:
產品功能簡潔,需求明確,所需知識點豐富;實現基本功能容易,涵蓋所學基礎知識;而可擴展性強,完善全部功能比較複雜,所需技術衆多;在學習中,能夠靈活取捨;
在項目目錄中執行 npm install
命令,下載所需靜態資源 ; 將Vue.js框架代碼,複製到 js 目錄,在index.html中引入 vue : <script src="./js/vue.js"></script>
同時 在 index.html 最下方,項目引入了app.js ; 而咱們要寫的 vuejs 代碼,都放在這個文件中;
const list_data = [ {id:1,title:'吃飯',stat:true}, {id:2,title:'睡覺',stat:false}, {id:3,title:'打豆豆',stat:true}, ] new Vue({ el:'#todoapp', data:{ // list_data:list_data, list_data,// es6屬性簡寫 } })
<ul class="todo-list"> <li v-for="(val,key) in list_data"> <div class="view"> <input class="toggle" type="checkbox" v-model="val.stat"> <label>{{val.title}}</label> <button class="destroy"></button> </div> <input class="edit" value="Rule the web"> </li> </ul>
標籤及內容都是在 section footer 兩個標籤中的,當 list_data 中沒有數據時,咱們只須要隱藏這個兩個標籤便可:
<section v-if="list_data.length" class="main"> …… </section> <footer v-if="list_data.length" class="footer"> …… </footer>
兩個標籤都有 v-if
判斷 ,所以咱們可使用一個 div
包裹兩個標籤,使 div
隱藏便可:
<div v-if="list_data.length"> <section class="main"> …… </section> <footer class="footer"> …… </footer> </div>
若是有內容,那麼 DOM 書中就會多出一個 div 標籤,那麼咱們能夠選擇使用 template
(vue中的模板標識),有內容時,瀏覽器渲染不會有此節點;
<template v-if="list_data.length"> <section class="main"> …… </section> <footer class="footer"> …… </footer> </template>
綁定 enter
鍵盤事件:
<input @keyup.enter="addTodo" class="new-todo" placeholder="請輸入" autofocus>
new Vue({ el:'#todoapp', data:{ // list_data:list_data, list_data,// es6屬性簡寫 }, //添加事件處理器 methods:{ // addTodo:function(){} // 簡寫形式 addTodo(){ console.log(123); } } })
修改代碼完成任務添加:
methods: { // addTodo:function(){} // 簡寫形式 addTodo(ev) { // 獲取當前觸發事件的元素 var inputs = ev.target; // 獲取value值,去除空白後判斷,若是爲空,則不添加任務 if (inputs.value.trim() == '') { return; } // 組裝任務數據 var todo_data = { id: this.list_data.length + 1 + 1, title: inputs.value, stat: false }; // 將數據添加進數組 this.list_data.push(todo_data); // 清空文本框內容 inputs.value = ''; } }
點擊文本框左邊的下箭頭,實現全選和反選操做
爲元素綁定點擊事件:
<input @click="toggleAll" id="toggle-all" class="toggle-all" type="checkbox">
添加處理程序:
toggleAll(ev){ // 獲取點擊的元素 var inputs = ev.target; // console.log(inputs.checked); // 循環全部數據爲狀態從新賦值 // 由於每一個元素的選中狀態都是使用 v-model 的雙向數據綁定, // 所以 數據發生改變,狀態即改變,狀態改變,數據也會改變 for(let i=0;i<this.list_data.length;i++){ this.list_data[i].stat = inputs.checked; } }
若是任務完成,狀態改成選中, li
的 class
屬性爲 completed
時文字有中劃線;
<li v-for="(val,key) in list_data" v-bind:class="{completed:val.stat}">
綁定點擊事件,將當前索引值傳入事件處理程序:
<button @click="removeTodo(key)" class="destroy"></button>
按照索引,刪除相應的數據:
removeTodo(key){ this.list_data.splice(key,1); },
綁定事件
<button @click="removeAllDone" class="clear-completed">Clear completed</button>
循環遍歷全部數據,刪除已被標記爲完成的任務:
removeAllDone(){ for(let i=0;i<list_data.length;i++){ if(list_data[i].stat == true){ this.list_data.splice(i,1); } } }
循環的代碼看起來很不舒服, Array.prototype.filter()
方法建立一個新數組, 其包含經過所提供函數實現的測試的全部元素。
var arr = [1,4,6,2,78,23,7,3,8]; // 原始寫法 // var new_arr = arr.filter(function(v){ // // if(v>8){ // // return true; // // } // return v>8; // }) // 箭頭函數寫法 // var new_arr = arr.filter((v)=>{ // return v>8; // }) // 精簡寫法 var new_arr = arr.filter((v)=> v>8); console.log(new_arr);
修改項目代碼:
removeAllDone(){ // 原始循環判斷用法 // for(let i=0;i<list_data.length;i++){ // if(list_data[i].stat == true){ // this.list_data.splice(i,1); // } // } // 上面是循環刪除符合條件的數據 // 下面是保留不符合條件的數據 // 原始標準庫對象方法 // this.list_data = this.list_data.filter(function(v){ // if(v.stat == false){ // return true; // } // }) // 箭頭函數方法 // this.list_data = this.list_data.filter(function(v){ // return !v.stat; // }) // 精簡方法 this.list_data = this.list_data.filter((v)=>!v.stat); },
TodoList案例暫時告一段落,咱們並無將產品作完,由於咱們須要用到其餘知識了;
Vue Devtools 調試工具
在使用 Vue 時,咱們推薦在你的瀏覽器上安裝 Vue Devtools。它容許你在一個更友好的界面中審查和調試 Vue 應用。
MVC 設計思想:
M: model 數據模型層 提供數據
V: Views 視圖層 渲染數據
C: controller 控制層 調用數據渲染視圖
MVVM 設計思想:
M: model 數據模型層 提供數據
V: Views 視圖層 渲染數據
VM:ViewsModel 視圖模型層 調用數據渲染視圖
由數據來驅動視圖(不須要過多考慮dom操做,把重心放在VM)
<div id="div"> <input type="text" v-model="xing"> <input type="text" v-model="ming"> {{xing + ming}} </div> <script> var app = new Vue({ el: '#div', data: { xing:'', ming:'', } }) </script>
模板內的表達式很是便利,可是設計它們的初衷是用於簡單運算的。在模板中放入太多的邏輯會讓模板太重且難以維護。所以咱們可使用方法,來進行運算並返回數據:
<div id="div"> <input type="text" v-model="xing"> <input type="text" v-model="ming"> {{ fullname() }} <!-- 一百次調用,觀察時間結果--> {{ fullname() }} </div> <script> var app = new Vue({ el: '#div', data: { xing:'', ming:'', }, methods:{ fullname(){ return this.xing+this.ming; } } }) </script>
注意,每次在模板中使用 {{ fullname() }}
fullname方法就會被調用執行一次;因此,對於任何複雜邏輯,你都應當使用計算屬性 ,由於計算屬性,會自動緩存數據:
<div id="div"> <input type="text" v-model="xing"> <input type="text" v-model="ming"> <br> {{fulln}} <!-- 一百次調用 --> {{fulln}} </div> <script> var app = new Vue({ el: '#div', data: { xing:'', ming:'', }, computed:{ fulln(){ return this.xing+this.ming+Date.now(); } } }) </script>
咱們能夠將同一函數定義爲一個方法而不是一個計算屬性。兩種方式的最終結果確實是徹底相同的。然而,不一樣的是計算屬性是基於它們的依賴進行緩存的。只在相關依賴發生改變時它們纔會從新求值;屢次調用,計算屬性會當即返回以前的計算結果,而沒必要再次執行函數。