我会每天都更新我写东西,学大概有一段时间了,我感触还是挺大的,把之前基础知识又复习一遍,活到老学到老。希望能给在FreecodeCamp有同学有帮助,我会每天更新我在写FreecodeCamp,代码。
在2015年,ECMAScript 发布了被称为 ECMAScript 6 (ES6) 的最新版本。在这个挑战里,我们会学习新版本添加的许多强大的功能,包括:
- 箭头函数
- 类
- 模块
- Promises 对象
- 异步生成器 Generators
let
以及const
语法
#1 ES6:探索 var 和 let 关键字之间的差异
使用var
关键字来声明变量,会出现重复声明导致变量被覆盖却不会报错的问题:
这是var 例子:
修改 let 局别例子
let catName;
let quote;
function catTalk() {
"use strict";
catName = "Oliver";
quote = catName + " says Meow!";
}
catTalk();
#2 ES6:比较 var 和 let 关键字的作用域
当你使用var 关键字来声明一个变量的时候,这个变量会声明成全局变量,或是函数内的局部变量。
let
关键字的作用类似,但会有一些额外的特性。如果你在代码块、语句或表达式中使用关键字let
声明变量,这个变量的作用域就被限制在当前的代码块,语句或表达式之中。
function checkScope() {
"use strict";
let i = "function scope";
if (true) {
let i = "block scope";
console.log("Block scope i is: ", i);
}
console.log("Function scope i is: ", i);
return i;
}
#3 ES6:用 const 关键字声明只读变量
let
并不是唯一的新的声明变量的方式。在 ES6里面,你还可以使用const
关键字来声明变量。
const
拥有let
的所有优点,所不同的是,通过code
声明的变量是只读的。这意味着通过const
声明的变量只能被赋值一次,而不能被再次赋值。
"use strict"
const FAV_PET = "Cats";
FAV_PET = "Dogs"; // 报错
可以看见,尝试给通过const
声明的变量再次赋值会报错。你应该使用const
关键字来对所有不打算再次赋值的变量进行声明。这有助于你避免给一个常量进行额外的再次赋值。一个最佳实践是对所有常量的命名采用全大写字母,并在单词之间使用下划线进行分隔。
function printManyTimes(str) {
"use strict";
// 在这行以下修改代码
const SENTENCE = str + " is cool!";
for(let i = 0; i < str.length; i+=2) {
console.log(SENTENCE);
}
// 在这行以上修改代码
}
printManyTimes("freeCodeCamp");
#4 改变一个用 const 声明的数组
在现代的 JavaScript 里,const
声明有很多用法。
一些开发者倾向默认使用const
来声明所有变量,但如果它们打算在后续的代码中修改某个值,那在声明的时候就会用let
。
然而,你要注意,对象(包括数组和函数)在使用const
声明的时候依然是可变的。使用const
来声明只会保证它的标识不会被重新赋值。
"use strict";
const s = [5, 6, 7];
s = [1, 2, 3]; // 试图给 const 变量赋值,报错
s[2] = 45; // 与用 var 或 let 声明的数组一样,这个操作也会成功
console.log(s); // 返回 [5, 6, 45]
从以上代码看出,你可以改变[5, 6, 7]
自身,所以s
变量指向了改变后的数组[5, 6, 45]
。和所有数组一样,数组s
中的数组元素是可以被改变的,但是因为使用了const
关键字,你不能使用赋值操作符将变量标识s
指向另外一个数组。
const s = [5, 7, 2];
function editInPlace() {
"use strict";
// 在这行以下修改代码
s[0] = 2;
s[1] = 5;
s[2] = 7;
// s = [2, 5, 7]; <- this is invalid
// 在这行以上修改代码
}
editInPlace();
# 5 ES6:防止对象改变
通过之前的挑战可以看出,const
声明并不会真的保护你的数据不被改变。为了确保数据不被改变,JavaScript 提供了一个函数Object.freeze
来防止数据改变。
当一个对象被冻结的时候,你不能再对它的属性再进行增、删、改的操作。任何试图改变对象的操作都会被阻止,却不会报错。
let obj = {
name:"FreeCodeCamp",
review:"Awesome"
};
Object.freeze(obj);
obj.review = "bad"; // obj 对象被冻结了,这个操作会被忽略
obj.newProp = "Test"; // 也会被忽略,不允许数据改变
console.log(obj);
// { name: "FreeCodeCamp", review:"Awesome"}
function freezeObj() {
"use strict";
const MATH_CONSTANTS = {
PI: 3.14
};
// 在这行以下修改代码
Object.freeze(MATH_CONSTANTS);
// 在这行以上修改代码
try {
MATH_CONSTANTS.PI = 99;
} catch( ex ) {
console.log(ex);
}
return MATH_CONSTANTS.PI;
}
const PI = freezeObj();
# 6 ES6:使用箭头函数编写简洁的匿名函数
在 JavaScript 里,我们会经常遇到不需要给函数命名的情况,尤其是在需要将一个函数作为参数传给另外一个函数的时候。这时,我们会创建匿名函数。因为这些函数不会在其他地方复用,所以我们不需要给它们命名。
这种情况下,我们通常会使用以下语法:
ES6 提供了其他写匿名函数的方式的语法糖。你可以使用箭头函数:
当不需要函数体,只返回一个值的时候,箭头函数允许你省略return
关键字和外面的大括号。这样就可以将一个简单的函数简化成一个单行语句。
const myFunc= () => "value"
这段代码仍然会返回value
。
const magic =() => {
"use strict";
return new Date();
};
#7 ES6:
和一般的函数一样,你也可以给箭头函数传递参数。
// 给传入的数值乘以 2 并返回结果
const doubler = (item) => item * 2;
你同样可以给箭头函数传递多个参数。
编写带参数的箭头函数
const myConcat = (arr1, arr2) => {
"use strict";
return arr1.concat(arr2);
};
// 测试你的代码
console.log(myConcat([1, 2], [3, 4, 5]));
#8 ES6:编写高阶箭头函数
箭头函数在类似map()
,filter()
,reduce()
等需要其他函数作为参数来处理数据的高阶函数里会很好用。
阅读以下代码:
FBPosts.filter(function(post) {
return post.thumbnail !== null && post.shares > 100 && post.likes > 500;
})
我们写下了filter
函数,并尽量保证可读性。现在让我们用箭头函数来写同样的代码看看:
FBPosts.filter((post) => post.thumbnail !== null && post.shares > 100 && post.likes > 500)
这段代码完成了同样的任务,却变得更加简短易懂了。
const realNumberArray = [4, 5.6, -9.8, 3.14, 42, 6, 8.34];
const squareList = (arr) => {
"use strict";
// 在这行以下修改代码
const squaredIntegers = arr.filter((num) => num > 0 && num % parseInt(num) === 0 ).map( (num) => Math.pow(num, 2));
// 在这行以上修改代码
return squaredIntegers;
};
// 测试你的代码
const squaredIntegers = squareList(realNumberArray);
console.log(squaredIntegers);
# 9 ES6:设置函数的默认参数
ES6 里允许给函数传入默认参数,来构建更加灵活的函数。
请看以下代码:
function greeting(name = "Anonymous") {
return "Hello " + name;
}
console.log(greeting("John")); // Hello John
console.log(greeting()); // Hello Anonymous
默认参数会在参数没有被指定(值为 undefined )的时候起作用。在上面的例子中,参数name
会在没有得到新的值的时候,默认使用值 "Anonymous"。你还可以给多个参数赋予默认值。
成功例子:
#10 ES6:将 rest 操作符与函数参数一起使用
ES6 推出了用于函数参数的 rest 操作符帮助我们创建更加灵活的函数。在rest
操作符的帮助下,你可以创建有一个变量来接受多个参数的函数。这些参数被储存在一个可以在函数内部读取的数组中。
请看以下代码:
rest
操作符可以避免查看args
数组的需求,并且允许我们在参数数组上使用map()
,fiter()
,和reduce()
。
成功案例:
#11 ES6:使用 spread 运算符展开数组项
ES6 允许我们使用 展开操作符 来展开数组,以及需要多个参数或元素的表达式。
下面的 ES5 代码使用了apply()
来计算数组的最大值:
var arr = [6, 89, 3, 45];
var maximus = Math.max.apply(null, arr); // 返回 89
我们必须使用Math.max.apply(null,arr)
,是因为直接调用Math.max(arr)
会返回NaN
。Math.max()
函数需要传入的是一系列由逗号分隔的参数,而不是一个数组。
展开操作符可以提升代码的可读性,这对后续的代码维护是有积极作用的。
const arr = [6, 89, 3, 45];
const maximus = Math.max(...arr); // 返回 89
...arr
返回了一个“打开”的数组。或者说它 展开 了数组。
然而,展开操作符只能够在函数的参数中,或者数组之中使用。下面的代码将会报错:
const spreaded = ...arr; // 将会发生语法错误
#12 ES6:使用解构赋值从对象中分配变量
看看以下 ES5 的代码:
var voxel = {x: 3.6, y: 7.4, z: 6.54 };
var x = voxel.x; // x = 3.6
var y = voxel.y; // y = 7.4
var z = voxel.z; // z = 6.54
使用 ES6 的解构语法可以完成同样的赋值语句:
const { x, y, z } = voxel; // x = 3.6, y = 7.4, z = 6.54
如果你想将voxel.x
,voxel.y
,voxel.z
的值分别赋给a
,b
,c
,可以用以下这种很棒的方式:
const { x : a, y : b, z : c } = voxel // a = 3.6, b = 7.4, c = 6.54
你可以这样理解:“将x
地址中的值拷贝到a
当中去。”,等等。
成功案例:
#13 ES6:使用解构赋值从嵌套对象中分配变量
同样,我们可以将 嵌套的对象解构到变量中。
请看以下代码:
const a = {
start: { x: 5, y: 6},
end: { x: 6, y: -9 }
};
const { start : { x: startX, y: startY }} = a;
console.log(startX, startY); // 5, 6
在上面的例子里,a.start
将值赋给了变量start
,start
同样也是个对象。
成功案例:
#14 ES6:使用解构赋值从数组中分配变量
在 ES6 里面,解构数组可以如同解构对象一样简单。
与数组解构不同,数组的扩展运算会将数组里的所有内容分解成一个由逗号分隔的列表。所以,你不能选择哪个元素来给变量赋值。
而对数组进行解构却可以让我们做到这一点:
const [a, b] = [1, 2, 3, 4, 5, 6];
console.log(a, b); // 1, 2
变量a
以及b
分别被数组的第一、第二个元素赋值。
我们甚至能在数组解构中使用逗号分隔符,来获取任意一个想要的值:
const [a, b,,, c] = [1, 2, 3, 4, 5, 6];
console.log(a, b, c); // 1, 2, 5
使用数组解构来交换变量a
与b
的值。使a
、b
能分别获得对方的值。
大家要注意一下
第一:不需要const [b,a],因为它将保持赋值的局部效果。
第二: const [b,a] = [a,b]将导致a,b的值为undefined(从左到右的简单赋值规则)
成功案例:
#15 ES6:使用解构赋值配合 rest 操作符来重新分配数组元素
在解构数组的某些情况下,我们可能希望将剩下的元素放进另一个数组里面。
以下代码的结果与使用Array.prototype.slice()
相同:
const [a, b, ...arr] = [1, 2, 3, 4, 5, 7];
console.log(a, b); // 1, 2
console.log(arr); // [3, 4, 5, 7]
变量a
与b
分别获取了数组的前两个元素的值。之后,因为rest
操作符的存在,arr
获取了原数组剩余的元素的值,并构成了一个新的数组。
rest
操作只能对数组列表最后的元素起作用。这意味着你不能使用rest
操作符来截取原数组中间元素的子数组。
大家注意一下:如果使用[...arr] =list 是不成功因为还是把原来source数组值全部传进去
成功案例:
#16 ES6:使用解构赋值将对象作为函数的参数传递
在某些情况下,你可以在函数的参数里直接解构对象。
请看以下代码:
const profileUpdate = (profileData) => {
const { name, age, nationality, location } = profileData;
// 对这些变量执行某些操作
}
上面的操作解构了传给函数的对象。这样的操作也可以直接在参数里完成:
const profileUpdate = ({ name, age, nationality, location }) => {
/* 对这些参数执行某些操作 */
}
这样的操作去除了多余的代码,使代码更加整洁。
这样做还有个额外的好处:函数不需要再去操作整个对象,而仅仅是操作复制到函数作用域内部的参数。
成功例子:
请注意,我们正在拆解可stats
通过其两个属性- max
和min
-的功能。不要忘记修改第二个return语句。更改stats.max
只max
,并改变stats.min
只min
。
#17 ES6:使用模板字面量创建字符串
模板字符串是 ES6 的另外一项新的功能。这是一种可以轻松构建复杂字符串的方法。
请看以下代码:
const person = {
name: "Zodiac Hasbro",
age: 56
};
// string interpolation
const greeting = `Hello, my name is ${person.name}!
I am ${person.age} years old.`;
console.log(greeting); // 打印出
// Hello, my name is Zodiac Hasbro!
// I am 56 years old.
这段代码有许多的不同:
首先,上面使用的${variable}
语法是一个占位符。这样一来,你将不再需要使用+
运算符来连接字符串。当需要在字符串里增加变量的时候,你只需要在变量的外面括上${
和}
,并将其放在字符串里就可以了。
其次,在例子使用了反引号(`
),而不是引号('
或者"
)将字符串括了起来,并且这个字符串可以换行。
这个新的方式使你可以更灵活的创建复杂的字符串。
注意
- 在
map()
使用中使用一个箭头函数,它具有element
作为参数并返回<li></li>
具有text-warning类并包含其element
内部的函数
成功案例:
#18 ES6:使用简单字段编写简洁的对象字面量声明
ES6 添加了一些很棒的功能,以便于更方便地定义对象。
请看以下代码:
const getMousePosition = (x, y) => ({
x: x,
y: y
});
getMousePosition
是一个返回了拥有2个属性的对象的简单函数。
ES6 提供了一个语法糖,消除了类似x: x
这种冗余的写法.你可以仅仅只写一次x
,解释器会自动将其转换成
x: x
。
下面是使用这种语法重写的同样的函数:
const getMousePosition = (x, y) => ({ x, y });
成功案例:
#19 ES6:用 ES6 编写简洁的函数声明
在 ES5 中,当我们需要在对象中定义一个函数的时候,我们必须如下面这般使用
function
关键字:const person = {
name: "Taylor",
sayHello: function() {
return `Hello! My name is ${this.name}.`;
}
};在 ES6 语法的对象中定义函数的时候,你可以完全删除
function
关键字和冒号。请看以下例子:const person = {
name: "Taylor",
sayHello() {
return `Hello! My name is ${this.name}.`;
}
};成功案例:
#20 ES6:使用 class 语法定义构造函数
ES6 提供了一个新的创建对象的语法,使用关键字
class
。值得注意的是,
class
只是一个语法糖,它并不像 Java、Python 或者 Ruby 这一类的语言一样,严格履行了面向对象的开发规范。在 ES5 里面,我们通常会定义一个构造函数,然后使用
new
关键字来实例化一个对象:var SpaceShuttle = function(targetPlanet){
this.targetPlanet = targetPlanet;
}
var zeus = new SpaceShuttle('Jupiter');
class
的语法只是简单地替换了构造函数的写法:class SpaceShuttle {
constructor(targetPlanet){
this.targetPlanet = targetPlanet;
}
}
const zeus = new SpaceShuttle('Jupiter');注意
class
关键字声明了一个新的函数,并在其中添加了一个会在使用new
关键字创建新对象时调用的构造函数。
成功案例:
#21 ES6:使用 getter 和 setter 来控制对象的访问
你可以从对象中获得一个值,也可以给对象的属性赋值。
这些通常行为被称为 getters 以及 setters。
Getter 函数的作用是可以让返回一个对象私有变量的值给用户,而不需要直接去访问私有变量。
Setter 函数的作用是可以基于传进的参数来修改对象中私有变量的值。这些修改可以是计算,或者是直接替换之前的值。
class Book {
constructor(author) {
this._author = author;
}
// getter
get writer(){
return this._author;
}
// setter
set writer(updatedAuthor){
this._author = updatedAuthor;
}
}
const lol = new Book('anonymous');
console.log(lol.writer); // anonymous
lol.writer = 'wut';
console.log(lol.writer); // wut
注意我们调用 getter 和 setter 的语法,它们看起来并不像一个函数调用。
Getter 和 Setter 非常重要,因为它们隐藏了内部的实现细节。
使用class
关键字来创建Thermostat
类,它的构造函数应该可以接收华氏温度作为参数。
在类中创建 getter
和setter
,将温度转换成摄氏温度。
温度转换的公式是C = 5/9 * (F - 32)
以及F = C * 9.0 / 5 + 32
,F 代表华氏温度,C 代表摄氏温度。
请注意
当你实现这个作业的时候,你应当在类中使用一个温度标准,无论是华氏温度还是摄氏温度。
是时候展现 getter 和 setter 的威力了——无论你的 API 内部使用的是哪种温度标准,用户都能得到正确的结果。
或者说,你从用户需求中抽象出了实现细节。
成功案例:
#22 ES6:了解 import 和 require 之间的差异
在过去,我们会使用require()
函数来从外部文件或模块中引入函数或者代码。这时候会遇到一个问题:有些文件或者模块会特别大,但你却往往只需要引入其中的一些核心代码。
ES6 给我们提供了import
这个便利的工具。通过它,我们能够从外部的文件或者模块中选择我们需要的部分进行引入,从而节约载入的时间和内存空间。
请看下面的例子:想象math_array_functions
拥有大概20个函数,但是我只需要countItems
这一个函数在我当前的文件里。使用老的require()
方式会强制我引入所有20个函数。而使用新的import
语法,我可以只引入需要的那个函数:
import { countItems } from "math_array_functions"
下面是对于上面代码的语义描述:
import { function } from "file_path_goes_here"
// 我们还可以用同样的方式来引入变量!
对import
的使用,有许多的写法,但是上面的例子是最常用的写法。
注意
在大括号里的函数名的两侧加上空格是一个最佳实践——这可以帮助我们轻松的阅读import
语句。
注意
本节课中进行的是一个非浏览器操作。import
以及与其相关的在后面课程中的语句,是无法直接在浏览器上运行的。但是,我们可以通过一些工具来使它可以在浏览器中运行。
注意
在许多的例子中,在文件的路径前会加上./
;否则, node.js 会先尝试去node_modules
目录中寻找依赖项。
注意ES6 import 引用函数数加上{}不然引用不成功;
#23 ES6:用 export 来重用代码块
在上一个挑战中,你学习了关于import
语句是如何从大文件中引入其中的部分代码的。但是,为了让其正常的工作,我们还必须了解一个与之相关的语句,叫做export
。当我们想要一些代码——函数或者变量——在其他文件中使用,我们必须将它们导出来供其他文件导入。和import
一样,export
也是一个非浏览器的功能。
下面的例子阐述了如何进行一个命名导出。通过这样,我们可以使用上节课学习的import
语法,将导出的代码导入到其他的文件中去。请看下面的例子:
const capitalizeString = (string) => {
return string.charAt(0).toUpperCase() + string.slice(1);
}
export { capitalizeString } //如何导出函数。
export const foo = "bar"; //如何导出变量。
另外,如果你想要将你所有的export
语句打包成一行,你可以像下面这个例子一样实现:
const capitalizeString = (string) => {
return string.charAt(0).toUpperCase() + string.slice(1);
}
const foo = "bar";
export { capitalizeString, foo }
两种方式都是非常棒的实践。
成功例子:
#24 用 * 从文件中导入所有内容
我们还可以用import
语法从文件中导入所有的内容。
下面是一个从同目录下的"math_functions"
文件中导入所有内容的例子:
import * as myMathModule from "math_functions";
myMathModule.add(2,3);
myMathModule.subtract(5,3);
让我们来分析一下这段代码:
import * as object_with_name_of_your_choice from "file_path_goes_here"
object_with_name_of_your_choice.imported_function
你可以在import * as
之后添加任意的名称。这个方法接收到的值是一个对象,你可以使用点表示法来获取对象里具体的值。
成功例子:
#25 ES6:用 export default 创建一个默认导出
在export
的课程中,你学习了命名导出的语法。这让你可以在其他文件中引用一些函数或者变量。
你还需要知道另外一种被称为默认导出的export
的语法。在文件中只有一个值需要导出的时候,你通常会使用这种语法。它也常常用于给文件或者模块创建返回值。
下面是一个简单的export default
例子:
export default function add(x,y) {
return x + y;
}
注意:当使用export default
去声明一个文件或者模块的返回值,你在每个文件或者模块中应当只默认导出一个值。特别地,你能将export deafult
与var
,let
与const
一起使用。
#26 ES6:导入一个默认的导出
在上一个挑战里,你学会了export default
的用法。还有一个重要的点,你可能需要另外一种import
的语法来导入默认导出。
在下面的例子里有一个add
函数, 它在"math_functions"
文件里默认被导出。让我们看看来如何导入它:
import add from "math_functions";
add(5,4); //将会返回 9
这个语法只有一处不同的地方 —— 被导入的add
值,并没有被花括号{}
所包围。与导出值的方法不同,导入默认导出的写法仅仅只是简单的讲变量名写在import
之后。