vue基础

343 阅读17分钟

1. 什么是Vue.js

是一套构建用户界面的渐进式框架。主要作用:动态构建用户界面

Vue 只关注视图层, 采用自底向上增量开发的设计

Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件

1.1 特点

  • 遵循MVVM模式
  • 编码简洁, 体积小, 运行效率高, 移动/PC端开发
  • 它本身只关注UI(视图层), 采用自底向上增量开发的设计,可以轻松引入vue插件和其它第三库开发项目
  • 它的目标是通过尽可能简单的API实现响应的数据绑定和组合的视图插件
  • 优点
    • Vue最大程度上减少了页面的DOM操作
    • 可以让开发人员更专注于业务操作
    • 通过简洁的指令结合页面结构与逻辑数据
    • 通过组件化方便模板重用以及增加可维护性
    • 代码结构更合理
    • 维护成本低
    • Vue.js解放了传统javascript中频繁的DOM操作

1.2 与其它框架的关联

  • 借鉴angular的模板和数据绑定技术
  • 借鉴react的组件化和虚拟DOM技术

1.3 vue包含的一系列的扩展插件(库)

  • vue-cli: vue脚手架
  • vue-resource(axios): ajax请求
  • vue-router: 路由
  • vuex: 状态管理
  • vue-lazyload: 图片懒加载
  • vue-scroller: 页面滑动相关
  • mint-ui: 基于vue的组件库(移动端)
  • element-ui: 基于vue的组件库(PC端)

1.4 单页面应用程序 SPA

单页面应用程序最主要的功能是实现前后端分离

1.4.1 网站交互方式

  • 经典的多页面
    • 前后端糅合在一起,开发和维护效率低下;以服务端为主导,前后端混合
    • 用户体验一般,一般是点击刷新跳转,等待时间过长
    • 每个页面都需要重新加载渲染,速度慢
    • 有利于SEO搜索引擎搜索
  • 现代式的单页面
    • 前后端分离,开发效率高,可维护性好
    • 前后端分离:服务端不关心页面,只关心数据;客户端不关心数据库和数据操作,只关心如何通过接口获取数据和服务端交互,处理页面
    • 用户体验好,就像一个原生的客户端软件一样使用
    • 只需要加载渲染局部视图即可,不需要整页刷新
    • 单页面技术兼容性一般是高于IE9及其以上
    • 单页面的数据都是异步加载过来,所以一般很难做SEO优化
    • 一般用于手机web网页、后台管理系统的开发等

2. vue安装方式

2.1 独立版本安装

官网上直接下载 vue.min.js 并用 标签引入

2.2 npm 下载安装

npm 相关命令

# 查看 vue-cli脚手架版本
npm -v

# 升级 npm
npm install npm -g

# 升级或安装 cnpm
npm install cnpm -g

下载安装vue

# 最新稳定版
npm install vue

# 如果想要查看当前项目的 vue版本,
# 可以找package.json文件, 找" dependencies "即可查看vue版本

3. MVVM模式

概念:数据驱动视图,当数据发生改变,那么所有绑定该数据的 DOM 都会跟着改变(MVVM)

双向数据绑定:当数据发生改变, DOM 会自动更新;当表单控件的值发生改变,数据也会自动得到更新

  • M: model 模型,data数据对象
  • V: view 视图 ,模板页面
  • VM:ViewModel 视图模型 (new Vue的实例对象 var vm = new Vue({})):主要包括DOM监听和数据绑定2个最主要的模块

image.png

4. vue常用配置项

Vue实例:每个 Vue 应用都是通过 Vue 函数创建一个新的 Vue实例开始的

var vm = new Vue({})

常用配置项

var Child = {
    template: '<h1>定义局部组件!</h1>'
};
var vm = new Vue({
    el: '#demo',
    data: {
        name:"五月",
        
        kilometers: 0, //千米
        meters: 0, //米

        jsonObj: [{
            name: '宋',
            age: '20'
        }, {
            name: '张',
            age: '30'
        }, {
            name: '刘',
            age: '40'
        }],
    },
    // 自定义方法  this是指当前的vue对象
    methods: {
        sayHello: function(name) {
            alert('您好,' + name);
        },
        //加数器
        addCount: function() {
            // 如果当前count字段没有被定义,那么必须使用Vue.set设置: Vue.set(this, 'count', '初始化value值');
            if (!this.count) {
                Vue.set(this, 'count', '1');
            } else {
                this.count++;
            }
        },
    },
    // 计算属性:可以不先在data对象中定义
    computed: { 
        //reversedMessageInfo可以直接当data对象中的数据使用
        reversedMessageInfo: function() {
            //this指向vm实例
            return this.messageInfo.split('').reverse().join('');
        }
    },
    // 监听属性:要监听的属性必须 是data中已经定义的属性
    watch: {
        kilometers: function(val) {
            this.kilometers = val;
            this.meters = val * 1000;
        },
        meters: function(val) {
            this.meters = val;
            this.kilometers = val / 1000;
        },
    },
    // 过滤器
    filters: {
        //首字母大写
        capitalize: function(value) {
            if (!value) return ''
            value = value.toString();
            return value.charAt(0).toUpperCase() + value.slice(1);
        },
        // 全部大写
        UpperCase: function(value) {
            return value.toUpperCase();
        }
    },
    // 局部组件的自定义
    components: {
        'childComponent': Child
    },
    // 局部自定义指令
    directives: {
        inserted: function(el) {
            el.focus();
        }
    }
})

常用配置项解释说明

4.1 el

指定dom标签容器的选择器 , Vue就会管理对应的标签及其子标签

4.2 data对象:初始化数据

响应式数据

可以通过 vm.$data访问原始数据对象

Vue实例也代理了data对象上所有的属性,因此访问 vm.a 等同于访问 vm.$data.a

视图中绑定的数据必须是显式的初始化到 data

指定初始化状态数据的对象或函数,返回一个对象;主要是用来指定对象的初始化状态属性数据

vue实例vm也会自动拥有data中所有属性

页面中可以直接访问使用 data中的所有属性

4.3 methods对象:自定义方法

包含多个自定义方法的对象 ,可以供页面中的事件指令来绑定回调

回调函数默认有event参数, 但也可以指定自己的参数

所有的方法由vue对象来调用, 访问data中的属性可以直接使用this.xxx

methods 将被混入到 Vue 实例中。可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用。方法中的 this 自动绑定为 Vue 实例

注意,不应该使用箭头函数来定义 method 函数 (例如 plus: () => this.a++)。理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,this.a 将是 undefined

4.4 computed:计算属性

计算属性的定义:一种带有行为的属性,本质是方法,但是不能当作方法来调用,必须当作属性来使用(计算属性只能当作属性来使用,不能用于事件处理函数);它相比方法的优势就在于会缓存计算的结果,效率很高;

是包含多个方法的对象 ,可以对 数据状态属性 进行计算返回一个新的数据, 供页面获取显示

在处理复杂逻辑的计算时,是很有用的

一般情况下是相当于是一个 只读 的属性:即不可以在计算属性里面操作 this.name=‘XXX’

如果非要在计算属性里面实现数据的读取操作,可以使用set/get方法,实现对数据的设置和监听的变化

计算属性的执行时间:1=初始化 2=计算属性里面相关的数据发生变化

例如:设置反转字符串

<div id="app">
  <p>原始字符串: {{ message }}</p>
  <p>计算后反转字符串: {{ reversedMessage }}</p>
</div>
 
<script>
var vm = new Vue({
  el: '#app',
  data: {
    message: 'Hello'
  },
  computed: {
    // 计算属性的 getter
    // reversedMessage命名必须和data中的属性不同名  
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }
})
</script>

例如:设置get和set

// 如何给对象定义get/set属性:
// 在创建对象时指定: 
get name () {return xxx} 
set name (value) {}

// 对象创建之后指定:
Object.defineProperty(obj, age, {get(){}, set(value){}})

计算属性里面的get和set

    //  计算属性FullName3(双向) 是一个对象
    FullName3: {
        //回调函数:当需要读取当前属性值时回调,计算并返回当前属性的值
        get() {
            return this.firstName + ' ' + this.lastName;
        },
        //回调函数:当属性值FullName3发生变化时回调,更新相关属性数据(firstName lastName) ,会监听数据的变化
        set(value) {
            //value就是fullName3的最新输入值
            this.firstName=value.split(' ')[0];
            this.lastName=value.split(' ')[1];
        }
    }  

4.5 计算属性computed与methods

computed:

  • computed会存在缓存,是基于它的依赖缓存,只有相关的依赖缓存发生变化时,才会重新取值
  • 执行时间:一是初始化显示,二是相关的data属性数据发生变化
  • 特性:computed计算属性里面的回调函数的函数名不是data里面已经存在的属性名,要重新命名,并调用
  • 计算属性存在缓存,多次读取值执行一次getter计算

methods:

  • 执行:在重新渲染的时候,函数总会调用执行
  • 每使用一次就调用一次,重复使用效率不高

4.6 watch:监听属性的变化

监听属性里面的回调函数的函数名必须是 data里面已经存在的属性名

可以包含多个属性监视的对象

分为一般监视和深度监视:

深度监视:一般是用于监视 引用类型 的数据,可以监视 引用类型 的子数据

监视一般有2个属性:

  • val : 属性变化后的最新值
  • oldVal : 变化之前的值
// 1. 一般变量
a: function(val, oldVal) {
    console.log(val)
},
    
// 2. 方法
b: 'getUserName',

// 3. 深度监视
c: {
    handler: function(val, oldVal) {
    	console.table(value)
    },
    deep: true
},
    
// 4. 该回调会在侦听开始后,立即调用
d: {
    handler: function(val, oldVal) {
    	console.table(value)
    },
    immediate: true
},

// 另一种添加监视方式: 
vm.$watch('xxx', function(value){})  

例如:监听千米和米之前单位的换算

<div id = "computed_props">
    千米 : <input type = "text" v-model = "kilometers">
      米 : <input type = "text" v-model = "meters">
</div>
<p id="info"></p>

<script type = "text/javascript">
    var vm = new Vue({
    el: '#computed_props',
    data: {
        kilometers : 0,
        meters:0
    },
    watch : {
        // 第一种监听方式(推荐)
        // 监听属性 必须在data对象中 先定义
        kilometers:function(val) {
            this.kilometers = val;
            this.meters = this.kilometers * 1000
        },
        meters : function (val) {
            this.kilometers = val/ 1000;
            this.meters = val;
        }
    }
    });
    
    // $watch 是一个实例方法
    // 第二种监听方式
    vm.$watch('kilometers', function (newValue, oldValue) {
    // 这个回调将在 vm.kilometers 改变后调用
    document.getElementById ("info").innerHTML = "修改前值为: " + oldValue + ",修改后值为: " + newValue;
        
})
</script>

4.7 computed和watch案例展示 fullName

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>computed计算属性和watch监听属性</title>
	</head>
	<body>
		<div id="demo">
			姓:<input type="text" placeholder="First Name" v-model="firstName"><br>
			名:<input type="text" placeholder="Last Name" v-model="lastName"><br>
			姓名1(计算属性-单向数据绑定):<input type="text" placeholder="full Name1" v-model="FullName1"><br>
			姓名2(监听属性-单向数据绑定):<input type="text" placeholder="full Name2" v-model="FullName2"><br>
			姓名3(计算属性-双向数据绑定):<input type="text" placeholder="full Name3" v-model="FullName3"><br>
		</div>
		<script type="text/javascript" src="lib/vue.js"></script>
		<script type="text/javascript">
			const vm = new Vue({
				el: '#demo',
				data: {
					firstName: 'A',
					lastName: 'B',
					FullName2: 'A B', // watch属性
				},
				computed: {

					//  计算属性FullName1(data中不需要事先定义)
					FullName1: function() {
						return this.firstName + ' ' + this.lastName;
					},

					/*
						上面的写法可以简写为:只是es6的一种简写的方法,没有任何实际意义
						FullName1() {
							return this.firstName + ' ' + this.lastName;
						},
					*/

					// 计算属性FullName3(双向数据绑定) 是一个对象
					FullName3: {
						// 回调函数:当需要读取当前属性值时回调,计算并返回当前属性的值
						get() {
							return this.firstName + ' ' + this.lastName;
						},
						// 回调函数:当属性值FullName3发生变化时回调,更新相关属性数据(firstName lastName)
						set(value) {
							// value就是fullName3的最新输入值
							this.firstName = value.split(' ')[0];
							this.lastName = value.split(' ')[1];
						}
					}
				},
				watch: {

					//第一种方法:配置监视
					//监视firstName属性(data中必须先定义)
					firstName: function(newVal, oldVal) {
						this.FullName2 = newVal + ' ' + this.lastName;
					}
				}
			});

			 /*
			 // 第二种监听的方法
			 // 监视lastName属性(data中必须先定义)
			 vm.$watch('lastName', function(newVal, oldVal) {
					 this.FullName2 = this.firstName + ' ' + newVal;
			 });
			 
			 */
		</script>
	</body>
</html>

5. Vue.set 新增对象属性

当我们给一个比如props中或者data中被观测的对象添加一个新的属性时,是不可以直接添加的,必须要通过Vue.set方法来设置

Vue.set方法用来新增对象的属性。如果要增加属性的对象是响应式的,那该方法可以确保属性被创建后也是响应式的,同时会触发视图的更新

// 当前count字段没有被定义,那么必须使用Vue.set设置:
//  Vue.set(this, 'count', '初始化value值');
export default {
	props: {
		food: {
			type: Object
		}
	},
	methods: {
		addCart() {
			if (!this.food.count) {
                // 这里的food对象是没有count属性的,所以我们要给他添加count属性,必须使用Vue.set方法
				Vue.set(this.food, 'count', '1');
			} else {
				this.food.count++;
			}
		}
	}
};


6. vue之模板语法

6.1 介绍

Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据

Vue.js 的核心是一个允许你采用简洁的模板语法来声明式的将数据渲染进 DOM 的系统

结合响应系统,在应用状态改变时, Vue 能够智能地计算出重新渲染组件的最小代价并应用到 DOM 操作上

6.2 插值

6.2.1 文本插值 {{}}

数据绑定最常见的形式就是使用 {{...}}(双大括号)的文本插值

<div id="app">
  <p>{{ message }}</p>
</div>

6.2.2 html插值:输出html

使用 v-html 指令用于输出 html 代码,更新元素的 innerHTML

v-text : 当作纯文本解析,可以解决 {{}} 表达式出现的闪现的问题,更新元素的 textContent


<div id="app">
    <div v-html="message"></div>
</div>
    

<script>
new Vue({
  el: '#app',
  data: {
    message: '<h1>菜鸟教程</h1>'
  }
})
</script>

6.2.3 属性:v-bind绑定

HTML 属性中的值应使用 v-bind 指令进行绑定

v-bind 只能用户绑定属性,它的值是一个 js 表达式,与 {{ }} 里面的语法是一样的;唯一的区别是:{{}}用于标签文本绑定,v-bind 用于标签属性绑定

 
 <div v-bind:class="classname"></div>
 <a :href="'todos/id='+item.id+">百度一下</a>
  

6.2.4 表达式

Vue.js 提供了完全的 JavaScript 表达式支持

<div id="app">
    {{5+5}}<br>
    {{ ok ? 'YES' : 'NO' }}<br>
    {{ message.split('').reverse().join('') }}
    <div v-bind:id="'list-' + id">hello vue</div>
</div>
    
<script>
new Vue({
  el: '#app',
  data: {
    ok: true,
    message: 'RUNOOB',
    id : 1
  }
})
</script>

6.2.5 一次性绑定 v-once

通过使用 v-once 指令,可以执行一次性的插值,当数据发生改变时,插值处的内容不会更新。

<span v-once>这个将不会变化 {{msg}} </span>

6.2.6 v-cloak解决表达式闪现问题

使用表达式 {{}} 的方式,容易出现闪现的问题

  1. 方法一:使用 v-text ,但是这种方式比较麻烦

  2. 方法二:使用 v-cloak 指令(推荐)

    v-cloak 原理:浏览器在解析的过程中,发现具有 v-cloak 的属性就隐藏不显示元素了,所以我们就看不见 {{}} 导致的闪现的问题了,当Vue 解析替换完成后,Vue会自动把 v-cloak 样式移除

// 1.  设置css样式
[v-cloak] {
  display: none;
}

// 2. 设置指令到指定的元素
<div v-cloak>
  {{ message }}
</div>

6.3 用户输入:使用 v-model 指令来实现双向数据绑定

v-model 指令用来在 input、select、textarea、checkbox、radio 等表单控件元素上创建双向数据绑定,根据表单上的值,自动更新绑定的元素的值

双向数据绑定:当数据发生改变, DOM 会自动更新;当表单控件的值发生改变,数据也会自动得到更新

6.4 缩写

Vue.js 为两个最为常用的指令提供了特别的缩写

6.4.1 v-on绑定click事件

<!-- 完整语法 -->
<a v-on:click="doSomething"></a>
<!-- 缩写 -->
<a @click="doSomething"></a>

6.4.2 v-bind绑定属性

<!-- 完整语法 -->
<a v-bind:href="url"></a>
<!-- 缩写 -->
<a :href="url"></a>

7. vue之条件语句

7.1 v-if条件判断

v-if 指令将根据表达式 ok的值(true 或 false )来决定是否插入(渲染或不渲染) 元素;如果表达式的值为false,根本就不插入元素(即在页面中根本就没有这个元素)

<div id="app">
    <p v-if="ok">数据显示了</p>
</div>
    
<script>
new Vue({
  el: '#app',
  data: {
    ok: true
  }
})
</script>

与v-else结合使用

<div id="app">
    <p v-if="ok">数据显示了</p>
    <p v-else>数据不显示</p>
</div>

与v-else-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>
new Vue({
  el: '#app',
  data: {
    type: 'C'
  }
})
</script>

7.2 v-show展示元素

使用 v-show 指令来根据条件展示元素:如果表达式的值是true,就显示元素;否则就不显示;元素会一直存在的,只是显示和隐藏了

<h1 v-show="ok">Hello!</h1>

7.3 v-ifv-show的区别

  • v-show无论条件真假,都渲染到DOM中(页面中元素一直存在),它的显示和隐藏只是元素进行 display='none'display='block'的切换
  • v-if 条件为true,就渲染元素到DOM中;条件为false,页面中就不渲染DOM(是指页面中根本就不存在这个元素)
  • v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建
  • v-if惰性的:如果在初始渲染时条件为假,则什么也不做,—直到条件第一次变为真时,才会开始渲染条件块。
  • 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。
  • 因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好

8. vue之循环语句 v-for

v-for 指令需要以 site in sites 形式的特殊语法, sites 是源数据数组, site 是数组元素迭代的别名

例如绑定渲染一个列表

<div id="app">
  <ol>
    <li v-for="site in sites">
      {{ site.name }}
    </li>
  </ol>
</div>
 
<script>
new Vue({
  el: '#app',
  data: {
    sites: [
      { name: 'Runoob' },
      { name: 'Google' },
      { name: 'Taobao' }
    ]
  }
})
</script>

8.1 迭代对象

1个参数:直接获取value

2个参数:获取value key 值

3个参数:获取value key index值

<div id="app">
  <ul>
    <!-- 1. 一个参数:直接获取value  -->
    <li v-for="value in object">
    {{ value }}
    </li>
    
     <!-- 2. 2个参数:获取value key 值  -->
     <li v-for="(value, key) in object">
          {{ key }} : {{ value }}
    </li>
      
     <!-- 3. 3个参数:获取value  key index值  -->
     <li v-for="(value, key, index) in object">
        {{ index }}. {{ key }} : {{ value }}
     </li>
      
  </ul>
</div>
 
<script>
new Vue({
  el: '#app',
  data: {
    object: {
      name: 'Jack',
      age:18
    }
  }
})
</script>

8.2 迭代整数

<div id="app">
  <ul>
    <li v-for="n in 10">
       {{ n }}
    </li>
  </ul>
</div>

9. vue之样式绑定

class 与 style 是 HTML 元素的属性,用于设置元素的样式,我们可以用 v-bind 来设置样式属性

9.1 class绑定字符串 :class="XXX"

XXX可以是:字符串、对象、数组

<p :class="myClass">xxx是字符串</p>

<p :class="{classA: hasClassA, classB: hasClassB}">xxx是对象</p>

<p :class="['classA', 'classB']">xxx是数组</p>

9.1.1 绑定对象:多个变量

每个class类名后面都是跟一个 布尔值,如果为true,显示相关类;否则不显示类名
<div v-bind:class="{'activeClassName': true,'textDangerClassName':false }"></div>

<div v-bind:class="{ 'activeClassName': isActive,'textDangerClassName':hasError }"></div>

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例 </title>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<style>
.activeClass {
	width: 100px;
	height: 100px;
	background: green;
}
.text-danger {
	background: red;
}
</style>
</head>
<body>
<div id="app">
  <div v-bind:class="{ 'activeClass': isActive,'text-danger':hasError }"></div>
</div>

<script>
new Vue({
  el: '#app',
  data: {
    isActive: true,
    hasError:true
  }
})
</script>
</body>
</html>

9.1.2 绑定一个对象

<!-- classObject是一个对象 -->
<div v-bind:class="classObject"></div>

<script>
new Vue({
  el: '#app',
  data: {
    classObject: {
      active: true,
      'text-danger': true
    }
  }
})
</script>

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例 - 菜鸟教程(runoob.com)</title>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<style>
.active {
	width: 100px;
	height: 100px;
	background: green;
}
.text-danger {
	background: red;
}
</style>
</head>
<body>
<div id="app">
  <div v-bind:class="classObject"></div>
</div>

<script>
new Vue({
  el: '#app',
  data: {
    classObject: {
      active: true,
      'text-danger': true
    }
  }
})
</script>
</body>
</html>

9.1.3 绑定一个数组(比较少用)

<div v-bind:class="[activeClass, errorClass]"></div>

<script>
new Vue({
  el: '#app',
  data: {
    activeClass: 'active',
    errorClass: 'text-danger'
  }
})
</script>

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例 - 菜鸟教程(runoob.com)</title>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<style>
.active {
	width: 100px;
	height: 100px;
	background: green;
}
.text-danger {
	background: red;
}
</style>
</head>
<body>
<div id="app">
	<div v-bind:class="[activeClass, errorClass]"></div>
</div>

<script>
new Vue({
  el: '#app',
  data: {
    activeClass: 'active',
    errorClass: 'text-danger'
  }
})
</script>
</body>
</html>

9.2 style内联样式绑定

9.2.1 直接设置内联样式 : v-bind:style


<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }">Hello</div>

<script>
new Vue({
  el: '#app',
  data: {
    activeColor: 'green',
	fontSize: 30
  }
})
</script>


9.2.2 绑定对象

<div v-bind:style="styleObject">HELLO</div>

<script>
new Vue({
  el: '#app',
  data: {
    // 样式对象  
    styleObject: {
      color: 'green',
      fontSize: '30px'
    }
  }
})
</script>

9.2.3 绑定数组

<div v-bind:style="[baseStyles, overridingStyles]">HELLO</div>

<script>
new Vue({
  el: '#app',
  data: {
    baseStyles: {
      color: 'green',
      fontSize: '30px'
    },
	overridingStyles: {
      'font-weight': 'bold'
    }
  }
})
</script>

10. vue之事件处理器

事件监听可以使用 v-on 指令

v-on:click=sayHello
// 等价于
@click=sayHello

<div id="app">
    
    <!-- 直接js语句计算 -->
    <button v-on:click="counter += 1">增加 1</button>

    <!-- 接收一个定义的方法来调用 -->
    <button v-on:click="greet">Greet</button>
    <button v-on:click="say('hi')">Say hi</button>
    <button v-on:click="say('what')">Say what</button>

</div>
<script src='vue.min.js'></script>
<script>
    new Vue({
        el: '#app',
        data: {
            counter: 0
        },
        methods: {
            // 如果不传参数,默认第一个参数就是 event
            greet: function(event) {
                // `this` 在方法里指当前 Vue 实例
                alert('Hello ' + this.name + '!')
                // `event` 是原生 DOM 事件
                if (event) {
                    alert(event.target.tagName)
                }
            },
            say: function(message) {
                alert(message)
            }
        }
    })
</script>

10.1 自定义事件

// 监听事件
$on(eventName)

// 触发事件
$emit(eventName,optionalPayload)

1. 父组件是使用 props 传递数据给子组件,但是如果子组件要把数据传递给父组件,就要使用自定义事件
2. 我们可以使用 v-on 绑定自定义事件,每个Vue实例都实现了 事件接口,即:
  • 使用 $on(eventName) 监听事件
  • 使用 $emit(eventName,optionalPayload) 触发事件
3. 另外,父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件
4. 如果你想在某个组件的根元素上监听一个原生事件,可以使用 .native 修饰符
<my-component v-on:click.native="doThings"></my-component>

10.2 常用事件修饰符

.prevent : 阻止事件的默认行为, event.preventDefault()

.stop : 停止事件冒泡, event.stopPropagation()

10.3 常用按键修饰符

.keycode : 操作的是某个 keycode 值的键 例如:@keyup.13

.keyName : 操作的某个按键名的键(少部分) 例如:@keyup.enter

11. vue之表单

11.1 简介

在vue.js表单中,可以用 v-model 指令在表单控件元素上创建双向数据绑定

v-model 会根据控件类型自动选取正确的方法来更新元素

11.2 输入框input和textarea

通过使用 v-model 可以实现双向数据绑定

<div id="app">
    <h1>input 元素:</h1>
    <!-- 绑定message1数据 -->
    <input v-model="message1" placeholder="编辑我……">
    <p>消息是: {{ message1 }}</p>

    <h1>textarea 元素:</h1>
    <p style="white-space: pre">{{ message2 }}</p>
    <!-- 绑定message2数据 -->
    <textarea v-model="message2" placeholder="多行文本输入……"></textarea>
</div>

<script src='vue.min.js'></script>

<script>
    new Vue({
        el: '#app',
        data: {
            message1: 'hello vue.js',
            message2: '欢迎使用hello vue.js'
        }
    })
</script>

11.3 复选框checkbox

复选框如果是一个,就设置一个逻辑值就行;如果是多个,就绑定到同一个数组中

	
<div id="app">
    <p>单个复选框:</p>
    <input type="checkbox" v-model="checked">
    <label>{{ checked }}</label>

    <p>多个复选框:</p>
    <input type="checkbox" value="苹果" v-model="checkedNames">
    <label>苹果</label>
    <input type="checkbox" value="香蕉" v-model="checkedNames">
    <label>香蕉</label>
    <input type="checkbox" value="栗子" v-model="checkedNames">
    <label>栗子</label>
    <br>
    <span>选择的值为: {{ checkedNames }}</span>
</div>

<script src='vue.min.js'></script>

<script>
    new Vue({
        el: '#app',
        data: {
            checked: false, // 单个复选框
            checkedNames: []  // 存储多个复选框里面的选值
        }
    })
</script>

11.4 单选按钮radio

<div id="app">
    <input type="radio" value="苹果" v-model="picked">
    <label>苹果</label>
    <br>
    <input type="radio" value="香蕉" v-model="picked">
    <label>香蕉</label>
    <br>
    <span>选中值为: {{ picked }}</span>
</div>

<script src='vue.min.js'></script>

<script>
    new Vue({
        el: '#app',
        data: {
            picked: '苹果'
        }
    })
</script>

11.5 select下拉列表

	
<div id="app">
    <select v-model="selected" name="fruit">
        <option value="">选择一个水果</option>
        <option value="苹果">苹果</option>
        <option value="香蕉">香蕉</option>
        <option value="梨子">梨子</option>
        <option value="芒果">芒果</option>
    </select>

    <div>
        选择的水果是: {{selected}}
    </div>
</div>

<script src='vue.min.js'></script>

<script>
    new Vue({
        el: '#app',
        data: {
            selected: ''
        }
    })
</script>

12. vue之生命周期

12.1 概述

每个Vue实例在被创建时,都要经历一系列的初始化过程,例如:设置数据监听、编译模板、将实例挂载到DOM,并在数据发生变化时,实时更新到视图等,这个过程就是Vue的生命周期

12.2 分类

vue的生命周期大致可以分为:初始化显示、更新显示、销毁死亡

初始化显示
beforeCreate() 
created()
beforeMount() 
mounted()---常用:发送ajax请求,启动定时器等异步任务

更新显示
beforeUpdate() 
updated()

销毁死亡
beforeDestory() --- 常用:一般是做收尾工作,例如清除定时器等
destoryed() 
// 当调用vm.$destory()的时候,当前实例就会被销毁了

12.3 各个阶段介绍

beforeCreate():组件实例化之前,还没有创建vue对象

  • 没有创建vue对象,组件实例化之前
  • 一般可以用于初始化动画

created():组件实例化完成

  • 组件实例化完成,页面没有显示,页面中的dom还未生成
  • 此时会寻找页面中是否存在el属性或mount:如果都没有,那么生命周期直接结束;如果有el属性或mount:如果都没有,那么生命周期直接结束;如果有el属性或mount,会接着查找页面中是否有template,如果没有,生命周期结束
  • 一般用于是获取ajax数据;结束动画

beforeMount():组件挂载之前,页面没有显示,虚拟DOM完成配置

组件挂载之前,页面未显示,但是数据在虚拟DOM中已经配置完成

mounted():组件挂载完成,页面完整显示

beforeUpdate():组件更新之前,页面仍未更新

组件更新之前,页面仍未更新,但是数据在虚拟DOM中已经配置完成

updated():组件更新完成,页面更新完成

组件更新完成,页面完整展示

beforeDestory():组件销毁之前

  • 组件销毁之前调用一次,页面仍未更新,但是数据在虚拟DOM中已经配置完成
  • 一般是做收尾工作,例如清除定时器等

destoryed():组件销毁

13. vue之指令

13.1 简介

  • 指令 (Directives) 是带有 v- 前缀的特殊属性。指令属性的值预期是单个 JavaScript 表达式(v-for 是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
  • 当你不可避免的需要操作 DOM 的时候,可以使用自定义指令来解决
  • 自定义指令的注册分为:全局注册、局部注册
  • 全局注册:在任何组件中都可以使用
  • 局部注册:只能在当前组件使用
  • 在模板中使用自定义指令必须加上 v- 前缀

13.2 自定义指令

13.2.1 注册全局指令

  • 第一个参数:指令的名称,如果是驼峰命名法myFocus,则在使用的时候需要把驼峰转为小写使用 - 连接起来v-my-focus
  • 第二个参数:配置指令的生命钩子函数:
    • el:作用于该指令的DOM对象
    • binding:包含指令相关信息数据的对象
// 注册一个全局指令myFocus,主要用于在页面加载时,获取元素的焦点
Vue.directive('myFocus', {
    inserted: function(el, binding) {
        el.focus();
    }
})

// 在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如这样写
Vue.directive('myFocus', function(el, binding) {
    el.focus();
})


// 使用指令 v-my-focus
<div id="app">
    <input type="text" v-my-focus >
</div>

案例展示:模拟 v-show 实现

	
	// 模拟 v-show 实现,根据值的真假来显示或者隐藏作用该指令的元素
			Vue.directive('my-show', {
				bind(el, binding) {},
				inserted(el, binding) {
					if (binding.value) {
						el.style.display = 'block'
					} else {
						el.style.display = 'none'
					}
				},
				//  update 和 componentUpdated 只有在指令的绑定的值发生更新的时候才会触发调用
				// update 和 componentUpdated 的区别是:
				//    update 中获取的是更新的之前的指令所在的 DOM 内容
				//    componentUpdated 获取的是更新之后的最新 DOM 内容
				update(el, binding) {
					if (binding.value) {
						el.style.display = 'block'
					} else {
						el.style.display = 'none'
					}
				},
				componentUpdated(el, binding) {
					console.log('my-show componentUpdated', el.innerHTML)
				},
				unbind() {
					console.log('my-show unbind')
				}
			})
			

			// 如果只关心 bind 和 update,所以可以这样简写
			Vue.directive('my-show', function(el, binding) {
				if (binding.value) {
					el.style.display = 'block'
				} else {
					el.style.display = 'none'
				}
			})

案例展示: 模拟 v-bind 指令功能

	
// v-bind 作用:动态绑定属性值
Vue.directive('my-bind', {
    bind(el, binding) {
        el.setAttribute(binding.arg, binding.value)
    },
    update(el, binding) {
        el.setAttribute(binding.arg, binding.value)
    }
})

// 很多时候,我们都会在 bind 和 update 中执行相同的代码,可以使用简写的方式
// 直接给一个函数,该函数会被作为 bind 和 update 的时候执行的函数
Vue.directive('my-bind', function(el, binding) {
    el.setAttribute(binding.arg, binding.value)
})

案例展示:模拟v-bind变量和class

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Vue自定义指令 模拟v-bind变量和class</title>
  <style>
    .box {
      background-color: pink;
    }

    .box2 {
      color: red;
    }
  </style>
</head>
<body>
  <div id="app">
    <h1 v-my-bind:title="123">{{ message }}</h1>
    <input type="text" v-model="message">
    <input type="checkbox" v-model="toggleBox"> toggleBox
    <input type="checkbox" v-model="toggleBox2"> toggleBox2
    <div v-my-bind:class="{box: toggleBox, box2: toggleBox2}">
      hello vue
    </div>
  </div>
  <script src="node_modules/vue/dist/vue.js"></script>
  <script>
    // v-bind:class = "{}"
    // v-bind:class = "xxx"
    Vue.directive('my-bind', function (el, binding) {
      if (binding.arg === 'class') {
        for (let key in binding.value) {
          if (binding.value[key]) {
            // 作用类名
            el.classList.add(key)
          } else {
            el.classList.remove(key)
          }
        }
      } else {
        el.setAttribute(binding.arg, binding.value)
      }
    })
    const app = new Vue({
      data: {
        message: 'Hello Vue.js!',
        toggleBox: true,
        toggleBox2: true
      },
      methods: {}
    }).$mount('#app')
  </script>
</body>
</html>

13.2.2 注册局部指令

directives : {
      'my-directive' : function(el, binding) {
          el.innerHTML = binding.value.toUpperCase()
      }
    }
    

注意:指令名不可以大写 类似这种方式:caseName

13.2.3 vue常见的内置指令

  • v-text:更新元素的textContent
  • v-html:更新元素的innerHTML
  • v-if:如果为true,当前标签输出到页面
  • v-else:如果为false,当前标签输出到页面
  • v-show:通过控制display样式来显示/隐藏元素
  • v-for:遍历数组/对象
  • v-on:绑定事件监听,一般是简写为@
  • v-bind:强制绑定解析表达式,简写为:
  • v-model:双向数据绑定
  • ref:指定唯一标识(pCont),vue对象通过refs属性访问这个元素对象this.refs属性访问这个元素对象 this.refs.pCont.textContent
  • v-cloak:防止闪现,一般是与css配合 [v-cloak]{display:none}
  • v-pre : 告诉Vue不要解析该节点及其内部节点;可以提高vue的解析速度,避免非必要的节点解析

案例展示

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>自定义指令</title>
		<meta name="keywords" content="关键字" />
		<meta name="description" content="描述" />
		<style>
			/* 防止闪现 */
			[v-cloak] {
				display: none;
			}
		</style>
	</head>
	<body>
		<div id="demo" style="width: 1200px;margin: 0 auto">
			<p>页面载入时,input自动获取焦点</p>
			<input type="text" v-focus placeholder="页面载入时,自动获取焦点">

			<p v-upper-case-text="msg">转换为大写的字母:{{msg}}</p>
			<p v-lower-case-text="msg">转换为小写的字母:{{msg}}</p>

			<!--ref设置唯一标识-->
			<h2 ref="pCont">通过v-text获取内容数据</h2>
			<button @click="getCont">点击获取内容数据</button>

			<!--
					v-clack防止闪现
					第一次加载的时候,页面是显示{{msg}},需要等待解析完毕再显示相应的内容
					如果加上v-cloak指令,那么他就会在解析完毕以后才会显示相应的内容
    -->
			<h2 v-cloak>{{msg}}</h2>
		</div>
		<script type="text/javascript" src="lib/vue.js"></script>
		<script>
			/*
			 * 注意:指令名不可以大写 类似这种方式:caseName
			 * 注册全局指令
			 * Vue.directive("my-directive-name",function(el,binding){
			 *       el.innerHTML=binding.value.toupperCase()
			 * })
			 *
			 * 注册局部指令
			 * directives:{
			 *   'my-directive-name':function(el,binding){
			 *        el.innerHTML=binding.value.toupperCase()
			 *   }
			 * }
			 *
			 * 使用指令
			 * my-directive-name=XXX
			 *
			 * */

			//自定义一个全局指令,获取元素的焦点
			Vue.directive('focus', {
				// 被绑定元素插入父节点时调用
				inserted: function(el) {
					el.focus(); //获取焦点
					console.log('inserted');
				},
				// 只调用一次,指令第一次绑定到元素时调用(经常用于初始化动作)
				bind: function(el, binding) {
					console.log('bind');
				},
				// 被绑定元素所在模板完成一次更新时调用,而不论绑定时是否变化
				update: function() {
					console.log('update');
				},
				// 被绑定元素的所在模板完成一次更新周期时调用
				componentUpdated: function() {
					console.log('componentUpdated');
				},
				// 只调用一次,指令与元素解绑时调用
				unbind: function() {
					console.log('unbind');
				}
			});
			// 大写(这是一种简写的方式)
			Vue.directive('upper-case-text', function(el, binding) {
				el.textContent = binding.value.toUpperCase();
			});

			var vm = new Vue({
				el: '#demo',
				data: {
					msg: 'Hello World'
				},
				methods: {
					getCont() {
						alert(this.$refs.pCont.textContent);
					}
				},
				directives: {
					'lower-case-text': {
						bind: function(el, binding) {
							el.textContent = binding.value.toLowerCase();
						}
					},
				}
			});
		</script>
	</body>
</html>

13.3 指令之钩子函数和

13.3.1 钩子函数

指令定义函数提供了几个钩子函数(可选)

bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作;另外此时是获取不到父元素的

inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中);此时是可以获取父元素的

bind 和 inserted 的相同之处是一上来都执行一次,以后再也不会执行;异同之处在于,bind 拿不到父元素,inserted 可以拿到父元素

update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新

componentUpdated: 被绑定元素所在模板完成一次更新周期时调用

update 和 componentUpdated 只有在指令绑定的值发生更新的时候才会触发调用;

update 和 componentUpdated 的区别是:

​ update 中获取的是更新的之前的指令所在的 DOM 内容,update 拿到的是数据改变视图之前的视图内容 ​ componentUpdated 获取的是更新之后的最新 DOM 内容,componentUpdated 拿到的是数据改变视图之后的视图内容

unbind: 只调用一次, 指令与元素解绑时调用;一般是做一些收尾工作

13.3.2 钩子函数参数

el: 指令所绑定的元素,可以用来直接操作 DOM

binding: 一个对象,包含以下属性:

  • name: 指令名,不包括 v- 前缀。
  • value: 指令的绑定值, 例如: v-my-directive="1 + 1", value 的值是 2
  • oldValue: 指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
  • expression: 绑定值的表达式或变量名。 例如 v-my-directive="1 + 1" , expression 的值是 "1 + 1"
  • arg: 传给指令的参数。例如 v-my-directive:foo, arg 的值是 "foo"
  • modifiers: 一个包含修饰符的对象。 例如: v-my-directive.foo.bar, 修饰符对象 modifiers 的值是 { foo: true, bar: true

vnode: Vue 编译生成的虚拟节点

oldVnode: 上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用

<div id="app"  v-runoob:hello.a.b="message">
</div>
 
<script>
Vue.directive('runoob', {
  bind: function (el, binding, vnode) {
    var s = JSON.stringify
    el.innerHTML =
      'name: '       + s(binding.name) + '<br>' +
      'value: '      + s(binding.value) + '<br>' +
      'expression: ' + s(binding.expression) + '<br>' +
      'argument: '   + s(binding.arg) + '<br>' +
      'modifiers: '  + s(binding.modifiers) + '<br>' +
      'vnode keys: ' + Object.keys(vnode).join(', ')
  }
})
new Vue({
  el: '#app',
  data: {
    message: 'hello vue.js'
  }
})
</script>

14. vue之过渡 & 动画

14.1 介绍

  • 利用vue去操控css的transition/animation动画
  • vue会给目标元素自动添加特定的class
  • 过渡的常见类名
XXX-enter-active:指定显示的trasition
XXX-leave-active:指定隐藏的trasition
XXX-leave-to/XXX-enter:指定隐藏时的样式

XXX-enter 显示之前
XXX-enter-to 显示之后
XXX-enter-active 显示过程中

XXX-leave 隐藏之前
XXX-leave-to 隐藏之后
XXX-leave-active 隐藏过程中
 

14.2 模板

使用<transition name='xxx'></transition> 包含带动画的标签

14.3 css样式

.fade-enter-active: 进入过程, 指定进入的transition
.fade-leave-active: 离开过程, 指定离开的transition
.xxx-enter, .xxx-leave-to: 指定隐藏的样式

14.4 案例展示

14.4.1 样式文件

//指定进入、离开过程的样式
    .xxx-enter-active, .xxx-leave-active {
      transition: opacity .5s
    }
    //指定隐藏的样式
    .xxx-enter, .xxx-leave-to {
      opacity: 0
    }
    

14.4.2 html文件

<transition name="xxx">
	<p v-if="show">hello</p>
</transition>
    

15. vue之混入

混入 (mixins)定义了一部分可复用的方法或者计算属性

混入对象可以包含任意组件的选项

当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项

案例展示

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例</title>
<script src="https://cdn.staticfile.org/vue/2.4.2/vue.min.js"></script>
</head>
<body>
<div id = "databinding"></div>
    
<script type = "text/javascript">
var vm = new Vue({
	el: '#databinding',
	data: {
	},
	methods : {
	},
});
    
// 定义一个混入对象
var myMixin = {
	created: function () {
		this.startmixin()
	},
	methods: {
		startmixin: function () {
			document.write("欢迎来到混入实例");
		}
	}
};
    
<!-- 声明混入对象 Vue.extend-->    
var Component = Vue.extend({
	mixins: [myMixin]
})
    
var component = new Component();
    
</script>
</body>
</html>

15.1 选项合并

当组件和混入对象含有同名选项时,这些选项将以恰当的方式混合

比如,数据对象在内部会进行浅合并 (一层属性深度),在和组件的数据发生冲突时以组件数据优先

// Vue 实例与混入对象包含了相同的方法
var mixin = {
    created: function () {
        document.write('混入调用' + '<br>')
    }
}
new Vue({
        mixins: [mixin],  设置混入
        created: function () {
        document.write('组件调用' + '<br>')
    }
});
返回结果: 
	混入调用
	组件调用

15.2 Vue实例优先级高

如果 methods 选项中有相同的函数名,则 Vue 实例优先级会较高

// 混入对象
var mixin = {
    methods: {
        hellworld: function () {
            document.write('HelloWorld 方法' + '<br>');
        },
        samemethod: function () {
            document.write('Mixin:相同方法名' + '<br>');
        }
    }
};
var vm = new Vue({
    mixins: [mixin],
    methods: {
        start: function () {
            document.write('start 方法' + '<br>');
        },
        samemethod: function () {
            document.write('Main:相同方法名' + '<br>');
        }
    }
});
vm.hellworld();
vm.start();
vm.samemethod();

输出结果
HelloWorld 方法
start 方法
Main:相同方法名


15.3 全局混入

注意使用! 一旦使用全局混入对象,将会影响到 所有 之后创建的 Vue 实例。

使用恰当时,可以为自定义对象注入处理逻辑。

// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
  created: function () {
    var myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})
 
new Vue({
  myOption: 'hello!'
})
// => "hello!"

谨慎使用全局混入对象,因为会影响到每个单独创建的 Vue 实例 (包括第三方模板)

16. vue之过滤器

vue.js运行我们自定义一些过滤器,一般会被用作一些常见的文本格式化,有管道符指示

过滤器的主要功能是对要操作的数据进行格式化设置

<!-- 在 两个大括号 {{}}  中 -->
{{ message | capitalize }}

<!-- 在 v-bind 指令中 -->
<div v-bind:id="rawId | formatId"></div>

定义全局过滤器

  
  /*
  * 定义全局过滤器
  *   Vue.filter(filterName,function(value[,arg1,arg2,...]){
  *           //进行数据处理
  *            return newValue
  *   })
  *
  * 使用过滤器
  *   <p>{{maData|filterName}}</p>
  *   <p>{{maData|filterName(arg)}}</p>
  
  * */
  

案例展示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>过滤器-</title>
  <script type="text/javascript" src="lib/vue.js"></script>
  <script type="text/javascript" src="lib/moment.min.js"></script>
</head>
<body>
<div id="test">
  <h2>格式化日期</h2>
  <p>年-月-日 时:分:秒==={{date | dateString}}</p>
  <p>年-月-日==={{date | dateString('YYYY-MM-DD')}}</p>
  <p>时:分:秒==={{date | dateString('HH:mm:ss')}}</p>
</div>

<script>
  // 自定义一个过滤器
  Vue.filter('dateString', function (value, format) {
    // value的值就是this.date的值 {{date|dateString}}传值
    //format是参数
    //moment().format('MMMM Do YYYY, h:mm:ss a') 如果不传参,默认是对当前时间格式化
    return format ? moment(value).format(format) : moment(value).format('YYYY-MM-DD HH:mm:ss');

    //或者是下面的写法 更加简洁
    //return moment(value).format(format||'YYYY-MM-DD HH:mm:ss');
  });

  // //使用ES6语法,设置默认值
  // Vue.filter('dateString', function (value, format='YYYY-MM-DD HH:mm:ss') {
  //     return  moment(value).format(format);
  // });


  new Vue({
    el: '#test',
    data: {
      date: new Date()
    }
  })
</script>
</body>
</html>

过滤器函数接受表达式的值作为第一个参数

<div id="app">
  {{ message | capitalize }}
</div>
    
<script>
new Vue({
  el: '#app',
  data: {
    message: 'runoob'
  },
  filters: {
    capitalize: function (value) {
      if (!value) return ''
      value = value.toString()
      return value.charAt(0).toUpperCase() + value.slice(1)
    }
  }
})
</script>

17. 数据代理

17.1 定义

通过一个对象代理对另一个对象中属性的操作(读/写):例如可以通过vm对象来代理data对象中所有属性的操作

好处:可以更方便的操作data中的数据

17.2 实现原理

2.1 通过Object.defineProperty()给vm添加与data对象的属性对应的属性描述符

2.2 所有添加的属性都包含getter/setter

2.3 在getter/setter内部去操作data中对应的属性数据

18. 模板解析 compile.js

模板解析的关键对象: compile对象

18.1. 基本流程

1.1 将el的所有子节点取出, 添加到一个新建的文档fragment对象中

1.2 对fragment中的所有层次子节点递归进行编译解析处理

* 对表达式文本节点进行解析
* 对元素节点的指令属性进行解析
* 事件指令解析
* 一般指令解析

18.2 解析表达式文本节点:textNode.textContent = value

  • 根据正则对象得到匹配出的表达式字符串: 子匹配/RegExp.$1
  • 从data中取出表达式对应的属性值
  • 将属性值设置为文本节点的textContent

18.3 事件指令解析: elementNode.addEventListener(事件名, 回调函数.bind(vm))

  • v-on:click="test"
  • 从指令名中取出事件名
  • 根据指令的值(表达式)从methods中得到对应的事件处理函数对象
  • 给当前元素节点绑定指定事件名和回调函数的dom事件监听
  • 指令解析完后, 移除此指令属性

18.4 一般指令解析: elementNode.xxx = value

  • 得到指令名和指令值(表达式)
  • 从data中根据表达式得到对应的值
  • 根据指令名确定需要操作元素节点的什么属性
    • v-text---textContent属性
    • v-html---innerHTML属性
    • v-class--className属性
  • 将得到的表达式的值设置到对应的属性上
  • 移除元素的指令属性

19. 数据绑定

1. 数据绑定 (model==>View):

一旦更新了data中的某个属性数据的值,所有界面上直接或间接使用了此属性的节点都会更新

2. 数据劫持

数据劫持是vue中 用来实现数据绑定 的一种技术 主要实现原理:

通过defineProperty()来监视data中所有属性(任意层次)数据的变化,一旦变化就去更新界面

3. 四个重要对象

3.1 Observer(观察者)

用来对data所有属性数据进行劫持的构造函数

给data中所有属性重新定义属性描述(get/set)

为data中的每个属性创建对应的dep对象

3.2 Dep(Depend)(依赖)

data中的每个属性(所有层次)都对应一个dep对象

创建的时机:

  • 在初始化define data中各个属性时创建对应的dep对象
  • 在data中的某个属性值被设置为新的对象时

对象的结构

{
  id, // 每个dep都有一个唯一的id
  subs //包含n个对应watcher的数组(subscribes的简写)
}

subs属性说明:
当一个watcher被创建时, 内部会将当前watcher对象添加到对应的dep对象的subs中
当此data属性的值发生改变时, 所有subs中的watcher都会收到更新的通知, 从而最终更新对应的界面

3.3 Compile

用来解析模板页面的对象的构造函数(一个实例)

利用compile对象解析模板页面

每解析一个表达式(非事件指令)都会创建一个对应的watcher对象, 并建立watcher与dep的关系

complie与watcher关系: 一对多的关系

3.4 Watcher

模板中每个非事件指令或表达式都对应一个watcher对象

可以监视当前表达式数据的变化

创建的时机: 在初始化编译模板时

对象的组成:

{
  vm,  //vm对象
  exp, //对应指令的表达式
  cb, //当表达式所对应的数据发生改变的回调函数
  value, //表达式当前的值
  depIds //表达式中各级属性所对应的dep对象的集合对象
          //属性名为dep的id, 属性值为dep
}

3.5 总结:dep与watcher的关系: 多对多

一个data中的属性对应对应一个dep,一个dep中可能包含多个watcher

模板中一个非事件表达式对应一个watcher,一个watcher中可能包含多个dep(表达式中包含了几个data属性)

数据绑定的2个核心技术:defineProperty()和消息订阅与发布

4. 双向数据绑定

向数据绑定是建立在单向数据绑定(model==>View)的基础之上的

双向数据绑定的实现流程:

  • 在解析v-model指令时, 给当前元素添加input监听
  • 当input的value发生改变时, 将最新的值赋值给当前表达式所对应的data属性

20. 项目的打包与发布

20.1 打包:会自动打包,并生成dist目录文件

npm run build

20.2 发布方法一:(使用静态服务器工具包)

npm install -g serve
serve dist
访问:http://localhost:8080

20.3 发布方法二:(使用动态web服务器tomcat)

* 修改配置文件 webpack.prod.conf.js
    output{
       publicPath:'XXX'  XXX是打包目录的名称
    } 
* 重新打包 cnpm run build
* 修改dist文件夹为项目名称 XXX
* 将XXX 拷贝到运行的tomcat的webapps目录下面
* 访问:http://localhost:8080/XXX/