ES6


title: ES6

date: 2019-09-05 13:59:32

tags: javascript

categories:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级

  • JS # 一级分类
  • ES6 # 二级分类

ES6 常见语法

let 和 const

let定义局部变量

var定义全局变量.用var定义的全部变量,有时候会污染整个 js 的作用域。

const定义常量

在全局作用域下使用letconst声明变量,变量并不会挂载到window

  1. var 可声明前置(变量提升)

变量会提升,函数也会提升,并且函数优先于变量提升,函数提升会把整个函数提到作用域顶部,变量提升只会把声明提上去.

1
2
3
a = 3;
var a;
var a = 4;
  1. let 不可声明前置

暂时性死区:不能在变量声明之前使用变量

1
2
a = 3; //报错
let a;
  1. let 不可重复声明
1
2
3
let a = 3;
let a = 4; //报错
var a = 5; //报错
  1. 存在块级作用域
1
2
3
4
for (let i = 0; i < 3; i++) {
console.log(i); //块级作用域只在大括号内,出了大括号,i并没有声明,会报错
}
console.log(i); //报错

暂时性死区: 在 let 声明变量之前都是该变量的死区,在死区内该变量不可使用.不能被声明也不能被获取.

  1. const 声明的常量不可改变
1
2
3
4
5
6
const a = 1;
a = 2; //报错

const obj = { a: 1 };
obj.a = 2; //正常
obj = { a: 2 }; //报错

const 对象等于引用类型,obj 存的是{a: 1}的地址,里面的东西改变并不影响地址.而obj = {a: 2}是赋给一个新的地址,发生了改变,会报错.

  1. 适用于let同样适用于const

letconst作用基本一致,const声明的变量不能再次赋值

ES6 可以大量使用let,如果认定模块不改变,可以使用const

解构赋值

关于数组的解构赋值

1
2
3
4
5
6
7
8
9
let [a, b, c] = [1, 2, 3];
console.log(a, b, c);

let [a, [b], c] = [2, [3], 4];
a; //2
b; //3
c; //4

let [a] = 1; //报错,因为[a]是一个数组,把值赋值给数组就报错了
  1. 默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let [a, b = 2] = [3]
a // 3
b // 2,因为b没有对应值,所以是undefined,使用默认值

let [a, b = 2] = [3, 4]
a //3
b //4
数组对应对值有没有?如果没有(数组对没有指undefined)就使用默认值,如果有就使用对应值

let [a=2, b=3] = [undefined, null]
a //2
b //null
let [a=1, b=a] = [2]
a //2
b //2,因为b没有对应值,所以是undefined,使用默认值,此时a的默认值为2
  1. 对象的解构赋值

前置知识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let [name, age] = ["hunger", 3];
let p1 = { name, age };
//等同于
let p2 = { name: name, age: age };
解构范例;

let { name, age } = { name: "jirengu", age: 4 };
name; //‘jirengu’
age; //4
以上代码等同于;

let name;
let age;
({ name: name, age: age } = { name: "jirengu", age: 4 });
  1. 默认值
1
2
3
let { x, y = 5 } = { x: 1 };
x; //1
y; //5
  1. 函数解构
1
2
3
4
5
6
7
8
9
10
11
function add([x = 1, y = 2]) {
return x + y;
}
add(); //Error
add([2]); //4
add([3, 4]); //7

function sum({ x, y } = { x: 0, y: 0 }, { a = 1, b = 1 }) {
return [x + a, y + b];
}
sum({ x: 1, y: 2 }, { a: 2 }); //[3, 3]
  1. 作用
1
2
3
4
5
6
7
let [x, y] = [1, 2];
[x, y] = [y, x]
x //2
y // 1
function ajax({url, type=‘GET’}){
}
ajax({url: ‘http://localhost:3000/getData’})

字符串,数组,对象

字符串

  1. 多行字符串
1
2
3
4
5
let str = `
Hi,
This is jirengu.com.
You can study frontend here.
`;
  1. 字符串模板
1
2
3
4
5
6
7
let website = "jirengucom";
let who = "You";
let str = `Hi
This is ${website}.
${who} can study frontend here
`;
//${},可以替换变量
1
2
3
4
5
var data = [1, 2, 3, 4];
var liArr = data.map((v) => `<li>${v}</li>`).join("");
var html = `<ul>${liArr}</ul>`;
console.log(html);
//"<ul><li>1</li><li>2</li><li>3</li><li>4</li></ul>"

数组

  1. 扩展运算符...

主要作用是将数组或对象进行展开.

对象中的扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中

1
2
3
4
5
6
7
var a = [1, 2];
console.log(...a); // 1, 2,这三个点相当于脱壳,把数组的壳拿掉
var b = [...a, 3];
b; // [1, 2, 3]

var c = b.concat([4, 5]); //b拼接[4,5]变成[1,2,3,4,5]
var d = [...b, 4, 5]; //直接把脱壳后的1,2,3放入数组中
  1. 函数参数的扩展
1
2
3
4
5
6
7
8
function sort(...arr) {
console.log(arr.sort());
}
sort(3, 1, 5); //[1, 3, 5]
function max(arr) {
return Math.max(...arr);
}
max([3, 4, 1]); // 4
  1. 类数组对象转数组
1
2
3
4
5
6
7
let ps = document.querySelectorAll("p");
Array.from(ps).forEach((p) => {
console.log(p.innerText);
});
[...ps].forEach((p) => {
console.log(p.innerText);
});

函数

  1. 默认值
1
2
3
4
5
function sayHi(name = "jirengu") {
console.log(`hi, ${name}`);
}
sayHi();
sayHi("ruoyu");
1
2
3
4
function fetch(url, { body = "", method = "GET", headers = {} } = {}) {
console.log(method);
}
fetch("http://example.com"); //如果没有定义那么就是undefined,使用默认值,如果传入了参数,那就使用传入值

以下两种写法的区别?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//ex1
function m1({ x = 0, y = 0 } = {}) {
return [x, y];
}

//ex2
function m2({ x, y } = { x: 0, y: 0 }) {
return [x, y];
}

// 函数没有参数的情况
m1(); // [0, 0]
m2(); // [0, 0]

// x 和 y 都有值的情况
m1({ x: 3, y: 8 }); // [3, 8]
m2({ x: 3, y: 8 }); // [3, 8]

// x 有值,y 无值的情况
m1({ x: 3 }); // [3, 0]
m2({ x: 3 }); // [3, undefined]

// x 和 y 都无值的情况
m1({}); // [0, 0];把{}赋给(x = 0,y = 0)
m2({}); // [undefined, undefined];把{}赋给{x,y},也就是把undefined, undefined赋给{x,y}

m1({ z: 3 }); // [0, 0]
m2({ z: 3 }); // [undefined, undefined]

ex1:调用函数需要你传递一个对象,如果你没传对象就用默认值对象{},默认值对象里面都是 undefined, 所以属性使用初始值

ex2:参数需要是一个对象,如果没传对象,就用默认值对象{ x: 0, y: 0 }如果传了对象,就使用你传递的对象

  1. 箭头函数

箭头后面的内容,就相当于return的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var f = (v) => v + 1;
//等价于
var f = function (v) {
return v + 1;
};

var f = () => 5;
// 等同于
var f = function () {
return 5;
};

var sum = (num1, num2) => num1 + num2; //适用于立刻返回,即把代码写成一行的情况
// 等同于
var sum = function (num1, num2) {
return num1 + num2;
};
1
2
3
4
//数组的平方
var arr = [1, 2, 3];
var arr2 = arr.map((v) => v * v);
arr2; //[1, 4, 9]
  1. 箭头函数里面的 this

1.箭头函数会捕获其所在上下文的 this 值作为自己的 this 值,自己本身并没有 this 值.

2.箭头函数的 this 永远指向其上下文的 this,任何方法都改变不了其指向,如 call,bind,apply.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ES6
function foo() {
setTimeout(() => {
console.log("id:", this.id);
}, 100);
}

// 等同于如下ES5
function foo() {
var _this = this;
setTimeout(function () {
console.log("id:", _this.id);
}, 100);
}

对象

1
2
3
var name = "jirengu";
var age = 3;
var people = { name, age }; //{name:'jirengu', age:3}
1
2
3
4
5
6
7
let app = {
selector: "#app",
//函数简写
init() {},
bind() {},
};
app.init();

类和继承

  1. 构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}

sayHello() {
console.log(`hello, ${this.name}, i am ${this.age} years old`);
}
}
等价于;

function Person(name, age) {
this.name = name;
this.age = age;
}

Person.prototype.sayHello = function () {
console.log(`hello, ${this.name}, i am ${this.age} years old`);
};

var p = new Person("hunger", 2);

静态方法

静态方法调用直接在类上进行,不能在类的实例上调用。

下面的例子说明了这几点:

静态方法如何在类上实现。

具有静态成员的类,可以被子类化 。

什么情况下静态方法可以调用,什么情况下不能调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Tripple {
static tripple(n = 1) {
return n * 3;
}
}

class BiggerTripple extends Tripple {
static tripple(n) {
return super.tripple(n) * super.tripple(n);
}
}

console.log(Tripple.tripple()); // 3
console.log(Tripple.tripple(6)); // 18

let tp = new Tripple();

console.log(BiggerTripple.tripple(3)); // 81(不会受父类实例化的影响)
//super继承父类的方法super.tripple(n) = 3n,BiggerTripple.tripple(n) = (3n)^2.当n=3,输出81
console.log(tp.tripple()); // 'tp.tripple 不是一个函数'.说明只能在类上实现.
  1. 继承

js 中并不存在类,class只是语法糖,本质是函数

class 继承的核心在于使用extends表明继承自哪个父类,并在子类构造函数中必须调用super

通过super获取父类的属性.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}

sayHello() {
//写的函数会直接放到原型上
console.log(`hello, ${this.name}, i am ${this.age} years old`);
}
}
class Student extends Person {
//这里的constructor是Student的构造函数,内含三个参数
constructor(name, age, score) {
//这里的super是继承自父类的参数
super(name, age);
this.score = score;
}

sayScore() {
console.log(
`hello, ${this.name}, i am ${this.age} years old, i get ${this.score}`
);
}
}

模块化

为什么使用模块化?

  1. 解决命名冲突
  2. 提供复用性
  3. 提高代码可维护性

写法 1

1
2
3
4
5
6
7
// profile.js
export var firstName = "Michael";
export var lastName = "Jackson";
export var year = 1958;
//useage.js
import { firstName, lastName, year } from "./profile";
console.log(firstName);

写法 2

1
2
3
4
5
6
7
8
var firstName = "Michael";
var lastName = "Jackson";
var year = 1958;

export { firstName, lastName, year };
//useage.js
import { firstName, lastName, year } from "./profile";
console.log(firstName);

写法 3

1
2
3
4
5
6
//helper.js
export function getName() {}
export function getYear() {}
//main.js
import { getName, getYear } from "./helper";
getName();

写法 4

1
2
3
4
5
6
7
//helper.js
function getName() {}
function getYear() {}
export { getName, getYear };
//main.js
import { getName, getYear } from "./helper";
getName();

写法 5

1
2
3
4
5
6
7
// export-default.js
export default function () {
console.log("foo");
}
// import-default.js
import getName from "./export-default";
getName();