最近的文章全都是初学、初探、初尝,方向也大相径庭。因为对未来的发展方向产生了些许困惑。还好这样的时光总是短暂的,云开雾散,我就继续做一个略偏前端的全栈工程师吧。钻研的前提是确定一个点。
语法
var
的替代:let
和const
let
:可被重新赋值,但不可在同一个作用域中被再次声明。
const
:必须在声明时赋予初始值,且不可被重新声明或赋值。
即:在同一作用域内(2 个花括号之间),定义变量则用let
,定义常量则用const
。var
是时候歇歇了。
注:用const
定义的对象,是 可以 修改内部的属性的:
1
2
3
const student = { name: 'James', age: 26, gender: 'male' };
student.name = 'Lucy';
console.log(student); // {name: "Lucy", age: 26, gender: "male"}
模板字符串
以` `
包裹的字符串内可包含以${}
包裹的变量名。例子:
1
2
3
const myName = 'shisaq';
const greeting = `Hello, my name is ${myName}`;
console.log(greeting); // Hello, my name is shisaq
字符串若为多行,同样奏效(在长字符串拼接 HTML 时,用此属性可省去大批的''、+
等符号):
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
// 定义teacher和student对象
const teacher = {
name: 'John'
};
const student = {
name: 'Alex',
guardian: 'Lisa'
};
// 定义长字符串
const note = `${teacher.name},
Please excuse ${student.name}.
He is recovering from the flu.
Thank you,
${student.guardian}`;
console.log(note);
/* 打印结果
John,
Please excuse Alex.
He is recovering from the flu.
Thank you,
Lisa
*/
从数组和对象中解构变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 数组
const fruits = ['apple', 'banana', 'orange'];
const [a, b, c] = fruits;
console.log(a, b, c); // apple banana orange
// 对象
const room = {
name: 'blitz',
width: 10,
length: 10,
height: 10,
size: function() {
return this.width * this.length * this.height;
},
type: 'seaview'
};
const { name, size, type } = room;
console.log(name, size(), type); // blitz 1000 seaview
对象字面量简写
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
29
30
31
// 早期写法
let type = 'quartz';
let color = 'rose';
let carat = 21.29;
const gemstone = {
type: type,
color: color,
carat: carat,
calculateWorth: function() {
return this.carat * 3;
}
};
console.log(gemstone); // {type: "quartz", color: "rose", carat: 21.29, calculateWorth: ƒ}
// ES6写法
let type = 'quartz';
let color = 'rose';
let carat = 21.29;
const gemstone = {
type,
color,
carat,
calculateWorth() {
return this.carat * 3;
}
};
console.log(gemstone); // {type: "quartz", color: "rose", carat: 21.29, calculateWorth: ƒ}
console.log(gemstone.calculateWorth()); // 63.87
For…of 循环
- 相对 For/ForEach:For…of 循环可以在不定义索引变量的情况下,直接获得可迭代对象的值
- 相对于 ForEach:For…of 循环可在规定的条件下跳过或退出循环
- 相对 For…in:For…of 循环只会取对象中元素的值,不会把 function 一并当成需要迭代的对象处理(如果数组被定义了一个额外的方法,for…in 会将该方法一起迭代出来,而 for…of 不会)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const digits = [0, 1, 2, 3, 4, 5];
for (const digit of digits) {
if (digit % 2 === 0) {
continue;
}
console.log(digit);
}
/** 打印结果
0
1
3
5
*/
扩展运算符(…)Spread Operator
将一个数组转换成以逗号分隔的参数序列。
1
2
3
4
5
6
7
const books = [
'Don Quixote',
'The Hobbit',
'Alice in Wonderland',
'Tale of Two Cities'
];
console.log(...books); // Don Quixote The Hobbit Alice in Wonderland Tale of Two Cities
简单用法:合并 2 个数组
1
2
3
4
5
6
const fruits = ['apples', 'bananas', 'pears'];
const vegetables = ['corn', 'potatoes', 'carrots'];
const produce = [...fruits, ...vegetables];
console.log(produce); // [ 'apples', 'bananas', 'pears', 'corn', 'potatoes', 'carrots' ]
遗留参数(Rest Parameter)
将遗留的值归为一个数组。多用于传参。
1
2
3
const order = [20.17, 18.67, 1.5, 'cheese', 'eggs', 'milk', 'bread'];
const [total, subtotal, tax, ...items] = order;
console.log(total, subtotal, tax, items); // 20.17 18.67 1.5 ["cheese", "eggs", "milk", "bread"]
Function
箭头函数(Arrow Functions =>)
普通函数可有 2 种书写方式:声明式和表达式;而箭头函数只能是表达式。
把常规函数转换成箭头函数的步骤如下:
- 删除函数标识符
function
; - 删除紧跟在后面的小括号
()
; - 删除后面的大括号
{}
; - 删除
return
; - 删除最后的分号
;
; - 在参数列表与函数体之间添加箭头
=>
; - (可选)若参数不是 1 个(0 个或多个),则需要把参数用小括号
()
括起来。当然,参数是 1 个的时候,也可以括起来。
举例,将一个数组所有元素都变成大写,返回这个新的数组:
1
2
3
4
5
6
7
8
9
// 常规函数写法
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(function(name) {
return name.toUpperCase();
});
// 箭头函数写法
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(name =>
name.toUpperCase()
);
箭头函数有 2 种语法:简洁函数体语法(concise body syntax)和块状函数体语法(block body syntax):
简洁函数体语法:
- 函数体没有被花括号
{}
包住; - 自动返回表达式的值。
块状函数体语法:
- 函数体被花括号
{}
包住; - 需要用
return
手动返回需要的值。
this
在常规函数与箭头函数中的不同:
常规函数:this
取决于该函数是怎么被调用的;
箭头函数:this
取决于这串代码在哪里被执行。
关于
this
在两种函数中的不同,我在另一篇文章中有详细说明。
函数中的参数默认值
常规函数写法
1
2
3
4
5
6
7
8
9
10
function greet(name, greeting) {
name = typeof name !== 'undefined' ? name : 'Student';
greeting = typeof greeting !== 'undefined' ? greeting : 'Welcome';
return `${greeting} ${name}!`;
}
greet(); // Welcome Student!
greet('James'); // Welcome James!
greet('Richard', 'Howdy'); // Howdy Richard!
带默认值的函数写法
1
2
3
4
5
6
7
function greet(name = 'Student', greeting = 'Welcome') {
return `${greeting} ${name}!`;
}
greet(); // Welcome Student!
greet('James'); // Welcome James!
greet('Richard', 'Howdy'); // Howdy Richard!
允许空值的写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function houseDescriptor({
houseColor = 'green',
shutterColors = ['red']
} = {}) {
return `I have a ${houseColor} house with ${shutterColors.join(
' and '
)} shutters`;
}
houseDescriptor({ houseColor: 'red', shutterColors: ['yellow', 'salmon'] });
// I have a red house with yellow and salmon shutters
houseDescriptor({ houseColor: 'cyan' });
// I have a cyan house with red shutters
houseDescriptor();
// I have a green house with red shutters
houseDescriptor({ shutterColors: ['blue'] });
// I have a green house with blue shutters
houseDescriptor({});
// I have a green house with red shutters
类(Class)
JavaScript 中的 Class 其实 仍然是一个函数。只是它有一些特别之处,如首字母要大写、专门用来构造对象等。从根本上说,它用了新的、更简练的写法,把本来需要用原型继承(prototypal inheritance)单独列出来的代码,封装到class
内部,从而使代码更精炼易懂,且不易出错。其实底层逻辑是没有变的。
常规写法:
1
2
3
4
5
6
7
8
9
10
function Plane(numEngines) {
this.numEngines = numEngines;
this.enginesActive = false;
}
// 如下方法可被所有实例化的对象继承
Plane.prototype.startEngines = function() {
console.log('starting engines...');
this.enginesActive = true;
};
用 ES6 重写:
1
2
3
4
5
6
7
8
9
10
11
class Plane {
constructor(numEngines) {
this.numEngines = numEngines;
this.enginesActive = false;
}
startEngines() {
console.log('starting engines...');
this.enginesActive = true;
}
}
静态方法(Static method)
静态方法 static
可直接作为类的方法调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Plane {
constructor(numEngines) {
this.numEngines = numEngines;
this.enginesActive = false;
}
static badWeather(planes) {
for (const plane of planes) {
plane.enginesActive = false;
console.log(plane.enginesActive);
}
}
startEngines() {
console.log('starting engines...');
this.enginesActive = true;
}
}
const plane1 = new Plane(2);
const plane2 = new Plane(3);
Plane.badWeather([plane1, plane2]); // false x 2
子类(Subclass)
子类引入了 2 个关键词:super
和extends
。举一个简单例子,对 ES5 和 ES6 的不同写法做个对比。定义父类Tree
,再定义子类Maple
继承父类的部分属性和方法,并加入自己独有的方法:
ES5 的写法
原型链、继承、多个代码块与容易混淆的构造函数,让 ES5 的代码很难阅读与维护:
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function Tree(size, leaves) {
this.size = typeof size === 'undefined' ? 10 : size;
var defaultLeaves = {
spring: 'green',
summer: 'green',
fall: 'orange',
winter: null
};
this.leaves = typeof leaves === 'undefined' ? defaultLeaves : leaves;
this.leafColor;
}
Tree.prototype.changeSeason = function(season) {
this.leafColor = this.leaves[season];
if (season === 'spring') {
this.size += 1;
}
};
function Maple(syrupQty, size, leaves) {
Tree.call(this, size, leaves);
this.syrupQty = typeof syrupQty === 'undefined' ? 15 : syrupQty;
}
Maple.prototype = Object.create(Tree.prototype);
Maple.prototype.constructor = Maple;
Maple.prototype.changeSeason = function(season) {
Tree.prototype.changeSeason.call(this, season);
if (season === 'spring') {
this.syrupQty += 1;
}
};
Maple.prototype.gatherSyrup = function() {
this.syrupQty -= 3;
};
var myMaple = new Maple(15, 5);
myMaple.changeSeason('fall');
myMaple.gatherSyrup();
myMaple.changeSeason('spring');
ES6 的写法
有了class
、super
和extends
的帮助,我们可以写出更易懂的代码:
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
29
30
31
32
33
34
35
36
37
38
39
40
41
class Tree {
constructor(
size = '10',
leaves = { spring: 'green', summer: 'green', fall: 'orange', winter: null }
) {
this.size = size;
this.leaves = leaves;
this.leafColor = null;
}
changeSeason(season) {
this.leafColor = this.leaves[season];
if (season === 'spring') {
this.size += 1;
}
}
}
class Maple extends Tree {
// extends 为子类(Maple)继承父类(Tree)
constructor(syrupQty = 15, size, leaves) {
super(size, leaves); // super 在 constructor 中,作为函数,继承父类的属性
this.syrupQty = syrupQty;
}
changeSeason(season) {
super.changeSeason(season); // super 在方法中,作为对象,继承父类的方法
if (season === 'spring') {
this.syrupQty += 1;
}
}
gatherSyrup() {
this.syrupQty -= 3;
}
}
const myMaple = new Maple(15, 5); // 糖浆数量(syrupQty)为15,树的尺寸(size)为5
myMaple.changeSeason('fall'); // 通过父类改变了季节
myMaple.gatherSyrup(); // 通过子类改变了糖浆数量
myMaple.changeSeason('spring'); // 通过父类改变季节后,通过子类改变糖浆数量
ES6 内置方法
唯一标识符(Symbol)
Symbol
是在现有的 6 种数据类型(Undefined
, Null
, Boolean
, Number
, String
, Object
)之外的第 7 种类型。它用于为一个对象/属性设置唯一标识符。
1
2
3
4
5
6
7
8
const bowl = {
[Symbol('apple')]: { color: 'red', weight: 136.078 },
[Symbol('banana')]: { color: 'yellow', weight: 183.15 },
[Symbol('orange')]: { color: 'orange', weight: 170.097 },
[Symbol('banana')]: { color: 'yellow', weight: 176.845 }
};
console.log(bowl);
// 结果:Object {Symbol(apple): Object, Symbol(banana): Object, Symbol(orange): Object, Symbol(banana): Object}
上文的代码中,即使bowl
中有 2 个banana
,由于使用了symbol
,即使描述一样,但 JavaScript 仍然允许它们并存,因为 symbol 是 唯一标识符。
Set
Set
是一个构造函数,它可以构造 可迭代、不重复、无索引 的数据结构。它是 数组 的变种。
1
2
const games = new Set();
console.log(games); // Set {}
Set
相关方法:
.add()
增加一个元素.delete()
删除一个元素,返回一个布尔类型的值(成功删除则为true
,失败则为false
).clear()
清空该 Set.size()
包含的元素数量.has()
传入一个元素,若 Set 包含该元素,则返回true
, 否则返回false
.values() 或 .keys()
返回 Set 中所有元素,返回类型为SetIterator
.next()
(配合.values()
使用) 返回一个对象,该对象包含下一个元素的值,以及一个布尔值,判断是否检索到最后一个元素:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const months = new Set([
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]);
const iterator = months.values();
iterator.next(); // Object {value: 'January', done: false}
iterator.next(); // Object {value: 'February', done: false}
WeakSet
与Set
类似,但只可包含对象,不能迭代,且没有clear()
方法:
1
2
3
4
5
6
7
let student1 = { name: 'James', age: 26, gender: 'male' };
let student2 = { name: 'Julia', age: 27, gender: 'female' };
let student3 = { name: 'Richard', age: 31, gender: 'male' };
const roster = new WeakSet([student1, student2, student3]);
roster.add('Amanda'); // Uncaught TypeError: Invalid value used in weak set(…)
roster.clear(); // Uncaught TypeError: roster.clear is not a function
Map
Map
是一个构造函数,它可以构造 可迭代、不重复、无索引 的数据结构。它是 对象 的变种。
1
2
const employees = new Map();
console.log(employees); // Map {}
Map
相关方法:
.set()
添加一个对象:
1
2
3
4
5
6
7
8
9
const employees = new Map();
employees.set('james.parkes@udacity.com', {
firstName: 'James',
lastName: 'Parkes',
role: 'Content Developer'
});
console.log(employees); // Map {'james.parkes@udacity.com' => Object {...}}
.delete()
删除一个对象.clear()
清空该 Map.has()
传入一个元素名称,若 Map 包含该元素,则返回true
, 否则返回false
.get()
传入一个元素名称,得到该名称对应的键值.values() 或 .keys()
返回 Map 中所有元素,返回类型为MapIterator
.next()
–- (配合
.keys()
使用) 返回一个对象,该对象包含下一个对象名称,以及一个布尔值,判断是否检索到最后一个元素; - (配合
.values()
使用)返回一个对象,该对象包含下一个对象对应的键值,以及一个布尔值,判断是否检索到最后一个元素:
- (配合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const members = new Map();
members.set('Evelyn', 75.68);
members.set('Liam', 20.16);
members.set('Sophia', 0);
members.set('Marcus', 10.25);
let iteratorObjForKeys = members.keys();
iteratorObjForKeys.next(); // Object {value: 'Evelyn', done: false}
iteratorObjForKeys.next(); // Object {value: 'Liam', done: false}
let iteratorObjForValues = members.values();
iteratorObjForValues.next(); // Object {value: 75.68, done: false}
iteratorObjForValues.next(); // Object {value: 20.16, done: false}
使用 for...of
循环,返回的是以对象名称、键值组成的数组:
1
2
3
4
5
6
7
8
9
for (const member of members) {
console.log(member);
}
/**
['Evelyn', 75.68]
['Liam', 20.16]
['Sophia', 0]
['Marcus', 10.25]
/*
WeakMap
与 weakSet
类似,只可包含对象,不能迭代,且没有clear()
方法。
Promise
详见 这篇文章。
代理(Proxy)
Proxy 对象介于真正的对象和调用代码之间。调用代码与 Proxy 交互,而不是真正的对象。要创建 Proxy 的话:
- 使用
new Proxy()
构造函数- 将被代理的对象传入为第一个参数
- 第二个参数是 handler(处理器)对象
- handler 对象由 13 种不同的 trap 之一构成
- trap 是一种函数,将截获对属性的调用,让你运行代码
- 如果未定义 trap,默认行为会被发送给目标对象
Proxy 是一种强大的创建和管理对象之间的交互的新方式。
生成器(Generator)
用一个 *
放在 function
和函数名之间,就创建了一个生成器。生成器的目的是生成一个可迭代操作的函数,使一个本来自上而下运行、直到无路可走的函数中的代码变得更加可控,运行和暂停随时听我们指挥。
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
29
function* getEmployee() {
console.log('the function has started');
const names = [
'Amanda',
'Diego',
'Farrin',
'James',
'Kagure',
'Kavita',
'Orit',
'Richard'
];
for (const name of names) {
console.log(name);
yield; // 生成器设置的暂停锚点,不经我们允许,它不会继续运行。
}
console.log('the function has ended');
}
const generatorIterator = getEmployee(); // 将生成器赋值给 generatorIterator
generatorIterator.next();
/** 输出结果
the function has started
Amanda
*/
generatorIterator.next(); // 输出结果:Diego
yield
同样可以返回值。我们把上面代码的 for...of
循环修改一下:
1
2
3
for (const name of names) {
yield name;
}
此时,我们可以在每次函数暂停的时候,取到暂停时 name
的值:
1
2
3
4
const generatorIterator = getEmployee();
let result = generatorIterator.next();
result.value; // Amanda
generatorIterator.next().value; // Diego
不仅如此,我们还可以向生成器发送数据:
1
2
3
4
5
6
7
8
function* displayResponse() {
const response = yield;
console.log(`Your response is "${response}"!`);
}
const iterator = displayResponse();
iterator.next(); // 开始运行生成器
iterator.next('es6'); // 发送数据给生成器,打印结果:Your response is es6!