经过前面的介绍,咱们对目前的项目工程化有了大致了了解,那么其中,在第二阶段的工程化演进中,有一个重要的工程设计理念诞生,他就是著名的 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>
咱们能够将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是彻底相同的。然而,不一样的是计算属性是基于它们的依赖进行缓存的。只在相关依赖发生改变时它们才会从新求值;屡次调用,计算属性会当即返回以前的计算结果,而没必要再次执行函数。