ES6 笔记
ES6 ECMAScript 6
ECMA(European Computer Manufacturers Association / 欧洲计算机制造商协会)
var 和 let
使用 var
关键字声明变量时,它是全局声明的,如果在函数内部声明则是局部声明的。
let
关键字的行为类似,但有一些额外的功能。 在代码块、语句或表达式中使用 let
关键字声明变量时,其作用域仅限于该代码块、语句或表达式。
如果创建一个函数,将它存储起来,稍后在使用 i
变量的 for
循环中使用。这么做可能会出现问题。 这是因为存储的函数将始终引用更新后的全局 i
变量的值。
var printNumTwo;
for (var i = 0; i < 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo()); // 3
可以看到,printNumTwo()
打印了 3 而不是 2。 这是因为赋值给 i
的值已经更新,printNumTwo()
返回全局的 i
,而不是在 for 循环中创建函数时 i
的值。
const
默认情况下,一些开发人员更喜欢使用 const
分配所有变量,除非他们知道需要重新分配值。 只有在这种情况下,他们才使用 let
。
但是,重要的是要了解使用 const
分配给变量的对象(包括数组和函数)仍然是可变的。 使用 const
声明只能防止变量标识符的重新分配。
const s = [5, 6, 7];
s = [1, 2, 3]; // error
s[2] = 45;
console.log(s); // [5, 6, 45]
如你所见,可以改变对象 [5, 6, 7]
本身,变量 s
仍将指向更改后的数组 [5, 6, 45]
。 像所有数组一样,s
中的数组元素是可变的,但是因为使用了 const
,所以不能使用变量标识符 s
来指向一个使用赋值运算符的不同数组。
防止对象改变 Prevent Object Mutation
为了确保数据不被改变,JavaScript 提供了一个函数 Object.freeze
。
任何更改对象的尝试都将被拒绝,如果脚本在严格模式下运行,将抛出错误。
const MATH_CONSTANTS = {
PI: 3.14
};
Object.freeze(MATH_CONSTANTS);
箭头函数、匿名函数 Arrow Function、Anonymous Function
在 JavaScript 里,会经常遇到不需要给函数命名的情况,尤其是在需要将一个函数作为参数传给另外一个函数的时候。 这时,创建匿名函数。 因为这些函数不会在其他地方复用,所以不需要给它们命名。
通常的语法:
const myFunc = function() {
const myVar = "value";
return myVar;
}
ES6 的写法:
const myFunc = () => {
const myVar = "value";
return myVar;
}
当不需要函数体,只返回一个值的时候,箭头函数允许省略 return
关键字和外面的大括号。 这样就可以将一个简单的函数简化成一个单行语句。
const myFunc = () => "value"; // value
如果箭头后有花括号,必须加上 return
关键字,如果没有括号则可以省略 return
const listItems = chemists.map(person => {
return <li>...</li>;
});
const listItems = chemists.map(person =>
<li>...</li>);
和一般的函数一样,也可以给箭头函数传递参数。
const doubler = (item) => item * 2;
doubler(4); // 8
如果箭头函数只有一个参数,则可以省略参数外面的括号。
const doubler = item => item * 2; // 8
默认参数 Default Parameter
如果定义一个函数,并调用:
function noDefaultParams(number) {
console.log('result':, number ** 2);
}
noDefaultParams() // NaN
由于 JS 的动态特性,不传入参数虽然不报错,但是返回了 Not a Number
无意义的值。
ES6 里允许给函数传入默认参数,来构建更加灵活的函数。默认参数会在参数没有被指定(值为 undefined)的时候起作用。
const increment = (number, value = 1) => number + value;
rest 操作符代表参数列表
ES6 推出了用于函数参数的 rest 操作符创建更加灵活的函数。 rest 操作符可以用于创建有一个变量来接受多个参数的函数。 这些参数被储存在一个可以在函数内部读取的数组中。
使用 rest 参数,就不需要查看 args
数组,并且允许在参数数组上使用 map()
、filter()
和 reduce()
。
注意:rest
必须是最后一个参数。
const sum = (...args) => {
return args.reduce((a, b) => a + b, 0);
}
可以把数组的一部分放入另一数组:
const top5 = [1,2,3,4,5];
const [first, second, ...rest] = top5;
console.log(rest); // [3,4,5]
使用 rest 拼接数组和对象
const fruit = ['apple', 'banana'];
const vegetable = ['carrot', 'potato'];
const sum = [...fruit, ...vegetable];
console.log(sum); // [ 'apple', 'banana', 'carrot', 'potato' ]
const arr1 = [1,2];
const arr2 = [...arr1, 3, 4];
console.log(arr2); // [ 1, 2, 3, 4 ]
const flying = { wings: 2 }
const car = { wheels: 4 }
const flyingCar = {...flying, ...car}
console.log(flyingCar) // {wings: 2, wheels: 4}
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 arr1 = ['JAN', 'FEB', 'MAR', 'APR', 'MAY'];
let arr2;
arr2 = [...arr1];
console.log(arr2); // ['JAN', 'FEB', 'MAR', 'APR', 'MAY']
使用 spread 把字符串转化为字符串数组
const greeting = "Hello";
const arrayOfChars = [...greeting];
console.log(arrayOfChars); // ['H', 'e', 'l', 'l', 'o']
使用 spread 拷贝对象,数组
spread
做的是浅拷贝(shallow copy)。
const car1 = {
speed: 200,
color: 'yellow'
}
const car 2 = {...car1}
car1.speed = 201
console.log(car1.speed, car2.speed)
// 201, 200
const fruits1 = ['apples', 'pears']
const fruits2 = [...fruits]
fruits1.pop()
console.log(fruits1, "not", fruits2)
// ['apples'] 'not' ['apples','pears']
解构 Destructure
解构赋值
解构赋值是 ES6 引入的新语法,用来从数组和对象中提取值,并优雅地对变量进行赋值。
let {PI} = Math;
PI // 3.1415926
PI === Math.PI // true
PI = 1;
PI === Math.PI // false
只能解构对象中存在的东西。解构的变量和原来的变量之间没有关联。
有如下 ES5 代码:
const user = { name: 'John Doe', age: 34 };
const name = user.name; // John Doe
const age = user.age; // 34
下面是使用 ES6 解构赋值语句,实现相同效果:
const { name, age } = user;
在这里,自动创建 name
和 age
变量,并将 user
对象相应属性的值赋值给它们。
赋予变量名
可以给解构的值赋予一个新的变量名, 通过在赋值的时候将新的变量名放在冒号后面来实现。
const HIGH_TEMPERATURES = {
yesterday: 75,
today: 77,
tomorrow: 80
};
const { today: highToday, tomorrow: highTomorrow } = HIGH_TEMPERATURES;
解构对象
解构嵌套对象中的值。
const LOCAL_FORECAST = {
yesterday: { low: 61, high: 75 },
today: { low: 64, high: 77 },
tomorrow: { low: 68, high: 80 }
};
const { today: { low: lowToday, high: highToday } } = LOCAL_FORECAST;
解构数组
与数组解构不同,数组的扩展运算会将数组里的所有内容分解成一个由逗号分隔的列表。 所以,不能选择哪个元素来给变量赋值。而对数组进行解构却可以。
let a = 8, b = 6;
[a, b] = [b, a];
配合
以下代码的结果与使用 Array.prototype.slice()
类似:
const [a, b, ...arr] = [1, 2, 3, 4, 5, 6];
console.log(a, b); // 1, 2
console.log(arr); // [3, 4, 5, 6]
在某些情况下,可以在函数的参数里直接解构对象。
请看以下代码:
const profileUpdate = (profileData) => {
const { name, age, nationality, location } = profileData;
}
上面的操作解构了传给函数的对象。 这样的操作也可以直接在参数里完成:
const profileUpdate = ({ name, age, nationality, location }) => {
}
当 profileData
被传递到上面的函数时,从函数参数中解构出值以在函数内使用。
使用模版字面量创建字符串 Template Literal
模板字符串是 ES6 的另外一项新的功能。
模板字符串可以使用多行字符串和字符串插值功能。
请看以下代码:
const person = {
name: "Zodiac Hasbro",
age: 56
};
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.`。
首先,这个例子使用反引号(`` ),而不是引号(
'或者
"`)将字符串括起来。
其次,注意代码和输出中的字符串都是多行的。 不需要在字符串中插入 \n
。 上面使用的 ${variable}
语法是一个占位符(变量插值 interpolation)。 这样一来,不再需要使用 +
运算符来连接字符串。 当需要在字符串里增加变量的时候,只需要在变量的外面括上 ${
和 }
,并将其放在模板字符串里就可以了。 同样,可以在字符串中包含其他表达式,例如 ${a + b}
。
简洁对象字面量声明 Concise Object Literal
ES6 添加了一些很棒的功能,用于更方便地定义对象。
请看以下代码:
const getMousePosition = (x, y) => ({
x: x,
y: y
});
getMousePosition
简单的函数,返回拥有两个属性的对象。 ES6 提供了一个语法糖,消除了类似 x: x
这种冗余的写法。 可以只写一次 x
,解释器会自动将其转换成 x: x
(或效果相同的内容)。 下面是使用这种语法重写的同样的函数:
const getMousePosition = (x, y) => ({ x, y });
简洁函数
在 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}.`;
}
};
class 构造函数 Constructor Function
ES6 提供了一个新的创建对象的语法,使用关键字 class。
值得注意的是,class
只是一个语法糖,它并不像 Java、Python 或者 Ruby 这一类的语言一样,严格履行了面向对象的开发规范。
在 ES5 里面,通常会定义一个构造函数 constructor
,然后使用 new
关键字来实例化一个对象:
var SpaceShuttle = function(targetPlanet) {
this.targetPlanet = targetPlanet;
}
vat zeus = new SpaceShuttle('Moon');
class
语法只是简单地替换了构造函数 constructor
的写法,class
关键字后跟大写的类名:
class SpaceShuttle {
constructor(targetPlanet) {
this.targetPlanet = targetPlanet;
}
takeOff() {
console.log("we are going to the moon!");
}
}
const zeus = new SpaceShuttle("Moon");
应该注意 class
关键字声明了一个新的函数,里面添加了一个构造函数。 当用 new
创建一个新的对象时,构造函数会被调用。
注意:首字母大写驼峰命名法 UpperCamelCase 是 ES6 class 命名的惯例,就像上面的 SpaceShuttle
。
constructor
方法是一个特殊方法,用于创建和初始化 class 创建的对象。
setter 和 getter
可以从对象中获得一个值,也可以给对象的属性赋值。
这些操作通常被称为 getters 以及 setters。
Getter 函数的作用是可以让对象返回一个私有变量,而不需要直接去访问私有变量。
Setter 函数的作用是可以基于传进的参数来修改对象中私有变量。 这些修改可以是计算,或者是直接替换之前的值。
class Thermostat {
constructor(fahrenheit) {
this.fahrenheit = fahrenheit;
}
get temperature() {
return (5 / 9) * (this.fahrenheit - 32);
}
set temperature(celsius) {
this.fahrenheit = (celsius * 9.0) / 5 + 32;
}
}
注意: 通常会在私有变量前添加下划线(_
)。 然而,这种做法本身并不是将变量变成私有的。
模块脚本 Module Script
在模块之前,JS 的函数在 window 范围内是全局的,如果引入的第三方库中的函数名和自定义的相同就会引起冲突。为了解决这一问题,Mozilla 的工程师创作了 Server JS,现在称为 CommonJS。CommonJS 指示了 JS 在浏览器外的环境应该怎么做,例如在 Node 中,它的问题是浏览器并不能理解 CommonJS 的语法(例如 require 和 module.export)。
ES6 引入了在多个 JavaScript 文件之间共享代码的机制。 它可以导出文件的一部分供其它文件使用,然后在需要它的地方按需导入。 为了使用这一功能, 需要在 HTML 文档里创建一个 type
为 module
的脚本。
使用了 module
类型的脚本可以使用 import
和 export
特性
<html>
<body>
<script type="module" src="index.js"></script>
</body>
</html>
export
假设有一个文件 math_functions.js
,该文件包含了数学运算相关的一些函数。 其中一个存储在变量 add
里,该函数接受两个数字作为参数返回它们的和。 想在几个不同的 JavaScript 文件中使用这个函数。 要实现这个目的,就需要 export
它。
export const add = (x, y) => {
return x + y;
}
上面是导出单个函数常用方法,还可以这样导出:
const add = (x, y) => {
return x + y;
}
export { add };
导出变量和函数后,就可以在其它文件里导入使用从而避免了代码冗余。 重复第一个例子的代码可以导出多个对象或函数,在第二个例子里面的导出语句中添加更多值也可以导出多项,例子如下:
export { add, subtract };
另外一种被称为默认导出的 export
的语法。 在文件中只有一个值需要导出的时候,通常会使用这种语法。 它也常常用于给文件或者模块创建返回值。
export default
用于为模块或文件声明一个返回值,在每个文件或者模块中应当只默认导出一个值。 此外,不能将 export default
与 var
、let
或 const
同时使用
export default function add(x, y) {
return x + y;
}
export default function(x, y) {
return x + y;
}
import
import
可以导入文件或模块的一部分。
import { add } from './math_functions.js';
在这里,import
会在 math_functions.js
里找到 add
,只导入这个函数,忽略剩余的部分。 ./
告诉程序在当前文件的相同目录寻找 math_functions.js
文件。 用这种方式导入时,相对路径(./
)和文件扩展名(.js
)都是必需的。
还需要一种 import
的语法来导入默认的导出。 在下面的例子里,add
是 math_functions.js
文件的默认导出。 以下是如何导入它:
import add from "./math_functions.js";
这个语法有一处特别的地方, 被导入的 add
值没有被花括号({}
)所包围。 add
只是一个变量的名字,对应 math_functions.js
文件的任何默认导出值。 在导入默认导出时,可以使用任何名字。
将某文件所有内容导入到当前文件中。 可以用 import * as
语法来实现
import * as myMathModule from "./math_functions.js";
Promise
定义
Promise 是异步编程的一种解决方案 - 它在未来的某时会生成一个值。 任务完成,分执行成功和执行失败两种情况。 Promise
是构造器函数,需要通过 new
关键字来创建。 构造器参数是一个函数,该函数有两个参数 - resolve
和 reject
。 通过它们来判断 promise 的执行结果。
const myPromise = new Promise((resolve, reject) => {
});
状态
Promise 有三个状态:pending
、fulfilled
和 rejected
。 上一个挑战里创建的 promise 一直阻塞在 pending
状态里,因为没有调用 promise 的完成方法。 Promise 提供的 resolve
和 reject
参数就是用来结束 promise 的。 Promise 成功时调用 resolve
,promise 执行失败时调用 reject
, 如下文所述,这些方法需要有一个参数。
const myPromise = new Promise((resolve, reject) => {
if (condition here) {
resolve("Promise was fulfilled");
} else {
reject("Promise was rejected");
}
});
上面的示例使用字符串作为这些函数的参数,但参数实际上可以是任何格式。
then
当程序需要花费未知的时间才能完成时(比如一些异步操作),一般是服务器请求,promise 很有用。 服务器请求会花费一些时间,当结束时,需要根据服务器的响应执行一些操作。 这可以用 then
方法来实现, 当 promise 完成 resolve
时会触发 then
方法。
例子如下:
myPromise.then(result => {
});
result
即传入 resolve
方法的参数。
catch
当 promise 失败时会调用 catch
方法。 当 promise 的 reject
方法执行时会直接调用。 用法如下:
myPromise.catch(error => {
});
error
是传入 reject
方法的参数。