图: Ahmad Awais ⚡️
Modern JavaScript cheatsheet 中文版
简介
初心
这份文档整理了在当前前端项目中经常需要查阅的内容,并给出了最新的代码示例。
你或许会因为不熟悉当前一些新的代码库(例如 React)所用到的 JavaScript 概念,而很难上手这些新框架。所以本文档的目的并非从零教你 JavaScript,而是帮助已经有一定编程基础的你。
除此之外,我(作者:Manuel Beaudru)偶尔会写上一些我的小技巧,也会注意提示这只是我的个人提议。
注: 这篇文档里提到的大多数概念来自于目前最新的 JavaScript(ES2015,即 ES6),你可以在这里查看新增的特性,网站做得很棒。
参考材料
当你觉得有的概念不容易理解时,你可以在下面的链接里面寻找答案。
- MDN (Mozilla Developer Network)
- You don’t know JS(书)
- ES6 新特性和例子
- WesBos 博客中 ES6 类别
- Reddit (JavaScript)
- Google 可直接查找特定的博客和资源
- StackOverflow
目录
正文
变量声明: var, const, let
在 JavaScript 中,声明变量时可以用三个不同的关键词,分别是 var
,let
以及 const
,它们各有异同。
简述
用 const
声明的变量,不能被重新赋值,而另两个 var
和 let
是可以的。
所以我建议默认情况下你都用 const
来声明变量,在你需要 改变 或是声明之后再重新指派它的时候,才用 let
来声明变量。
- | 作用域 | 是否可重新赋值 | 是否可变 | 暂存死区 |
---|---|---|---|---|
const | 块级 | × | √ | √ |
let | 块级 | √ | √ | √ |
var | 函数 | √ | √ | × |
代码示例
const person = "Nick";
person = "John" // 因为 person 不能被重新赋值,所以会报错
let person = "Nick";
person = "John";
console.log(person) // "John", 使用 let 声明的变量可以重新赋值
详述
简单来讲,变量的作用域(scope)是指“在这部分代码中可以访问到此变量”。
var
使用 var
定义的变量,其作用域是定义它的函数内部(function scoped),也就是说在函数内部创建一个 var
变量的时候,在此函数内部可以任意访问这个变量,但在函数之外,这样的局部变量是无法被访问的。
我建议你这样理解,如果一个变量是 X 作用域(scoped) 类型的,那就是说这个变量是 X 的属性之一。(译注:X 有 function 和 block 两类,代表函数作用域和块级作用域。)
function myFunction() {
var myVar = "Nick";
console.log(myVar); // "Nick" - 在这个函数中 myVar 可被访问到
}
console.log(myVar); // 抛出错误 ReferenceError, 在函数之外 myVar 则无法访问
继续来看变量的作用域,下面有更多精妙的例子:
function myFunction() {
var myVar = "Nick";
if (true) {
var myVar = "John";
console.log(myVar); // "John"
// 实际上 myVar 是函数级作用域变量,重新声明的时候,相当于用 "John" 抹去了 myVar 之前的值 "Nick"
}
console.log(myVar); // "John" - 可见 if 块中的代码会如何影响变量
}
console.log(myVar); // 抛出错误 ReferenceError, 在函数之外 myVar 则无法访问
另外,var 声明的变量在执行的时候,就像会被移动到作用域的开始,这就是我们所说的变量声明提升(var hoisting)。
所以看下面这段代码:
console.log(myVar) // undefined -- 没有提示错误
var myVar = 2;
之所以没有发生错误,是因为它执行时会被解释为这样:
var myVar;
console.log(myVar) // undefined -- 没有提示错误
myVar = 2;
let
var
和 let
几乎是一样的,但是用 let
声明的变量有如下特性:
- 块级作用域( block scoped )
- 在被赋值之前,是无法访问使用的
- 在同一个作用域之下,不能被重新声明
我们来看看之前例子中提到的块级作用域( block scoping )的效果:
function myFunction() {
let myVar = "Nick";
if (true) {
let myVar = "John";
console.log(myVar); // "John"
// 实际上 myVar 是块级作用域的变量,在 if 块中,我们相当于是创建了一个新变量,
// 这个变量在此块之外是无法被访问的,而且它完全区别于我们创建的第一个 myVar 变量!
}
console.log(myVar); // "Nick", 可见 if 块中的代码,并没有影响到这个变量的值
}
console.log(myVar); // 抛出错误 ReferenceError,在函数外部无法访问到 myVar。
现在,来看看 let(和 const )声明的变量在赋值前无法访问是什么意思:
console.log(myVar) // 提示错误 ReferenceError !
let myVar = 2;
这就是它们和 var 变量的区别,如果你在还未赋值给 let 或者 const 变量之前,就想读写它,是会提示错误的。这种情况常被称作暂存死区(Temporal dead zone)或者 TDZ。
注意: 从技术上讲,let 和 const 变量声明时也存在提升,但并不代表它们的赋值也会被提升。但由于它被设计成了赋值之前无法使用,所以我们直观感觉上它没有被提升,但其实是存在提升的。如果想了解更多细节,请看这篇文章。
另外,在同一作用域内你不能重新声明一个 let 变量。
let myVar = 2;
let myVar = 3; // 提示语法错误 SyntaxError
const
const
声明的变量很像 let
,但它不能被重新赋值。
总结一下 const
变量的特点如下:
- 块级作用域
- 赋值之前无法使用
- 在同一个作用域内部,你不能重新声明一个变量
- 不能被重新指派
const myVar = "Nick";
myVar = "John" // 提示错误,不允许重新赋值 const 变量
但这里有一个小细节:const
变量并非完全不可变,如果这个变量是 object
和 array
类型的值,那它的值是可以改变的。assign
对于对象类型来说:
const person = {
name: 'Nick'
};
person.name = 'John' // 这会生效的!person 并非完全重新指派( reassigned ),只是值变化了( mutated )
console.log(person.name) // "John"
person = "Sandra" // 提示错误,因为用 const 声明的变量不能被重新指派
对于数组类型来说:
const person = [];
person.push('John'); // 这也会生效!person 并非完全重新指派( reassigned ),只是值变化了( mutated )
console.log(person[0]) // "John"
person = ["Nick"] // 提示错误,因为用 const 声明的变量不能被重新指派
延伸资料
箭头函数
ES6 JS 的最新版本已经介绍了箭头函数, 箭头函数是以另一种方式声明和使用函数。以下是箭头函数带来的一些好处:
- 更加简洁
- 从上下文获取this
- 隐式的返回方式
代码示例
- 简洁性和隐式的返回方式
function double(x) { return x * 2; } // 传统函数声明方式
console.log(double(2)) // 4
const double = x => x * 2; // 同样的函数,使用具有隐式返回方式的箭头函数来表示
console.log(double(2)) // 4
- this 关键字
在箭头函数中, this的值就等于函数所处的封闭的可执行上下文的this。简单来说,就是在箭头函数中,当你调用一个位于函数体内部的函数时,在内部函数中,你不需要使用”that = this” 这样的声明语句。
function myFunc() {
this.myVar = 0;
setTimeout(() => {
this.myVar++;
console.log(this.myVar) // 1
}, 0);
}
详述
简洁性
箭头函数从很多方面都比传统的函数简洁。案例如下:
- 隐式返回 VS 显式返回
显式返回 指的是函数的返回语句使用了return 关键字
function double(x) {
return x * 2; // 使用了*return*关键字,显式返回 x * 2
}
传统函数总是伴随着显式返回。使用箭头函数,你可以使用隐式返回,即不需要在函数体内使用return关键字就可以返回值。
隐式返回需要将所需代码写在一条语句中。
const double = (x) => {
return x * 2; // 显式返回
}
鉴于只返回单值,我们可以使用隐式返回。
const double = (x) => x * 2;
为实现隐式返回,我们只需要 移除花括号 和 return 关键字。之所以被称为隐式返回,是因为return关键字不存在的情况下,函数仍可返回 x * 2
。
注意: 如果你的函数不是返回一个单值(伴有连带值),那么既不可以使用显式返回也不可以使用隐式返回。
除此之外, 如果你想隐式返回一个object 则必须使用圆括号对其修饰,
const getPerson = () => ({ name: "Nick", age: 24 })
console.log(getPerson()) // { name: "Nick", age: 24 } -- 箭头函数返回的对象
- 函数只有一个参数
如果你的箭头函数只有一个参数,你可以省略修饰参数的圆括号,重新观察上面的代码:
const double = (x) => x * 2; // 箭头函数只有一个参数
参数外面的圆括号可以省略:
const double = x => x * 2; // 箭头函数只有一个参数
- 函数无参数
当箭头函数无参数时,必须使用圆括号,否则会出现语法错误.
() => { // 必须提供圆括号
const x = 2;
return x;
}
=> { // 无圆括号,错误!
const x = 2;
return x;
}
this 关键字
要理解箭头函数中this的微妙之处,你必须首先了解JavaScript中this 的行为。
在箭头函数中, this的值就等于函数所处的封闭可执行上下文的this。这句话的意思就是箭头函数不创建一个新的this, 而是从其所处的上下文环境中获取。
没有箭头函数,如果你想要从this访问函数内部的函数中的一个变量,你必须使用that = this或者self = this这样的技巧。
例如, 使用位于myFunc内部的函数setTimeout:
function myFunc() {
this.myVar = 0;
var that = this; // that = this
setTimeout(
function() { // 在函数的内部创建一个新的
that.myVar++;
console.log(that.myVar) // 1
console.log(this.myVar) // 未定义 -- 请参照上面的函数this定义
},
0
);
}
但是一旦使用箭头函数, this 将从包含这个箭头函数的上下文中获取:
function myFunc() {
this.myVar = 0;
setTimeout(
() => { // 从上下文中获取this, 在这里就是 myFunc
this.myVar++;
console.log(this.myVar) // 1
},
0
);
}
相关资料
函数参数默认值
从 ES2015 以后开始,你可以使用下面的语法,给你的方法参数来设置默认值
function myFunc(x = 10) {
return x;
}
console.log(myFunc()) // 10 -- 没有值传入,所以默认的值10传给了myFunc
console.log(myFunc(5)) // 5 -- 一个值被传入,所以x等于5
console.log(myFunc(undefined)) // 10 -- undefined 值提供了,所以默认值关联了x
console.log(myFunc(null)) // null -- 提供了 (null) , 见一下详细解释
默认参数有且只有在以下两种情况下才会生效:
- 没有参数提供的时候
- undefined 参数被提供的时候
换句话说,如果你传入NULL 默认值将不会生效
注意: 默认值赋值也可以与析构参数一起使用(參见下一节的例子)。
相关资料
Objects 和 Arrays 解构
通过 解构 来从对象或数组中提取值来赋值给新变量非常方便。
举几个例子, 如解构可以用来解构函数参数或者 React 项目中的 this.props
对象。
代码示例
- Object
以下面这个对象为例:
const person = {
firstName: "Nick",
lastName: "Anderson",
age: 35,
sex: "M"
}
不使用解构
const first = person.firstName;
const age = person.age;
const city = person.city || "Paris";
使用解构,只需一行:
const { firstName: first, age, city = "Paris" } = person; // That's it !
console.log(age) // 35 -- A new variable age is created and is equal to person.age
console.log(first) // "Nick" -- A new variable first is created and is equal to person.firstName
console.log(firstName) // ReferenceError -- person.firstName exists BUT the new variable created is named first
console.log(city) // "Paris" -- A new variable city is created and since person.city is undefined, city is equal to the default value provided "Paris".
注意:在
const { age } = person;
中,关键词const
后的花括号并不是用来声明一个对象或块,仅仅是解构的语法。
- 函数参数
解构经常用来解构函数的对象参数。
不使用解构
function joinFirstLastName(person) {
const firstName = person.firstName;
const lastName = person.lastName;
return firstName + '-' + lastName;
}
joinFirstLastName(person); // "Nick-Anderson"
通过解构对象参数person
, 我们得到了一个更简洁的函数:
function joinFirstLastName({ firstName, lastName }) { // we create firstName and lastName variables by destructuring person parameter
return firstName + '-' + lastName;
}
joinFirstLastName(person); // "Nick-Anderson"
解构搭配箭头函数更棒:
const joinFirstLastName = ({ firstName, lastName }) => firstName + '-' + lastName;
joinFirstLastName(person); // "Nick-Anderson"
- Array
以下面这个数组为例:
const myArray = ["a", "b", "c"];
不使用解构:
const x = myArray[0];
const y = myArray[1];
使用解构:
const [x, y] = myArray; // That's it !
console.log(x) // "a"
console.log(y) // "b"
相关资料
Array 的操作方法 - map / filter / reduce
Map
, filter
和 reduce
是Array 的操作方法,来自于函数式编程的一个编程范例。
概括来说:
- Array.prototype.map() 接受一个数组,并对数组中的元素进行某些操作并返回转换后的元素的数组。
- Array.prototype.filter() 接受一个数组,依照元素本身決定是否保留,并將返回保留元素的数组。
- Array.prototype.reduce() 接受一个数组,并将这些值聚合成一个值(返回的值)
我建议在遵循函数式编程原则时尽可能地使用它们,因为它们是可组合的,简洁而优雅的。
有这三个方法,你可以在大多数情况下避免使用 for
和 forEach
循环。当你想要做一个for循环时,尝试用map,filter和reduce组合来做。一开始你可能很难做到这一点,因为它需要你学习一种新的思维方式,但是一旦你掌握了它就会变得很容易。
代码示例
const numbers = [0, 1, 2, 3, 4, 5, 6];
const doubledNumbers = numbers.map(n => n * 2); // [0, 2, 4, 6, 8, 10, 12]
const evenNumbers = numbers.filter(n => n % 2 === 0); // [0, 2, 4, 6]
const sum = numbers.reduce((prev, next) => prev + next, 0); // 21
通过 Map
, filter
和 reduce
这几个方法计算出成绩大于等于10分的得分总和。
const students = [
{ name: "Nick", grade: 10 },
{ name: "John", grade: 15 },
{ name: "Julia", grade: 19 },
{ name: "Nathalie", grade: 9 },
];
const aboveTenSum = students
.map(student => student.grade) // we map the students array to an array of their grades
.filter(grade => grade >= 10) // we filter the grades array to keep those 10 or above
.reduce((prev, next) => prev + next, 0); // we sum all the grades 10 or above one by one
console.log(aboveTenSum) // 44 -- 10 (Nick) + 15 (John) + 19 (Julia), Nathalie below 10 is ignored
详解
以下面的 numbers 数组为例:
const numbers = [0, 1, 2, 3, 4, 5, 6];
Array.prototype.map()
const doubledNumbers = numbers.map(function(n) {
return n * 2;
});
console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12]
发生了什么?我们对 numbers 调用了 .map()
方法,map方法会遍历数组中的每个元素并将它传给我们的方法。该方法的目标是通过传入的值生成并返回一个新的值以便map可以替换它。
提取出这个函数以便让它更清晰明了:
const doubleN = function(n) { return n * 2; };
const doubledNumbers = numbers.map(doubleN);
console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12]
注意:你将会经常碰到该方法与箭头函数结合使用
const doubledNumbers = numbers.map(n => n * 2);
console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12]
numbers.map(doubleN)
生成一个等同于 [0, 2, 4, 6, 8, 10, 12]
的数组 [doubleN(0), doubleN(1), doubleN(2), doubleN(3), doubleN(4), doubleN(5), doubleN(6)]
。
注意:如果您不需要返回一个新数组并且只想进行具有其他操作的循环,您可以用for / forEach循环替代map。
Array.prototype.filter()
const evenNumbers = numbers.filter(function(n) {
return n % 2 === 0; // true if "n" is par, false if "n" isn't
});
console.log(evenNumbers); // [0, 2, 4, 6]
注意:与箭头函数结合使用
const evenNumbers = numbers.filter(n => n % 2 === 0);
console.log(evenNumbers); // [0, 2, 4, 6]
我们对 numbers 调用了 .filter()
方法,filter方法会遍历数组中的每个元素并将它传给我们的方法。该方法返回一个布尔值来决定当前元素保留。Filter 会返回一个保留元素的数组。
Array.prototype.reduce()
reduce() 方法的目标是对数组中的每个元素执行一个由您提供的reducer函数,将其结果汇总为单个返回值。如何聚合这些元素取决于你。
const sum = numbers.reduce(
function(acc, n) {
return acc + n;
},
0 // accumulator variable value at first iteration step
);
console.log(sum) // 21
注意:与箭头函数结合使用
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum) // 21
和.map
、.filter
一样,.reduce
别应用在数组上且第一个参数也是一个函数。
但是这次有了一些变化:
reduce
接受两个参数
第一个参数是一个函数,每一次迭代时都会被调用。
第二个参数是第一个迭代步骤中累加器变量(acc)的值(阅读下一点来理解)。- 函数参数
作为.reduce的第一个参数传递的函数有两个参数。第一个(acc )是累加器变量,而第二个参数(n)是当前元素。
累加器变量等于上一次迭代中函数的返回值。在第一此迭代中,acc等于您传给.reduce
的第二个参数。
第一次迭代
acc = 0 因为我们给第二个参数传入0
n = 0 number数组第一个元素
函数返回 acc + n —> 0 + 0 —> 0
第二次迭代
acc = 0 上一步迭代函数的返回值
n = 1 number数组第二个元素
函数返回 acc + n —> 0 + 1 —> 1
第三次迭代
acc = 1 上一步迭代函数的返回值
n = 2 number数组第三个元素
函数返回 acc + n —> 1 + 2 —> 3
第四次迭代
acc = 3 上一步迭代函数的返回值
n = 3 number数组第四个元素
函数返回 acc + n —> 3 + 3 —> 6
[…] 最后一次迭代
acc = 15 上一步迭代函数的返回值
n = 6 number数组最后一个元素
函数返回 acc + n —> 15 + 6 —> 21
最后一个迭代结束, .reduce
返回 21.
相关资源
展开运算符”…”
ES2015已经引入了展开运算符...
,用于将可迭代元素(如数组)扩展到一个可以容纳更多元素的地方。
代码示例
const arr1 = ["a", "b", "c"];
const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"]
function myFunc(x, y, ...params) {
console.log(x);
console.log(y);
console.log(params)
}
myFunc("a", "b", "c", "d", "e", "f")
// "a"
// "b"
// ["c", "d", "e", "f"]
const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }
const n = { x, y, ...z };
console.log(n); // { x: 1, y: 2, a: 3, b: 4 }
详解
迭代元素(如数组)
假如我们有以下两个数组:
const arr1 = ["a", "b", "c"];
const arr2 = [arr1, "d", "e", "f"]; // [["a", "b", "c"], "d", "e", "f"]
arr2第一个元素是一个数组,因为arr1按原样注入arr2。但我们想要的是arr2是一个字母数组。为此,我们可以将arr1的元素展开到arr2中。
使用展开元算符展开
const arr1 = ["a", "b", "c"];
const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"]
函数参数展开
在函数参数中,我们可以使用展开运算符将参数注入到一个我们可以循环的数组中。这里已经有一个arguments
对象在每一个函数中,它的值等同于一个包涵所有传入函数的参数的数组。
function myFunc() {
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
myFunc("Nick", "Anderson", 10, 12, 6);
// "Nick"
// "Anderson"
// 10
// 12
// 6
但是如果说我们希望这个函数能够创建一个包含成绩和平均成绩的新学生。将前两个参数提取到两个单独的变量中,然后将所有分数通过一个可迭代的数组传入,是不是会更方便?
这正是展开运算符允许我们做的事情!
function createStudent(firstName, lastName, ...grades) {
// firstName = "Nick"
// lastName = "Anderson"
// [10, 12, 6] -- "..." takes all other parameters passed and creates a "grades" array variable that contains them
const avgGrade = grades.reduce((acc, curr) => acc + curr, 0) / grades.length; // computes average grade from grades
return {
firstName: firstName,
lastName: lastName,
grades: grades,
avgGrade: avgGrade
}
}
const student = createStudent("Nick", "Anderson", 10, 12, 6);
console.log(student);
// {
// firstName: "Nick",
// lastName: "Anderson",
// grades: [10, 12, 6],
// avgGrade: 9,33
// }
注意:createStudent函数很不优雅,因为我们不检查grades.length是否存在或者是否为0。但是这样更容易阅读,所以我没有处理这种情况。
对象属性展开
对于这个,我建议你先阅读上面关于迭代和函数参数的展开运算。
const myObj = { x: 1, y: 2, a: 3, b: 4 };
const { x, y, ...z } = myObj; // object destructuring here
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }
// z is the rest of the object destructured: myObj object minus x and y properties destructured
const n = { x, y, ...z };
console.log(n); // { x: 1, y: 2, a: 3, b: 4 }
// Here z object properties are spread into n
相关资料
- TC39 - Object rest/spread
- Spread operator introduction - WesBos
- JavaScript & the spread operator
- 6 Great uses of the spread operator
Object 属性简写
当你将某个变量赋值给对象某个属性时,如果属性名和变量名相等,你可以这么做:
const x = 10;
const myObj = { x };
console.log(myObj.x) // 10
详解
通常(ES2015之前),当你声明一个新的对象字面量并希望将变量当作对象的属性值时,你或许需要写下面这些代码:
const x = 10;
const y = 20;
const myObj = {
x: x, // assigning x variable value to myObj.x
y: y // assigning y variable value to myObj.y
};
console.log(myObj.x) // 10
console.log(myObj.y) // 20
如你所见,这显得很多余,因为myObj的属性名和要分配给这些属性的变量名相同。
在ES2015中,当变量名和属性名相同时,你可以使用简写:
const x = 10;
const y = 20;
const myObj = {
x,
y
};
console.log(myObj.x) // 10
console.log(myObj.y) // 20
相关资料
Promises
promise是一个可以从异步函数同步返回的对象(参考)。
Promises 可以避免回调地狱, 并且它越来越频繁的应用与现代项目中。
代码示例
const fetchingPosts = new Promise((res, rej) => {
$.get("/posts")
.done(posts => res(posts))
.fail(err => rej(err));
});
fetchingPosts
.then(posts => console.log(posts))
.catch(err => console.log(err));
详解
当你发起一个Ajax请求时,响应不是同步返回的,因为你想要的资源需要一些时间来才能返回。如果你需要的资源某些资源不可用(404)响应可能永远也不会返回。
为了处理这种情况,ES2015给我们带来了promise。Promises可以有三种不同的状态:
- Pending
- Fulfilled
- Rejected
假设我们想使用promises来处理Ajax请求以获取资源X。
创建一个Promise
我们需要先创建一个Promise对象。然后用 jQuery 的 get
方法发起Ajax请求获取X。
const xFetcherPromise = new Promise( // 使用 `new` 关键词创建promise并赋值到一个变量上
function(resolve, reject) { // Promise 构造函数接收一个包含 `resolve`和`reject`两个参数的函数参数
$.get("X") // 发起 Ajax 请求
.done(function(X) { // 一旦请求结束...
resolve(X); // ... promise 使用 resolve 返回 X
})
.fail(function(error) { // 如果请求失败...
reject(error); // ... promise 通过 reject 抛出错误
});
}
)
如上例所示,Promise 对象接收一个包含 resolve 和 reject两个参数的执行函数。这些两个参数都是可调用函数,在被调用时分别将 promise 由 pending 状态转移到 fulfilled 状态和 rejected 状态。
实例创建后,promise处于pending状态,并立即执行其执行函数。一旦resolve或reject在执行函数中被调用,promise会调用其相关的处理函数。
Promise 处理函数
要获得promise 的结果(或错误),我们需要依赖于其下例中的方法:
xFetcherPromise
.then(function(X) {
console.log(X);
})
.catch(function(err) {
console.log(err)
})
如果promise调用成功,则执行resolve
方法并以其作为参数执行.then
。
如果失败,则执行reject
方法并以其作为参数执行.catch
。
注意:当一个处理函数关联到Promised,该promise已经是fulfilled或rejected状态,处理函数会被调用。所以异步操作和处理函数之间没有竞争条件。
相关资料
- JavaScript Promises for dummies - Jecelyn Yeen
- JavaScript Promise API - David Walsh
- 使用Promise - MDN
- What is a promise - Eric Elliott
- JavaScript Promises: an Introduction - Jake Archibald
- Promise 文档 - MDN
字符串模板
字符串模版是一个用于单行或多行字符串的插值表达式。
换句话说,它是一种新的字符串语法,您可以方便地使用任何JavaScript表达式(如变量)。
代码示例
const name = "Nick";
`Hello ${name}, the following expression is equal to four : ${2+2}`;
// Hello Nick, the following expression is equal to four: 4
相关资料
带标签的模板字符串
模板标签是可以作为模板文字前缀的函数。当一个函数以这种方式调用时,第一个参数是出现在模板的插值变量之间的字符串数组,后续参数是插值。使用扩展运算符…来捕获所有这些插值。(MDN)
styled-components这个很出名的库很大程度上依赖于此功能。
一个有趣的小例子来解释它是怎么运行的
function highlight(strings, ...values) {
const interpolation = strings.reduce((prev, current) => {
return prev + current + (values.length ? "<mark>" + values.shift() + "</mark>" : "");
}, "");
return interpolation;
}
const condiment = "jam";
const meal = "toast";
highlight`I like ${condiment} on ${meal}.`;
// "I like <mark>jam</mark> on <mark>toast</mark>."
一个更有趣的例子
function comma(strings, ...values) {
return strings.reduce((prev, next) => {
let value = values.shift() || [];
value = value.join(", ");
return prev + next + value;
}, "");
}
const snacks = ['apples', 'bananas', 'cherries'];
comma`I like ${snacks} to snack on.`;
// "I like apples, bananas, cherries to snack on."
相关资料
Imports / Exports
ES6模块用于访问·当前模块引入的模块显示导出的变量和方法。
强烈推荐看一下MDN上关于 import/export 上的资源(下面的相关资料),既简单又完整。
代码示例
命名导出
命名导出用于从一个模块中导出多个值。
注意: 你只能命名导出一个一个具有名字的第一类对象
// mathConstants.js
export const pi = 3.14;
export const exp = 2.7;
export const alpha = 0.35;
// -------------
// myFile.js
import { pi, exp } from './mathConstants.js'; // Named import -- destructuring-like syntax
console.log(pi) // 3.14
console.log(exp) // 2.7
// -------------
// mySecondFile.js
import * as constants from './mathConstants.js'; // Inject all exported values into constants variable
console.log(constants.pi) // 3.14
console.log(constants.exp) // 2.7
尽管命名导入看起来像 解构,但是它们拥有不同的语法且完全不一样。它们既不支持默认值也不支持深度解构。
此外,您可以使用别名,但是其语法与用于解构的语法不同:
import { foo as bar } from 'myFile.js'; // foo is imported and injected into a new bar variable
默认 import / export
关于默认导出,每个模块只有一个默认导出。默认导出可以是函数,类,对象或其他任何东西。这个值被认为是“主要”导出值,因为它将是最简单的导入。参考:MDN
// coolNumber.js
const ultimateNumber = 42;
export default ultimateNumber;
// ------------
// myFile.js
import number from './coolNumber.js';
// Default export, independently from its name, is automatically injected into number variable;
console.log(number) // 42
函数导出:
// sum.js
export default function sum(x, y) {
return x + y;
}
// -------------
// myFile.js
import sum from './sum.js';
const result = sum(1, 2);
console.log(result) // 3
相关资料
- ES6 Modules in bulletpoints
- Export - MDN
- Import - MDN
- Understanding ES6 Modules
- Destructuring special case - import statements
- Modules in JavaScript
JavaScript this
this 运算符的行为与其他语言中的不大一样且大多数情况下取决于函数的调用方式。(参考:MDN)
这个概念有很多微妙之处,并且不是那么容易理解,我強烈建议你好好阅读下面的相关支持资料。因此,我将会提供一点我个人对于this的一点理解和想法。我是从Yehuda Katz 写的这篇文章学到这个技巧的。
function myFunc() {
...
}
// After each statement, you find the value of *this* in myFunc
myFunc.call("myString", "hello") // "myString" -- first .call parameter value is injected into *this*
// In non-strict-mode
myFunc("hello") // window -- myFunc() is syntax sugar for myFunc.call(window, "hello")
// In strict-mode
myFunc("hello") // undefined -- myFunc() is syntax sugar for myFunc.call(undefined, "hello")
var person = {
myFunc: function() { ... }
}
person.myFunc.call(person, "test") // person Object -- first call parameter is injected into *this*
person.myFunc("test") // person Object -- person.myFunc() is syntax sugar for person.myFunc.call(person, "test")
var myBoundFunc = person.myFunc.bind("hello") // Creates a new function in which we inject "hello" in *this* value
person.myFunc("test") // person Object -- The bind method has no effect on the original method
myBoundFunc("test") // "hello" -- myBoundFunc is person.myFunc with "hello" bound to *this*
相关资料
Class
JavaScript 是一个基于原型 的语言 (然而 Java 是 基于类别 的语言)。ES6引入了JavaScript类,它们是基于原型的继承的语法糖,而不是基于类的新继承模型(参考)。
…..积极翻译中…….
写于 2019年05月09日Javascript 8975
如非特别注明,文章皆为原创。
转载请注明出处: https://www.liayal.com/article/5cd3e93bc0ab13505eeefadb
记小栈小程序上线啦~搜索【记小栈】或【点击扫码】体验
~ 评论还没有,沙发可以有 O(∩_∩)O~