解构
- 解构赋值是对赋值运算符的扩展。
- 是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
- 在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
在解构中,有下面两部分参与:
- 解构的源,解构赋值表达式的右边部分。
- 解构的目标,解构赋值表达式的左边部分。
数组
- 基本
let [a, b, c] = [1, 2, 3];
console.log(a, b, c); //1 2 3
- 可嵌套
let [a, [[b], c]] = [1, [[2], 3]];
console.log(a, b, c); //1 2 3
- 可忽略
let [a, , b] = [1, 2, 3];
console.log(a, b); //1 3
- 不完全解构
let [a = 1, b] = []; // a = 1, b = undefined
- 剩余运算符
let [a, ...b] = [1, 2, 3];
console.log(a, b); //a = 1, b = [2,3]
- 字符串
在数组的解构中,解构的目标若为可遍历对象,皆可进行解构赋值。可遍历对象即实现 Iterator 接口的数据。
let [a, b, c, d, e] = 'hello';
console.log(a, b, c, d, e); //h e l l o
- 解构默认值
let [a = 2] = [undefined]; // a = 2
当解构模式有匹配结果,且匹配结果是 undefined 时,会触发默认值作为返回结果。
let [a = 3, b = a] = []; // a = 3, b = 3
let [a = 3, b = a] = [1]; // a = 1, b = 1
let [a = 3, b = a] = [1, 2]; // a = 1, b = 2
- a 与 b 匹配结果为 undefined ,触发默认值:a = 3; b = a =3
- a 正常解构赋值,匹配结果:a = 1,b 匹配结果 undefined ,触发默认值:b = a =1
- a 与 b 正常解构赋值,匹配结果:a = 1,b = 2
对象
- 基本
let { foo, bar } = { bar: 'bbb' , foo: 'aaa'};
// foo = 'aaa'
// bar = 'bbb'
let { baz : foo } = { baz : 'ddd' };
// foo = 'ddd'
- 可嵌套可忽略
let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, { y }] } = obj;
// x = 'hello'
// y = 'world'
let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, { }] } = obj;
// x = 'hello'
- 不完全解构
let obj = {p: [{y: 'world'}] };
let {p: [{ y }, x ] } = obj;
// x = undefined
// y = 'world'
- 剩余运算符
let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40};
// a = 10
// b = 20
// rest = {c: 30, d: 40}
- 解构默认值
let {a = 10, b = 5} = {a: 3};
// a = 3; b = 5;
let {a: aa = 10, b: bb = 5} = {a: 3};
// aa = 3; bb = 5;
扩展运算符
数组
- 复制数组:
let arr = [1, 2],
arr1 = [...arr];
console.log(arr1); // [1, 2]
// 数组含空位
let arr2 = [1, , 3],
arr3 = [...arr2];
console.log(arr3); [1, undefined, 3]
- 合并数组:
console.log([...[1, 2],...[3, 4]]); // [1, 2, 3, 4]
对象
拓展运算符(…)用于取出参数对象所有可遍历属性然后拷贝到当前对象。
- 基本用法
let person = {name: "Amy", age: 15};
let someone = { ...person };
console.log(someone); //{name: "Amy", age: 15}
- 可用于合并两个对象
let age = {age: 15};
let name = {name: "Amy"};
let person = {...age, ...name};
console.log(person); //{age: 15, name: "Amy"}
注意:自定义的属性和拓展运算符对象里面属性的相同的时候
- 自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉。
let person = {name: "Amy", age: 15};
let someone = { ...person, name: "Mike", age: 17};
console.log(someone); //{name: "Mike", age: 17}
- 自定义的属性在拓展运算度前面,则变成设置新对象默认属性值。
let person = {name: "Amy", age: 15};
let someone = {name: "Mike", age: 17, ...person};
console.log(someone); //{name: "Amy", age: 15}
- 拓展运算符后面是空对象,没有任何效果也不会报错。
let a = {...{}, a: 1, b: 2};
console.log(a); //{a: 1, b: 2}
- 拓展运算符后面是null或者undefined,没有效果也不会报错。
let b = {...null, ...undefined, a: 1, b: 2};
console.log(b); //{a: 1, b: 2}
let
在ES6中我们可以使用新的关键字来声明变量,let声明的变量只在 let 命令所在的代码块内有效(块级作用域、局部作用),ES6 推荐在函数中使用 let 定义变量,而非 var。
基本用法
//{}括起来的代码块
{
let a = 0;
console.log(a); // 0
}
console.log(a); // 报错 ReferenceError: a is not defined
代码块内有效
{
let a = 0;
var b = 1;
}
console.log(b); // 1
console.log(a); // ReferenceError: a is not defined
不能重复声明
var a = 1;
var a = 2;
console.log(a); // 2
let b = 3;
let b = 4;
console.log(b); // Identifier 'a' has already been declared
不存在变量提升
console.log(a); //undefined
var a = "banana";
console.log(b); //ReferenceError: b is not defined
let b = "apple";
变量 a 用 var 声明存在变量提升,所以当脚本开始运行的时候,a 已经存在了,但是还没有赋值,所以会输出 undefined。
变量 b 用 let 声明不存在变量提升,在声明变量 b 之前,b 不存在,所以会报错。
const
const 声明一个只读变量,声明之后不允许改变。意味着,一旦声明必须初始化,否则会报错。
暂时性死区
var PI = "a";
if(true){
console.log(PI); // ReferenceError: PI is not defined
const PI = "3.1415926";
}
- let 和const 关键词声明的变量不具备变量提升(hoisting)特性
- let 和 const 声明只在最靠近的一个块中(花括号内)有效
- 当使用常量 const 声明时,请使用大写变量,如:CAPITAL_CASING
- const 在声明时必须被赋值
- const 如何做到变量在声明初始化之后不允许改变的,使用 const 声明复杂类型对象时要慎重。
块级作用域
ES6 明确规定,代码块内如果存在 let 或者 const,代码块会对这些命令声明的变量从块的开始就形成一个封闭作用域。
- 块级作用域内 var 和 function不允许定义同名变量重复声明
{ var a; function a() {} }
{ function a() {} function a() {} }
{ var a; var a; }
- 块级作用域内部用 var声明的变量 和 function声明的函数,会在全局预编译阶段提升为undefined(这个过程中,内部function声明的提升到全局对象window中 值为undefined,类似于var提升—将会比全局中function声明的函数早一步提升),块级内部代码一开始执行时, 函数发生声明, 则function声明的函数会立马提升到块级作用域头部,此时全局中的值也立马变成函数体
console.log(a, fn);
if (true) {
console.log(a, fn);
var a = 1;
function fn() {}
console.log(a, fn);
}
console.log(a, fn);
定义
箭头函数提供了一种更加简洁的函数书写方式。基本语法是:
参数 => 函数体
(参数) => {函数体}
- 基本语法:
//普通函数
var f = function(a){
return a;
}
f(1); //1
//箭头函数
var f = a => a
f(10); //10
当箭头函数没有参数或者有多个参数,要用 () 括起来。
var f = (a,b) => a+b;
f(6,2); //8
当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
var f = (a,b) => {
let result = a+b;
return result;
}
f(6,2); // 8
当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来
var f = (id,name) => ({id: id, name: name});
f(6,2); // {id: 6, name: 2}
注意点:没有 this、super、arguments 和 new.target 绑定。
var a = () => {
// 箭头函数里面没有 this 对象,
// 此时的 this 是外层的 this 对象,即 Window
console.log(this);
}
a(11);
var b = () => {
console.log(arguments);
}
b(111); // ReferenceError: arguments is not defined
对象中使用箭头函数,this表示全局Window对象
var obj = {
name: "xx",
show: function() {
console.log(this); //this表示当前对象
},
say: () => {
console.log(this); //this表示全局window对象
}
}
箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象。
理解为:箭头函数中的this 指向的是 最近的父级的正常函数(不是箭头函数)里面的this 所指向的地方 找不到就是window
var obj2 = {
a: 10,
fn: function() {
console.log(this.a);
return {
a: 20,
fn: () => {
console.log(this.a);
return {
a: 30,
fn: function() {
console.log(this.a);
return {
a: 40,
fn: () => {
console.log(this.a);
}
}
}
}
}
}
}
}
var obj3 = { a: 88 }
obj2.fn().fn().fn().fn();
obj2.fn().fn.call(obj3); // 箭头函数的this是固定的 不可以通过call bind apply改变
注意:箭头函数不可以作为构造函数,也就是不能使用 new 命令,否则会报错
function Person() {
console.log(this);
}
new Person(); //Person {}
var People = ()=>{
console.log(this);
}
new People(); //TypeError: People is not a constructor
使用场景
- 箭头函数在参数中使用
var arr= [1,2,3];
arr = arr.map((a)=>a*a);
console.log(arr);
- 箭头函数可以与解构一起使用
//变量为目标,返回值为源
let cal = (a, b) => {
return {
add: a+b,
sub: a-b,
mul: a*b,
div: a/b
};
}
let {add, sub, mul, div} = cal(10, 5);
//形参为目标,实参为源
var show = ({one, two}) => {
console.log(one + "---" + two);
}
show({one: "hello", two: "你好"});
ES6 之前,JavaScript 的 this 对象一直很令人头大,回调函数,经常看到 var self = this 这样的代码,为了将外部 this 传递到回调函数中,那么有了箭头函数,就不需要这样做了,直接使用 this 就行。
所以,当我们需要维护一个 this 上下文的时候,就可以使用箭头函数。
总结
- 要有个箭头
- 箭头的前面是小括号,放形参,只有一个形参的时候可以省略小括号;
- 箭头的后面是函数体;
- 如果函数体只有一个语句,没有{},此时的返回值不需要return;
- 箭头函数里面的this总是指向最靠近的function 内部的this;
- 对象里面的方法,尽可能不要使用箭头函数;
- 箭头函数里面没有arguments,可以使用…reset,接收过来就是数组类型,接收的是形参之外的所有的实参;
var show = (a, b, ...reset) => {
console.log(a + b);
console.log(reset);
}
show(1, 2, 3, 4, 5);
定义
类可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法
- class 的本质是 function。
- 它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
- 类不可重复声明
- 类定义不会被提升,这意味着必须在访问前对类进行定义,否则就会报错
// 1. 如何创建类 语法:class 类名 {}
class Fn {};
console.dir(Fn);
// 2. 类不能重复声明
class Fn {}; // Identifier 'Fn' has already been declared
// 3. 类不会提升
console.log(An); // Cannot access 'An' before initialization
class An {};
// 命名类
class Example {
constructor(a) {
this.a = a;
}
}
// 匿名类
let Example = class {
constructor(a) {
this.a = a;
}
}
构造器
- constructor 方法是类的默认方法,创建类的对象(new 类名)时被调用。也被称为类的构造方法(构造函数、构造器)。一个类中有且仅有一个构造方法。
class People {
constructor() { // 固定写法 不能写成其他的方式-例如:1. constructor: function() {} 2. constructor = function() {}
console.log("我是构造方法,使用new创建对象时调用");
}
}
new People(); // 将执行constructor方法
- 作用:通过this为类的实例化添加属性和方法(所以在constructor中不能单独定义属性或方法,必须加this)
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
// count = 111; // 报错 Uncaught ReferenceError: count is not defined
}
}
let person1 = new Person('111', '222');
console.log(person1.name);
console.log(person1.age);
方法
原型方法
- 类的方法是直接放在类中的函数,没有放在constructor中声明
- 类的方法存在类的原型对象上,类本身无法访问,类的实例对象可以访问
class Person {
say() {
console.log('类中的say方法');
};
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let person1 = new Person('大海', 26);
console.log(person1.say);
console.log(Person.say); // undefined
// 注意:函数的声明使用赋值语句 或 对象的方法简洁表达式 而不是:因为class内部不是对象的语法环境
class Person {
say: function(params) {}; // 报错
say = function(params) {}; // 正确写法(不推荐)
say() {}; // 正确写法(推荐)
constructor(name, age) {
this.name = name;
this.age = age;
}
}
静态方法
- 类的静态方法和属性直接创建为类本身的属性,只能通过类访问,类的实例对象不能访问
- 不放在constructor中声明
class Person {
say() {
console.log('类中的say方法');
};
constructor(name, age) {
this.name = name;
this.age = age;
};
static num = 1; // 用static标识的
static say1() {
console.log('类的静态方法');
}
}
let person1 = new Person('大海', 26);
console.log(Person.say1);
console.log(person1.say1); // undefined
console.dir(Person);
- 类的普通方法(存在类原型对象上的方法) 可以与 类的静态方法(类本身的方法)同名
class Person {
say() {
console.log('类中的say方法');
};
constructor(name, age) {
this.name = name;
this.age = age;
};
say1() {
console.log('类的方法say1');
}
static num = 1;
static say1() {
console.log('类的静态方法say1');
}
}
let person1 = new Person('大海', 26);
Person.say1(); // '类的静态方法say1'
person1.say1(); // '类的方法say1'
类中最外层的属性
- 写在类中最外层的属性会作为实例对象的属性,不是类的属性,也不是类原型属性
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
};
num = 10;
}
let person1 = new Person('大海', 26);
console.log(person1); // Person {num: 10, name: '大海', age: 26}
继承
- 作用:解决代码的复用
- 使用extends关键字实现继承
class Animal { }
class Dog extends Animal {
constructor() {
super();
}
};
console.log(Dog);
- 子类可以继承父类中所有的方法和属性(包括静态方法和属性)
class Animal {
constructor() {
this.foots = 4;
this.head = 1;
this.ears = 2;
}
static jump() {
console.log('I can jump!');
};
running() {
console.log('I can running!');
}
}
class Dog extends Animal {
constructor() {
super();
}
};
console.log(Dog.jump);
console.dir(Dog);
const dahuang = new Dog();
dahuang.jump();
dahuang.running();
console.log(dahuang.foots, dahuang.head, dahuang.ears);
- 子类只能继承一个父类(单继承类不能重复定义),一个父类可以有多个子类
- 子类的构造方法中必须有super()来指定调用父类的构造方法(没有super()会报错),并且位于子类构造方法中的this之前
class Animal {
constructor() {
this.foots = 4;
this.head = 1;
this.ears = 2;
}
jump() {
console.log('I can jump!');
};
running() {
console.log('I can running!');
}
}
class Person {}
class Dog extends Animal {
constructor() {
console.log(111); // 可正常打印
this.name = '111'; // 报错
super();
}
};
const dahuang = new Dog();
- 子类中如果有与父类相同的方法和属性,将会优先使用子类的(覆盖)
// 方法
class Animal {
constructor() {
this.foots = 4,
this.head = 1,
this.ears = 2;
}
static jump() {
console.log('I can jump in Animal!');
};
running() {
console.log('I can running in Animal!');
}
}
class Dog extends Animal {
constructor() {
super();
};
static jump() {
console.log('I can jump in Dog!');
};
running() {
console.log('I can running in Dog!');
}
};
const dahuang = new Dog();
dahuang.running(); // I can running in Dog!
Dog.jump(); // I can jump in Dog!
// 属性
class Animal {
constructor() {
this.name = "huahua";
};
count = 99;
}
class Dog extends Animal {
constructor(name) {
super();
this.name = name;
};
running() {
console.log(this.name + ' can running in Dog!');
}
count = 88;
};
const dahuang = new Dog('dahuang');
dahuang.name; // dahuang
dahuang.count; // 88
内部类:属于外部类的成员,必须通过“外部类.内部类”访问
// 外部类
class Outer {
constructor() {
console.log("outer");
}
}
// 内部类
Outer.Inner = class {
constructor() {
console.log("Inner");
}
}
new Outer.Inner();
let p1= new Outer.Inner();
console.log(p1.name);
Promise
概述
promise是异步编程的一个解决方案,它是一个对象,创建方式与普通对象相同。可以通过promise构造函数创建出来,从他可以获取异步操作的消息.
promise有三种状态:进行中,已完成,已失败
Promise是一个构造器,原型上有then,catch方法,对象上有reject、resolve方法。
创建
通过new关键字创建promise对象
var p = new Promise(function(resolve, reject){
// 做一些异步操作
setTimeout(function(){
console.log('执行完成');
resolve('随便什么数据');
}, 2000);
});
resolve
resolve:异步操作执行成功后的回调函数
let p = new Promise((resolve, reject) => {
let n = 1;
console.log(n);
if (n > 2) {
resolve('666');
} else {
reject('error');
}
})
p.then(value => {
console.log(value)
}, reason => {
console.log(reason)
})
reject
reject:异步操作执行失败后的回调函数
事实上,我们前面的例子都是只有“执行成功”的回调,还没有“失败”的情况,reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调。
const pro = new Promise((resolve, reject) => {
reject('1111');
})
pro.then((val) => {
console.log('then方法中的第一个参数被执行了');
console.log(val);
}, (err) => {
console.log('then方法中的第二个参数被执行了');
console.log(err);
})
then
promise.then()方法会返回一个新的promise对象 在then中进行return 会被新的promise对象的then捕获
function getPromise() {
return new Promise((resolve) => {
setTimeout(function() {
console.log('异步操作执行完成');
resolve('执行成功1');
}, 1000);
})
}
const pro1 = getPromise();
pro1.then((v) => {
console.log(v);
return '执行成功2';
}).then((v) => {
console.log(v);
return '执行成功3';
}).then((data) => {
console.log(data);
})
.then中的第一个函数被触发 一定是resolve方法调用 而且代码逻辑没有出错
.then中的第二个函数被触发的情况:
- resolve参数中有错误代码 (无论有无resolve() || reject())
- reject方法被调用
resolve之后的代码不会被捕获
- resolve()/ reject()之后的代码逻辑上有错误 不会被.then中的第二个参数函数捕获 也不会报错
- resolve之后的代码逻辑上没有问题,就会有执行
catch
们知道Promise对象除了then方法,还有一个catch方法,它是做什么用的呢?其实它和then的第二个参数一样,用来指定reject的回调,用法是这样:
getNumber();
.then(function(data){
console.log('resolved');
console.log(data);
}).catch(function(reason){
console.log('rejected');
console.log(reason);
});
效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
console.log(somedata); //此处的somedata未定义
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
});
在resolve的回调中,我们使用了console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到这样的结果:

也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。
all
- Promise.all() 方法用于将多个 Promise 实例, 包装成一个新的 Promise 实例。
- 语法:const p = Promise.all([p1, p2, p3]);
- 只有p1、 p2、 p3的状态都变成fulfilled, p的状态才会变成fulfilled
- 只要p1、 p2、 p3之中有一个被rejected, p的状态就变成rejected
const p1 = new Promise((res, rej) => {
res(1);
})
const p2 = new Promise((res, rej) => {
res(2);
})
const p3 = new Promise((res, rej) => {
res(3);
});
const p = Promise.all([p1, p2, p3]);
console.log(p);
p.then((v) => {
console.log(v);
}).catch((err) => {
console.log(err);
})
finally
- finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
- finally方法的回调函数不接受任何参数
new Promise((reso, reje) => {
reso(1);
}).then((val) => {
console.log(val);
}).catch((err) => {
console.log(err);
}).finally(() => {
console.log('一定会执行的代码');
})
async函数
async
- 语法
async function name(param) { statements }
参数说明:
- name: 函数名称。
- param: 要传递给函数的参数的名称。
- statements: 函数体语句。
- 返回值
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。
async function fn() {
// return '函数的返回值';
console.log(x);
}
var res = fn();
console.log(res);
res.then(v => {
console.log(v);
}).catch(err => {
console.log(err, 6666);
})
await
async 函数中可能会有 await 表达式,async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值。
await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用。如果在 async function 函数体外使用 await ,你只会得到一个语法错误。
- 语法:
[return_value] = await expression;
参数说明:
- expression: 一个 Promise 对象或者任何要等待的值。
- 返回值
返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。
如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。
正常情况下,await 命令后面是一个 Promise 对象,它也可以跟其他值,如字符串,布尔值,数值以及普通函数。(await加普通函数其实就是和不加一样效果) - await针对所跟不同表达式的处理方式:
- Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
- 非 Promise 对象:直接返回对应的值。
示例1: await后面是一个Promise对象(函数调用结果):
function testAwait() {
return new Promise((resolve, reject)=>{
setTimeout(()=>{
console.log("返回一个promise对象");
resolve("返回数据"); // 如果没有resolve(),代表这个promise没有执行完成 那么下面的helloAsync函数内部的代码也会阻塞,不会继续往下执行
}, 2000)
});
}
async function helloAsync() {
console.log('helloAsync开始执行')
var a = await testAwait();
console.log(a);
console.log('helloAsync结束执行')
}
helloAsync();
// 执行结果:
// 返回一个promise对象
// 返回数据
// helloAsync
async function haha(){
await new Promise(function(res,rej){
setTimeout(function(){
console.log(1);
res(123123)
},2000)
})
console.log(2);
}
haha();
// 1 2
示例2:await后面是一个普通值
async function myAsync() {
var res = await 111;
console.log(res); // 111
}
myAsync();
function createPromise() {
return new Promise((resolve, reject) => {
setTimeout(function() {
console.log('请求成功');
const data = new Array(10).fill({ name: 'Florence', employee: '前端开发工程师' });
resolve(data);
}, 2000)
})
}
async function fn() { // async标识的函数返回值是一个promise对象 函数的return值将会被then接收
console.log('async执行了');
// const res = createPromise();
const res = await createPromise();
console.log(res);
return 666;
}
let fn_res = fn();
fn_res.then((val) => {
console.log(val); // 666
}).catch((err) => {
console.log(err);
})