ES6
1. let/var
块级作用域
- JS中使用 var 来声明一个变量时,变量的作用域主要是和函数的定义有关
- 针对于其它块定义来水是没有作用域的,比如 for/if
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
<script>
// ES5中的var是没有块级作用域的(if/for)
// ES6中的let是由块级作用的(if/for)
// ES5之前因为if和for都没有块级作用域的概念, 所以在很多时候, 我们都必须借助于function的作用域来解决应用外面变量的问题.
// ES6中,加入了let, let它是有if和for的块级作用域.
// 1.变量作用域: 变量在什么范围内是可用.
// {
// var name = 'why';
// console.log(name);
// }
// console.log(name);
// 2.没有块级作用域引起的问题: if的块级
// var func;
// if (true) {
// var name = 'why';
// func = function () {
// console.log(name);
// }
// // func()
// }
// name = 'kobe'
// func()
// // console.log(name);
var name = 'why'
function abc(bbb) { // bbb = 'why'
console.log(bbb);
}
// 将why传进去,无论外面怎么改,都不会有影响
abc(name)
name = 'kobe'
// 3.没有块级作用域引起的问题: for的块级
// 为什么闭包可以解决问题: 函数是一个作用域.ES5必须要借助闭包去解决块级作用域
// var btns = document.getElementsByTagName('button');
// for (var i=0; i<btns.length; i++) {
// (function (num) { // 0
// btns[i].addEventListener('click', function () {
// console.log('第' + num + '个按钮被点击');
// })
// })(i)
// }
const btns = document.getElementsByTagName('button')
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
</script>
</body>
2. const
- 使用const修饰的标识符为常量, 不可以再次赋值
建议: 在ES6开发中,优先使用const, 只有需要改变某一个标识符的时候才使用let
<script>
// 1.注意一: 一旦给const修饰的标识符被赋值之后, 不能修改
// const name = 'why';
// name = 'abc';
// 2.注意二: 在使用const定义标识符,必须进行赋值
// const name;
// 3.注意三: 常量的含义是指向的对象不能修改, 但是可以改变对象内部的属性.
const obj = {
name: 'why',
age: 18,
height: 1.88
}
// 内存地址不能改变
// obj = {}
console.log(obj);
obj.name = 'kobe';
obj.age = 40;
obj.height = 1.87;
console.log(obj);
</script>
3. 对象字面量的增强写法
ES6中,对对象字面量进行了很多增强。
<script>
// const obj = new Object()
// const obj = {
// name: 'why',
// age: 18,
// run: function () {
// console.log('在奔跑');
// },
// eat: function () {
// console.log('在次东西');
// }
// }
// 1.属性的增强写法
const name = 'why';
const age = 18;
const height = 1.88
// ES5的写法
// const obj = {
// name: name,
// age: age,
// height: height
// }
// ES6的写法
const obj = {
name,
age,
height,
}
console.log(obj);
// 2.函数的增强写法
// ES5的写法
// const obj = {
// run: function () {
//
// },
// eat: function () {
//
// }
// }
// ES6的写法
const obj = {
run() {
},
eat() {
}
}
</script>
4. 箭头函数
基本使用
const ccc = () => { }
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// 箭头函数: 也是一种定义函数的方式
// 1.定义函数的方式: function
const aaa = function () {
}
// 2.对象字面量中定义函数
const obj = {
bbb() {
}
}
// 3.ES6中的箭头函数
// const ccc = (参数列表) => {
//
// }
const ccc = () => {
}
</script>
</body>
</html>
箭头函数参数和返回值
- 一个参数时,省略小括号
- 函数代码块只有一行,省略大括号和return 如:render: h => h(App)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// 1.参数问题:
// 1.1.放入两个参数
const sum = (num1, num2) => {
return num1 + num2
}
// 1.2.放入一个参数,小括号可以省掉
const power = num => {
return num * num
}
// 2.函数中
// 2.1.函数代码块中有多行代码时
const test = () => {
// 1.打印Hello World
console.log('Hello World');
// 2.打印Hello Vuejs
console.log('Hello Vuejs');
}
// 2.2.函数代码块中只有一行代码
// const mul = (num1, num2) => {
// return num1 + num2
// }
const mul = (num1, num2) => num1 * num2
console.log(mul(20, 30)); // 600 不需要return执行的结果自动返回
// const demo = () => {
// console.log('Hello Demo'); // Hello Demo
// }
const demo = () => console.log('Hello Demo')
console.log(demo()); // undefined
// render: (h) => {
// return h(App)
// }
// 只有一个参数小括号省略,函数代码块只有一行,省略大括号和return
// render: h => h(App)
</script>
</body>
</html>
箭头函数中的this使用
- 一般将一个函数作为另一个函数的参数的时候,使用箭头函数
- 箭头函数的this查找:向外层作用域中, 一层层查找this, 直到有this的定义
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// 什么时候使用箭头,将一个函数作为另一个函数的参数的时候
setTimeout(function () {
console.log(this); // Window
}, 1000)
setTimeout(() => {
console.log(this); // Window
}, 1000)
// 问题: 箭头函数中的this是如何查找的了?
// 答案: 向外层作用域中, 一层层查找this, 直到有this的定义.
const obj = {
aaa() {
setTimeout(function () {
console.log(this); // window
})
setTimeout(() => {
console.log(this); // obj对象
})
}
}
obj.aaa()
const obj1 = {
aaa() {
setTimeout(function () {
setTimeout(function () {
console.log(this); // window
})
setTimeout(() => {
console.log(this); // window
})
})
setTimeout(() => {
setTimeout(function () {
console.log(this); // window
})
setTimeout(() => {
console.log(this); // obj
})
})
}
}
obj1.aaa()
</script>
</body>
</html>
5. Promise
什么是Promise
- ES6中一个非常重要和好用的特性就是Promise
- Promise到底是做什么的呢?
- Promise是异步编程的一种解决方案。
- 那什么时候我们会来处理异步事件呢?
- 一种很常见的场景应该就是网络请求了。
- 我们封装一个网络请求的函数,因为不能立即拿到结果,所以不能像简单的3+4=7一样将结果返回。
- 所以往往我们会传入另外一个函数,在数据请求成功时,将数据通过传入的函数回调出去。
- 如果只是一个简单的网络请求,那么这种方案不会给我们带来很大的麻烦。
- 但是,当网络请求非常复杂时,就会出现回调地狱。
网络请求的回调地狱
- 以下场景
-
我们需要通过一个url1从服务器加载一个数据data1,data1中包含了下一个请求的url2
-
我们需要通过data1取出url2,从服务器加载数据data2,data2中包含了下一个请求的url3
-
我们需要通过data2取出url3,从服务器加载数据data3,data3中包含了下一个请求的url4
-
发送网络请求url4,获取最终的数据data4
这样的代码难看而且不容易维护。
我们更加期望的是一种更加优雅的方式来进行这种异步操作。
Promise可以以一种非常优雅的方式来解决这个问题。
-
Promise的基本语法
定时器的异步事件
-
这里,我们用一个定时器来模拟异步事件:
- 假设下面的data是从网络上1秒后请求的数据
- console.log就是我们的处理方式。
// 1.使用setTimeout 是异步操作 setTimeout(() => { console.log('Hello World'); }, 1000)
-
这是我们过去的处理方式,我们将它换成Promise代码
```js new Promise((resolve, reject) => { setTimeout(() => { // 成功的时候调用resolve resolve('Hello World') // 失败的时候调用reject reject('error message') }, 1000) }).then((data) => { console.log(data); // Hello World }).catch((err) => { console.log(err); // error message }) ``` -
这个例子会让我们感觉多此一举
- 首先,下面的Promise代码明显比上面的代码看起来还要复杂。
- 其次,下面的Promise代码中包含的resolve、reject、then、catch都是些什么东西?
-
我们先不管第一个复杂度的问题,因为这样的小程序根本看不出来Promise真正的作用。
定时器异步事件解析
- new Promise很明显是创建一个Promise对象
- 小括号中((resolve, reject) => {})也很明显就是一个函数,而且我们这里用的是之前刚刚学习过的箭头函数。
- 但是resolve, reject它们是什么呢?
- 我们先知道一个事实:在创建Promise时,传入的这个箭头函数是固定的(一般我们都会这样写)
- resolve和reject它们两个也是函数,通常情况下,我们会根据请求数据的成功和失败来决定调用哪一个。
- 成功还是失败?
- 如果是成功的,那么通常我们会调用resolve(messsage),这个时候,我们后续的then会被回调。
- 如果是失败的,那么通常我们会调用reject(error),这个时候,我们后续的catch会被回调。
- 这就是Promise最基本的使用了。
Promise三种状态
- 当我们开发中有异步操作时, 就可以给异步操作包装一个Promise
- 异步操作之后会有三种状态
- 我们一起来看一下这三种状态:
pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()
Promise写法
new Promise((resolve, reject) => {}).then().catch()
new Promise((resolve, reject) => {}).then(参数一, 参数二)
Promise链式调用
- 我们在看Promise的流程图时,发现无论是then还是catch都可以返回一个Promise对象。
- 所以,我们的代码其实是可以进行链式调用的:
- 这里我们直接通过Promise包装了一下新的数据,将Promise对象返回了
- Promise.resovle():将数据包装成Promise对象,并且在内部回调resolve()函数
- Promise.reject():将数据包装成Promise对象,并且在内部回调reject()函数
链式调用简写
return Promise(resolve => resolve(data))return Promise.resolve(data)return data
- 简写一
return Promise(resolve => resolve(结果))
// 一、new Promise(resolve => resolve(结果))
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa')
}, 1000)
}).then(res => {
console.log(res, '第一层的10行处理代码');
// 对结果进行第一次处理
return new Promise((resolve, reject) => {
resolve(res + '111')
// reject('err')
})
}).then(res => {
console.log(res, '第二层的10行处理代码');
return new Promise(resolve => {
resolve(res + '222')
})
}).then(res => {
console.log(res, '第三层的10行处理代码');
}).catch(err => {
console.log(err);
})
- 简写二
return Promise.resolve(结果)
// 二、new Promise.resolve(结果)
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa')
}, 1000)
}).then(res => {
console.log(res, '第一层的10行处理代码');
// 对结果进行第一次处理
return Promise.resolve(res + '111')
}).then(res => {
console.log(res, '第二层的10行处理代码');
return Promise.resolve(res + '222')
}).then(res => {
console.log(res, '第三层的10行处理代码');
})
- 简写三
return 结果
// 三、省略掉Promise.resolve
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa')
}, 1000)
}).then(res => {
console.log(res, '第一层的10行处理代码');
// 对结果进行第一次处理
// 内部会对res + '111'进行Promise包装,并且内部会去调用resolve
return res + '111'
}).then(res => {
console.log(res, '第二层的10行处理代码');
return res + '222'
}).then(res => {
console.log(res, '第三层的10行处理代码');
})
Promise抛出异常
// 四、如果某一层失败了,
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa')
}, 1000)
}).then(res => {
// 1.自己处理10行代码
console.log(res, '第一层的10行处理代码');
// 2.对结果进行第一次处理 (1)失败调用reject进入catch执行
// return Promise.reject('error message')
// (2)手动抛出异常throw
throw 'error message'
}).then(res => {
console.log(res, '第二层的10行处理代码');
return Promise.resolve(res + '222')
}).then(res => {
console.log(res, '第三层的10行处理代码');
}).catch(err => {
console.log(err);
})
Promise的all方法
// 五、需求:将两个请求返回的结果合并后再做其他操作
// 没有Promise之前
// 网络请求一:
let isResult1 = false
let isResult2 = false
$ajax({
url: '',
success: function () {
console.log('结果1');
isResult1 = true
handleResult()
}
})
// 网络请求二:
$ajax({
url: '',
success: function () {
console.log('结果2');
isResult2 = true
handleResult()
}
})
// 如何判断两个请求都拿到了再做后续操作,通过添加两个变量
function handleResult() {
if (isResult1 && isResult2) {
//
}
}
// 更优方案:
// 使用Promise直接包装两个异步请求,在两个异步请求都完成的时候,在一个地方统一进行回调
// 六、通过Promise的all方法解决多个异步请求都完成后获取结果
// all 传入数组
// 数组里new两个Promise
// 会在内部帮你判断这两个网络请求有没有都完成,如果都完成了,就会执行all([]).then() 进入这里的then
// results为数组
Promise.all([
// new Promise((resolve, reject) => {
// $.ajax({
// url: 'url1',
// success: function (data) {
// resolve(data)
// }
// })
// }),
// new Promise((resolve, reject) => {
// $.ajax({
// url: 'url2',
// success: function (data) {
// resolve(data)
// }
// })
// })
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({name: 'why', age: 18})
}, 2000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({name: 'kobe', age: 19})
}, 1000)
})
]).then(results => {
console.log(results);
// results[0] 为第一个请求执行的结果
// results[1] 为第二个请求执行的结果
// 最终是把两个异步请求的结果都放到了results数组里,这里的ajax请求通过setTimeout模拟异步请求
})