使用VueJS(2.X)
准备
什么是Vue.js
Vue.js 是目前最火的一个前端框架,React是最流行的一个前端框架(React除了开发网站,还可以开发手机App, Vue语法也是可以用于进行手机App开发的,需要借助于Weex)
Vue.js 是前端的主流框架之一,和Angular.js、React.js 一起,并成为前端三大主流框架!
Vue.js 是一套构建用户界面的框架,只关注视图层,它不仅易于上手,还便于与第三方库或既有项目整合。(Vue有配套的第三方类库,可以整合起来做大型项目的开发)
前端的主要工作?主要负责MVC中的V这一层;主要工作就是和界面打交道,来制作前端页面效果;
为什么要学习流行框架
- 企业为了提高开发效率:在企业中,时间就是效率,效率就是金钱;
- 企业中,使用框架,能够提高开发的效率;
提高开发效率的发展历程:原生JS -> Jquery之类的类库 -> 前端模板引擎 -> Angular.js / Vue.js(能够帮助我们减少不必要的DOM操作;提高渲染效率;双向数据绑定的概念【通过框架提供的指令,我们前端程序员只需要关心数据的业务逻辑,不再关心DOM是如何渲染的了】)
在Vue中,一个核心的概念,就是让用户不再操作DOM元素,解放了用户的双手,让程序员可以更多的时间去关注业务逻辑;
增强自己就业时候的竞争力, 人无我有,人有我优
框架和库的区别
框架:是一套完整的解决方案;对项目的侵入性较大,项目如果需要更换框架,则需要重新架构整个项目。
- node 中的 express;
库(插件):提供某一个小功能,对项目的侵入性较小,如果某个库无法完成某些需求,可以很容易切换到其它库实现需求。
- 从Jquery 切换到 Zepto
- 从 EJS 切换到 art-template
MVC 与MV VM
MVC 是后端的分层开发概念;
MVVM是前端视图层的概念,主要关注于 视图层分离,也就是说:MVVM把前端的视图层,分为了 三部分 Model, View , VM ViewModel
为什么有了MVC还要有MVVM
通过vm对象来代理data对象中所有属性的操作
- 好处: 更方便的操作data中的数据
基本实现流程
- 通过Object.defineProperty()给vm添加与data对象的属性对应的属性描述符
- 所有添加的属性都包含getter/setter
- 在getter/setter内部去操作data中对应的属性数据
基本使用
代码结构
基本结构如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<!-- 1. 导入Vue的包 -->
<script src="./lib/vue-2.6.10.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.10/vue.min.js"></script>
</head>
<body>
<!-- 将来 new 的Vue实例,会控制这个 元素中的所有内容 -->
<!-- Vue 实例所控制的这个元素区域,就是我们的 V -->
<div id="app">
<p>{{ msg }}</p>
</div>
<script>
// 2. 创建一个Vue的实例
// 当我们导入包之后,在浏览器的内存中,就多了一个 Vue 构造函数
// 注意:我们 new 出来的这个 vm 对象,就是我们 MVVM中的 VM调度者
var vm = new Vue({
el: "#app", // 表示,当前我们 new 的这个 Vue 实例,要控制页面上的哪个区域
// 这里的 data 就是 MVVM中的 M,专门用来保存 每个页面的数据的
data: {
// data 属性中,存放的是 el 中要用到的数据
msg: "欢迎学习Vue", // 通过 Vue 提供的指令,很方便的就能把数据渲染到页面上,程序员不再手动操作DOM元素了【前端的Vue之类的框架,不提倡我们去手动操作DOM元素了】
},
});
</script>
</body>
</html>
文本展示
插值表达式: 刷新页面过程中会有表达式闪烁的问题
1
2<!-- {{msg2}}会先被渲染出来后替换掉 -->
<div>{{msg2}}</div>v-cloak + 插值表达式解决闪烁问题
1
<p v-cloak>++++++++ {{ msg }} ----------</p>
v-text
:默认 v-text 是没有闪烁问题的1
2<!-- v-text会覆盖元素中原本的内容,但是 插值表达式 只会替换自己的这个占位符,不会把整个元素的内容清空 -->
<h4 v-text="msg">==================</h4>v-html
: 会将数据中的标签进行渲染1
<div v-html="msg2">1212112</div>
属性/事件绑定
v-bind
是 Vue中,提供的用于绑定属性的指令。v-bind
: 指令可以被简写为:要绑定的属性
v-bind
中,可以写合法的JS表达式Vue 中提供了
v-on:
事件绑定机制 ,可以简写为@事件名
1
2
3
4
5
6
7
8
9
10<input type="button" value="按钮" v-bind:title="mytitle + '123'" />
<!-- v-bind Vue提供的属性绑定机制 缩写是 : -->
<input
type="button"
value="按钮"
:title="mytitle + '123'"
v-on:click="alert('hello')"
/>
<!-- v-on Vue提供的事件绑定机制 缩写是 @ -->
<input *type*="button" *value*="按钮" @click="show" />跑马灯效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<!-- 1. 导入Vue包 -->
<script src="./lib/vue-2.4.0.js"></script>
</head>
<body>
<!-- 2. 创建一个要控制的区域 -->
<div id="app">
<input type="button" value="浪起来" @click="lang" />
<input type="button" value="低调" @click="stop" />
<h4>{{ msg }}</h4>
</div>
<script>
// 注意:在 VM实例中,如果想要获取 data 上的数据,或者 想要调用 methods 中的 方法,必须通过 this.数据属性名 或 this.方法名 来进行访问,这里的this,就表示 我们 new 出来的 VM 实例对象
var vm = new Vue({
el: "#app",
data: {
msg: "猥琐发育,别浪~~!",
intervalId: null, // 在data上定义 定时器Id
},
methods: {
lang() {
// console.log(this.msg)
// 获取到头的第一个字符
// this
if (this.intervalId != null) return;
this.intervalId = setInterval(() => {
var start = this.msg.substring(0, 1);
// 获取到 后面的所有字符
var end = this.msg.substring(1);
// 重新拼接得到新的字符串,并赋值给 this.msg
this.msg = end + start;
}, 400);
// 注意: VM实例,会监听自己身上 data 中所有数据的改变,只要数据一发生变化,就会自动把 最新的数据,从data 上同步到页面中去;【好处:程序员只需要关心数据,不需要考虑如何重新渲染DOM页面】
},
stop() {
// 停止定时器
clearInterval(this.intervalId);
// 每当清除了定时器之后,需要重新把 intervalId 置为 null
this.intervalId = null;
},
},
});
// 分析:
// 1. 给 【浪起来】 按钮,绑定一个点击事件 v-on @
// 2. 在按钮的事件处理函数中,写相关的业务逻辑代码:拿到 msg 字符串,然后 调用 字符串的 substring 来进行字符串的截取操作,把 第一个字符截取出来,放到最后一个位置即可;
// 3. 为了实现点击下按钮,自动截取的功能,需要把 2 步骤中的代码,放到一个定时器中去;
</script>
</body>
</html>
事件修饰符:
.stop 阻止冒泡
.prevent 阻止默认事件
.capture 添加事件侦听器时使用事件捕获模式
.self 只当事件在该元素本身(比如不是子元素)触发时触发回调
.once 事件只触发一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64<body>
<div id="app">
<!-- 使用 .stop 阻止冒泡 -->
<div class="inner" @click="div1Handler">
<input type="button" value="戳他" @click.stop="btnHandler" />
</div>
<!-- 使用 .prevent 阻止默认行为 -->
<a href="http://www.baidu.com" @click.prevent="linkClick"
>有问题,先去百度</a
>
<!-- 使用 .capture 实现捕获触发事件的机制 -->
<div class="inner" @click.capture="div1Handler">
<input type="button" value="戳他" @click="btnHandler" />
</div>
<!-- 使用 .self 实现只有点击当前元素时候,才会触发事件处理函数 -->
<div class="inner" @click="div1Handler">
<input type="button" value="戳他" @click="btnHandler" />
</div>
<!-- 使用 .once 只触发一次事件处理函数 -->
<a href="http://www.baidu.com" @click.prevent.once="linkClick"
>有问题,先去百度</a
>
<!-- 演示: .stop 和 .self 的区别 -->
<div class="outer" @click="div2Handler">
<div class="inner" @click="div1Handler">
<input type="button" value="戳他" @click.stop="btnHandler" />
</div>
</div>
<!-- .self 只会阻止自己身上冒泡行为的触发,并不会真正阻止 冒泡的行为 -->
<div class="outer" @click="div2Handler">
<div class="inner" @click.self="div1Handler">
<input type="button" value="戳他" @click="btnHandler" />
</div>
</div>
</div>
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {},
methods: {
div1Handler() {
console.log("这是触发了 inner div 的点击事件");
},
btnHandler() {
console.log("这是触发了 btn 按钮 的点击事件");
},
linkClick() {
console.log("触发了连接的点击事件");
},
div2Handler() {
console.log("这是触发了 outer div 的点击事件");
},
},
});
</script>
</body>
双向数据绑定
v-bind
只能实现数据的单向绑定,从 M 自动绑定到 V, 无法实现数据的双向绑定1
<input type="text" v-bind:value="msg" style="width:100%;" />
使用 v-model 指令,可以实现 表单元素和 Model 中数据的双向数据绑定
1
2<!-- 注意: v-model 只能运用在 表单元素中 -->
<!-- input(radio, text, address, email....) select checkbox textarea -->简易计算器案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<script src="./lib/vue-2.4.0.js"></script>
</head>
<body>
<div id="app">
<input type="text" v-model="n1" />
<select v-model="opt">
<option value="+">+</option>
<option value="-">-</option>
<option value="*">*</option>
<option value="/">/</option>
</select>
<input type="text" v-model="n2" />
<input type="button" value="=" @click="calc" />
<input type="text" v-model="result" />
</div>
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {
n1: 0,
n2: 0,
result: 0,
opt: "+",
},
methods: {
calc() {
// 计算器算数的方法
// 逻辑:
/* switch (this.opt) {
case '+':
this.result = parseInt(this.n1) + parseInt(this.n2)
break;
case '-':
this.result = parseInt(this.n1) - parseInt(this.n2)
break;
case '*':
this.result = parseInt(this.n1) * parseInt(this.n2)
break;
case '/':
this.result = parseInt(this.n1) / parseInt(this.n2)
break;
} */
// 注意:这是投机取巧的方式,正式开发中,尽量少用
var codeStr =
"parseInt(this.n1) " + this.opt + " parseInt(this.n2)";
this.result = eval(codeStr);
},
},
});
</script>
</body>
</html>
使用class样式
示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50<head>
<script src="./lib/vue-2.4.0.js"></script>
<style>
.red {
color: red;
}
.thin {
font-weight: 200;
}
.italic {
font-style: italic;
}
.active {
letter-spacing: 0.5em;
}
</style>
</head>
<div id="app">
<h1 class="red thin">这是一个很大很大的H1,大到你无法想象!!!</h1>
<!-- 第一种使用方式,直接传递一个数组,注意: 这里的 class 需要使用 v-bind 做数据绑定 -->
<h1 :class="['thin', 'italic']">
这是一个很大很大的H1,大到你无法想象!!!
</h1>
<!-- 在数组中使用三元表达式 -->
<h1 :class="['thin', 'italic', flag?'active':'']">
这是一个很大很大的H1,大到你无法想象!!!
</h1>
<!-- 在数组中使用 对象来代替三元表达式,提高代码的可读性 -->
<h1 :class="['thin', 'italic', {'active':flag} ]">
这是一个很大很大的H1,大到你无法想象!!!
</h1>
<!-- 在为 class 使用 v-bind 绑定 对象的时候,对象的属性是类名,由于 对象的属性可带引号,也可不带引号,所以 这里我没写引号; 属性的值 是一个标识符 -->
<h1 :class="classObj">这是一个很大很大的H1,大到你无法想象!!!</h1>
</div>
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {
flag: true,
classObj: { red: true, thin: true, italic: false, active: false },
},
methods: {},
});
</script>
数组
1
<h1 :class="['red', 'thin']">这是一个邪恶的H1</h1>
数组中使用三元表达式
1
<h1 :class="['red', 'thin', isactive?'active':'']">这是一个邪恶的H1</h1>
数组中嵌套对象
class:boolean
1
<h1 :class="['red', 'thin', {'active': isactive}]">这是一个邪恶的H1</h1>
直接使用对象
1
2
3<h1 :class="{red:true, italic:true, active:true, thin:true}">
这是一个邪恶的H1
</h1>
使用内联样式
直接在元素上通过
:style
的形式,书写样式对象1
<h1 :style="{color: 'red', 'font-size': '40px'}">这是一个善良的H1</h1>
将样式对象,定义到
data
中,并直接引用到:style
中1
2
3
4
5data: { h1StyleObj: { color: 'red', 'font-size': '40px', 'font-weight': '200'
} }
<!-- 在元素中,通过属性绑定的形式,将样式对象应用到元素中: -->
<h1 :style="h1StyleObj">这是一个善良的H1</h1>在
:style
中通过数组,引用多个data
上的样式对象1
2
3
4
5data: { h1StyleObj: { color: 'red', 'font-size': '40px', 'font-weight': '200'
}, h1StyleObj2: { fontStyle: 'italic' } }
<!-- 在元素中,通过属性绑定的形式,将样式对象应用到元素中: -->
<h1 :style="[h1StyleObj, h1StyleObj2]">这是一个善良的H1</h1>
迭代指令v-for
迭代普通数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14<ul>
<li v-for="(item, i) in list">索引值:{{i}} --- 每一项:{{item}}></li>
</ul>
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {
list: [1, 2, 3, 4, 5, 6],
},
methods: {},
});
</script>迭代对象数组,索引从0开始
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<!-- 循环遍历对象身上的属性 -->
<div id="app">
<p v-for="(user, i) in list">
Id:{{ user.id }} --- 名字:{{ user.name }} --- 索引:{{i}}
</p>
</div>
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {
list: [
{ id: 1, name: "zs1" },
{ id: 2, name: "zs2" },
{ id: 3, name: "zs3" },
{ id: 4, name: "zs4" },
],
},
methods: {},
});
</script>迭代对象,索引从0开始
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<div id="app">
<!-- 注意:在遍历对象身上的键值对的时候, 除了 有 val key ,在第三个位置还有 一个 索引 -->
<p v-for="(val, key, i) in user">
值是: {{ val }} --- 键是: {{key}} -- 索引: {{i}}
</p>
</div>
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {
user: {
id: 1,
name: "托尼·屎大颗",
gender: "男",
},
},
methods: {},
});
</script>迭代数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14<div id="app">
<!-- in 后面我们放过 普通数组,对象数组,对象, 还可以放数字 -->
<!-- 注意:如果使用 v-for 迭代数字的话,前面的 count 值从 1 开始 -->
<p v-for="count in 10">这是第 {{ count }} 次循环</p>
</div>
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {},
methods: {},
});
</script>
v-for与 key
2.2.0+ 的版本里,当在组件中使用 v-for 时,key 现在是必须的。
当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用 “就地复用” 策略。如果数据项的顺序被改变,Vue将不是移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。
注意: v-for 循环的时候,key 属性只能使用 number获取string
注意: key 在使用的时候,必须使用 v-bind 属性绑定的形式,指定 key 的值
1
2
3<p v-for="item in list" :key="item.id">
<input type="checkbox" />{{item.id}} --- {{item.name}}
</p>
v-if和v-show
一般来说,v-if 有更高的切换消耗,v-show 有更高的初始渲染消耗。
因此,如果需要频繁切换 v-show 较好,如果在运行时条件不大可能改变 v-if 较好。
1
2
3
4
5
6
7<!-- v-if 的特点:每次都会重新删除或创建元素 -->
<!-- v-show 的特点: 每次不会重新进行DOM的删除和创建操作,只是切换了元素的 display:none 样式 -->
<!-- 如果元素涉及到频繁的切换,最好不要使用 v-if, 而是推荐使用 v-show -->
<!-- 如果元素可能永远也不会被显示出来被用户看到,则推荐使用 v-if -->
<h3 v-if="flag">这是用v-if控制的元素</h3>
<h3 v-show="flag">这是用v-show控制的元素</h3>
CRUD案例
添加操作
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<div class="panel-body form-inline">
<label>
Id:
<input type="text" class="form-control" v-model="id" />
</label>
<label>
Name:
<input type="text" class="form-control" v-model="name" />
</label>
<!-- 在Vue中,使用事件绑定机制,为元素指定处理函数的时候,如果加了小括号,就可以给函数传参了 -->
<input type="button" value="添加" class="btn btn-primary" @click="add()" />
<label>
搜索名称关键字:
<input type="text" class="form-control" v-model="keywords" />
</label>
</div>脚本如下
1
2
3
4
5add() {
var car = { id: this.id, name: this.name, ctime: new Date() }
this.list.push(car)
this.id = this.name = ''
}
删除操作
脚本如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18del(id) {
/* this.list.some((item, i) => {
if (item.id == id) {
this.list.splice(i, 1)
// 在 数组的 some 方法中,如果 return true,就会立即终止这个数组的后续循环
return true;
}
}) */
var index = this.list.findIndex(item => {
if (item.id == id) {
return true;
}
})
// console.log(index)
this.list.splice(index, 1)
}
根据条件查找
1.x 版本中的filterBy指令,在2.x中已经被废除:filterBy - 指令
1
2
3
4
5
6
7
8<tr v-for="item in list | filterBy searchName in 'name'">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.ctime}}</td>
<td>
<a href="#" @click.prevent="del(item.id)">删除</a>
</td>
</tr>在2.x版本中手动实现筛选的方式:
筛选框绑定到 VM 实例中的
searchName
属性:1
2<hr />
输入筛选名称: <input type="text" v-model="searchName" />在使用
v-for
指令循环每一行数据的时候,不再直接item in list
,而是in
一个 过滤的methods 方法,同时,把过滤条件searchName
传递进去:1
2
3
4
5
6
7
8
9
10<tbody>
<tr v-for="item in search(searchName)">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.ctime}}</td>
<td>
<a href="#" @click.prevent="del(item.id)">删除</a>
</td>
</tr>
</tbody>search
过滤方法中,使用 数组的filter
方法进行过滤:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20search(name) {
this.list.forEach(item => {
if (item.name.indexOf(keywords) != -1) {
newList.push(item)
}
})
return newList
// 注意: forEach some filter findIndex 这些都属于数组的新方法,
// 都会对数组中的每一项,进行遍历,执行相关的操作;
return this.list.filter(item => {
// 注意 : ES6中,为字符串提供了一个新方法,叫做 String.prototype.includes('要包含的字符串')
// 如果包含,则返回 true ,否则返回 false
// contain
if (item.name.includes(keywords)) {
return item
}
})
}
文本格式化
概念:Vue.js 允许你自定义过滤器,可被用作一些常见的文本格式化。过滤器可以用在两个地方:mustache 插值和 v-bind 表达式。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符指示;
注意:当有局部和全局两个名称相同的过滤器时候,会以就近原则进行调用,即:局部过滤器优先于全局过滤器被调用!
Vue.filter('过滤器的名称', function(){})
:过滤器中的 function第一个参数,已经被规定死了,永远都是 过滤器 管道符前面 传递过来的数据
1
2
3Vue.filter('过滤器的名称', function (data) {
return data + '123'
})
私有过滤器
HTML元素:
1
<td>{{item.ctime | dataFormat('yyyy-mm-dd')}}</td>
私有
filters
定义方式:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31var vm2 = new Vue({
el: '#app2',
data: {
dt: new Date()
},
methods: {},
filters: {
// 定义私有过滤器 过滤器有两个 条件 【过滤器名称 和 处理函数】
// 过滤器调用的时候,采用的是就近原则,如果私有过滤器和全局过滤器名称一致了,这时候 优先调用私有过滤器
dateFormat: function (dateStr, pattern = '') {
// 根据给定的时间字符串,得到特定的时间
var dt = new Date(dateStr)
// yyyy-mm-dd
var y = dt.getFullYear()
var m = (dt.getMonth() + 1).toString().padStart(2, '0')
var d = dt.getDate().toString().padStart(2, '0')
if (pattern.toLowerCase() === 'yyyy-mm-dd') {
return `${y}-${m}-${d}`
} else {
//使用ES6中的字符串新方法 String.prototype.padStart(maxLength, fillString='')
//或 String.prototype.padEnd(maxLength, fillString='')来填充字符串进行补0;
var hh = dt.getHours().toString().padStart(2, '0')
var mm = dt.getMinutes().toString().padStart(2, '0')
var ss = dt.getSeconds().toString().padStart(2, '0')
return `${y}-${m}-${d} ${hh}:${mm}:${ss} ~~~~~~~`
}
}
}
})
全局过滤器
1 | <script> |
键盘修饰符
1.x中自定义键盘修饰符【了解即可】
1 | Vue.directive('on').keyCodes.f2 = 113; |
2.x中自定义键盘修饰符
通过
Vue.config.keyCodes.名称 = 按键值
来自定义案件修饰符的别名:1
Vue.config.keyCodes.f2 = 113;
使用自定义的按键修饰符:
1
<input type="text" v-model="name" @keyup.f2="add">
自定义指令
- 使用
Vue.directive()
定义全局的指令参数1
: 指令的名称,注意,在定义的时候,指令的名称前面,不需要加 v- 前缀, 在调用的时候,必须 在指令名称前 加上v-
前缀来进行调用参数2
: 是一个对象,这个对象身上,有一些指令相关的函数,这些函数可以在特定的阶段,执行相关的操作
自定义全局和局部的 自定义指令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 定义全局的指令 v-focus
Vue.directive("focus", {
bind: function (el) {
// 每当指令绑定到元素上的时候,会立即执行这个 bind 函数,只执行一次
// 注意: 在每个 函数中,第一个参数,永远是 el ,表示 被绑定了指令的那个元素,这个 el 参数,是一个原生的JS对象
// 在元素 刚绑定了指令的时候,还没有 插入到 DOM中去,这时候,调用 focus 方法没有作用
// 因为,一个元素,只有插入DOM之后,才能获取焦点
// el.focus()
},
inserted: function (el) {
// inserted 表示元素 插入到DOM中的时候,会执行 inserted 函数【触发1次】
el.focus();
// 和JS行为有关的操作,最好在 inserted 中去执行,放置 JS行为不生效
},
updated: function (el) {
// 当VNode更新的时候,会执行 updated, 可能会触发多次
},
});
// 自定义一个 设置字体颜色的 指令
Vue.directive("color", {
// 样式,只要通过指令绑定给了元素,不管这个元素有没有被插入到页面中去,这个元素肯定有了一个内联的样式
// 将来元素肯定会显示到页面中,这时候,浏览器的渲染引擎必然会解析样式,应用给这个元素
bind: function (el, binding) {
// el.style.color = 'red'
// console.log(binding.name)
// 和样式相关的操作,一般都可以在 bind 执行
// console.log(binding.value)
// console.log(binding.expression)
el.style.color = binding.value;
},
});自定义指令的使用方式:
1
2
3
4
5
6
7
8
9<!-- 注意: Vue中所有的指令,在调用的时候,都以 v- 开头 -->
<input
type="text"
class="form-control"
v-model="keywords"
id="search"
v-focus
v-color="'green'"
/>
Vue 1.x 中 自定义元素指令
【已废弃,了解即可】
1
2
3
4
5Vue.elementDirective("red-color", {
bind: function () {
this.el.style.color = "red";
},
});使用方式:
1
<red-color>1232</red-color>
vue实例的生命周期
什么是生命周期:从Vue实例创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期!
生命周期钩子:就是生命周期事件的别名而已;
生命周期钩子 = 生命周期函数 = 生命周期事件
主要的生命周期函数分类:
创建期间的生命周期函数:
- beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
- created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板
- beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中
- mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示
运行期间的生命周期函数:
- beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点
- updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!
销毁期间的生命周期函数:
- beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
- destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
代码体验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./lib/vue-2.4.0.js"></script>
</head>
<body>
<div id="app">
<input type="button" value="修改msg" @click="msg='No'">
<h3 id="h3">{{ msg }}</h3>
</div>
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {
msg: 'ok'
},
methods: {
show() {
console.log('执行了show方法')
}
},
beforeCreate() { // 这是我们遇到的第一个生命周期函数,表示实例完全被创建出来之前,会执行它
// console.log(this.msg)
// this.show()
// 注意: 在 beforeCreate 生命周期函数执行的时候,data 和 methods 中的 数据都还没有没初始化
},
created() { // 这是遇到的第二个生命周期函数
// console.log(this.msg)
// this.show()
// 在 created 中,data 和 methods 都已经被初始化好了!
// 如果要调用 methods 中的方法,或者操作 data 中的数据,最早,只能在 created 中操作
},
beforeMount() { // 这是遇到的第3个生命周期函数,表示 模板已经在内存中编辑完成了,但是尚未把 模板渲染到 页面中
// console.log(document.getElementById('h3').innerText)
// 在 beforeMount 执行的时候,页面中的元素,还没有被真正替换过来,只是之前写的一些模板字符串
},
mounted() { // 这是遇到的第4个生命周期函数,表示,内存中的模板,已经真实的挂载到了页面中,用户已经可以看到渲染好的页面了
// console.log(document.getElementById('h3').innerText)
// 注意: mounted 是 实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完全创建好了,此时,如果没有其它操作的话,这个实例,就静静的 躺在我们的内存中,一动不动
},
// 接下来的是运行中的两个事件
beforeUpdate() { // 这时候,表示 我们的界面还没有被更新【数据被更新了吗? 数据肯定被更新了】
/* console.log('界面上元素的内容:' + document.getElementById('h3').innerText)
console.log('data 中的 msg 数据是:' + this.msg) */
// 得出结论: 当执行 beforeUpdate 的时候,页面中的显示的数据,还是旧的,此时 data 数据是最新的,页面尚未和 最新的数据保持同步
},
updated() {
console.log('界面上元素的内容:' + document.getElementById('h3').innerText)
console.log('data 中的 msg 数据是:' + this.msg)
// updated 事件执行的时候,页面和 data 数据已经保持同步了,都是最新的
}
});
</script>
</body>
</html>
vue-resource发请求
除了 vue-resource 之外,还可以使用
axios
的第三方包实现实现数据的请求常见的数据请求类型? get post jsonp
JSONP的实现原理
- 由于浏览器的安全性限制,不允许AJAX访问 协议不同、域名不同、端口号不同的 数据接口,浏览器认为这种访问不安全;
- 可以通过动态创建script标签的形式,把script标签的src属性,指向数据接口的地址,因为script标签不存在跨域限制,这种数据获取方式,称作JSONP(注意:根据JSONP的实现原理,知晓,JSONP只支持Get请求);
具体实现过程:
- 先在客户端定义一个回调方法,预定义对数据的操作;
- 再把这个回调方法的名称,通过URL传参的形式,提交到服务器的数据接口;
- 服务器数据接口组织好要发送给客户端的数据,再拿着客户端传递过来的回调方法名称,拼接出一个调用这个方法的字符串,发送给客户端去解析执行;
- 客户端拿到服务器返回的字符串之后,当作Script脚本去解析执行,这样就能够拿到JSONP的数据了;
带大家通过 Node.js ,来手动实现一个JSONP的请求例子;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35const http = require("http");
// 导入解析 URL 地址的核心模块
const urlModule = require("url");
const server = http.createServer();
// 监听 服务器的 request 请求事件,处理每个请求
server.on("request", (req, res) => {
const url = req.url;
// 解析客户端请求的URL地址
var info = urlModule.parse(url, true);
// 如果请求的 URL 地址是 /getjsonp ,则表示要获取JSONP类型的数据
if (info.pathname === "/getjsonp") {
// 获取客户端指定的回调函数的名称
var cbName = info.query.callback;
// 手动拼接要返回给客户端的数据对象
var data = {
name: "zs",
age: 22,
gender: "男",
hobby: ["吃饭", "睡觉", "运动"],
};
// 拼接出一个方法的调用,在调用这个方法的时候,把要发送给客户端的数据,序列化为字符串,作为参数传递给这个调用的方法:
var result = `${cbName}(${JSON.stringify(data)})`;
// 将拼接好的方法的调用,返回给客户端去解析执行
res.end(result);
} else {
res.end("404");
}
});
server.listen(3000, () => {
console.log("server running at http://127.0.0.1:3000");
});
配置步骤
直接在页面中,通过
script
标签,引入vue-resource
的脚本文件;注意:引用的先后顺序是:先引用
Vue
的脚本文件,再引用vue-resource
的脚本文件;1
2
3
4<script src="./lib/vue-2.4.0.js"></script>
<!-- 注意:vue-resource 依赖于 Vue,所以先后顺序要注意 -->
<!-- this.$http.jsonp -->
<script src="./lib/vue-resource-1.3.4.js"></script>
发送请求
html
1
2
3
4
5<div id="app">
<input type="button" value="get请求" @click="getInfo">
<input type="button" value="post请求" @click="postInfo">
<input type="button" value="jsonp请求" @click="jsonpInfo">
</div脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {},
methods: {
// 发起get请求
getInfo() {
// 当发起get请求之后, 通过 .then 来设置成功的回调函数
this.$http.get('http://127.0.0.1:8899/api/get').then(function (result) {
// 通过 result.body 拿到服务器返回的成功的数据
// console.log(result.body)
})
},
// 发起post请求
postInfo() {
var url = 'http://127.0.0.1:8899/api/post';
// post 方法接收三个参数:
// 参数1: 要请求的URL地址
// 参数2: 要发送的数据对象
// 参数3: 指定post提交的编码类型为 application/x-www-form-urlencoded
// { emulateJSON: true } 设置 提交的内容类型 为 普通表单数据格式
this.$http.post(url, { name: 'zs' }, { emulateJSON: true }).then(res => {
console.log(res.body);
});
},
// 发送JSONP请求
jsonpInfo() { // JSONP形式从服务器获取数据
var url = 'http://127.0.0.1:8899/api/jsonp';
this.$http.jsonp(url).then(res => {
console.log(res.body);
});
}
}
});
</script>
jsonp示例
使用Node建立服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 导入 http 内置模块
const http = require("http");
// 这个核心模块,能够帮我们解析 URL地址,从而拿到 pathname query
const urlModule = require("url");
// 创建一个 http 服务器
const server = http.createServer();
// 监听 http 服务器的 request 请求
server.on("request", function (req, res) {
// const url = req.url
const { pathname: url, query } = urlModule.parse(req.url, true);
if (url === "/getscript") {
// 拼接一个合法的JS脚本,这里拼接的是一个方法的调用
// var scriptStr = 'show()'
var data = {
name: "xjj",
age: 18,
gender: "女孩子",
};
var scriptStr = `${query.callback}(${JSON.stringify(data)})`;
// res.end 发送给 客户端, 客户端去把 这个 字符串,当作JS代码去解析执行
res.end(scriptStr);
} else {
res.end("404");
}
});
// 指定端口号并启动服务器监听
server.listen(3000, function () {
console.log("server listen at http://127.0.0.1:3000");
});调用Jsonp接口,打印输出的结果
1
2
3
4
5
6
7
8
9<body>
<script>
function showInfo123(data) {
console.log(data);
}
</script>
<script src="http://127.0.0.1:3000/getscript?callback=showInfo123"></script>
</body>
Vue中的动画
- 为什么要有动画:动画能够提高用户的体验,帮助用户更好的理解页面中的功能;
动画示例
点击按钮,让 h3 显示,再点击,让 h3 隐藏 ,非常直观
1
2
3
4
5<div id="app">
<input type="button" value="toggle" @click="flag=!flag" />
<!-- 需求: 点击按钮,让 h3 显示,再点击,让 h3 隐藏 -->
<h3 v-if="flag">这是一个H3</h3>
</div>
使用过渡类名
html代码
1
2
3
4
5
6
7
8<div id="app">
<input type="button" value="动起来" @click="myAnimate" />
<!-- 使用 transition 将需要过渡的元素包裹起来 -->
<!-- transition 元素,是 Vue 官方提供的 -->
<transition>
<div v-if="flag">动画哦</div>
</transition>
</div>VM 实例:
1
2
3
4
5
6
7
8
9
10<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {
flag: false
},
methods: {}
});
</script>
定义两组类样式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<style>
/* v-enter 【这是一个时间点】 是进入之前,元素的起始状态,此时还没有开始进入 */
/* v-leave-to 【这是一个时间点】 是动画离开之后,离开的终止状态,此时,元素 动画已经结束了 */
.v-enter,
.v-leave-to {
opacity: 0;
transform: translateX(150px);
}
/* v-enter-active 【入场动画的时间段】 */
/* v-leave-active 【离场动画的时间段】 */
.v-enter-active,
.v-leave-active{
transition: all 0.8s ease;
}
</style>
使用自定义的名字
默认使用的是 v-enter,v-leave二组标签
在transition上可以指定name属性添加替换前缀v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<style>
.my-enter,
.my-leave-to {
opacity: 0;
transform: translateY(70px);
}
.my-enter-active,
.my-leave-active {
transition: all 0.8s ease;
}
</style>
<transition name="my">
<h6 v-if="flag2">这是一个H6</h6>
</transition>
使用第三方 CSS 动画库
导入动画类库:
1
<link rel="stylesheet" type="text/css" href="./lib/animate.css" />
定义 transition 及属性:
1
2
3
4
5
6
7<transition
enter-active-class="fadeInRight"
leave-active-class="fadeOutRight"
:duration="{ enter: 500, leave: 800 }"
>
<div class="animated" v-show="isshow">动画哦</div>
</transition>
使用动画钩子函数
定义 transition 组件以及三个钩子函数:
1
2
3
4
5
6
7
8
9
10<div id="app">
<input type="button" value="切换动画" @click="isshow = !isshow" />
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
>
<div v-if="isshow" class="show">OK</div>
</transition>
</div>定义三个 methods 钩子方法:
1
2
3
4
5
6
7
8
9
10
11
12
13methods: {
beforeEnter(el) { // 动画进入之前的回调
el.style.transform = 'translateX(500px)';
},
enter(el, done) { // 动画进入完成时候的回调
el.offsetWidth;
el.style.transform = 'translateX(0px)';
done();
},
afterEnter(el) { // 动画进入完成之后的回调
this.isshow = !this.isshow;
}
}定义动画过渡时长和样式:
1
2
3.show{
transition: all 0.4s ease;
}
v-for 的列表过渡
定义过渡样式:
1
2
3
4
5
6
7
8
9
10
11
12<style>
.list-enter,
.list-leave-to {
opacity: 0;
transform: translateY(10px);
}
.list-enter-active,
.list-leave-active {
transition: all 0.3s ease;
}
</style>定义DOM结构,其中,需要使用 transition-group 组件把v-for循环的列表包裹起来:
- 在实现列表过渡的时候,如果需要过渡的元素,是通过 v-for 循环渲染出来的,不能使用 transition 包裹,需要使用 transitionGroup
- 如果要为 v-for 循环创建的元素设置动画,必须为每一个 元素 设置
:key
属性 - 给 ransition-group 添加 appear 属性,实现页面刚展示出来时候,入场时候的效果
- 通过 为 transition-group 元素,设置 tag 属性,指定 transition-group 渲染为指定的元素,如果不指定 tag 属性,默认,渲染为 span 标签
1
2
3
4
5
6
7<div id="app">
<input type="text" v-model="txt" @keyup.enter="add" />
<transition-group appear tag="ul" name="list">
<li v-for="(item, i) in list" :key="i">{{item}}</li>
</transition-group>
</div>定义 VM中的结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {
txt: "",
list: [1, 2, 3, 4],
},
methods: {
add() {
this.list.push(this.txt);
this.txt = "";
},
},
});
列表的排序过渡
<transition-group>
组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的v-move
特性,它会在元素的改变定位的过程中应用。
v-move
和v-leave-active
结合使用,能够让列表的过渡更加平缓柔和:1
2
3
4
5
6
7/* 下面的 .v-move 和 .v-leave-active 配合使用,能够实现列表后续的元素,渐渐地漂上来的效果 */
.v-move{
transition: all 0.8s ease;
}
.v-leave-active{
position: absolute;
}
定义Vue组件
- 什么是组件: 组件的出现,就是为了拆分Vue实例的代码量的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可;
组件化和模块化的不同:
- 模块化: 是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一;
- 组件化: 是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用;
全局组件定义的三种方式
不论是哪种方式创建出来的组件,组件的 template 属性指向的模板内容,必须有且只能有唯一的一个根元素
Vue.extend 来创建全局的Vue组件,配合 Vue.component 方法:
- 如果使用 Vue.component 定义全局组件的时候,组件名称使用了 驼峰命名,则在引用组件的时候,需要把 大写的驼峰改为小写的字母,同时,两个单词之前,使用 - 链接;
- 如果不使用驼峰,则直接拿名称来使用即可;
1
2
3
4
5var login = Vue.extend({
template: '<h1>登录</h1>' // 通过 template 属性,指定了组件要展示的HTML结构
});
// Vue.component('组件的名称', 创建出来的组件模板对象)
Vue.component('login', login);直接使用 Vue.component 方法:
- `Vue.component 第一个参数:组件的名称,将来在引用组件的时候,就是一个 标签形式 来引入 它的
- 第二个参数: Vue.extend 创建的组件 ,其中 template 就是组件将来要展示的HTML内容
1
2
3Vue.component('register', {
template: '<h1>注册</h1>'
});将模板字符串,定义到script标签种:
1
2
3
4
5
6
7
8<script id="tmpl" type="x-template">
<div><a href="#">登录</a> | <a href="#">注册</a></div>
</script>
// 或者使用<template>标签
<template id="tmpl2">
<h1>这是私有的 login 组件</h1>
</template>同时,需要使用 Vue.component 来定义组件:
1
2
3Vue.component('account', {
template: '#tmpl'
});定义内部的私有组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25var vm2 = new Vue({
el: '#app2',
data: {},
methods: {},
filters: {},
directives: {},
components: { // 定义实例内部私有组件的
login: {
template: '#tmpl2'
}
},
beforeCreate() { },
created() { },
beforeMount() { },
mounted() { },
beforeUpdate() { },
updated() { },
beforeDestroy() { },
destroyed() { }
})
</script>
// 使用
<login></login>
组件的数据和事件
- 组件可以有自己的 data 数据,
- 组件的 data 和 实例的 data 有点不一样,实例中的 data 可以为一个对象,但是 组件中的 data 必须是一个方法,方法内部,还必须返回一个对象才行;
- 组件中 的data 数据,使用方式,和实例中的 data 使用方式完全一样
在组件中,
data
需要被定义为一个方法,例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Vue.component("mycom1", {
template: "<h1>这是全局组件 --- {{msg}}</h1>",
data: function () {
return {
msg: "这是组件的中data定义的数据",
};
},
});
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {},
methods: {},
});
计数器案例
为什么组件中的data属性必须定义为一个方法并返回一个对象
- 对于多个组件,可以保证数据互相独立
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39<body>
<div id="app">
<counter></counter>
<hr />
<counter></counter>
</div>
<template id="tmpl">
<div>
<input type="button" value="+1" @click="increment" />
<h3>{{count}}</h3>
</div>
</template>
<script>
var dataObj = { count: 0 };
// 这是一个计数器的组件, 身上有个按钮,每当点击按钮,让 data 中的 count 值 +1
Vue.component("counter", {
template: "#tmpl",
data: function () {
// return dataObj
return { count: 0 };
},
methods: {
increment() {
this.count++;
},
},
});
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {},
methods: {},
});
</script>
</body>
局部子组件components
组件实例定义方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {},
methods: {},
components: { // 定义子组件
account: { // account 组件
template: '<div><h1>这是Account组件{{name}}</h1><login></login></div>', // 定义的子组件
components: { // 定义子组件的子组件
login: { // login 组件
template: "<h3>这是登录组件</h3>"
}
}
}
}
});
</script>引用组件:
1
2
3<div id="app">
<account></account>
</div>
组件切换
使用
flag
标识符结合v-if
和v-else
切换1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<div id="app">
<input type="button" value="toggle" @click="flag=!flag" />
<my-com1 v-if="flag"></my-com1>
<my-com2 v-else="flag"></my-com2>
</div>
<script>
Vue.component("myCom1", {
template: "<h3>奔波霸</h3>",
});
Vue.component("myCom2", {
template: "<h3>霸波奔</h3>",
});
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {
flag: true,
},
methods: {},
});
</script>使用
:is
属性来切换不同的子组件,并添加切换动画1
2
3
4
5
6
7
8
9
10
11// 登录组件 const login = Vue.extend({ template: `
<div>
<h3>登录组件</h3>
</div>
` }); Vue.component('login', login); // 注册组件 const register =
Vue.extend({ template: `
<div>
<h3>注册组件</h3>
</div>
` }); Vue.component('register', register); // 创建 Vue 实例,得到 ViewModel
var vm = new Vue({ el: '#app', data: { comName: 'login' }, methods: {} });使用
component
标签,来引用组件,并通过:is
属性来指定要加载的组件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<style>
.v-enter,
.v-leave-to {
opacity: 0;
transform: translateX(30px);
}
.v-enter-active,
.v-leave-active {
position: absolute;
transition: all 0.3s ease;
}
</style>
<div id="app">
<a href="#" @click.prevent="comName='login'">登录</a>
<a href="#" @click.prevent="comName='register'">注册</a>
<hr />
<!-- 通过 mode 属性,设置组件切换时候的 模式 -->
<transition mode="out-in">
<!-- component 是一个占位符, :is 属性,可以用来指定要展示的组件的名称 -->
<component :is="comName"></component>
</transition>
</div>
组件渲染
页面中渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<div id="app">
<login></login>
</div>
<script>
var login = {
template: "<h1>这是登录组件</h1>",
};
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {},
methods: {},
components: {
login,
},
});
</script>使用render 函数渲染
- createElements 是一个 方法,调用它能够把指定的组件模板,渲染为 html 结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<div id="app"></div>
<script>
var login = {
template: "<h1>这是登录组件</h1>",
};
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {},
methods: {},
render: function (createElements) {
return createElements(login);
// 注意:这里 return 的结果,会替换页面中 el 指定的那个容器
},
});
</script>
组件传值
父组件向子组件传值
组件实例定义方式,注意:一定要使用
props
属性来定义父组件传递过来的数据1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {
msg: '这是父组件中的消息'
},
components: {
son: {
template: '<h1>这是子组件 --- {{finfo}}</h1>',
// 注意: 组件中的 所有 props 中的数据,都是通过 父组件传递给子组件的
// props 中的数据,都是只读的,无法重新赋值
props: ['finfo'] // 在 props 数组中定义一下,才能使用这个数据
}
}
});
</script>使用
v-bind
或简化指令,将数据传递到子组件中:1
2
3<div id="app">
<son :finfo="msg"></son>
</div>
子组件向父组件传值
原理:
- 父组件将方法的引用,传递到子组件内部
- 子组件在内部调用父组件传递过来的方法引用
- 同时把要发送给父组件的数据,在调用方法的时候当作参数传递进去
父组件将方法的引用传递给子组件,其中,
getMsg
是父组件中methods
中定义的方法名称,func
是子组件调用传递过来方法时候的方法名称1
2// 在子组件中定义方法的引用 func,指向父组件中method中getMsg()
<son @func="getMsg"></son>子组件内部通过
this.$emit('方法名', 要传递的数据)
方式,来调用父组件中的方法,同时把数据传递给父组件使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40<div id="app">
<!-- 引用父组件 -->
<son @func="getMsg"></son>
<!-- 组件模板定义 -->
<templateid="son">
<div>
<input type="button" value="向父组件传值" @click="sendMsg" />
</div>
</template>
</div>
<script>
// 子组件的定义方式
Vue.component('son', {
template: '#son', // 组件模板Id
data() {
return {
sonmsg: { name: '小头儿子', age: 6 }
}
},
methods: {
sendMsg() { // 按钮的点击事件
// emit 英文原意: 是触发,调用、发射的意思
this.$emit('func', this.sonmsg); // 调用父组件传递过来的方法,同时把数据传递出去
}
}
});
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {},
methods: {
getMsg(val){ // 子组件中,通过 this.$emit() 实际调用的方法,在此进行定义
alert(val.name);
}
}
});
</script>
评论列表案例
1 |
|
this.$refs
来获取元素和组件
1 | <div id="app"> |
路由
对于普通的网站,所有的超链接都是URL地址,所有的URL地址都对应服务器上对应的资源;
对于单页面应用程序来说,主要通过URL中的hash(#号)来实现不同页面之间的切换,同时,hash有一个特点:HTTP请求中不会包含hash相关的内容;所以,单页面程序中的页面跳转主要用hash实现;
在单页面应用程序中,这种通过hash改变来切换页面的方式,称作前端路由(区别于后端路由);
在 vue 中使用 vue-router
导入 vue-router 组件类库:
1
2
3<script src="./lib/vue-2.4.0.js"></script>
<!-- 1. 导入 vue-router 组件类库 -->
<script src="./lib/vue-router-2.7.0.js"></script>使用 router-link 组件来导航
1
2
3
4
5
6<!-- <a href="#/login">登录</a> -->
<!-- <a href="#/register">注册</a> -->
<!-- router-link 默认渲染为一个a 标签 -->
<!-- 2. 使用 router-link 组件来导航 -->
<router-link to="/login">登录</router-link>
<router-link to="/register">注册</router-link>使用 router-view 组件来显示匹配到的组件
- 这是 vue-router 提供的元素,专门用来 当作占位符的,将来,路由规则,匹配到的组件,就会展示到这个 router-view 中去
- 我们可以把 router-view 认为是一个占位符
1
2<!-- 3. 使用 router-view 组件来显示匹配到的组件 -->
<router-view></router-view>创建使用
Vue.extend
创建组件1
2
3
4
5
6
7
8
9// 4.1 使用 Vue.extend 来创建登录组件
var login = Vue.extend({
template: "<h1>登录组件</h1>",
});
// 4.2 使用 Vue.extend 来创建注册组件
var register = Vue.extend({
template: "<h1>注册组件</h1>",
});创建一个路由 router 实例,通过 routers 属性来定义路由匹配规则
- 每个路由规则,都是一个对象,这个规则对象,身上,有两个必须的属性:
- 属性1 是 path, 表示监听 哪个路由链接地址;
- 属性2 是 component, 表示,如果 路由是前面匹配到的 path ,则展示 component 属性对应的那个组件
1
2
3
4
5
6
7
8
9// 5. 创建一个路由 router 实例,通过 routers 属性来定义路由匹配规则
var router = new VueRouter({
routes: [
// 这个配置对象中的 route 表示 【路由匹配规则】 的意思
{ path: "/", redirect: "/login" }, // 这里的 redirect 和 Node 中的 redirect 完全是两码事
{ path: "/login", component: login },
{ path: "/register", component: register },
],
});- 每个路由规则,都是一个对象,这个规则对象,身上,有两个必须的属性:
使用 router 属性来使用路由规则
1
2
3
4
5// 6. 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
router: router, // 使用 router 属性来使用路由规则
});
设置路由高亮,切换动效
1 | var routerObj = new VueRouter({ |
路由规则中定义参数
在规则中定义参数:
1
{ path: '/login/:id/:name', component: register }
通过
this.$route.params
来获取路由中的参数:1
2
3
4
5var register = Vue.extend({
template: '<h1>注册组件 --- {{this.$route.params.id}}</h1>'
});
<router-link to="/login/12/ls">登录</router-link>
路由嵌套
- 使用 children 属性,实现子路由,同时子路由的 path 前面,不要带 /
- 否则永远以根路径开始请求,这样不方便我们用户去理解URL地址
1 | <div id="app"> |
命名视图实现经典布局
标签代码结构:
1
2
3
4
5
6
7<div id="app">
<router-view></router-view>
<div class="container">
<router-view name="left"></router-view>
<router-view name="main"></router-view>
</div>
</div>JS代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40<script>
var header = {
template: '<h1 class="header">Header头部区域</h1>'
}
var leftBox = {
template: '<h1 class="left">Left侧边栏区域</h1>'
}
var mainBox = {
template: '<h1 class="main">mainBox主体区域</h1>'
}
// 创建路由对象
var router = new VueRouter({
routes: [
/* { path: '/', component: header },
{ path: '/left', component: leftBox },
{ path: '/main', component: mainBox } */
{
path: '/', components: {
'default': header,
'left': leftBox,
'main': mainBox
}
}
]
})
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {},
methods: {},
router
});
</script>CSS 样式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33<style>
html,
body {
margin: 0;
padding: 0;
}
.header {
background-color: orange;
height: 80px;
}
h1 {
margin: 0;
padding: 0;
font-size: 16px;
}
.container {
display: flex;
height: 600px;
}
.left {
background-color: lightgreen;
flex: 2;
}
.main {
background-color: lightpink;
flex: 8;
}
</style>
watch、computed和methods
computed
属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;methods
方法表示一个具体的操作,主要书写业务逻辑;watch
一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;可以看作是computed
和methods
的结合体;
watch属性的使用
- 考虑一个问题:想要实现
名
和姓
两个文本框的内容改变,则全名的文本框中的值也跟着改变; - 特点:第一次初始化页面的时候,是不会对定义的属性进行监听操作
监听
data
中属性的改变:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27<div id="app">
<input type="text" v-model="firstName" /> +
<input type="text" v-model="lastName" /> =
<span>{{fullName}}</span>
</div>
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {
firstName: "jack",
lastName: "chen",
fullName: "jack - chen",
},
methods: {},
watch: {
firstName: function (newVal, oldVal) {
// 第一个参数是新数据,第二个参数是旧数据
this.fullName = newVal + " - " + this.lastName;
},
lastName: function (newVal, oldVal) {
this.fullName = this.firstName + " - " + newVal;
},
},
});
</script>监听路由对象的改变:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38<div id="app">
<router-link to="/login">登录</router-link>
<router-link to="/register">注册</router-link>
<router-view></router-view>
</div>
<script>
var login = Vue.extend({
template: "<h1>登录组件</h1>",
});
var register = Vue.extend({
template: "<h1>注册组件</h1>",
});
var router = new VueRouter({
routes: [
{ path: "/login", component: login },
{ path: "/register", component: register },
],
});
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {},
methods: {},
router: router,
watch: {
$route: function (newVal, oldVal) {
if (newVal.path === "/login") {
console.log("这是登录组件");
}
},
},
});
</script>
handler
第一次初始化时候是不会进行监听操作,如果希望监听,需要引入handler方法和Immediate属性。
1
2
3
4
5
6
7
8
9watch: {
firstName: {
handler(newVal, oldVal) {
// 第一个参数是新数据,第二个参数是旧数据
this.fullName = newVal + ' - ' + this.lastName;
},
immediate: true
}
}immediate: true 的含义就是在watch声明了firstName的话,会立即执行里面的handler方法
deep属性
watch中有一个属性deep,含义:是否深度监听某个对象的值,该值默认为false。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28<template>
<div>
<input v-model="obj.age" />
<p>{{ obj.msg }}</p>
</div>
</template>
<script>
export default {
data() {
return {
obj: {
msg: '',
age: 0
}
};
},
watch: {
obj: {
handler(newVal, oldVal) {
// 第一个参数是新数据,第二个参数是旧数据
this.obj.msg = '输入的年龄为:' + newVal.age;
},
immediate: true
// deep: true
}
}
</script>- 不添加
deep: true
属性,在输入框修改了obj.age后,handler函数不会被执行到 - 受JS的限制,Vue不能检测到对象的添加或删除,指定将听到这个obj对象的变化(赋值操作)
- 不添加
computed计算属性的使用
默认只有
getter
的计算属性:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<div id="app">
<input type="text" v-model="firstName" /> +
<input type="text" v-model="lastName" /> =
<span>{{fullName}}</span>
</div>
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {
firstName: "jack",
lastName: "chen",
},
methods: {},
computed: {
// 计算属性; 特点:当计算属性中所以来的任何一个 data 属性改变之后,都会重新触发属性的重新计算
// 从而更新 fullName 的值
fullName() {
return this.firstName + " - " + this.lastName;
},
},
});
</script>定义有
getter
和setter
的计算属性:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36<div id="app">
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
<!-- 点击按钮重新为 计算属性 fullName 赋值 -->
<input type="button" value="修改fullName" @click="changeName" />
<span>{{fullName}}</span>
</div>
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {
firstName: "jack",
lastName: "chen",
},
methods: {
changeName() {
this.fullName = "TOM - chen2";
},
},
computed: {
fullName: {
get: function () {
return this.firstName + " - " + this.lastName;
},
// 反向操作: 给出姓名,修改对应的姓&名
set: function (newVal) {
var parts = newVal.split(" - ");
this.firstName = parts[0];
this.lastName = parts[1];
},
},
},
});
</script>
区别
- computed: 是基于响应性依赖来进行缓存的。只有在响应依赖发生改变的时候才会重新计算,就是说当多次访问
<span>{{fullName}}</span>
的时候,不会再次执行computed的就算操作(methods比较)。
webpack
webpack 是前端的一个项目构建工具,它是基于 Node.js 开发出来的一个前端工具
借助于webpack这个前端自动化构建工具,可以完美实现资源的合并、打包、压缩、混淆等诸多功能。
安装方式
- 运行
npm i webpack -g
全局安装webpack,这样就能在全局使用webpack的命令 - 在项目根目录中运行
npm i webpack --save-dev
安装到项目依赖中
初步使用
创建一个基于 webpack 模板的新项目:
$ vue init webpack my-project
cd my-project
安装依赖, 运行npm init
初始化项目,使用npm管理项目中的依赖包创建项目基本的目录结构
使用
cnpm i jquery --save
安装jquery类库创建
main.js
并书写各行变色的代码逻辑:1
2
3
4
5
6
7
8// 导入jquery类库
import $ from "jquery";
// const $ = require('jquery')
// 设置偶数行背景色,索引从0开始,0是偶数
$("#list li:even").css("backgroundColor", "lightblue");
// 设置奇数行背景色
$("#list li:odd").css("backgroundColor", "pink");直接在页面上引用
main.js
会报错,因为浏览器不认识import
这种高级的JS语法,需要使用webpack进行处理,webpack默认会把这种高级的语法转换为低级的浏览器能识别的语法;运行
webpack 入口文件路径 输出文件路径
对main.js
进行处理:1
webpack src/js/main.js dist/bundle.js
当我们在 控制台,直接输入 webpack 命令执行的时候,webpack 做了以下几步:
- 首先,webpack 发现,我们并没有通过命令的形式,给它指定入口和出口
- webpack 就会去 项目的 根目录中,查找一个叫做
webpack.config.js
的配置文件 - 当找到配置文件后,webpack 会去解析执行这个 配置文件,当解析执行完配置文件后,就得到了 配置文件中,导出的配置对象
- 当 webpack 拿到 配置对象后,就拿到了 配置对象中,指定的 入口 和 出口,然后进行打包构建;
Webpack下的Vuejs项目文件结构
全局的文件结构:
1
2
3
4
5
6
7
8▸ build/ // 编译用到的脚本
▸ config/ // 各种配置
▸ dist/ // 打包后的文件夹
▸ node_modules/ // node第三方包
▸ src/ // 源代码
▸ static/ // 静态文件, 暂时无用
index.html // 最外层文件
package.json // node项目配置文件build: 保留各种打包脚本。不可或缺,不要随意修改。
展开后如下:
1
2
3
4
5
6
7
8
9
10▾ build/
build.js
check-versions.js
dev-client.js
dev-server.js
utils.js
vue-loader.conf.js
webpack.base.conf.js
webpack.dev.conf.js
webpack.prod.conf.jsbuild.js:打包使用, 不要修改。
check-versions.js: 检查npm的版本, 不要修改。
dev-client.js 和 dev-server.js:是在开发时使用的服务器脚本。不要修改。(借助于node这个后端语言,我们 在做vuejs开发时,可以通过
$npm run dev
这个命令,打开一个小的server, 运行vuejs. )utils.js 不要修改。 做一些css/sass 等文件的生成。
vue-loader.conf.js 非常重要的配置文件,不要修改。内容是用来辅助加载vuejs用到的css source map等内容。
webpack.base.conf.js, webpack.dev.conf.js,webpack.prod.conf.js 这三个都是基本的配置文件。不要修改。
config:跟部署和配置相关。
1
2
3
4▾ config/
dev.env.js
index.js
prod.env.js- dev.env.js 开发模式下的配置文件,一般不用修改。
- prod.env.js 生产模式下的配置文件,一般不用修改。
- index.js 很重要的文件, 定义了 开发时的端口(默认是8080),定义了图片文件夹(默认static), 定义了开发模式下的 代理服务器. 我们修改的还是比较多的。
dist 打包之后的文件所在目录,如下。
- 可以看到,对应的css, js, map, 都在这里。这个文件夹不要放到git中。
1
2
3
4
5
6
7
8
9
10
11
12
13▾ dist/
▾ static/
▾ css/
app.d41d8cd98f00b204e9800998ecf8427e.css
app.d41d8cd98f00b204e9800998ecf8427e.css.map
▾ js/
app.c482246388114c3b9cf0.js
app.c482246388114c3b9cf0.js.map
manifest.577e472792d533aaaf04.js
manifest.577e472792d533aaaf04.js.map
vendor.5f34d51c868c93d3fb31.js
vendor.5f34d51c868c93d3fb31.js.map
index.htmlnode_modules: node项目所用到的第三方包,特别多,特别大。
$ npm install
所产生。这个文件夹不要放到git中。src: 最最核心的源代码所在的目录。
1
2
3
4
5
6
7
8
9
10
11▾ src/
▾ assets/
logo.png
▾ components/
Book.vue
BookList.vue
Hello.vue
▾ router/
index.js
App.vue
main.js- assets: 用到的图片
- components: 用到的”视图”和”组件”所在的文件夹。(最最核心)
- router/index.js 路由文件。 定义了各个页面对应的url.
- App.vue 如果index.html 是一级页面模板的话,这个App.vue就是二级页面模板。 所有的其他vuejs页面,都作为该模板的 一部分被渲染出来。
- main.js 废代码。没有实际意义,但是为了支撑整个vuejs框架,存在很必要。
webpack配置文件
简化打包时候的命令
在项目根目录中创建
webpack.config.js
由于运行webpack命令的时候,webpack需要指定入口文件和输出文件的路径,所以,我们需要在
webpack.config.js
中配置这两个路径:1
2
3
4
5
6
7
8
9
10
11
12// 导入处理路径的模块
var path = require("path");
// 导出一个配置对象,将来webpack在启动的时候,会默认来查找webpack.config.js,并读取这个文件中导出的配置对象,来进行打包处理
module.exports = {
entry: path.join(__dirname, "./src/js/main.js"), // 项目入口文件,要使用 webpack 打包哪个文件
output: {
// 配置输出选项
path: path.join(__dirname, "./dist"), // 配置输出的路径
filename: "bundle.js", // 配置输出的文件名
},
};
实时打包构建
- 由于每次重新修改代码之后,都需要手动运行webpack打包的命令,比较麻烦,所以使用
webpack-dev-server
来实现代码实时打包编译,当修改代码之后,会自动进行打包构建。 - 运行
cnpm i webpack-dev-server --save-dev
安装到开发依赖 - 安装完成之后,在命令行直接运行
webpack-dev-server
来进行打包,发现报错,此时需要借助于package.json
文件中的指令,来进行运行webpack-dev-server
命令,在scripts
节点下新增"dev": "webpack-dev-server"
指令,发现可以进行实时打包,但是dist目录下并没有生成bundle.js
文件,这是因为webpack-dev-server
将打包好的文件放在了内存中
把
bundle.js
放在内存中的好处是:由于需要实时打包编译,所以放在内存中速度会非常快这个时候访问webpack-dev-server启动的
http://localhost:8080/
网站,发现是一个文件夹的面板,需要点击到src目录下,才能打开我们的index首页,此时引用不到bundle.js文件,需要修改index.html中script的src属性为:<script src="../bundle.js"></script>
为了能在访问
http://localhost:8080/
的时候直接访问到index首页,可以使用--contentBase src
指令来修改dev指令,指定启动的根目录:1
"dev": "webpack-dev-server --contentBase src"
同时修改index页面中script的src属性为
<script src="bundle.js"></script>
使用html-webpack-plugin
插件配置启动页面
由于使用--contentBase
指令的过程比较繁琐,需要指定启动的目录,同时还需要修改index.html中script标签的src属性,所以推荐大家使用html-webpack-plugin
插件配置启动页面.
运行
cnpm i html-webpack-plugin --save-dev
安装到开发依赖两个作用:自动在内存中根据指定页面生成一个内存的页面,自动把打包好的 bundle.js 追加到页面中去
修改
webpack.config.js
配置文件如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 导入处理路径的模块
var path = require("path");
// 导入自动生成HTMl文件的插件
var htmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: path.resolve(__dirname, "src/js/main.js"), // 项目入口文件
output: {
// 配置输出选项
path: path.resolve(__dirname, "dist"), // 配置输出的路径
filename: "bundle.js", // 配置输出的文件名
},
plugins: [
// 添加plugins节点配置插件
new htmlWebpackPlugin({
template: path.resolve(__dirname, "src/index.html"), //模板路径
filename: "index.html", //自动生成的HTML文件的名称
}),
],
};修改
package.json
中script
节点中的dev指令如下:1
"dev": "webpack-dev-server"
将index.html中script标签注释掉,因为
html-webpack-plugin
插件会自动把bundle.js注入到index.html页面中!
实现自动打开浏览器、热更新和配置浏览器的默认端口号
注意:热更新在JS中表现的不明显,可以从一会儿要讲到的CSS身上进行介绍说明!
方式1: 修改
package.json
的script节点如下,其中--open
表示自动打开浏览器,--port 4321
表示打开的端口号为4321,--hot
表示启用浏览器热更新:1
"dev": "webpack-dev-server --hot --port 4321 --open"
方式2:修改
webpack.config.js
文件,新增
devServer
节点如下:1
2
3
4
5
6
7
8
9
10module.exports = {
devServer: { // 这是配置 dev-server 命令参数的第二种形式,相对来说,这种方式麻烦一些
// --open --port 3000 --contentBase src --hot
open: true, // 自动打开浏览器
port: 3000, // 设置启动时候的运行端口
contentBase: 'src', // 指定托管的根目录
hot: true // 启用热更新 的 第1步
}
...
}在头部引入
webpack
模块:1
var webpack = require("webpack");
在
plugins
节点下新增:1
2
3
4
5
6
7
8
9
10
11
12
13
14module.exports = {
devServer: { // 这是配置 dev-server 命令参数的第二种形式,相对来说,这种方式麻烦一些
// --open --port 3000 --contentBase src --hot
open: true, // 自动打开浏览器
port: 3000, // 设置启动时候的运行端口
contentBase: 'src', // 指定托管的根目录
hot: true // 启用热更新 的 第1步
},
plugins: [ // 配置插件的节点
new webpack.HotModuleReplacementPlugin(), // new 一个热更新的 模块对象
],
module: {
}
}
使用webpack打包css文件
运行
cnpm i style-loader css-loader --save-dev
修改
webpack.config.js
这个配置文件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15module.exports = {
devServer: {
// 这是配置 dev-server 命令参数的第二种形式,相对来说,这种方式麻烦一些
},
plugins: [
// 配置插件的节点
],
module: {
// 用来配置第三方loader模块的
rules: [
// 文件的匹配规则
{ test: /\.css$/, use: ["style-loader", "css-loader"] }, //处理css文件的规则
],
},
};注意:
use
表示使用哪些模块来处理test
所匹配到的文件;use
中相关loader模块的调用顺序是从后向前调用的
使用webpack打包less文件
运行
cnpm i less-loader less -D
修改
webpack.config.js
这个配置文件:1
{ test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
使用webpack打包sass文件
运行
cnpm i sass-loader node-sass --save-dev
在
webpack.config.js
中添加处理sass文件的loader模块:1
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] }
使用webpack处理css中的路径
运行
cnpm i url-loader file-loader --save-dev
在
webpack.config.js
中添加处理url路径的loader模块:1
{ test: /\.(png|jpg|gif)$/, use: 'url-loader' }
可以通过
limit
指定进行base64编码的图片大小;只有小于指定字节(byte)的图片才会进行base64编码:1
{ test: /\.(png|jpg|gif)$/, use: 'url-loader?limit=43960' },
使用babel处理高级JS语法
运行
cnpm i babel-core babel-loader babel-plugin-transform-runtime --save-dev
安装babel的相关loader包运行
cnpm i babel-preset-es2015 babel-preset-stage-0 --save-dev
安装babel转换的语法在
webpack.config.js
中添加相关loader模块,其中需要注意的是,一定要把node_modules
文件夹添加到排除项:1
{ test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }
在项目根目录中添加
.babelrc
文件,并修改这个配置文件如下:1
2
3
4{
"presets":["env", "es2015", "stage-0"],
"plugins":["transform-runtime"]
}注意:语法插件
babel-preset-es2015
可以更新为babel-preset-env
,它包含了所有的ES相关的语法;
配置.vue组件页面的解析
运行
cnpm i vue -S
将vue安装为运行依赖;运行
cnpm i vue-loader vue-template-compiler -D
将解析转换vue的包安装为开发依赖;运行
cnpm i style-loader css-loader -D
将解析转换CSS的包安装为开发依赖,因为.vue文件中会写CSS样式;在
webpack.config.js
中,添加如下module
规则:1
2
3
4
5
6module: {
rules: [
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
{ test: /\.vue$/, use: "vue-loader" },
];
}创建
App.js
组件页面:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25<template>
<!-- 注意:在 .vue 的组件中,template 中必须有且只有唯一的根元素进行包裹,一般都用 div 当作唯一的根元素 -->
<div>
<h1>这是APP组件 - {{ msg }}</h1>
<h3>我是h3</h3>
</div>
</template>
<script>
// 注意:在 .vue 的组件中,通过 script 标签来定义组件的行为
// 需要使用 ES6 中提供的 export default 方式,导出一个vue实例对象
export default {
data() {
return {
msg: "OK",
};
},
};
</script>
<style scoped>
h1 {
color: red;
}
</style>创建
main.js
入口文件:1
2
3
4
5
6
7
8
9
10
11// 导入 Vue 组件
import Vue from "vue";
// 导入 App组件
import App from "./components/App.vue";
// 创建一个 Vue 实例,使用 render 函数,渲染指定的组件
var vm = new Vue({
el: "#app",
render: (c) => c(App),
});
构建的Vue项目中使用模板对象
在
webpack.config.js
中添加resolve
属性:1
2
3
4
5
6
7
8module.exports = {
resolve: {
alias: {
// 修改 Vue 被导入时候的包的路径
// "vue$": "vue/dist/vue.js"
},
},
};
webpack 中如何使用 vue
安装vue的包:
cnpm i vue -S
由于 在 webpack 中,推荐使用 .vue 这个组件模板文件定义组件,所以,需要安装 能解析这种文件的 loader
cnpm i vue-loader vue-template-complier -D
在 main.js 中,导入 vue 模块
import Vue from 'vue'
- 在 webpack 中, 使用
import Vue from 'vue'
导入的 Vue 构造函数,功能不完整,只提供了 runtime-only 的方式,并没有提供 像网页中那样的使用方式 - 完整
import Vue from '../node_modules/vue/dist/vue.js'
- 包的查找规则:
- 找 项目根目录中有没有 node_modules 的文件夹
- 在 node_modules 中 根据包名,找对应的 vue 文件夹
- 在 vue 文件夹中,找 一个叫做 package.json 的包配置文件
- 在 package.json 文件中,查找 一个 main 属性【main属性指定了这个包在被加载时候,的入口文件】
- 在 webpack 中, 使用
定义一个 .vue 结尾的组件,其中,组件有三部分组成:
template
,script
,style
使用
import login from './login.vue'
导入这个组件创建 vm 的实例
var vm = new Vue({ el: '#app', render: c => c(login) })
在index页面中创建一个 id 为 app 的 div 元素,作为我们 vm 实例要控制的区域;
1
2<!-- 这是容器 -->
<div id="app"></div>
vue组件集成路由模块
导入路由模块:
1
2
3import VueRouter from "vue-router";
// 安装路由模块:
Vue.use(VueRouter);导入需要展示的组件:
1
2import login from "./components/account/login.vue";
import register from "./components/account/register.vue";创建路由对象:
1
2
3
4
5
6
7
8
9var router = new VueRouter({
routes: [
{ path: "/", redirect: "/login" },
{ path: "/login", component: login },
{ path: "/register", component: register },
],
});将路由对象,挂载到 Vue 实例上:
1
2
3
4
5
6
7
8var vm = new Vue({
el: "#app",
// render: c => { return c(App) }
render(c) {
return c(App);
},
router, // 将路由对象,挂载到 Vue 实例上
});改造App.vue组件,在 template 中,添加
router-link
和router-view
:1
2
3<router-link to="/login">登录</router-link>
<router-link to="/register">注册</router-link>
<router-view></router-view>
ES6中语法
- 使用
export default
和export
导出模块中的成员; 对应ES5中的module.exports
和export
- 使用
import from
和import '路径'
还有import {a, b} from '模块标识'
导入其他模块 - 使用箭头函数:
(a, b)=> { return a-b; }
import
import 用来引入 第三方程序, 是es6的语法标准
1
2import Vue from "vue";
import Router from "vue-router";上面两个,是引入 package.json 中的第三方包,所以可以直接
import ... from <包名>
.1
import SayHi from "@/components/SayHi";
上面这个,在from后面,有
@
符号, 表示是在本地文件系统中,引入文件.@
代表 源代码目录,一般是 src. 在@
出现之前,我们在编码的时候也会这样写:1
2
3
4
5
6import Swiper from '../components/swiper'
import SwiperItem from '../components/swiper-item'
import XHeader from '../components/header/x-header'
import store from '../vuex/store'
// 重命名
import {store as store 1 } from '../vuex/store'大量使用了
../..
这样的代码,会引起代码的混乱. 所以推荐使用@
方法.import * as obj from 'XXXX'
的作用: 将若干个 export 导出的内容组合成一个对象返回假设 xxx 模块里的内容如下
1
2
3
4
5
6
7
8
9export function test(){
return 'test';
}
export function login(){
return 'login';
}
export defautl de;调用函数:
obj.test() obj.login()
如果是不带
* as
, 即:import xxx from xxxx
, 只会到处默认的对象作为一个对象。
export default {..}
我们会看到,在每个 vue文件中的
<script>
代码端中,都会存在这个代码.它的作用是方便其他代码对这个代码 的引用.对于vue程序员,我们就记住这个写法就好了. 没它不行, 有它也没太大意义. 鸡肋代码. 参考: http://www.jianshu.com/p/710e66547bbb
在ES6之前js没有一个统一的模块定义方式,流行的定义方式有AMD,CommonJS等,而ES6从语言层面对定义模块的方式进行了统一。
假设有:
lib/math.js
文件内容如下:1
2
3
4export function sum(x, y) {
return x + y;
}
export var pi = 3.141593;app.js
文件内容如下:1
2import * as math from "lib/math";
alert("2π = " + math.sum(math.pi, math.pi));other_app.js
文件内容如下:1
2import { sum, pi } from "lib/math";
alert("2π = " + sum(pi, pi));export default { ... }
则是暴露出一段没有名字的代码, (不像export function sum(a,b){ .. }
这样有个名字(叫sum) . )
一些简写
我们会发现,这样的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13<script>
export default {
data () {
return { }
}
}
</script>
let title = '...';
return {
title
}实际上,上面的代码是一种简写形式,它等同于下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14<script>
export default {
data: function() {
return { }
}
}
</script>
let title = '...';
return {
title: title;
}函数简写:
addName: function() {}
=>addName() {}
属性的简写:
data:data
=>data
.也就是说,可以直接在对象中直接写入变量,当函数的返回值为对象时候,使用简写方式更加简洁直观:1
2
3
4
5
6
7
8
9
10
11
12function getPerson() {
let name = "Jack";
let age = 10;
return { name, age };
// 等价于
// return {
// name : name,
// age : age
// }
}
getPerson();v-on:click
等于@click
v-bind:xx
等于:xx
let, var, 常量 与全局变量
声明本地变量,使用 let 或者 var .两者的区别是:
- var: 有可能引起 变量提升,或者块级作用域的问题.
- let: 就是为了解决这两个问题存在的.
- 最佳实践: 多用let, 少用var. 遇到诡异变量问题的时候,就查查是不是var的问题.
es6 之前,JavaScript 并没有块级作用域,所谓的块,就是大括号里面的语句所组成的代码块,比如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34function fire(bool) {
if (bool) {
var foo = "bar";
}
console.log(foo);
}
fire(true); //=> bar
// 虽然变量 foo 位于 if 语句的代码块中,但是 JavaScript 并没有块级作用域的概念,因此被添加到了当前的执行环境
// 即函数中,在函数内都可以访问到。
function fire(bool) {
if (bool) {
var foo = "bar";
} else {
console.log(foo);
}
}
fire(false); //=> undefined
// 直接访问一个未定义的变量,会报错:
console.log(nope); //=> Uncaught ReferenceError: nope is not defined
// 上述的例子中,会返回 undefined。也就是说,变量的定义被提升到了作用域的顶部 等价于:
function fire(bool) {
// 在 JavaScript 中,声明但是未赋值的变量会被赋值为 undefined,因此,结果输出 undefined。
var foo;
if (bool) {
foo = "bar";
} else {
console.log(foo);
}
}
fire(false);let解决var的问题
- 定义的变量只在代码块内有效
- 变量不存在变量提升:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function fire(bool) {
if (bool) {
let foo = "bar";
}
console.log(foo);
}
fire(true); //=> Uncaught ReferenceError: foo is not defined
function fire(bool) {
if (bool) {
let foo = "bar";
} else {
console.log(foo);
}
}
fire(false); //=> Uncaught ReferenceError: foo is not defined常量:const 与 let 的基本用法相同,定义的变量都具有块级作用域,也不会发生变量提升。不同的地方在于,const 定义的变量,只能赋值一次。
- 在一些不需要重复赋值的场合可以使用 const
1
2
3
4
5
6
7
8const a = 1;
a = 2; // Uncaught TypeError: Assignment to constant variable.
++a; // Uncaught TypeError: Assignment to constant variable.
// 对于数组和对象来说,值是可以改变的:
const arr = ["a", "b", "c"];
arr.push("d");
arr.pop();对于全局变量, 直接在 index.html 中声明即可. 例如:
1
window.title = "我的博客列表";
箭头函数 =>
跟coffeescript一样, es script也可以通过箭头来表示函数.
1
2
3
4
5
6
7.then(response => ... );
// 等同于:
.then(function (response) {
// ...
})在 Vue 中,使用箭头函数的最大好处就是可以让 this 指向 Vue 实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36var vm = new Vue({
el:'#root',
data:{
tasks:[]
},
mounted(){
axios.get('/tasks')
.then(function (response) {
// 不能使用this.tasks 回调函数的 this 指向全局对象 window
// 需要通过 vm 来访问实例的方
vm.tasks = response.data;
})
}
});
// 使用箭头函数
new Vue({
el:'#root',
data:{
tasks:[]
},
mounted(){
axios.get('/tasks')
// 箭头函数的 this 对象始终指向定义函数时所在的对象
.then(response => this.tasks = response.data);
}
});
// 相当于:
mounted(){
var that = this;
axios.get('/tasks')
.then(function (response) {
that.tasks = response.data;
})
}
模板字符串
模板字符串为 Vue 的组件模板定义带来了巨大的便利,在此之前,需要这样定义一个模板:
1
2
3
4
5
6
7
8
9
10
11
12
13
14let template = '<div class="container"><p>Foo</p></div>';
// 如果要写成多行,可以用反斜杠:
let template =
'<div class="container">\
<p>Foo</p>\
</div>';
// 或者使用数组形式:
let template = ['<div class="container">', "<p>Foo</p>", "</div>"].join("");
// 如果要嵌入变量,可以写成:
let name = "jack";
let template = '<div class="container"><p>' + name + "</p></div>";使用模板字符串,则可以方便的在多行里面编写模板:
- 模板字符串的空格和换行会被保留,可以使用 trim() 方法从字符串中移除 前导 空格、尾随空格和行终止符。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20let template = `
<div class="container">
<p>Foo</p>
</div>
`;
// 移除 前导 空格、尾随空格和行终止符。
let template = `
<div class="container">
<p>Foo</p>
</div>
`.trim();
// 嵌入变量或者表达式
let name = "jack";
let template = `
<div class="container">
<p>${name} is {100 + 100}</p>
</div>
`.trim();
默认参数
在 es6 之前,JavaScript 不能像 PHP 那样支持默认参数,因此需要自己手动定义:
1
2
3
4
5function takeDiscount(price, discount) {
discount = discount || 0.9;
return price * discount;
}
takeDiscount(100);es6 则允许定义默认参数
1
2
3
4function takeDiscount(price, discount = 0.9) {
return price * discount;
}
takeDiscount(100);甚至可以以函数形式传递参数:
1
2
3
4
5
6
7
8function getDiscount() {
return 0.9;
}
function takeDiscount(price, discount = getDiscount()) {
return price * discount;
}
takeDiscount(100);
rest 参数
先从函数的参数传递说起:在 JavaScript 中,函数参数实际上以数组的方式进行传递,参数会被保存在 arguments 数组中
1
2
3
4
5function sum(a, b, c) {
let total = a + b + c;
return total;
}
sum(1, 2, 3);上例等价于:
1
2
3
4
5function sum() {
let total = arguments[0] + arguments[1] + arguments[2];
return total;
}
sum(1, 2, 3);不过 arguments 不单单包括参数,也包括了其他东西,因此没法直接用数组函数来操作 arguments。如果要扩展成任意多个数值相加,可以使用循环:
1
2
3
4
5
6
7
8function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total = total + arguments[i];
}
return total;
}
sum(1, 2, 3, 4, 6);es6 则提供了 rest 参数来访问多余变量,上例等价于:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function sum(...num) {
let total = 0;
for (let i = 0; i < num.length; i++) {
total = total + num[i];
}
return total;
}
sum(1, 2, 3, 4, 6);
// 可以以变量形式进行传递:
function sum(...num) {
let total = 0;
for (let i = 0; i < num.length; i++) {
total = total + num[i];
}
return total;
}
let nums = [1, 2, 3, 4, 6];
sum(...nums);在函数中体内,num 就是单纯由参数构成的数组,因此可以用数组函数 reduce 来实现同样的功能:
1
2
3
4
5
6function sum(...num) {
return num.reduce((preval, curval) => {
return preval + curval;
});
}
sum(1, 2, 3, 4, 6);...
还可以与其他参数结合使用,只需要将其他参数放在前面即可:1
2
3
4
5
6
7
8
9
10
11function sum(total = 0, ...num) {
return (
total +
num.reduce((preval, curval) => {
return preval + curval;
})
);
}
let nums = [1, 2, 3, 4];
sum(100, ...nums);
解构赋值
解构赋值可以方便的取到对象的可遍历属性:
1
2
3
4
5
6
7
8
9
10
11
12
13let person = {
firstname: "steve",
lastname: "curry",
age: 29,
sex: "man",
};
let { firstname, lastname } = person;
console.log(firstname, lastname);
// 等价于
// let firstname = person.firstname;
// let lastname = person.lastname;可以将其用于函数传参中:但是不建议大家这样使用. 有一些奇淫技巧的感觉. 另外,浏览器和一些第三方支持的不是太好, 我们在实际项目中,曾经遇到过与之相关的很奇葩的问题.
1
2
3
4
5
6
7
8function greet({ firstname, lastname }) {
console.log(`hello,${firstname}.${lastname}!`);
}
greet({
firstname: "steve",
lastname: "curry",
});
require
require的基本语法:
- 在导出的文件中定义module.export,导出的对象的类型不予限定(可以是任何类型,字符串,变量,对象,方法),
- 在引入的文件中调用require()方法引入对象即可
换一种说法就是require相当于module.exports的传送门,module.exports后面的内容是什么,require的结果就是什么(对象、数字、字符串、函数……),再把require的结果赋值给某个变量
1
2
3
4
5
6
7
8
9
10//a.js中
module.export = {
a: function () {
console.log(666);
},
};
//b.js中
var obj = require("../a.js");
obj.a(); //666
import
核心概念:导出的对象必须与模块中的值一一对应,换一种说法就是导出的对象与整个模块进行解构赋值。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14//tt.js中
export default{
//(这种方法是最常使用的方法,加入default关键字代表在import时可以使用任意变量名并且不需要花括号{})
b: function(){
console.log("这是一个函数")
}
}
export function(){ //导出函数
}
// 解构赋值语法(as关键字在这里表示将newF作为aa的数据接口暴露给外部,外部不能直接访问aa)
export {newF as aa ,bb,cc}1
2
3
4
5
6
7
8//bb.js中
import aa from '...' //import常用语法(需要export中带有default关键字)可以任意指定import的名称
import {...} from '...' // 基本方式,导入的对象需要与export对象进行解构赋值。
import aa as As from '...' //使用as关键字,这里表示将aa代表As引入(当变量名称有冲突时可以使用这种方式解决冲突)
import {a as Aa,b,c} //as关键字的其他使用方法
二者区别
require和import相互转换使用:
1
2
3import list from "./list";
//等价于
var list = require("./list");require和import分别使用在:
- require 是赋值过程并且是运行时才执行,也就是异步加载。
- require可以理解为一个全局方法,因为它是一个方法所以意味着可以在任何地方执行。
- import 是解构过程并且是编译时执行。
- import必须写在文件的顶部。
require和import的优缺点比较:
- require的性能相对于import稍低,因为require是在运行时才引入模块并且还赋值给某个变量,而import只需要依据import中的接口在编译时引入指定模块所以性能稍高。
获取对象属性
js对象属性 通过点(.) 和 方括号([]) 的不同之处
点操作符: 静态的。右侧必须是一个以属性名称命名的简单标识符。
- 属性名用一个标识符来表示。标识符必须直接出现再js程序中,它们不是数据类型,因此程序无法修改它们。
中括号操作符: 动态的。方括号里必须是一个计算结果为字符串的表达式,
- 属性名通过字符串表示。字符串是js的数据类型
主要有以下区别:
中括号法可以用变量作为属性名,而点方法不可以
1
2
3
4
5var obj = {};
obj.name = "张三";
var myName = "name";
console.log(obj.myName); //undefined,访问不到对应的属性
console.log(obj[myName]); //张三
中括号法可以用数字作为属性名,而点语法不可以
1
2
3
4
5
6
7var obj1 = {};
// obj1.1=1;//Unexpected number
obj1[2] = 2;
// console.log(obj1.1)
console.log(obj1[2]); //2
// console.log(obj1.2)
console.log(obj1); //{2: 2}中括号法可以动态访问的属性名,可以在程序运行时创建和修改属性,点操作符就不行!
1
2
3
4
5
6
7
8
9
10
11
12var customer = {};
var addr = ["北京", "上海", "广州", "深圳"];
for (i = 0; i < 4; i++) {
customer["address" + i] = addr[i];
}
console.log(addr);
console.log(customer);
var str = "";
for (i = 0; i < 4; i++) {
str += customer["address" + i] + "\t";
}
console.log(str);中括号法可以使用js的关键字和保留字作为属性名,而点语法不可以(尽量避免在变量或者属性中使用关键字或保留字)
1
2
3
4
5
6
7
8
9person["first name"] = "gogo2"; //first name包含一个空格
console.log(person["first name"]);
// console.log(person.first name)//书写都通不过
person["for"] = "gogo_for"; //first name包含一个空格
person.if = "gogo_if"; //first name包含一个空格
console.log(person["for"]); //gogo_for
console.log(person.for); //gogo_for
console.log(person["if"]); //gogo_if
console.log(person.if); //gogo_if简单利用:在数组原型链上增加一个去重得的方法,并能实现链式写法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24Array.prototype.myDistinct = function () {
var obj = {};
for (var i = 0; i < this.length; i++) {
var cur = this[i];
if (obj[cur] == cur) {
//对象的属性名不能重复,重复就是修改;让对象的属性名和属性值相同,借以保存不重复的数组元素
//--中括号法可以用数字作为属性名,而点语法不可以;
this[i] = this[this.length - 1];
this.length--;
i--;
continue;
}
obj[cur] = cur;
}
// console.log(obj);//{2: 2, 3: 3, 4: 4, 5: 5}
obj = null;
return this;
};
var arr = [5, 3, 3, 4, 5, 4, 2];
arr.myDistinct().sort().pop();
console.log(arr); //[2, 3, 4]
var arr1 = [3, "a", 4, 5, 4, "b", "a"];
console.log(arr1.myDistinct()); //[3, "a", 4, 5, "b"]