Vue2学习笔记

1、Vue概述

Vue:前端体系、前后端分离

一套用于构建用户界面渐进式JavaScript框架(发布2014年2月)

  • 构建用户界面: 数据 => 界面
  • 渐进式: Vuek跨域自底向上逐层的应用
    • 简单应用:只需一个轻量小巧的核心库
    • 复杂应用:可以引入各式各样的Vue插件

特点:

  1. 采用组件化模式,提高代码复用率、且让代码更好维护
  2. 声明式编码,让编码人员无需直接操作DOM,提高开发效率
  3. 使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点

学Vue之前需掌握:

ES6语法规范

ES6模块化

包管理器

原型、原型链

数组常用方法

axios
promise …


  • MVC(同步通信为主) Model View Controller
  • MVP(异步通信为主) Model View Presenter
  • MVVM(异步通信为主)
    • Model 模型层,在这里表示JavaScript对象
    • View 视图层,在这里表示DOM
    • ViewModel:连接视图和数据的中间件,Vue.js就是MVVM中的ViewModel层的实现者

MVVM架构

不允许数据和视图直接通信的,只能通过ViewModel来通信,而ViewModel就是定义了一个Observer观察者

  • ViewModel 能够观察到数据的变化,并对视图对应的内容进行更新
  • ViewModel 能够监听到视图的变化,并能够通知数据发生改变

Vue.js就是一个MVVM的实现者,核心就是实现了DOM监听与数据绑定

  • 低耦合
  • 可复用
  • 独立开发
  • 可测试

**交互式:**页面或界面会根据用户的行为(键盘、鼠标、触摸等)进行相应的变化。

**响应式:**页面或解码会根据屏幕和浏览器的不同而显示不同的样式(排列、显隐)。

“交互式”是针对用户(人)的,“响应式”是针对设备的。

2、Vue核心

2.1 第一个Vue程序测试

1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;
2.root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法;
3.root容器里的代码被称为【Vue模板】;
4.Vue实例和容器是一一对应的;
5.真实开发中只有一个Vue实例,并且会配合着组件一起使用;
6.{{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性;(插值语法)
7.一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新;

注意区分:js表达式 和 js代码(语句)
1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方:
(1). a
(2). a+b
(3). demo(1)
(4). x === y ? 'a' : 'b'

2.js代码(语句)
(1). if(){}
(2). for(){}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>

<!--view层  模板-->
<div id="app">
    {{message}}
</div>


<!--1.导入Vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<script>
    var vm = new Vue({
        //element绑定一个标签
        el:"#app",
        //model  数据
        data:{
            message:"hello,vue!"
        }
    });
</script>
</body>
</html>

在谷歌浏览器中F12,打开控制台输入

vm.message=“hello,yg” 可以实现 实时更新数据

此过程并没有操作DOM对象,就让页面的内容发生了变化,者就是借助了Vue的数据绑定功能实现的,MVVM模式中要求ViewModel层就是使用观察者模式来实现数据的监听与绑定,以做到数据与视图的快速响应!

2.2 Vue基本语法

Vue模板语法有2大类:
1.插值语法:
功能:用于解析标签体内容。
写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性。
2.指令语法:
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)。
举例:v-bind:href=“xxx” 或 简写为 :href=“xxx”,xxx同样要写js表达式
且可以直接读取到data中的所有属性。
备注:Vue中有很多的指令,且形式都是:v-???,此处我们只是拿v-bind举个例子。

2.3 单向以及双向绑定

Vue中有2种数据绑定的方式:
1.单向绑定(v-bind):数据只能从data流向页面。
2.双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。
备注:
1.双向绑定一般都应用在表单类元素上(如:input、select等)
2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。

这里就是通过V-model标签可以让表单里控件的属性值data中的数据双向绑定

v-model会忽略所有表单元素的value、checked、selected特性的初始值而总是将Vue实例的数据作为数据来源。你应该通过JavaScript在组件中的data选项中声明初始值!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>初始Vue</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <div id="app">
        单向绑定 <input type="text" :value="name"> <br>
        双向绑定 <input type="text" v-model="name2"> <br>
    </div>
<script>
    Vue.config.productionTip = false;//阻止vue在启动时生成生产提示
    new Vue({
        el:"#app",
        data:{
            name:'input1',
            name2:'input2'
        }
    })
</script>    
</body>
</html>

2.4 el与data的两种写法

data与el的2种写法
1.el有2种写法
(1).new Vue时候配置el属性。
(2).先创建Vue实例,随后再通过vm.$mount(’#app’)指定el的值。
2.data有2种写法
(1).对象式
(2).函数式
如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。
3.一个重要的原则:
由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <div id="app">
        {{name}}
    </div>
<script>
    Vue.config.productionTip = false;//阻止vue在启动时生成生产提示
    //el的两种方式
    // const vm = new Vue({
    //     // el:"#app",
    //     data:{
    //         name:'hello'
    //     }
    // })
    // vm.$mount('#app')
    
    //data的两种方式
    new Vue({
        el:'#app',
        // data:{  //对象式
        //     name:"123123123"
    
        // }

        // data:function(){ //函数式
        //     return{

        //     }
        // }
        data(){ //简化函数式
            console.log(this) //this指的是Vue实例
            return{
                name:'123'
            }
        }
    })
</script>    
</body>
</html>

2.5 MVVM模型

  1. M:模型(Model) :对应 data 中的数据

  2. V:视图(View) :模板

  3. VM:视图模型(ViewModel) : Vue 实例对象

观察发现:
1.data中所有的属性,最后都出现在了vm身上。
2.vm身上所有的属性 及 Vue原型上所有属性,在Vue模板中都可以直接使用。

1

2.6 事件处理

事件的基本使用:
1.使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名;
2.事件的回调需要配置在methods对象中,最终会在vm上;
3.methods中配置的函数,不要用箭头函数!否则this就不是vm了;
4.methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
5.@click=“demo” 和 @click=“demo($event)” 效果一致,但后者可以传参;

事件修饰符

Vue中的事件修饰符:
1.prevent:阻止默认事件(常用);
2.stop:阻止事件冒泡(常用);
3.once:事件只触发一次(常用);
4.capture:使用事件的捕获模式;
5.self:只有event.target是当前操作的元素时才触发事件;
6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕;

键盘事件

1.Vue中常用的按键别名:
回车 => enter
删除 => delete (捕获“删除”和“退格”键)
退出 => esc
空格 => space
换行 => tab (特殊,必须配合keydown去使用)
上 => up
下 => down
左 => left
右 => right

2.Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)

3.系统修饰键(用法特殊):ctrl、alt、shift、meta
(1).配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
(2).配合keydown使用:正常触发事件。

4.也可以使用keyCode去指定具体的按键(不推荐)

5.Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名

2.7 计算属性和监视

计算属性

1.定义:要用的属性不存在,要通过已有属性计算得来。
2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
3.get函数什么时候执行?
(1).初次读取时会执行一次。
(2).当依赖的数据发生改变时会被再次调用。
4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
5.备注:
1.计算属性最终会出现在vm上,直接读取使用即可。
2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。

计算属性简写版本(不需要set函数)
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>vue</title>
        <!-- 引入Vue -->
        <script type="text/javascript" src="../js/vue.js"></script>
    </head>
    <body>

        <div id="root">
            姓:<input type="text" v-model="x"> <br><br>
            名:<input type="text" v-model="y"> <br><br>
            姓名: <span>{{fullName}}</span>
        </div>
    </body>

    <script type="text/javascript">
        Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

        const vm = new Vue({
            el:'#root',
            data:{
                x:'张',
                y:'三',
            },
            computed:{
                //完整写法
                /* fullName:{
					get(){
						console.log('get被调用了')
						return this.x + '-' + this.y
					},
					set(value){
						console.log('set',value)
						const arr = value.split('-')
						this.x = arr[0]
						this.y = arr[1]
					}
				} */
                //简写
                fullName(){  //直接读取数据即可
                    console.log('get被调用了')
                    return this.x + '-' + this.y
                }
            }
        })
    </script>
</html>
监视watch

监视属性watch:
1.当被监视的属性变化时, 回调函数自动调用, 进行相关操作
2.监视的属性必须存在,才能进行监视!!
3.监视的两种写法:
(1).new Vue时传入watch配置
(2).通过vm.$watch监视

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
    el:'#root',
    data() {
        return {
            isHot:true
        }
    },
    computed:{
        info(){
            return this.isHot?'炎热':'凉爽'
        }
    },
    methods: {
        changeWeather(){
            this.isHot = !this.isHot
        }
    },
    // watch:{
    //     isHot:{
    //         immediate:true,//为true代表初始化的时候就执行handler方法
    //         //handler 是当isHot属性发生改变的时候执行
    //         handler(newValue,oldValue){
    //             console.log('isHot被修改了',newValue,oldValue)
    //         }
    //     }
    // }

})
//第二种监视
vm.$watch('isHot',{
    immediate:true,//为true代表初始化的时候就执行handler方法
    //handler 是当isHot属性发生改变的时候执行
    handler(newValue,oldValue){
        console.log('isHot被修改了',newValue,oldValue)
    }
})
</script>
深度监视

深度监视:
(1).Vue中的watch默认不监测对象内部值的改变(一层)。
(2).配置deep:true可以监测对象内部值改变(多层)。
备注:
(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
(2).使用watch时根据数据的具体结构,决定是否采用深度监视。

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
    el:'#root',
    data:{
        isHot:true,
        numbers:{
            a:1,
            b:1,
            c:{
                d:{
                    e:100
                }
            }
        }
    },
    computed:{
        info(){
            return this.isHot ? '炎热' : '凉爽'
        }
    },
    methods: {
        changeWeather(){
            this.isHot = !this.isHot
        }
    },
    watch:{
        isHot:{
            // immediate:true, //初始化时让handler调用一下
            //handler什么时候调用?当isHot发生改变时。
            handler(newValue,oldValue){
                console.log('isHot被修改了',newValue,oldValue)
            }
        },
        //监视多级结构中某个属性的变化
        /* 'numbers.a':{
					handler(){
						console.log('a被改变了')
					}
				} */
        //监视多级结构中所有属性的变化
        numbers:{
            deep:true,
            handler(){
                console.log('numbers改变了')
            }
        }
    }
})

</script>
监视简写版本(只需要handler方法的情况下)
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>天气案例_监视属性_简写</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root">
			<h2>今天天气很{{info}}</h2>
			<button @click="changeWeather">切换天气</button>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
		
		const vm = new Vue({
			el:'#root',
			data:{
				isHot:true,
			},
			computed:{
				info(){
					return this.isHot ? '炎热' : '凉爽'
				}
			},
			methods: {
				changeWeather(){
					this.isHot = !this.isHot
				}
			},
			watch:{
				//正常写法
				/* isHot:{
					// immediate:true, //初始化时让handler调用一下
					// deep:true,//深度监视
					handler(newValue,oldValue){
						console.log('isHot被修改了',newValue,oldValue)
					}
				}, */
				//简写
				/* isHot(newValue,oldValue){
					console.log('isHot被修改了',newValue,oldValue,this)
				} */
			}
		})

		//正常写法
		/* vm.$watch('isHot',{
			immediate:true, //初始化时让handler调用一下
			deep:true,//深度监视
			handler(newValue,oldValue){
				console.log('isHot被修改了',newValue,oldValue)
			}
		}) */

		//简写
		/* vm.$watch('isHot',(newValue,oldValue)=>{
			console.log('isHot被修改了',newValue,oldValue,this)
		}) */

	</script>
</html>
计算属性和监视的对比

computed和watch之间的区别:
1.computed能完成的功能,watch都可以完成。
2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
两个重要的小原则:
1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。

2.8 class和style绑定

绑定样式:

​ 1. class样式

​ 写法:class=“xxx” xxx可以是字符串、对象、数组。

​ 字符串写法适用于:类名不确定,要动态获取。

​ 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。

​ 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。

​ 2. style样式

​ :style="{fontSize: xxx}"其中xxx是动态值。

​ :style="[a,b]"其中a、b是样式对象。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>绑定样式</title>
        <style>
            .basic{
                width: 400px;
                height: 100px;
                border: 1px solid black;
            }

            .happy{
                border: 4px solid red;;
                background-color: rgba(255, 255, 0, 0.644);
                background: linear-gradient(30deg,yellow,pink,orange,yellow);
            }
            .sad{
                border: 4px dashed rgb(2, 197, 2);
                background-color: gray;
            }
            .normal{
                background-color: skyblue;
            }

            .atguigu1{
                background-color: yellowgreen;
            }
            .atguigu2{
                font-size: 30px;
                text-shadow:2px 2px 10px red;
            }
            .atguigu3{
                border-radius: 20px;
            }
        </style>
        <script type="text/javascript" src="../js/vue.js"></script>
    </head>
    <body>
        <!-- 
绑定样式:
1. class样式
写法:class="xxx" xxx可以是字符串、对象、数组。
字符串写法适用于:类名不确定,要动态获取。
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
2. style样式
:style="{fontSize: xxx}"其中xxx是动态值。
:style="[a,b]"其中a、b是样式对象。
-->
        <!-- 准备好一个容器-->
        <div id="root">
            <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
            <div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/>

            <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
            <div class="basic" :class="classArr">{{name}}</div> <br/><br/>

            <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
            <div class="basic" :class="classObj">{{name}}</div> <br/><br/>

            <!-- 绑定style样式--对象写法 -->
            <div class="basic" :style="styleObj2">{{name}}</div> <br/><br/>
            <!-- 绑定style样式--数组写法 -->
            <div class="basic" :style="styleArr">{{name}}</div>
        </div>
    </body>

    <script type="text/javascript">
        Vue.config.productionTip = false

        const vm = new Vue({
            el:'#root',
            data:{
                name:'尚硅谷',
                mood:'normal',
                classArr:['atguigu1','atguigu2','atguigu3'],
                classObj:{
                    atguigu1:false,
                    atguigu2:false,
                },
                styleObj:{
                    fontSize: '40px',
                    color:'red',
                },
                styleObj2:{
                    backgroundColor:'orange'
                },
                styleArr:[
                    {
                        fontSize: '40px',
                        color:'blue',
                    },
                    {
                        backgroundColor:'red'
                    }
                ]
            },
            methods: {
                changeMood(){
                    const arr = ['happy','sad','normal']
                    const index = Math.floor(Math.random()*3)
                    this.mood = arr[index]
                }
            },
        })
    </script>

</html>

2.9 条件渲染

条件渲染:

​ 1.v-if

​ 写法:

​ (1).v-if=“表达式”

​ (2).v-else-if=“表达式”

​ (3).v-else=“表达式”

​ 适用于:切换频率较低的场景。

​ 特点:不展示的DOM元素直接被移除。

​ 注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”

​ 2.v-show

​ 写法:v-show=“表达式”

​ 适用于:切换频率较高的场景。

​ 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉

​ 3.备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。

<!-- v-if与template的配合使用 -->
<template v-if="n === 1">
    <h2>你好</h2>
    <h2>尚硅谷</h2>
    <h2>北京</h2>
</template>

2.10 列表渲染

基本列表

v-for指令:

​ 1.用于展示列表数据

​ 2.语法:v-for="(item, index) in xxx" :key=“yyy” (index即默认索引值 从0开始) key(代表每条内容的唯一标识)

​ 3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>基本列表</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 
				v-for指令:
						1.用于展示列表数据
						2.语法:v-for="(item, index) in xxx" :key="yyy"
						3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
		-->
		<!-- 准备好一个容器-->
		<div id="root">
			<!-- 遍历数组 -->
			<h2>人员列表(遍历数组)</h2>
			<ul>
				<li v-for="(p,index) in persons" :key="index">
					{{p.name}}-{{p.age}}
				</li>
			</ul>

			<!-- 遍历对象 -->
			<h2>汽车信息(遍历对象)</h2>
			<ul>
				<li v-for="(value,k) in car" :key="k">
					{{k}}-{{value}}
				</li>
			</ul>

			<!-- 遍历字符串 -->
			<h2>测试遍历字符串(用得少)</h2>
			<ul>
				<li v-for="(c,index) in str" :key="index">
					{{c}}-{{index}}
				</li>
			</ul>
			
			<!-- 遍历指定次数 -->
			<h2>测试遍历指定次数(用得少)</h2>
			<ul>
				<li v-for="(number,index) in 5" :key="index">
					{{index}}-{{number}}
				</li>
			</ul>
		</div>

		<script type="text/javascript">
			Vue.config.productionTip = false
			
			new Vue({
				el:'#root',
				data:{
					persons:[
						{id:'001',name:'张三',age:18},
						{id:'002',name:'李四',age:19},
						{id:'003',name:'王五',age:20}
					],
					car:{
						name:'奥迪A8',
						price:'70万',
						color:'黑色'
					},
					str:'hello'
				}
			})
		</script>
</html>
key作用与原理
面试题:react、vue中的key有什么作用?(key的内部原理)

1. 虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

2.对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。

(2).旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。

3. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。

4. 开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。
列表过滤
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>列表过滤</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root">
			<h2>人员列表</h2>
			<input type="text" placeholder="请输入名字" v-model="keyWord">
			<ul>
				<li v-for="(p,index) of filPerons" :key="index">
					{{p.name}}-{{p.age}}-{{p.sex}}
				</li>
			</ul>
		</div>

		<script type="text/javascript">
			Vue.config.productionTip = false
			
			//用watch实现
			//#region 
			/* new Vue({
				el:'#root',
				data:{
					keyWord:'',
					persons:[
						{id:'001',name:'马冬梅',age:19,sex:'女'},
						{id:'002',name:'周冬雨',age:20,sex:'女'},
						{id:'003',name:'周杰伦',age:21,sex:'男'},
						{id:'004',name:'温兆伦',age:22,sex:'男'}
					],
					filPerons:[]
				},
				watch:{
					keyWord:{
						immediate:true,
						handler(val){
							this.filPerons = this.persons.filter((p)=>{
								return p.name.indexOf(val) !== -1
							})
						}
					}
				}
			}) */
			//#endregion
			
			//用computed实现
			new Vue({
				el:'#root',
				data:{
					keyWord:'',
					persons:[
						{id:'001',name:'马冬梅',age:19,sex:'女'},
						{id:'002',name:'周冬雨',age:20,sex:'女'},
						{id:'003',name:'周杰伦',age:21,sex:'男'},
						{id:'004',name:'温兆伦',age:22,sex:'男'}
					]
				},
				computed:{
					filPerons(){
						return this.persons.filter((p)=>{
							return p.name.indexOf(this.keyWord) !== -1
						})
					}
				}
			}) 
		</script>
</html>
列表排序
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>列表排序</title>
        <script type="text/javascript" src="../js/vue.js"></script>
    </head>
    <body>
        <!-- 准备好一个容器-->
        <div id="root">
            <h2>人员列表</h2>
            <input type="text" placeholder="请输入名字" v-model="keyWord">
            <button @click="sortType = 2">年龄升序</button>
            <button @click="sortType = 1">年龄降序</button>
            <button @click="sortType = 0">原顺序</button>
            <ul>
                <li v-for="(p,index) of filPerons" :key="p.id">
                    {{p.name}}-{{p.age}}-{{p.sex}}
                    <input type="text">
                </li>
            </ul>
        </div>

        <script type="text/javascript">
            Vue.config.productionTip = false

            new Vue({
                el:'#root',
                data:{
                    keyWord:'',
                    sortType:0, //0原顺序 1降序 2升序
                    persons:[
                        {id:'001',name:'马冬梅',age:30,sex:'女'},
                        {id:'002',name:'周冬雨',age:31,sex:'女'},
                        {id:'003',name:'周杰伦',age:18,sex:'男'},
                        {id:'004',name:'温兆伦',age:19,sex:'男'}
                    ]
                },
                computed:{
                    filPerons(){
                        const arr = this.persons.filter((p)=>{
                            return p.name.indexOf(this.keyWord) !== -1
                        })
                        //判断一下是否需要排序
                        if(this.sortType){
                            arr.sort((p1,p2)=>{
                                return this.sortType === 1 ? p2.age-p1.age : p1.age-p2.age
                            })
                        }
                        return arr
                    }
                }
            }) 

        </script>
        </html>

2.11 收集表单数据

收集表单数据:
若:,则v-model收集的是value值,用户输入的就是value值。
若:,则v-model收集的是value值,且要给标签配置value值。
若:
1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
2.配置input的value属性:
(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
(2)v-model的初始值是数组,那么收集的的就是value组成的数组
备注:v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>收集表单数据</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root">
			<form @submit.prevent="demo">
				账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
				密码:<input type="password" v-model="userInfo.password"> <br/><br/>
				年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
				性别:
				男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
				女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
				爱好:
				学习<input type="checkbox" v-model="userInfo.hobby" value="study">
				打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
				吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
				<br/><br/>
				所属校区
				<select v-model="userInfo.city">
					<option value="">请选择校区</option>
					<option value="beijing">北京</option>
					<option value="shanghai">上海</option>
					<option value="shenzhen">深圳</option>
					<option value="wuhan">武汉</option>
				</select>
				<br/><br/>
				其他信息:
				<textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
				<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.atguigu.com">《用户协议》</a>
				<button>提交</button>
			</form>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false

		new Vue({
			el:'#root',
			data:{
				userInfo:{
					account:'',
					password:'',
					age:18,
					sex:'female',
					hobby:[],
					city:'beijing',
					other:'',
					agree:''
				}
			},
			methods: {
				demo(){
					console.log(JSON.stringify(this.userInfo))
				}
			}
		})
	</script>
</html>

2.12 过滤器

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>过滤器</title>
		<script type="text/javascript" src="../js/vue.js"></script>
		<script type="text/javascript" src="../js/dayjs.min.js"></script>
	</head>
	<body>
		<!-- 
			过滤器:
				定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
				语法:
						1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
						2.使用过滤器:{{ xxx | 过滤器名}}  或  v-bind:属性 = "xxx | 过滤器名"
				备注:
						1.过滤器也可以接收额外参数、多个过滤器也可以串联
						2.并没有改变原本的数据, 是产生新的对应的数据
		-->
		<!-- 准备好一个容器-->
		<div id="root">
			<h2>显示格式化后的时间</h2>
			<!-- 计算属性实现 -->
			<h3>现在是:{{fmtTime}}</h3>
			<!-- methods实现 -->
			<h3>现在是:{{getFmtTime()}}</h3>
			<!-- 过滤器实现 -->
			<h3>现在是:{{time | timeFormater}}</h3>
			<!-- 过滤器实现(传参) -->
			<h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
			<h3 :x="msg | mySlice">尚硅谷</h3>
		</div>

		<div id="root2">
			<h2>{{msg | mySlice}}</h2>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false
		//全局过滤器
		Vue.filter('mySlice',function(value){
			return value.slice(0,4)
		})
		
		new Vue({
			el:'#root',
			data:{
				time:1621561377603, //时间戳
				msg:'你好,尚硅谷'
			},
			computed: {
				fmtTime(){
					return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
				}
			},
			methods: {
				getFmtTime(){
					return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
				}
			},
			//局部过滤器
			filters:{
				timeFormater(value,str='YYYY年MM月DD日 HH:mm:ss'){
					// console.log('@',value)
					return dayjs(value).format(str)
				}
			}
		})

		new Vue({
			el:'#root2',
			data:{
				msg:'hello,atguigu!'
			}
		})
	</script>
</html>

2.13 内置指令和自定义指令

内置指令

我们学过的指令:
v-bind : 单向绑定解析表达式, 可简写为 :xxx
v-model : 双向数据绑定
v-for : 遍历数组/对象/字符串
v-on : 绑定事件监听, 可简写为@
v-if : 条件渲染(动态控制节点是否存存在)
v-else : 条件渲染(动态控制节点是否存存在)
v-show : 条件渲染 (动态控制节点是否展示)

v-text指令:
1.作用:向其所在的节点中渲染文本内容。
2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。


v-html指令:
1.作用:向指定节点中渲染包含html结构的内容。
2.与插值语法的区别:
(1).v-html会替换掉节点中所有的内容,{{xx}}则不会。
(2).v-html可以识别html结构。
3.严重注意:v-html有安全性问题!!!!
(1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
(2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!


v-cloak指令(没有值):
1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。


v-once指令:
1.v-once所在节点在初次动态渲染后,就视为静态内容了。
2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。

v-pre指令:
1.跳过其所在节点的编译过程。
2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
自定义指令
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>自定义指令</title>
        <script type="text/javascript" src="../js/vue.js"></script>
    </head>
    <body>
        <!-- 
需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
自定义指令总结:
一、定义语法:
(1).局部指令:
new Vue({															new Vue({
directives:{指令名:配置对象}   或   		directives{指令名:回调函数}
}) 																		})
(2).全局指令:
Vue.directive(指令名,配置对象) 或   Vue.directive(指令名,回调函数)

二、配置对象中常用的3个回调:
(1).bind:指令与元素成功绑定时调用。
(2).inserted:指令所在元素被插入页面时调用。
(3).update:指令所在模板结构被重新解析时调用。

三、备注:
1.指令定义时不加v-,但使用时要加v-;
2.指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
-->
        <!-- 准备好一个容器-->
        <div id="root">
            <h2>{{name}}</h2>
            <h2>当前的n值是:<span v-text="n"></span> </h2>
            <!-- <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2> -->
            <h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
            <button @click="n++">点我n+1</button>
            <hr/>
            <input type="text" v-fbind:value="n">
        </div>
    </body>

    <script type="text/javascript">
        Vue.config.productionTip = false

        //定义全局指令
        /* Vue.directive('fbind',{
			//指令与元素成功绑定时(一上来)
			bind(element,binding){
				element.value = binding.value
			},
			//指令所在元素被插入页面时
			inserted(element,binding){
				element.focus()
			},
			//指令所在的模板被重新解析时
			update(element,binding){
				element.value = binding.value
			}
		}) */

        new Vue({
            el:'#root',
            data:{
                name:'尚硅谷',
                n:1
            },
            directives:{
                //big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
                /* 'big-number'(element,binding){
					// console.log('big')
					element.innerText = binding.value * 10
				}, */
                big(element,binding){
                    console.log('big',this) //注意此处的this是window
                    // console.log('big')
                    element.innerText = binding.value * 10
                },
                fbind:{
                    //指令与元素成功绑定时(一上来)
                    bind(element,binding){
                        element.value = binding.value
                    },
                    //指令所在元素被插入页面时
                    inserted(element,binding){
                        element.focus()
                    },
                    //指令所在的模板被重新解析时
                    update(element,binding){
                        element.value = binding.value
                    }
                }
            }
        })

    </script>
</html>

2.14 Vue实例生命周期

生命周期:
1.又名:生命周期回调函数、生命周期函数、生命周期钩子。
2.是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数。
3.生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
4.生命周期函数中的this指向是vm 或 组件实例对象。

如created(){},mounted(){}…

2

  1. 初始化显示

* beforeCreate()

* created()

* beforeMount()

* mounted()

  1. 更新状态: this.xxx = value

* beforeUpdate()

* updated()3) 销毁 vue 实例: vm.$destory()

* beforeDestory()

* destoryed()

总结

常用的生命周期钩子:
1.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。

关于销毁Vue实例
1.销毁后借助Vue开发者工具看不到任何信息。
2.销毁后自定义事件会失效,但原生DOM事件依然有效。
3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。

3、Vue组件

3.1 模块与组件、模块化与组件化

3.1.1. 模块
  1. 理解: 向外提供特定功能的 js 程序, 一般就是一个 js 文件

  2. 为什么: js 文件很多很复杂

  3. 作用: 复用 js, 简化 js 的编写, 提高 js 运行效率

3.1.2. 组件
  1. 理解: 用来实现局部(特定)功能效果的代码集合(html/css/js/image……)

  2. 为什么: 一个界面的功能很复杂3. 作用: 复用编码, 简化项目编码, 提高运行效率

3.1.3. 模块化

当应用中的 js 都以模块来编写的, 那这个应用就是一个模块化的应用。

3.1.4. 组件化

当应用中的功能都是多组件的方式来编写的, 那这个应用就是一个组件化的应用,。

3.2. 非单文件组件

  1. 模板编写没有提示

  2. 没有构建过程, 无法将 ES6 转换成 ES5

  3. 不支持组件的 CSS

  4. 真正开发中几乎不用

3.3. 单文件组件

3.3.1. 一个.vue 文件的组成(3 个部分)
**1. 模板页面** 

<template> 

页面模板 

</template>**2. JS 模块对象** 

<script> 

export default { 

data() {return {}}, 

methods: {}, 

computed: {}, 

components: {} 

}

</script> 

**3. 样式**

<style> 

样式定义 

</style> 
3.3.2. 基本使用
  1. 引入组件

  2. 映射成标签

  3. 使用组件标签

3.4 使用组件的三大步骤

Vue中使用组件的三大步骤:
一、定义组件(创建组件)
二、注册组件
三、使用组件(写组件标签)

一、如何定义一个组件?
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
区别如下:
1.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
2.data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
备注:使用template可以配置组件结构。

二、如何注册组件?
1.局部注册:靠new Vue的时候传入components选项
2.全局注册:靠Vue.component(‘组件名’,组件)

三、编写组件标签:

3.4.1 注意点

1.关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):my-school (需要用引号)
第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
备注:
(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
(2).可以使用name配置项指定组件在开发者工具中呈现的名字。

2.关于组件标签:
第一种写法:
第二种写法:
备注:不用使用脚手架时,会导致后续组件不能渲染。

3.一个简写方式:
const school = Vue.extend(options) 可简写为:const school = options

3.5 VueComponent构造函数(组件实例对象)

关于VueComponent:
1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
2.我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,
即Vue帮我们执行的:new VueComponent(options)。

3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!

4.关于this指向:
(1).组件配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
(2).new Vue(options)配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。

5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。
Vue的实例对象,以后简称vm。

3.6 vm和vc的区别

  1. vm是vue实例对象,vc是组件实例对象,构造函数不一样。

  2. vc不能有el参数,vm可以指定el即对应的容器,vc只能跟着vm混,作为组件嵌套于vm中。

  3. vc的data只能用函数式,而vm可以用对象式也可以用函数式。(避免数据引用)

  4. 因为组件是可复用的Vue实例,所以它们与new Vue 接收相同的选项,例如data、computed、watch、methods以及生命周期钩子等。仅有例外是像el这样根实例特有的选项。

3.7 一个重要的内置关系

​ 1.一个重要的内置关系:VueComponent.prototype.proto === Vue.prototype

​ 2.为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

4、Vue脚手架

4.1 初始化脚手架

  1. Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)。

  2. 最新的版本是 4.x。

  3. 文档: https://cli.vuejs.org/zh/。

4.1.1 具体步骤

第一步(仅第一次执行):全局安装@vue/cli。

npm install -g @vue/cli

第二步:切换到你要创建项目的目录,然后使用命令创建项目

vue create xxxx

第三步:启动项目

npm run serve

备注:

  1. 如出现下载缓慢请配置 npm 淘宝镜像:npm config set registry https://registry.npm.taobao.org

  2. Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,

    请执行:vue inspect > output.js

4.1.2 分析模板项目结构
├── node_modules 
├── public
│   ├── favicon.ico: 页签图标
│   └── index.html: 主页面
├── src
│   ├── assets: 存放静态资源
│   │   └── logo.png
│   │── component: 存放组件
│   │   └── HelloWorld.vue
│   │── App.vue: 汇总所有组件
│   │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
4.1.3 main.js中的render函数

因为脚手架main.js文件中没有引入完整版的vue.js没法直接加载template所以使用了render函数。否则无法加载App.vue

关于不同版本的Vue:
1.vue.js与vue.runtime.xxx.js的区别:
(1).vue.js是完整版的Vue,包含:核心功能+模板解析器。
(2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。

2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用
render函数接收到的createElement函数去指定具体内容。

4.1.4 vue.config.js配置文件
  1. 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
  2. 使用vue.config.js可以对脚手架进行个性化定制

例如:

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  lintOnSave:false 
    //语法检查的时候把不规范的代码(即命名不规范)当成了错误官方文档上面的风格建议我们使用大驼峰或-衔接的方式而已
    //修改配置项,关闭语法检查
})

4.2 ref和props属性

ref
  1. 被用来给元素或子组件注册引用信息(id的替代者)
  2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
  3. 使用方式:
    1. 打标识:<h1 ref="xxx">.....</h1><School ref="xxx"></School>
    2. 获取:this.$refs.xxx
props
  1. 功能:让组件接收外部传过来的数据

  2. 传递数据:<Demo name="xxx"/>

  3. 接收数据:

    1. 第一种方式(只接收):props:['name']

    2. 第二种方式(限制类型):props:{name:String}

    3. 第三种方式(限制类型、限制必要性、指定默认值):

      props:{
      	name:{
      	type:String, //类型
      	required:true, //必要性
      	default:'老王' //默认值
      	}
      }
      

    备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

4.3 mixin(混入)

  1. 功能:可以把多个组件共用的配置提取成一个混入对象

  2. 使用方式:

    第一步定义混合:

    {
        data(){....},
        methods:{....}
        ....
    }
    

    第二步使用混入:

    ​ 全局混入:Vue.mixin(xxx)
    ​ 局部混入:mixins:['xxx']

4.4 插件

  1. 功能:用于增强Vue

  2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

  3. 定义插件:

    对象.install = function (Vue, options) {
        // 1. 添加全局过滤器
        Vue.filter(....)
    
        // 2. 添加全局指令
        Vue.directive(....)
    
        // 3. 配置全局混入(合)
        Vue.mixin(....)
    
        // 4. 添加实例方法
        Vue.prototype.$myMethod = function () {...}
        Vue.prototype.$myProperty = xxxx
    }
    
  4. 使用插件:Vue.use()

4.5 scoped样式

  1. 作用:让样式在局部生效,防止冲突。
  2. 写法:<style scoped>

4.6 总结TodoList案例

  1. 组件化编码流程:

    ​ (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。

    ​ (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

    ​ 1).一个组件在用:放在组件自身即可。

    ​ 2). 一些组件在用:放在他们共同的父组件上(状态提升)。

    ​ (3).实现交互:从绑定事件开始。

  2. props适用于:

    ​ (1).父组件 ==> 子组件 通信

    <Item v-for="todo in todos" :key="todo.id" :todoObj="todo"/>

    ​ (2).子组件 ==> 父组件 通信(要求父先给子一个函数)

    <Header :addTodo="addTodo"></Header>

  3. 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!

  4. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

4.7 webStorage

  1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

  2. 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。

  3. 相关API:

    1. xxxxxStorage.setItem('key', 'value');
      该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。

    2. xxxxxStorage.getItem('person');

      ​ 该方法接受一个键名作为参数,返回键名对应的值。

    3. xxxxxStorage.removeItem('key');

      ​ 该方法接受一个键名作为参数,并把该键名从存储中删除。

    4. xxxxxStorage.clear()

      ​ 该方法会清空存储中的所有数据。

  4. 备注:

    1. SessionStorage存储的内容会随着浏览器窗口关闭而消失。
    2. LocalStorage存储的内容,需要手动清除才会消失。
    3. xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。
    4. JSON.parse(null)的结果依然是null。
    5. 存储对象时使用JSON.stringify§

4.8 组件的自定义事件

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

    2. 第二种方式,在父组件中:

      <Demo ref="demo"/>
      ......
      mounted(){
         this.$refs.xxx.$on('atguigu',this.test)
      }
      
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

  4. 触发自定义事件:this.$emit('atguigu',数据)

  5. 解绑自定义事件this.$off('atguigu')

  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

  7. 注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

4.9 全局事件总线(GlobalEventBus)

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 安装全局事件总线:

    new Vue({
    	......
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    	},
        ......
    }) 
    
  3. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
      
    2. 提供数据:this.$bus.$emit('xxxx',数据)

  4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

4.10 消息订阅与发布(pubsub)

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

    3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
        //方法参数有两个(msgName,data) 第一个为消息name,第二个才是数据
      }
      
    4. 提供数据:pubsub.publish('xxx',数据)

    5. 最好在beforeDestroy钩子中,用pubsub.unsubscribe(this.pubId)去取消订阅。

4.11 nextTick

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

4.12 Vue封装的过度与动画

  1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。

  2. 图示:

  3. 写法:

    1. 准备好样式:

      • 元素进入的样式:
        1. v-enter:进入的起点
        2. v-enter-active:进入过程中
        3. v-enter-to:进入的终点
      • 元素离开的样式:
        1. v-leave:离开的起点
        2. v-leave-active:离开过程中
        3. v-leave-to:离开的终点
    2. 使用<transition>包裹要过度的元素,并配置name属性:

      <transition name="hello">
      	<h1 v-show="isShow">你好啊!</h1>
      </transition>
      
    3. 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

5、Axios异步通信

Axios是一个开源的开源用在浏览器端和NodeJS的异步通信框架,它的主要作用就是实现AJAX异步通信,其功能特点如下

3

5.1 为什么要使用Axios异步通信

由于Vue.js是一个视图层框架并且作者(尤雨溪)严格准守SoC (关注度分离原则),所以Vue.js并不包含AJAX的通信功能,为了解决通信问题,作者单独开发了一个名为vue-resource的插件,不过在进入2.0版本以后停止了对该插件的维护并推荐了Axios框架。少用jQuery,因为它操作Dom太频繁!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<style>
	/* 解决闪烁的问题,在数据没加载出来的时候,变成白屏,而不是以文本的形式输出 */
	[v-clock]{
		display: none;
	}
</style>
<body>
<div id="vue" v-clock>
	<div>{{info.name}}</div>
	<div>{{info.url}}</div>
	<div>{{info.address.street}}</div>
	<div>{{info.address.city}}</div>
	<div>{{info.address.country}}</div>
	<div>{{info.links[1].name}}</div>
	<a v-bind:href="info.url">点我</a>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="text/javascript">
    var vm = new Vue({

        el:"#vue",
        data(){
            return{
                //请求的返回参数必须和json字符串一样,一一对应
                // info:{
				// 	name:null,
				// 	url:null,
				// 	address:{
				// 		street:null,
				// 		city:null,
				// 		country:null
				// 	}
				// }
                //或者直接将data.json中的数据赋值给info这样比较方便
                info:null
            }
        },
        mounted(){//钩子函数  链式编程
            axios.get('../data.json').then(response=>(this.info=response.data));
        }
    });
</script>
    
<!-- 
    <script src="vue.min.js"></script>
<script src="axios.min.js"></script>
<script>
    new Vue({
        el: '#app',
        data: {
            userList:[]
        },
        created() { //在页面渲染之前执行
            //调用方法,得到返回json数据
            this.getList()
        },
        methods:{
            getList() {
                //使用axios方式ajax请求
                axios.get("user.json")
                    .then(response => {//请求成功
                        //console.log(response)
                        this.userList =  response.data.data.items
                        console.log(this.userList)
                    }) 
                    .catch(error => {
                        console.log(error)
                    }) //请求失败
            }
        }
    })
</script>
-->
</body>
</html>

5.2 解决Ajax跨域问题

vue脚手架配置代理

方法一

​ 在vue.config.js中添加如下配置:

devServer:{
  proxy:"http://localhost:5000"
}

说明:

  1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
方法二

​ 编写vue.config.js配置具体代理规则:

module.exports = {
	devServer: {
      proxy: {
      '/api1': {// 匹配所有以 '/api1'开头的请求路径
        target: 'http://localhost:5000',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api1': ''}  //正则表达式  将前缀替换为空字符串
      },
      '/api2': {// 匹配所有以 '/api2'开头的请求路径
        target: 'http://localhost:5001',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api2': ''}
      }
    }
  }
}
/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/

说明:

  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
  2. 缺点:配置略微繁琐,请求资源时必须加前缀。

6、插槽

  1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件

  2. 分类:默认插槽、具名插槽、作用域插槽

  3. 使用方式:

    1. 默认插槽:

      父组件中:
              <Category>
                 <div>html结构1</div>
              </Category>
      子组件中:
              <template>
                  <div>
                     <!-- 定义插槽 -->
                     <slot>插槽默认内容...</slot>
                  </div>
              </template>
      
    2. 具名插槽:

      父组件中:
              <Category>
                  <template slot="center">
                    <div>html结构1</div>
                  </template>
      
                  <template v-slot:footer>
                     <div>html结构2</div>
                  </template>
              </Category>
      子组件中:
              <template>
                  <div>
                     <!-- 定义插槽 -->
                     <slot name="center">插槽默认内容...</slot>
                     <slot name="footer">插槽默认内容...</slot>
                  </div>
              </template>
      
    3. 作用域插槽:

      1. 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

      2. 具体编码:

        父组件中:
        		<Category>
        			<template scope="scopeData">
        				<!-- 生成的是ul列表 -->
        				<ul>
        					<li v-for="g in scopeData.games" :key="g">{{g}}</li>
        				</ul>
        			</template>
        		</Category>
        
        		<Category>
        			<template slot-scope="scopeData">
        				<!-- 生成的是h4标题 -->
        				<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
        			</template>
        		</Category>
        子组件中:
                <template>
                    <div>
                        <slot :games="games"></slot>
                    </div>
                </template>
        		
                <script>
                    export default {
                        name:'Category',
                        props:['title'],
                        //数据在子组件自身
                        data() {
                            return {
                                games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                            }
                        },
                    }
                </script>
        

7、Vuex

原理图

4

1.概念

在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。

2.何时使用?

多个组件需要共享数据时

vue2只能使用vuex3版本

vue3只能使用vuex4版本

安装步骤

  1. npm i vuex@3

3.搭建vuex环境

  1. 创建文件:src/store/index.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //应用Vuex插件
    Vue.use(Vuex)
    
    //准备actions对象——响应组件中用户的动作
    const actions = {}
    //准备mutations对象——修改state中的数据
    const mutations = {}
    //准备state对象——保存具体的数据
    const state = {}
    
    //创建并暴露store
    export default new Vuex.Store({
    	actions,
    	mutations,
    	state
    })
    
  2. main.js中创建vm时传入store配置项

    ......
    //引入store
    import store from './store'
    ......
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	store
    })
    

4.基本使用

  1. 初始化数据、配置actions、配置mutations,操作文件store.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //引用Vuex
    Vue.use(Vuex)
    
    const actions = {
        //响应组件中加的动作
    	jia(context,value){
    		// console.log('actions中的jia被调用了',miniStore,value)
    		context.commit('JIA',value)
    	},
    }
    
    const mutations = {
        //执行加
    	JIA(state,value){
    		// console.log('mutations中的JIA被调用了',state,value)
    		state.sum += value
    	}
    }
    
    //初始化数据
    const state = {
       sum:0
    }
    
    //创建并暴露store
    export default new Vuex.Store({
    	actions,
    	mutations,
    	state,
    })
    
  2. 组件中读取vuex中的数据:$store.state.sum

  3. 组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)$store.commit('mutations中的方法名',数据)

    备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

5.getters的使用

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。

  2. store.js中追加getters配置

    ......
    
    const getters = {
    	bigSum(state){
    		return state.sum * 10
    	}
    }
    
    //创建并暴露store
    export default new Vuex.Store({
    	......
    	getters
    })
    
  3. 组件中读取数据:$store.getters.bigSum

6.四个map方法的使用

引入函数 import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'

  1. mapState方法:用于帮助我们映射state中的数据为计算属性

    computed: {
        //借助mapState生成计算属性:sum、school、subject(对象写法)
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
             
        //借助mapState生成计算属性:sum、school、subject(数组写法)
        ...mapState(['sum','school','subject']),
    },
    
  2. mapGetters方法:用于帮助我们映射getters中的数据为计算属性

    computed: {
        //借助mapGetters生成计算属性:bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
    
        //借助mapGetters生成计算属性:bigSum(数组写法)
        ...mapGetters(['bigSum'])
    },
    
  3. mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
        //靠mapActions生成:incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
    
  4. mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

    methods:{
        //靠mapActions生成:increment、decrement(对象形式)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA','JIAN']),
    }
    

备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

7.模块化+命名空间

  1. 目的:让代码更好维护,让多种数据分类更加明确。

  2. 修改store.js

    const countAbout = {
      namespaced:true,//开启命名空间
      state:{x:1},
      mutations: { ... },
      actions: { ... },
      getters: {
        bigSum(state){
           return state.sum * 10
        }
      }
    }
    
    const personAbout = {
      namespaced:true,//开启命名空间
      state:{ ... },
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        countAbout,
        personAbout
      }
    })
    
  3. 开启命名空间后,组件中读取state数据:

    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取:
    ...mapState('countAbout',['sum','school','subject']),
    
  4. 开启命名空间后,组件中读取getters数据:

    //方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
    
  5. 开启命名空间后,组件中调用dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
  6. 开启命名空间后,组件中调用commit

    //方式一:自己直接commit
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
    

8、路由(vue-router)

8.1 基本使用

  1. 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
  2. 前端路由:key是路径,value是组件。

Vue Router 是Vue.js 官方的路由管理器。它和Vue.js的核心深度集成,让构建单页面应用变得易如反掌

安装vue-router,命令:npm i vue-router

应用插件:Vue.use(VueRouter)

编写router配置项:

  1. 安装以及配置路由

    import Vue from 'vue'
    import VueRouter from "vue-router";
    import Content from '../components/Content.vue'
    import Main from '../components/Main'
    
    //安装路由
    Vue.use(VueRouter);
    
    //配置导出路由
    export default new VueRouter({
      routes:[
        {
          //路由的路径  @RequestMappering()
          path:'/content',
          //路由名字
          name:'content',
          //跳转的组件
          component:Content
        },
        {
          //路由的路径
          path:'/main',
          //路由名字
          name:'main',
          //跳转的组件
          component:Main
        }
      ]
    })
    
    
  2. 在main.js(入口文件)中配置路由

    import Vue from 'vue'
    import App from './App'
    import router from './router'   //自动扫描文件夹中的路由配置 index.js
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,  //配置路由
      components: { App },
      template: '<App/>'
    })
    
  3. 在App.vue中测试(使用路由)VueRouter

    <template>
      <div id="app">
          <h1>Vue-Router</h1>
          <router-link to="/main">首页</router-link>
          <router-link to="/content">内容页</router-link>
          <router-view></router-view>
      </div>
    </template>
    
    <script>
    export default {
      name: 'App'
    }
    </script>
    
    <style>
    #app {
      font-family: 'Avenir', Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    
    

注意点:

  1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
  2. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
  3. 每个组件都有自己的$route属性,里面存储着自己的路由信息。
  4. 整个应用只有一个router,可以通过组件的$router属性获取到。

$ router 对象是全局路由的实例 (路由器)

this.$router.push({path:’/hospSet/list’})

$ route对象表示当前的路由信息 (单个路由)

this.$route.params

this.$route.params.id

8.2 嵌套(多级)路由

通过配置路由的时候添加属性children

  1. 配置路由规则,使用children配置项:

     routes:[
         {
       path:'/about',
       component:About,
         },
         {
       path:'/home',
       component:Home,
       children:[ //通过children配置子级路由
                 {
         path:'news', //此处一定不要写:/news
         component:News
                 },
                 {
         path:'message',//此处一定不要写:/message
         component:Message
                 }
             ]
         }
     ]
    
  2. 跳转(要写完整路径):

     <router-link to="/home/news">News</router-link>
    

8.3 路由的query参数

  1. 传递参数

    <!-- 跳转并携带query参数,to的字符串写法 -->
    <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
    				
    <!-- 跳转并携带query参数,to的对象写法 -->
    <router-link 
    	:to="{
    		path:'/home/message/detail',
    		query:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳转</router-link>
    
  2. 接收参数:

    $route.query.id
    $route.query.title
    

8.4 命名路由

  1. 作用:可以简化路由的跳转。

  2. 如何使用

    1. 给路由命名:

      {
       path:'/demo',
       component:Demo,
       children:[
      		{
         path:'test',
         component:Test,
         children:[
      				{
                            name:'hello' //给路由命名
                          path:'welcome',
           component:Hello,
      				}
      			]
      		}
      	]
      }
      
    2. 简化跳转:

      <!--简化前,需要写完整的路径 -->
      <router-link to="/demo/test/welcome">跳转</router-link>
      
      <!--简化后,直接通过名字跳转 -->
      <router-link :to="{name:'hello'}">跳转</router-link>
      
      <!--简化写法配合传递参数 -->
      <router-link 
      	:to="{
      		name:'hello',
      		query:{
      		   id:666,
                  title:'你好'
      		}
      	}"
      >跳转</router-link>
      

8.5路由的params参数

  1. 配置路由,声明接收params参数

    {
     path:'/home',
     component:Home,
     children:[
    		{
       path:'news',
       component:News
    		},
    		{
       component:Message,
       children:[
    				{
         name:'xiangqing',
         path:'detail/:id/:title', //使用占位符声明接收params参数
         component:Detail
    				}
    			]
    		}
    	]
    }
    
  2. 传递参数

    <!-- 跳转并携带params参数,to的字符串写法 -->
    <router-link :to="/home/message/detail/666/你好">跳转</router-link>
    				
    <!-- 跳转并携带params参数,to的对象写法 -->
    <router-link 
    	:to="{
    		name:'xiangqing',
    		params:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳转</router-link>
    

    特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

  3. 接收参数:

    $route.params.id
    $route.params.title
    

8.6 路由的props配置

作用:让路由组件更方便的收到参数

{
 name:'xiangqing',
 path:'detail/:id',
 component:Detail,

 //第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
 // props:{a:900}

 //第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
 // props:true
 
 //第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
 props(route){
  return {
   id:route.query.id,
   title:route.query.title
		}
	}
}

8.7 router-link的replace属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:分别为pushreplacepush是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
  3. 如何开启replace模式:<router-link replace .......>News</router-link>

8.8 编程式路由导航

  1. 作用:不借助实现路由跳转,让路由跳转更加灵活

  2. 具体编码:

    //$router的两个API
    this.$router.push({
     name:'xiangqing',
      params:{
       id:xxx,
       title:xxx
    		}
    })
    
    this.$router.replace({
     name:'xiangqing',
      params:{
       id:xxx,
       title:xxx
    		}
    })
    this.$router.forward() //前进
    this.$router.back() //后退
    this.$router.go() //可前进也可后退
    

8.9 缓存路由组件

  1. 作用:让不展示的路由组件保持挂载,不被销毁。

  2. 具体编码:

    <keep-alive include="News"> 
        <router-view></router-view>
    </keep-alive>
    

8.10 两个新的生命周期钩子

  1. 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
  2. 具体名字:
    1. activated路由组件被激活时触发。
    2. deactivated路由组件失活时触发。

8.11 路由守卫

  1. 作用:对路由进行权限控制

  2. 分类:全局守卫、独享守卫、组件内守卫

  3. 全局守卫:

    //全局前置守卫:初始化时执行、每次路由切换前执行
    router.beforeEach((to,from,next)=>{
     console.log('beforeEach',to,from)
     if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
      if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
       next() //放行
    		}else{
       alert('暂无权限查看')
       // next({name:'guanyu'})
    		}
    	}else{
      next() //放行
    	}
    })
    
    //全局后置守卫:初始化时执行、每次路由切换后执行
    router.afterEach((to,from)=>{
     console.log('afterEach',to,from)
     if(to.meta.title){ 
      document.title = to.meta.title //修改网页的title
    	}else{
      document.title = 'vue_test'
    	}
    })
    
  4. 独享守卫:

    beforeEnter(to,from,next){
     console.log('beforeEnter',to,from)
     if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
      if(localStorage.getItem('school') === 'atguigu'){
       next()
    		}else{
       alert('暂无权限查看')
       // next({name:'guanyu'})
    		}
    	}else{
      next()
    	}
    }
    
  5. 组件内守卫:

    //进入守卫:通过路由规则,进入该组件时被调用
    beforeRouteEnter (to, from, next) {
    },
    //离开守卫:通过路由规则,离开该组件时被调用
    beforeRouteLeave (to, from, next) {
    }
    

8.12 路由器的两种工作模式

  1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
  2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
  3. hash模式:
    1. 地址中永远带着#号,不美观 。
    2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
    3. 兼容性较好。
  4. history模式:
    1. 地址干净,美观 。
    2. 兼容性和hash模式相比略差。
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。

8.13 组件重用问题

5

9、webpack创建vue项目

手动创建:

进入创建项目的文件夹命令行打开,输入 vue init webpack 项目名

vue-cli是官方提供的一个脚手架,用于快速生成一个vue的项目模板

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

10、webpack了解

webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。 在 webpack 看来, 前端的所有资源文件(js/json/css/img/less/…)都会作为模块处理。 它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。

单页Web应用(single page web application,SPA): SPA 是一种特殊的 Web 应用,是加载单个 HTML 页面并在用户与应用程序交互时动态更新该页面的。它将所有的活动局限于一个 Web 页面中,仅在该 Web 页面初始化时加载相应的 HTML 、 JavaScript 、 CSS 。一旦页面加载完成, SPA 不会因为用户的操作而进行页面的重新加载或跳转,而是利用 JavaScript 动态的变换 HTML(采用的是 div 切换显示和隐藏),从而实现UI与用户的交互。在 SPA 应用中,应用加载之后就不会再有整页刷新。相反,展示逻辑预先加载,并有赖于内容Region(区域)中的视图切换来展示内容。

要实现单页面应用,现在已经有很多现成的框架了,它们都是很全面的开发平台,为单页面应用开发提供了必需的页面模板、路径解析和处理、后台服务 api 访问、 DOM 操作等功能。

在这里插入图片描述

1. 优点

1) 有良好的交互体验
能提升页面切换体验,用户在访问应用页面是不会频繁的去切换浏览页面,从而避免了页面的重新加载;
2) 前后端分离开发
单页Web应用可以和 RESTful 规约一起使用,通过 REST API 提供接口数据,并使用 Ajax 异步获取,这样有助于分离客户端和服务器端工作。更进一步,可以在客户端也可以分解为静态页面和页面交互两个部分;
3) 减轻服务器压力
服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍;
4) 共用一套后端程序代码
不用修改后端程序代码就可以同时用于 Web 界面、手机、平板等多种客户端;

2. 缺点:

1) SEO难度较高
由于所有的内容都在一个页面中动态替换显示,所以在SEO上其有着天然的弱势,所以如果你的站点对SEO很看重,且要用单页应用,那么就做些静态页面给搜索引擎用吧;
2) 前进、后退管理
由于单页Web应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理,当然此问题也有解决方案,比如利用URI中的散列+iframe实现;
3) 初次加载耗时多
为实现单页Web应用功能及显示效果,需要在加载页面的时候将JavaScript、CSS统一加载,部分页面可以在需要的时候加载。所以必须对JavaScript及CSS代码进行合并压缩处理;

3. 性能优化

1) 加载优化
在SPA中,通常一开始就会加载所有必需的代码(HTML,JavaScript和CSS),有时候考虑到首屏加载太慢会按需加载,按需加载就是按照当前呈现的不同页面加载不同的文件,而不是最开始就把所有文件都加载出来,从而避免首屏加载很慢。
当首屏加载完毕后,设备&网络处于空闲状态,可以对其他路由组件进行预加载,以便提升页面切换性能。
根据路由拆分减少初始加载体积,利用异步加载方式,在路由注册时提供异步拉取组件的方法,仅在需要进入对应路由时,对应组件才会被加载进来。

  • 初次加载的速度

单页应用的第一页加载会比基于服务器的应用慢。这是因为首次加载必须先拿到框架和应用程序的代码,再在浏览器中呈现所需的视图。基于服务器的应用程序只需将所需的HTML推送到浏览器,从而减少了延迟和下载用时。

  • 加快页面加载速度

有一些方法可以加快单页应用的初次加载速度,比如采用多项缓存措施、需要时再加载某些模块(懒加载)。

  • 页面生命周期

单页应用在初始页面加载时被完全加载,然后页面区域被替换或更新为按需从服务器加载的新页面片段。为避免过度下载未使用的功能,单页应用通常会逐渐下载更多内容,如所需要的功能、页面的一小块,或者完整的一页。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-48qyH46s-1650008006811)(Vue.assets/20190109221137217.jpeg)]

2) SEO优化
由于我们在处理单页应用的时候页面是不刷新的,所以会导致我们的网页记录和内容很难被搜索引擎抓取到。搜索引擎抓取页面首先要遵循http协议,可是#不是协议内的内容。而实际上也是这样,我们没有见过搜索引擎的搜索结果中,哪一条记录可以快速定位到网页内的某个位置的。解决的方法是用 #!号代替#号,因为谷歌会抓取带有#!的URL。(Google规定,如果你希望Ajax生成的内容被浏览引擎读取,那么URL中可以使用"#!"(这种URL在一般页面一般不会产生定位效果)),这样我们可以解决ajax的不被搜索引擎抓取的问题。在vueJs里面,我们可以看到作者就是这样做的。
3) 前进后退功能优化
配置好路由信息,通过记录浏览过的历史路由信息,可以很好的记录或历史查看过的界面,也可以独立写个足迹功能实现。

4. 体验优化

1) 构建骨架图
SPA 首屏加载面临较长时间白屏,骨架图是一个完美的”缓兵之计”,相当于加载到下个界面时先把下个页面的雏形加载出来,再加载其余的组件,做到缓冲作用,骨架图对用户体验有极大的提升:

  • 快速展示

配合 PWA 首屏缓存,骨架图可实现瞬间加载&展示,首屏视觉上有冲击性地提升;

  • 稳定加载

消除页面初始加载因多次重绘&资源加载导致的”抖动”需要注意的是,骨架图应尽量保持足够小巧与简单,以确保不会严重影响页面后续加载;

2) 页面切换
无论如何优化性能加载,在页面切换时候依旧需要获取页面数据,若处理不好,可能会在数据返回前有短暂的不友好”空白”。通过以下方式可以很好处理这个问题:

  • 切换前:

在确保组件&数据加载完毕前,可保证页面可交互性,减少用户阻塞感;

  • 转场动画:

在大多数原生应用,转场动画属于标配,即时组件&数据已经完全加载,在切换至新页面瞬间,依旧需要页面渲染时间,这段时间可能导致页面短暂空白或者”视觉阻塞”,通过转场动画时间,可以很好地缓解这个问题,大多数页面保证在转场动画完毕之后依然渲染完毕。

PWA(Progressive Web App)渐进式网页应用,是一种理念,使用多种技术来增强web app的功能,可以让网站的体验变得更好,能够模拟一些原生功能,比如通知推送。在移动端利用标准化框架,让网页应用呈现和原生应用相似的体验。

12、vue+elementUI

创建个新项目,进入安装文件夹

  1. 开启cmd命令行 vue init webpack hello-vue
  2. cd hello-vue
  3. 安装路由 npm install vue-router --save-dev
  4. 安装elementUI npm i element-ui -s
  5. 安装依赖 npm install
  6. 安装SASS 加载器 cnpm install sass-loader node-sass --save-dev
  7. 启动测试 npm run dev

在 main.js 中写入以下内容:

import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';

Vue.use(ElementUI);

npm 是 JavaScript 世界的包管理工具,并且是 Node.js 平台的默认包管理工具。通过 npm 可以安装、共享、分发代码,管理项目依赖关系。

npm 可以管理本地项目的所需模块并自动维护依赖情况,也可以管理全局安装的 JavaScript 工具。

Vue重要知识点

1、vue , vue-cli , webpack 的区别以及关联

一:vue 和 vue-cli 的区别以及关联

基本概念

vue:
1: 是一套框架,用于构建用户界面的渐进式框架。
2: vue主要是从基础知识、组件的了解、动画的过渡、可复用性和组合以及工具、模块化的管理。

Vue-cli
1:而vue-cli 是一个基于 Vue.js进行快速开发的完整系统。
2:vue-cli 主要是从搭建交互式脚手架、零配置原型开发、基于webpack构建并进行配置、插件和Preset的扩展以及图形化的创建和Vue.js项目的用户界面管理等。

区别和关联

区别:
vue是一整套框架,而vue-cli只是它其中的一个脚手架

关联:
vue-cli 是vue的命令行工具
一个完整的vue项目核心构成

二:vue-cli是什么?和 webpack是什么关系?

vue-cli是什么:

vue-cli是vue的命令行工具,只要按照官网敲几行命令就可以新建一个基本的vue项目框架。方便快捷。(command-line interface - 简称 cli)

vue-cli和webpack是什么关系:

vue-cli 里面包含了webpack, 并且配置好了基本的webpack打包规则,

2、vue-cli创建项目和基于webpack创建的项目区别

cli方式
vue create hello-world

基于webpack
vue init webpack hello-world

区别:

vue-cli 内部封装了 webpack,对外仅仅提供几个依赖。而且做了很多适合 vue 项目的优化,同时你可以用 vue.config.js 来管理项目。package.json 非常清爽
webpack 更符合针有特定需求,毕竟是原生。不过管理起来也更加复杂。不过社区有升级,可以第一时间获取升级优势。前一种只能等待 vue-cli 项目升级
一般来说,vue-cli 够用了,但是 vue-cli 能实现的,webpack 一定能实现,反之,不一定

3、Vue中的data定义的两种方法和区别

vue中定义data:{} 和 data(){ return }的区别1.第一种写法:对象

var app = new Vue({
  el: ‘#app’,
  data: {
    name: ‘yang’
  }
})
2.第二种写法:函数ES6规范中的写法

var app = new Vue({
  el: ‘#app’,
  data() {
    return {
      name: ‘yang’
    }
  }
})
区别:
在简单的vue实例应用中,两种写法几乎是没有什么区别的,因为你定义的#app对象不会被复用。

但是如果是在Vue组件应用的环境中,就可能会存在多个地方调用同一个组件的情况,为了不让多个地方的组件共享同一个data对象,只能返回函数。这个与JavaScript的作用域特性有关,函数自己拥有私有的作用域,函数之间的作用域相互独立,也就不会出现组件对数据的绑定出现交错的情况。

  • 在大型项目中data会造成数据污染(data是全局的)
  • 将data封装成一个函数,我们在实例化组件的时候只是调用了这个函数生成的数据副本,这就避免了数据污染。

4、vue中,属性加不加冒号的区别

加冒号:表示后边是一个变量,或者是一个表达式

不加冒号:表示后边是一个字符串字面量

Tips
① (:)是(v-bind:)的简写,v-bind是vue用来绑定属性的,属于vue的基础知识——模板语法 > > > 指令 > > > v-bind,详见官网https://cn.vuejs.org/v2/guide/syntax.html#%E6%8C%87%E4%BB%A4
② 指令 (Directives) 是带有 v- 前缀的特殊 attribute。除v-for外,其余attribute的预期值是一个js表达式

5、ES6—箭头函数()=>{} 与function的区别

1.箭头函数与function定义函数的写法:

//function
function fn(a, b){
	return a + b;
}
//arrow function
var foo = (a, b)=>{ return a + b };


2.this的指向:
使用function定义的函数,this的指向随着调用环境的变化而变化的,而箭头函数中的this指向是固定不变的,一直指向的是定义函数的环境。

//使用function定义的函数
function foo(){
	console.log(this);
}
var obj = { aa: foo };
foo(); //Window
obj.aa() //obj { aa: foo }

使用function定义的函数中this指向是随着调用环境的变化而变化的

//使用箭头函数定义函数
var foo = () => { console.log(this) };
var obj = { aa:foo };
foo(); //Window
obj.aa(); //Window

明显使用箭头函数的时候,this的指向是没有发生变化的。


3.构造函数
//使用function方法定义构造函数
function Person(name, age){
this.name = name;
this.age = age;
}
var lenhart = new Person(lenhart, 25);
console.log(lenhart); //{name: ‘lenhart’, age: 25}

//尝试使用箭头函数
var Person = (name, age) =>{
this.name = name;
this.age = age;
};
var lenhart = new Person(‘lenhart’, 25); //Uncaught TypeError: Person is not a constructor

function是可以定义构造函数的,而箭头函数是不行的。


4.变量提升
由于js的内存机制,function的级别最高,而用箭头函数定义函数的时候,需要var(let const定义的时候更不必说)关键词,而var所定义的变量不能得到变量提升,故箭头函数一定要定义于调用之前!

foo(); //123
function foo(){
console.log(‘123’);
}

arrowFn(); //Uncaught TypeError: arrowFn is not a function
var arrowFn = () => {
console.log(‘456’);
};

Npm命令

NPM是什么?
它是一个命令行工具,npm已经被全球超过1100万开发人员所依赖,- 拥有超过一百万个软件包,是世界上最大的软件注册表。也可以形象的理解为一个应用商城,我们可以在里面下载各种已经编写好的代码,像常用的jQuery,webpack等等。官网链接

NPM的安装
npm是Node.js默认的软件包管理系统,安装完node后,会默认安装好npm
安装完毕后,使用cmd控制台(win + R),使用命令node -v查看node版本,npm -v查看npm版本

使用命令npm i npm -g全局安装npm,会默认更新最新版本
NPM 的基本使用
npm -v :通过查看版本,看npm是否安装成功

npm install : 使用 npm命令安装模块,例如npm install jquery

npm uninstall : 使用命令卸载模块,例如npm uninstall jquery

npm update : 使用命令更新模块,例如npm update jquery

npm install -g : 可以直接在命令行里使用,安装在全局

npm list -g:查看所有全局安装的模块

npm list vue:查看某个模块的版本号

npm -g install npm@5.9.1:(@后跟版本号)这样我们就可以更新npm版本,指定安装版本号

npm install -save moduleName:-save 在package文件的dependencies节点写入依赖。默认值

npm install -save-dev moduleName :-save-dev 在package文件的devDependencies节点写入依赖

dependencies:运行时的依赖,发布后,即生产环境下还需要用的模块

devDependencies:开发时的依赖。里面的模块是开发时用的,发布时用不到它。

NPM 镜像的设置和查看
查看镜像配置结果 :npm config get registry

将npm设置为淘宝镜像:npm config set registry https://registry.npm.taobao.org --global

使用nrm工具切换淘宝源:npx nrm use taobao

切换到官方源:npx nrm use npm

Package.json 属性说明
name :包名。

version :包的版本号。

description :包的描述。

homepage :包的官网 url 。

author :包的作者姓名。

dependencies :依赖包列表

repository:包代码存放的地方的类型。

main :main 字段指定了程序的主入口文件,require(‘moduleName’)就会加载这个文件。

keywords :关键字

注意:

package.json文件中版本号的说明,安装的时候代表不同的含义:

“7.14.0” 表示安装指定的7.14.0版本

“~7.14.0” 表示安装 7.0.x 中最新的版本

“^7.14.0” 表示安装7.x.x中最新的版本

特别注意:当我们将代码文件拷贝给别人时,如果只拷贝了package.json文件,可以使用命令npm install会直接安装package.json下的所有依赖

yarn
yarn解决了npm的一些缺陷!

yarn 安装
在有了npm的基础上这个就很简单了

使用npm安装npm install -g yarn 查看版本:yarn --version
淘宝源安装:

分别运行一下两行命令:

yarn config set registry https://registry.npm.taobao.org -g
yarn config set sass_binary_site http://cdn.npm.taobao.org/dist/node-sass -g
成功提示:success Set “registry” to “https://registry.npm.taobao.org”.

yarn的基本使用
yarn init:初始化项目 同npm init,执行输入信息后,会生成package.json文件

yarn install:安装package.json里所有包,并将包及它的所有依赖项保存进yarn.lock

yarn install --flat:安装一个包的单一版本

yarn install --force :强制重新下载所有包

yarn install --production:只安装dependencies里的包

yarn install --no-lockfile:不读取或生成yarn.lock

yarn install --pure-lockfile:不生成yarn.lock

yarn add [package]: 在当前的项目中添加一个依赖包,会自动更新到package.json和yarn.lock文件中

yarn add [package]@[version]:安装指定版本,这里指的是主要版本,如果需要精确到小版本,使用-E参数

yarn add [package]@[tag] :安装某个tag(比如beta,next或者latest)

yarn add --dev/-D: 加到 devDependencies

yarn add --peer/-P :加到 peerDependencies

yarn add --optional/-O :加到 optionalDependencies

yarn的优点
速度快
安装版本统一
更简洁的输出
多注册来源处理
更好的语义化
以上就是npm包管理工具的全部内容了!

Node.js

Node.js是一个基于Chrome V8引擎的JavaScript运行环境。Node对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好。

我们都知道计算机处理器智能识别机器语言,而JavaScript是一门高级语言,计算机并不能直接读懂。所以我们需要所谓的引擎来将其转化成计算机所能理解的语言。

v8引擎是由Google推出的,为其浏览器Chrome所设计的开源JavaScript引擎。得益于JIT,编译模式的改变与编译阶段的优化,JavaScript的性能得到了一个飞跃。其源代码是用c++写的。

除了对JavaScript性能的大幅提升,v8引擎也提供了“嵌入”的功能,使得开发者也可以在自己的c++程序中使用“嵌入”的v8引擎,从而高效地编译JavaScript,并加入c++的feature。

要知道,作为一个底层得多的语言,c++可以实现的feature可要比JavaScript多得多。举例说明,JavaScript本身并没有read这么一个function。然而通过v8,我们可以将其绑定到一个用c++写的read callback上,从而通过JavaScript我们也可以直接加载文件了。

于是,借助于v8种种便利的功能,Node.js诞生了。

Node.js是一项服务器技术。我们都知道客户端提出服务请求,而服务器端负责处理请求并提供服务。而对于互联网来说,在Node.js之前JavaScript是一项完全的客户端技术,被用于浏览器中实现各种动画,对DOM的操作等等。而后端,即服务端则是由PHP、Python、Ruby、Java等等语言来实现。

Node.js的出现,使得前后端使用同一种语言,统一模型的梦想得以实现。

所以Node.js到底解决了JavaScript的什么痛点和问题?

  • 更好的组织代码,提升复用性。当然在ES6中这一点也得到了很大的提升。
  • 处理文件与数据库。
  • 与互联网进行沟通,以标准化的格式处理请求并发送回答。
  • 快速地解决如上问题。

同时,Node.js还带来了许多别的后端技术所不具备,或是不完善的优点,如其他人回答中的事件驱动,异步编程,非阻塞式io等等。JavaScript本身语言的特性,以及其的流行程度与社区活跃度给Node.js带来了各种意义上的优势。

在Node启动的很短时间内,社区就已经贡献了大量的扩展库(模块)。其中很多是连接数据库或是其他软件的驱动,但还有很多是凭他们的实力制作出来的非常有用的软件。

应用方向:

在几年的时间里,Node.JS逐渐发展成一个成熟的开发平台,吸引了许多开发者。有许多大型高流量网站都采用Node.JS进行开发,此外,开发人员还可以使用它来开发一些快速移动Web框架。

除了Web应用外,NodeJS也被应用在许多方面,涉及到应用程序监控、媒体流、远程控制、桌面和移动应用等等。

Node对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好。V8引擎执行Javascript的速度非常快,性能非常好。

Node是一个基于Chrome JavaScript运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node 使用事件驱动, 非阻塞I/O 模型而得以轻量和高效,非常适合在分布式设备上运行数据密集型的实时应用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值