本篇整理了一下ES6的一些常用基础知识,关于ES6的知识将会继续学习和补充。
如有记录得不对的地方,欢迎探讨。
ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。
ES6 实际上是一个泛指,泛指 ES2015 及后续的版本。
每一次标准的诞生都意味着语言的完善,功能的加强。JavaScript语言本身也有一些令人不满意的地方。
ES6中新增的用于声明变量的关键字
let声明的变量只在所处于的块级有效
使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性
if (true){
let a = 10;
}
console.log(a); //ReferenceError: a is not defined
特点:
不存在变量提升
a = 1
console.log(a)//ReferenceError: Cannot access 'a' before initialization
let a
暂时性死区
var tmp = 123;
if(true){
tmp = 'abc';
let tmp;
} //ReferenceError: Cannot access 'tmp' before initialization
经典面试题:
Q1:请问下面代码输出结果是什么?
var arr = [];
for (var i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[0](); //2
arr[1](); //2
此题的关键点在于变量i是全局的,函数执行时输出的都是全局作用域下的i值。
**Q2 :请问下面代码输出结果是什么?**
let arr = [];
for (let i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[0](); //0
arr[1](); //1
此题的关键点在于每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值.
作用:声明常量,常量就是值(内存地址)不能变化的量。
特点:
具有块级作用域
if(true){
const a = 10;
}
console.log(a); //ReferenceError: a is not defined
声明常量时必须赋值
const PI; //SyntaxError: Missing initializer in const declaration
常量赋值后,值(内存地址)不能修改
const PI = 3.14;
PI = 10; //Assignment to constant variable.
这里所说的值不能修改,其实指的是内存地址不能修改。我们来看下面这个数组的例子:
const arr = [100,200];
arr[0] = 1;
arr[1] = 2;
console.log(arr); //[1,2]
arr = [3,4]; //Assignment to constant variable.
可见,我们可以修改数组里面的数值,但是不能修改数组的内存地址。
其实,在ES6中,常量的含义是指向的对象不能修改,但是可以改变对象内部的属性。
var | let | const |
---|---|---|
函数级作用域 | 块级作用域 | 块级作用域 |
变量提升 | 不存在变量提升 | 不存在变量提升 |
值可更改 | 值可更改 | 值不可更改 |
ES5中:
const name = "Kobe";
const age = "18";
const obj = {
name: name,
age: age
}
ES6中字面量增强写法:
const name = "Kobe";
const age = "18";
const obj = {
name,
age
}
ES5中:
const obj = {
run: function () {
},
eat: function () {
}
}
ES6中:
const obj = {
run () {
},
eat () {
}
}
按照一定模式,从数组中或对象中提取值,将提取出来的值赋值给另外的变量。
let [a,b,c] = [1,2,3];
console.log(a); //1
console.log(b); //2
console.log(c); //3
如果解构不成功,变量的值为undefined:
let [a] = [];
console.log(a); //undefined
let [b,c] = [1];
console.log(b); //1
console.log(c); //undefined
let person = { name: 'zhangsan', age: 20 };
let { name, age } = person;
console.log(name); // 'zhangsan'
console.log(age); // 20
let {name: myName, age: myAge} = person; // myName myAge 属于别名
console.log(myName); // 'zhangsan'
console.log(myAge); // 20
ES6 中新增函数定义方式:箭头函数
() => {}
const fn = () => {}
其中,fn 是函数名,小括号内是形参,大括号内是函数体。
函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号
如下面两种写法都可以表示一个返回加法结果的函数:
function sum(num1, num2) {
return num1 + num2;
}
const sum = (num1, num2) => num1 + num2;
如果形参只有一个,可以省略小括号
例如:
function fn (v) {
return v;
}
const fn = v => v;
箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this。即向外层作用域中, 一层层查找this, 直到有this的定义.
如:
function fn (){
console.log(this);
return () => {
console.log(this);
}
}
const obj = { name:'zhangsan' };
const refn = fn.call(obj); //obj
refn(); //obj
var age = 100;
var obj = {
age: 20,
say: () => {
console.log(this)//window
alert(this.age)//100
}
}
obj.say();
上面这段代码的输出结果是100。
我们说箭头函数不绑定this,因此在调用 say 属性中的箭头函数时如果不指定this,那么this指向window。而window中的age是100。
const obj = {
aaa() {
console.log(this)//obj
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
})
})
}
}
obj.aaa()
剩余参数语法允许我们将一个不定数量的参数表示为一个数组。
function fn(a,...b){
console.log(a); //1
console.log(b); //[2,3,4]
}
fn(1,2,3,4);
剩余参数可以和解构配合使用:
let students = ['张三','李四','王五'];
let [a,...b] = students;
console.log(a); //张三
console.log(b); //["李四", "王五"]
扩展运算符可以将数组或者对象转为用逗号分隔的参数序列。
let arr = [1,2,3];
console.log(...arr); // 1 2 3
let arr1 = [1,2,3];
let arr2 = [4,5,6];
let arr3 = [...arr1, ...arr2];
console.log(arr3); //[1,2,3,4,5,6]
应用2:将类数组转化为真正的数组
let oDivs = document.getElementsByTagName('div');
oDivs = [...oDivs];
将类数组或可遍历对象转换为真正的数组
let arrLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
}
let arr = Array.from(arrLike);
console.log(arr); //['a','b','c']
方法还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组
let arrLike = {
'0': '1',
'1': '2',
'2': '3',
length: 3
}
let arr = Array.from(arrLike, item => item*2 )
console.log(arr); //[2,4,6]
作用:用于找出第一个符合条件的数组成员,如果没有找到返回undefined
let arr = [{
id: 1,
name: 'zhangsan'
},{
id: 2,
name: 'lisi'
}];
let target = arr.find( (item,index) => item.id == 2);
console.log(target.name); //lisi
用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
let arr = [1,10,30,40];
let index = arr.findIndex( value => value > 20);
console.log(index); //2
表示某个数组是否包含给定的值,返回布尔值。
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
ES6新增的创建字符串的方式,使用反引号定义。
let name = `zhangsan`;
let name = '路飞';
let sayLove = `我喜欢${name}`;
console.log(sayLove); //我喜欢李琳琦
let obj = {
name: '张三',
age: 20,
sex: '男'
}
let html = `
${obj.name}
${obj.age}
${obj.sex}
`;
console.log(html);
/*
张三
20
男
*/
let sayHaha = () => '哈哈哈哈';
let result = `我太帅了${sayHaha()}`;
console.log(result); //我太帅了哈哈哈哈
startsWith():表示参数字符串是否在原字符串的头部,返回布尔值
endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值
let str = 'Hello world!';
str.startsWith('Hello') // true
str.endsWith('!') // true
repeat方法表示将原字符串重复n次,返回一个新字符串。
let name = '路飞';
console.log(name.repeat(3)); //李琳琦李琳琦李琳琦
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set本身是一个构造函数,用来生成 Set 数据结构
const s = new Set();
Set函数可以接受一个数组作为参数,用来初始化。
const set = new Set([1, 2, 3, 4, 4]);
console.log(set); //[1,2,3,4]
const s = new Set([1,2,3,4]);
s.add(3).add(4).add(5);
console.log(s); //Set(5) {1, 2, 3, 4, 5}
console.log(s.delete(4)); //true
console.log(s.has(4)); //false
s.clear();
console.log(s); //Set(0) {}
Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
s.forEach(value => console.log(value))
Promise 是异步编程的一种解决方案
什么时候我们会来处理异步事件呢?
一种很常见的场景应该就是网络请求了。
假如我们封装一个网络请求的函数,因为不能立即拿到结果,所以不能像简单的3+4=7一样将结果返回。所以往往我们会传入另外一个函数,在数据请求成功时,将数据通过传入的函数回调出去。
如果只是一个简单的网络请求,那么这种方案不会给我们带来很大的麻烦。但是,当网络请求非常复杂时,就会出现回调地狱。比如如下的一个场景(比较夸张的例子):
$.ajax('url1', function(data1) {
$.ajax(data1['url2'], function(data2) {
$.ajax(data2['url3'], function(data3) {
$.ajax(data3['url4'], function(data4) {
console.log(data4);
})
})
})
})
在上面的例子中,
我们需要通过一个url1从服务器加载一个数据data1,data1中包含了下一个请求的url2
我们需要通过data1取出url2,从服务器加载数据data2,data2中包含了下一个请求的url3
我们需要通过data2取出url3,从服务器加载数据data3,data3中包含了下一个请求的url4
发送网络请求url4,获取最终的数据data4
这样的代码难看而且不容易维护,我们更加期望的是一种更加优雅的方式来进行这种异步操作。Promise 就可以以一种非常优雅的方式来解决这个问题。
我们用一个定时器来模拟异步事件,假设下面的 data 是从网络上2秒后请求的数据,console.log 就是我们的处理方式:
setTimeout(() => {
let data = 'hahaha'
console.log(data);
},2000)
这是我们过去的处理方式,我们将它换成 Promise 代码:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
reject('error')
},2000)
}).then(data => {
console.log(data);
}).catch(data => {
console.log(data);
})
这个例子会让我们感觉脱裤放屁,多此一举,但是在之后的学习和运用中会发现这种链式编程是很实用的。
Promise 是类,需要用 new 来实例化
Promise 的参数是一个函数(一般用箭头函数书写),在这个函数中一般有两个参数,resolve/reject
resolve 和 reject 也是函数,通常情况下,我们会根据请求数据的成功和失败来决定调用哪一个:
如果是成功的,那么通常我们会调用resolve(success),这个时候,我们后续的then会被回调。
如果是失败的,那么通常我们会调用reject(error),这个时候,我们后续的catch会被回调。
这就是 Promise 的基本使用了。
当我们开发中有异步操作时, 就可以给异步操作包装一个Promise
异步操作之后会有三种状态:
pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。
fulfilled:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()
rejected:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QLevzyIV-1572850721130)(D:\52007前端学习\前端笔记\images\javascript\Promise的三种状态.png)]
我们在看Promise的流程图时,发现无论是then还是catch都可以返回一个Promise对象。所以,我们的代码其实是可以进行链式调用的:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('li')
},1000)
}).then(data => {
console.log(data)//li
return new Promise((resolve, reject) => {
resolve(data + 'lin')
})
}).then(data => {
console.log(data);//lilin
return Promise.resolve(data + 'qi')
}).then(data => {
console.log(data)//lilinqi
return Promise.reject(data + 'dashabi')
}).then(data => {
console.log(data)//不会有打印
return Promise.resolve(data + 'dashazhu')
}).catch(data => {
console.log(data)//lilinqidashabi
return Promise.resolve(data + '傻逼')
}).then(data => {
console.log(data)//lilinqidashabi傻逼
})
在上面的第12行中,我们直接通过Promise包装了一下新的数据,将Promise对象返回了
Promise.resovle():将数据包装成Promise对象,并且在内部回调resolve()函数
Promise.reject():将数据包装成Promise对象,并且在内部回调reject()函数
除此之外,链式调用中如果我们希望数据直接包装成Promise.resolve,那么在return时我们可以直接返回数据:
then(data => {
//return Promise.resolve(data + 'dashazhu')
return data + 'dashazhu'
})
如果数据直接包装成Promise.reject,那么在throw中可以直接返回数据:
catch(data => {
//return Promise.reject(data + 'dashazhu')
throw data + 'dashazhu'
})
结果依然是一样的。
我们来假设一种场景,当有两个网络请求同时发送请求时,我们不知道哪一个先返回了数据,一般来说我们需要在各自的函数中定义一个变量 isTrue1 和 isTrue2,然后用if判断当两个变量都为true时执行一些代码:
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 中提供了一个 all 属性,优雅地解决了这个问题:
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('True1')
}, 1000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('True2')
}, 2000);
})
]).then(results => {
console.log(results)//["True1", "True2"]
console.log(results[0])//True1
console.log(results[1])//True2
})
Promise.all() 中需要传入参数,参数是一个可以迭代的对象,比如数组。
在数组中我们可以 new 若干个Promise,并统一用then处理,此时then拿到的数据是一个数组,数组下标对应着all参数数组中的Promise对象。