一、es6的语法
1、let与var的区别
ES6 新增了let命令,用来声明变量。它的用法类似于var(ES5),但是所声明的变量,只在let命令所在的代码块内有效。如下代码:
{ let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1
上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。
for循环的计数器,就很适合使用let命令,如下代码:
for (let i = 0; i < 10; i++) { // ... } console.log(i); // ReferenceError: i is not defined
上面代码中,计数器i只在for循环体内有效,在循环体外引用就会报错。
2、var变量提升现象,如下代码:
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10
上面代码中,变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10。如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。
3、另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc// abc// abc
上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。
4、let不存在变量提升
var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined。而let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。如下代码:
// var 的情况 console.log(foo); // 输出undefined var foo = 2; // let 的情况 console.log(bar); // 报错ReferenceError let bar = 2;
上面代码中,变量foo用var命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量bar用let命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。
5、let不允许在相同作用域内重复声明同一个变量,如下代码:
// 报错 function func() { let a = 10; var a = 1; } // 报错 function func() { let a = 10; let a = 1; }
6、ES6还添加了const命令,const声明一个只读的常量,一旦声明,常量的值就不能改变,如下:
const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable.
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。也就是说,const只声明不赋值,就会报错。
const的作用域与let命令相同,即只在声明所在的块级作用域内生效,不能重复声明,并且不存在变量提升。
7、ES6模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。模板字符串中嵌入变量,需要将变量名写在${}之中。
let name = 'tom'; let str = `我是 ${name}.`
8、变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
ES6 允许写成下面这样:
let [a, b, c] = [1, 2, 3];
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。如果解构不成功,变量的值就等于undefined。
9、函数
(1)es5的普通函数,function声明函数,如下:
function add(x) { return x; }; add(10); // 10
(2)函数对象的写法,如下:
let add = function (x) { return x; }; add(10); // 10
(3)es6箭头函数,如下:
let add = (x) => { return x; }; add(10) // 10
(4)上面的箭头函数可简写成如下形式:
let add = x => x; add(10); // 10 // 如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。如下: var sum = (num1, num2) => { return num1 + num2; } // 如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。如下: let getTempItem = id => { id: id, name: "Temp" }; // 报错 let getTempItem = id => ({ id: id, name: "Temp" }); // 不报错
10、es6的对象(this指向问题)
(1)匿名函数中this指向
// 字面量方式声明一个对象person let person = { name: "tom", age: 30, fav: function () { console.log(this); // this指向 当前的调用者person对象 console.log(this.name); } }; person.fav() // {name: "tom", age: 30, fav: ƒ} // tom
(2)对象的单体模式,本质与上个示例一样:
let person = { name: "tom", age: 30, fav(){ console.log(this); // this指向 当前的调用者person对象 console.log(this.name); } }; person.fav() // {name: "tom", age: 30, fav: ƒ} // tom
(3)箭头函数中this指向
let person = { name: "tom", age: 30, fav: () => { console.log(this); // this指向 person的父级对象(上下文),即window console.log(this.name); } }; person.fav() // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
11、es6的类
(1)es5
function Person(name, age) { this.name = name; this.age = age; } // 基于原型给对象声明方法,原型prototype是当前类的父类(继承性) Person.prototype.showName = function () { console.log(this.name); }; let p1 = new Person('alex', 29); p1.showName() // alex
(2)es6
class Person{ constructor(name='alex', age=29){ // 单体模式 this.name = name; this.age = age; } showname(){ // 单体模式 console.log(this.name); // this指向当前对象Person } showage(){ console.log(this.age); } } let p1 = new Person(); p1.showname(); // alex
二、vue的基本语法
1、vue的介绍
(1)前端三大框架(可以去github查看三个框架的 star星):
vue 尤雨溪,渐进式的JavaScript框架
react Facebook公司,里面的(高阶函数 es6)非常多,对初学者不友好
angular 谷歌公司,目前更新到6.0,学习angular得需要玩一下typescript
(2)cdn方式引用
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
(3)下载到本地引用
<script src='./vue.js'></script>
(4)库和框架
上面vue.js文件是一个库,小而精;
框架是功能比较齐全,像Django,大而全,功能比较多;
(5)实例化对象
示例代码:
<script src="./vue.js"></script> <script> // 实例化对象 new Vue({ el:"#app", // 这里data是一个对象,在后续学习中发现data中一般是一个函数 data:{ // 数据属性 msg1:"黄瓜", person:{ name:"alex" }, msg2:'hello Vue', isShow:'True' }, methods:{ // 该组件中声明的方法 }, watch:{ // 该组件中监听的数据属性 } }); </script>
注意:如果是我们自己定义的属性和方法,则全部暴露在外面,如果是vue实例对象自己属性和方法,会在前边加一个”$”进行区分。另外,data中有一个观察者Observer,在观察着一些数据是否发生了改变,若改变,则将改变后的值立马渲染到DOM中对应的地方,控制台查看data效果如下图:
2、vue的模板语法
<div id="app"> <!--模板语法--> <h2>{{ msg1 }}</h2> <h3>{{ 'haha' }}</h3> <h3>{{ 1+1 }}</h3> <h4>{{ {'name':'alex'} }}</h4> <h5>{{ person.name }}</h5> <h2>{{ 1>2?'真的':'假的' }}</h2> <p>{{ msg2.split('').reverse().join('') }}</p> </div>
3、vue的思想
数据驱动视图,设计模式MVVM(model view viewmodel)
4、vue的基本指令(使用指令系统后边一定是字符串,且字符串中的变量一定是数据属性中已有的变量)
(1)vue的指令系统之v-text和v-html(***),如下:
<div id="content"> {{ msg }} <div v-text="msg"></div> <!-- v-text相当于innerText --> <div v-html="msg"></div> <!-- v-html相当于innerHTML --> </div> <script src="./vue.js"></script> <script> // 实例化对象 new Vue({ el:"#content", // data中是一个函数 函数中return一个对象,可以是空对象,但不能不return data(){ // 函数的单体模式 return{ msg:"<h2>alex</h2>" } } }); </script>
效果如下图:
(2)条件渲染v-if和v-show,如下:效果如下图:
<div class="box1" v-show="isShow">hello</div>
<div class="box2" v-if="isShow">hello</div>
分析:isShow为真则显示div,为假则不显示;
区别:v-show为假时相当于display:none;v-if为假时相当于移除该div,但是有一个占位的注释”<!-- -->”;
官网对v-if和v-show的区别:
1)v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
2)v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
3)相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
4)一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
v-if与v-else:可以使用 v-else 指令来表示 v-if 的“else 块”:
<div v-if="Math.random() > 0.5"> Now you see me </div> <div v-else> Now you don't </div> // 注意:v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别。
(3)v-bind和v-on
v-bind:标签中所有属性,如img标签的src alt,a标签的href title id class等,如下:
<img v-bind:src="imgSrc" v-bind:alt="msg">
v-bind:class='{active:isActive}'表示若isActive(数据属性中定义的变量)为真,则对应div增加active类,否则不增加,如下:
<div class="box" v-bind:class='{active:isActive}'></div>
v-on:监听js中的所有事件,如click,mouseover,mouseout等,如下:
<button v-on:click="clickHandler">切换颜色</button>
v-bind的简便写法是":",如:<div class="box" :class='{active:isActive}'></div>
v-on的简便写法是"@",如:<button @click="clickHandler">切换颜色</button>
(4)列表渲染v-for(不仅可以遍历数组,还可以遍历对象),如下:
<div id="app"> <ul v-if="res.status === 'ok'"> <!-- v-for的优先级是最高的 diff算法 --> <li v-for='(item,index) in res.users' :key="item.id"> <h3>{{ item.id }} -- {{ item.name }} -- {{ item.age }}</h3> </li> </ul> <div v-for='(value,key) in person'> {{ key }}-----{{ value }} </div> </div> <script src="./vue.js"></script> <script> new Vue({ el:"#app", data(){ return { res: { status: 'ok', users: [ {id: 1, name: 'alex', age: 18}, {id: 2, name: 'wusir', age: 30}, {id: 3, name: 'yuan', age: 48} ] }, person: { name: 'tom' } } }, methods:{ // 该组件中声明的方法 }, watch:{ // 该组件中监听的数据属性 } }); </script>
总结:遍历数组时,一个参数是值,两个参数是(值,索引);遍历对象时,一个参数是值,两个参数是(值,键)。
注意:一定要绑定一个标识(有id就绑定id,没有id绑定index),则值改变会直接通过key查找,而不用再去遍历查找,提升效率。
三、Vue应用示例
1、实现轮播图,代码如下:
<div id="app"> <img :src="images[currentIndex].imgSrc" @click="imgHandler" alt=""> <br /> <button @click="prevHandler">上一张</button> <button @click="nextHandler">下一张</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> // Vue实例对象,可以有返回值vm,官网中就是用vm代表Vue实例对象 let vm = new Vue({ el: '#app', data() { return { // 实际项目中images这个数据以及图片是后端返回过来的 images:[ {id:1, imgSrc:'./images/1.jpg'}, {id:2, imgSrc:'./images/2.jpg'}, {id:3, imgSrc:'./images/3.jpg'}, {id:4, imgSrc:'./images/4.jpg'} ], currentIndex:0 } }, methods:{ nextHandler(){ this.currentIndex++; if(this.currentIndex == 4){ this.currentIndex = 0; } }, prevHandler(){ this.currentIndex--; if(this.currentIndex == -1){ this.currentIndex = 3; } }, imgHandler(e){ console.log(e); // e是当前事件对象 console.log(e.target); // 当前事件对象的目标对象 console.log(this); // this指当前Vue实例对象 } }, // 组件创建完成时调用created函数,可发送ajax created(){ // 注意:开定时器就要清定时器,后面会介绍在destroyed函数中清掉 setInterval(()=>{ // 定时器中若不用箭头函数则this指则定时器对象 console.log(this); // 这时this指当前Vue实例对象 this.currentIndex++; if(this.currentIndex == 4){ this.currentIndex = 0; } },2000); // 若定时器中不用箭头函数,也可以用下面方式 let _this = this; setInterval(function () { console.log(_this); // _this指当前Vue实例对象 },2000) } }) </script>
2、Vue中使用ajax(created是组件创建完成时执行),代码如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> span.active{color:red} </style> </head> <body> <div id="app"> <span @click="clickHandler(index, category.id)" v-for="(category,index) in categoryList" :key="category.id" :class="{active:index==currentIndex}"> {{ category.name }} </span> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="jquery.min.js"></script> <script> let vm = new Vue({ el: '#app', data() { return { categoryList:[], currentIndex:0 } }, methods:{ clickHandler(i, id){ this.currentIndex = i; // 发起请求 $.ajax({ url:`https://www.luffycity.com/api/v1/courses/?sub_category=${id}`, type:'get', success:function (res) { console.log(res); } }) } }, // 组件创建完成时调用created函数,可发送ajax created(){ $.ajax({ url:'https://www.luffycity.com/api/v1/course_sub/category/list/', type:'get', success:(res) => { // 若不用箭头函数,则this指当前ajax对象 console.log(res); // res是请求接口收到的数据 // 根据响应数据的状态字段判断是否成功 if(res.error_no === 0){ var data = res.data; this.categoryList = data; // this指当前Vue实例对象 let obj = { id:0, name:'全部', category:0 }; // unshift方法表示在数组前插入一个元素 this.categoryList.unshift(obj) } }, error:function (err) { console.log(err); } }) } }) </script> </body> </html>
页面运行效果如下图:
依次点击每一项,控制台效果如下图:
3、实现音乐播放器,代码如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> .active{color:blue} </style> </head> <body> <div id="music"> <!-- audio 是HTML5的新标签 --> <!-- @ended 播放完成会自动调用该方法 --> <audio @ended="nextHandler" :src="musicList[currentIndex].songSrc" controls autoplay></audio> <ul> <li @click="songHandler(index)" v-for="(item,index) in musicList" :key="item.id" :class="{active:index==currentIndex}"> <h5>歌名:{{ item.name}}</h5> <p>歌手:{{ item.author}}</p> </li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> let musicList = [ { id:1, name:'于荣光 - 少林英雄', author:'于荣光', songSrc:'./static/于荣光 - 少林英雄.mp3' }, { id:2, name:'Joel Adams - Please Dont Go', author:'Joel Adams', songSrc:'./static/Joel Adams - Please Dont Go.mp3' }, { id:3, name:'MKJ - Time', author:'MKJ', songSrc:'./static/MKJ - Time.mp3' }, { id:4, name:'Russ - Psycho (Pt. 2)', author:'Russ', songSrc:'./static/Russ - Psycho (Pt. 2).mp3' } ]; new Vue({ el: '#music', data() { return { musicList:[], currentIndex:0 } }, methods:{ songHandler(i){ this.currentIndex = i }, nextHandler(){ this.currentIndex++; if(this.currentIndex == 4){ this.currentIndex = 0; } } }, created(){ // 实际项目中往往向后台要数据,赋值给数据属性 this.musicList = musicList } }) </script> </body> </html>
四、Vue基础知识
1、计算属性(主要产生缓存的数据属性,防止DOM性能消耗)
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:
<div id="example"> {{ message.split('').reverse().join('') }} </div>
在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中多次引用此处的翻转字符串时,就会更加难以处理。所以,对于任何复杂逻辑,你都应当使用计算属性。例如:
<div id="example"> <p>原数据: "{{ message }}"</p> <p>翻转后的数据: "{{ reversedMessage }}"</p> </div> var vm = new Vue({ el: '#example', data: { message: 'Hello' }, computed: { // 计算属性的 getter(计算属性默认只有getter方法),计算属性要有返回值 reversedMessage: function () { return this.message.split('').reverse().join('') // `this`指向vm实例 } } })
2、侦听器(watch)
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
watch可以监听单个属性,如果想监听多个属性,则声明多个属性的监听,如下:
<div id="app"> <p>{{ msg }}</p> <button @click='clickHandler'>修改</button> </div> <script src="vue.js"></script> <script> new Vue({ el:'#app', data(){ return { msg:"alex", age:18 } }, methods:{ clickHandler(){ this.msg = "wusir" } }, watch:{ // 监听属性'msg' 'msg':function (value) { console.log(value); if (value === 'wusir'){ this.msg = '大武sir'; } }, // 监听属性'age' 'age':function (value) { } } }) </script>
注意:计算属性即可以监听单个属性,又可以监听多个属性,如下示例:
<div id="app"> <p>{{ myMsg }}</p> <button @click='clickHandler'>修改</button> </div> <script src="vue.js"></script> <script> new Vue({ el:'#app', data(){ return { msg:"alex", age:18 } }, methods:{ clickHandler(){ this.msg = "wusir"; this.age = 20; } }, computed:{ myMsg: function () { // 即监听msg属性,又监听age属性 return `我的名字叫${this.msg},年龄是${this.age}`; } } }) </script>
3、计算属性的应用(上例中音乐播放器改为计算属性实现)
修改audio标签的src属性值,如下:
<audio @ended="nextHandler" :src="currentSong" controls autoplay></audio>
Vue实例中增加计算属性computed,如下:
computed:{ currentSong(){ // 既监听了musicList,又监听了currentIndex return this.musicList[this.currentIndex].songSrc } }
总结:计算属性的方法即可以在模板语法中使用,又可以在指令系统中使用。
4、关于函数中this指向的问题
Vue实例对象中,一般methods和computed中定义的函数中的this是指当前Vue实例对象,而created中的ajax和定时器中定义的函数中的this是指ajax或者定时器对象,这时,ajax和定时器中的函数改为箭头函数,就可以改变this的指向,即this指向当前Vue实例对象。