知识点1:ES6与ES5
ES6
是JavaScript在ES5之后的下一代标准,于2015年6月正式发布,旨在使js能编写复杂大型应用程序,成为企业级开发语言。
ES6
标准的开发过程中,决定在每年6月份正式发布一次作为当年的正式版本,其他时间则在这个版本基础上改动,因此版本号就不再需要,只要用年份标记即可。所以,ES6
泛指ES5.1
版后的JavaScript
下一代标准,涵盖了ES2015
、ES2016
、ES2017
等。
使用Babel
、Traceur
等转码器能将ES6代码转为ES5代码。
知识点2:let
与const
ES6
新增的变量声明关键字,let声明的变量只在其所在代码块内有效,实现了“块级作用域”的效果;const
声明一个只读的常量,一旦声明就不能改变值(因此一旦声明必须立即初始化)。
const
同样只在块级作用域内有效。但本质上const
是让变量指向的那个内存地址保存的数据不得改动,对于对象和数组,变量指向的内存地址保存的只是一个指向实际数据的指针,const
只能保证这个指针固定,对象或数组本身是可变的。
let
与const
不存在变量提升。在一个区块中如果存在let
和const
,这个区块对声明的变量从一开始就会形成封闭作用域,如果在声明前使用这些变量就会报错,这称为“暂时性死区TDZ”
。
从ES6
开始,全局变量将逐步与顶层对象的属性脱钩,因此let
、const
声明的全局变量不属于顶层对象的属性。
知识点3:变量的解构赋值
ES6
允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。解构的本质类似于“模式匹配”,解构不成功则值等于undefined。例:
let [a, b, c] = [1, 2, 3];
类似于“模式匹配”:
let [foo, [[bar], baz]] = [1, [[2], 3]];
而对象解构要求变量与属性同名,例:
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
如果变量名与属性名不一致,则应写成:
let { foo: baz } = { foo: 'aaa' }; // foo是模式,baz才是变量
解构赋值还允许指定默认值,例:
let [x, y = 'b'] = ['a'];
字符串也可以解构赋值,此时字符串被转换成了一个类似数组的对象。例:
const [a, b, c, d, e] = 'hello';
可以利用变量的解构赋值交换变量的值。例:
[x, y] = [y, x];
知识点4:字符串的扩展
1.模板字符串是增强版的字符串,用反引号`标识。既可当作普通字符串,又可在字符串中嵌入变量(将变量名、表达式甚至函数调用写在${}中)。例:
let name = "Bob", time = "today";
Hello ${name}, how are you ${time}?
模板字符串可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串,这被称为“标签模板”功能。
2.新增方法:
String.fromCodePoint()
。用于从Unicode码点返回对应字符,可识别大于0xFFFF
的字符(js
中字符以UTF-16
存储每个字符固定为2字节,因此对于那些Unicode码点大于0xFFFF
的字符,js
会认为它们是两个字符)。如有多个参数会被合并成一个字符串返回;
3.新增实例方法:
codePointAt()
,参数是字符在字符串中的位置,可识别大于0xFFFF
的字符会正确返回32位的码点(2字节存储的常规字符其返回结果与charCodeAt()
相同);
includes()
,返回布尔值,表示是否找到了参数字符串;
startsWith()
、endsWith()
,返回布尔值,表示参数字符串是否在原字符串的头/尾部;
repeat(),返回一个新字符串,表示将原字符串重复n次;
trimStart()
、trimEnd()
,其行为与trim()一致,只是分别用来消除头部和尾部的空格,均返回新字符串;
知识点5:数值的扩展
1.二进制和八进制数值有了新的写法,分别用前缀0b
和0o
表示;
2.新增方法:
Number.isFinite()
、Number.isNaN()
,用来检查一个数值是否有限与是否为NaN
。它们与传统的全局方法isFinite()
和isNaN()
的区别在于,这两个方法只对数值有效;
Number.parseInt()
、Number.parseFloat()
,将这两个全局方法移植到Number对象上,为了逐步减少全局性方法,使语言逐步模块化;
Number.isInteger()
,用于判断一个数值是否为整数。对数据精度要求高不建议使用;
3.Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER,js有一个能准确表示的整数范围,超过范围则无法精确表示这个值。这两个常量来表示这个范围的上下限。Number.isSafeInteger()则用来判断一个整数是否落在这个范围内;
4.新增指数运算符 **;
知识点6:函数的扩展
1.新增箭头函数,允许使用=>定义函数。例:
var sum = (num1, num2) => num1 + num2;
// 等同于var sum = function(num1, num2) {return num1 + num2;};
如果函数代码块部分多于一条语句,就要用大括号括起来并用retrun
返回。
箭头函数没有自己的this
,内部的this
就是外层代码块的this
,因此其指向会固定化,就是定义时所在对象;
2.允许为函数参数设置默认值,例:
function log(x, y = 'World') {console.log(x, y);}。
3.引入rest参数(形式为...变量名),用于获取函数的多余参数并放入一个数组中;
4.name
属性返回函数的函数名;
5.尾调用优化。尾调用是函数式编程的一个重要概念,指某个函数的最后一步是调用另一个函数。尾调用优化会只保留内层函数的调用帧,因此如果使用“尾递归”,就不会发生栈溢出。尾调用优化只在严格模式下开启;
知识点7:数组的扩展
1.扩展运算符...。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。例:
Math.max(...[14, 3, 77]); //直接数组无法使用max
const a2 = [...a1]; //复制一个数组
[...'hello'] //将字符串转为真正的数组
2.新增方法:
Array.from()
用于将“类似数组的对象”和“可遍历的对象”转为真正的数组;
Array.of()
用于将一组值转换为数组,无参数则返回空数组,
例:
Array.of(3, 11, 8);
3.新增实例方法:
find()
、findIndex()
找出第一个符合条件的数组成员,分别返回该成员与成员位置。其参数是一个回调函数,直到找出第一个返回值为true的成员。
// 例:
[1, 4, -5, 10].find((n) => n < 0);
fill()
使用给定值填充一个数组,第二第三参数指定填充的起始结束位置;
include()
返回某个数组是否包含给定的值;
flat()
该方法返回一个新数组,将数组中的子数组成员取出来添加在原来的位置,“拉平”变成一维的数组。方法默认只会“拉平”一层,如果想对多层的嵌套数组使用可以传入一个整数表示想拉平的层数;
知识点8:对象的扩展
1.ES6允许在对象中直接写变量和函数,这时属性/方法名为变量/函数名,属性值为变量/函数的值;
2.ES6允许用字面量定义对象时,把表达式放在方括号内,作为对象属性名;
3.super关键字指向当前对象的原型对象,该关键字只能用在对象的方法之中;
4.对象的扩展运算符...用于取出参数对象的所有可遍历属性,并拷贝到当前对象之中,例:
let z = { a: 3, b: 4 }; let n = { ...z }; //使得n为{ a: 3, b: 4 };
5.新增方法:
Object.assign()
用于对象合并,将源对象的所有自身且可枚举属性复制到目标对象。其中第一个参数为目标对象,其他参数为源对象。该方法实行的是浅拷贝,另外当出现同名属性时后面的属性会覆盖前面的属性;
Object.setPrototypeOf()
设置一个对象的prototype
对象,参数为obj
, proto
;
Object.getPrototypeOf()
读取一个对象的原型对象;
Object.values()
返回一个数组,成员是参数对象自身的所有可遍历属性的键值;
Object.entries()
返回一个数组,成员是参数对象自身的所有可遍历属性的键值对数组;
Object.fromEntries()
是Object.entries()
的逆操作,特别适合将map
结构转为对象;
知识点9:Symbol
ES6
引入了一种新的原始数据类型Symbol
,表示独一无二的值,它是js语言的第七种数据类型。
Symbol
通过Symbol
函数生成,可接受一个字符串作为参数,该字符串只是作为对当前Symbol值的描述,因此相同参数的Symbol函数的返回值并不相等。例:
let s = Symbol('foo');
s.description //'foo'
Symbol
主要作为对象属性名,以保证不会出现同名的属性,定义时需要放在方括号之中。例:
let mySymbol = Symbol();
let a = {[mySymbol]: 'Hello!'};
a[mySymbol] // "Hello!"
Symbol
作为属性名,不会出现在for...in
循环中,也不会被keys
、getOwnpropertyNames()
等方法返回。通过Object.getOwnPropertySymbols()
可获取对象的所有Symbol
值。
知识点10:Set与Map
ES6
中新增Set
与Map
数据结构。Set
中成员的值都是唯一的;Map
提供了“值—值”的对应,是一种更完善的Hash结构实现。例:
const set = new Set();
const map = new Map();
Set
的属性和方法有:size
属性返回结构成员总数,add()
添加某个值,delete()
删除某个值,has()
查找某值是否为其成员,clear()
清除所有成员;
Map
的属性和方法有:size、set(key, value)、get(key)、delete(key)、has(key)、clear()
。
与数组一样Set
和Map
都有forEach()
使用回调函数遍历每个成员,参数依次为键值、键名(set的键名就是键值),例:
mySet.forEach((value, key) => console.log(key + ' : ' + value));
另外,扩展运算符...也可以用于Set
和Map
// 例:
let arr = [...set];
知识点11:for...of循环
任何数据结构只要部署 Iterator
接口(Symbol.iterator
属性),就可以被ES6
中遍历命令for...of
循环遍历。有些数据结构原生具备Iterator接口,如Array、Map、Set、String、arguments、NodeList
等。例:
var es6 = new Map();
for (let [name, value] of es6) {
console.log(name + ": " + value);
}
知识点12:Class类
1.ES6引入了类的概念,使用class关键字可定义类。例:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
定义类的方法时,前面不需要加function
,方法之间也不需要逗号。事实上类的所有方法都是定义在类的prototype
属性上面。
一个类必须有constructor
方法,使用new生成对象实例会自动调用该方法。
2.实例属性除了定义在constructor()
里面的this
上面,也可以定义在类的最顶层。例:
class foo {
bar = 'hello';
baz = 'world';
constructor() {
// ...
}}
3.在类的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数。例:
class MyClass {
constructor() {
// ...
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}
inst.prop = 123;
4.类相当于实例的原型,所有在类中定义的方法都会被实例继承。使用static能使方法变成静态方法,只能通过类来调用。
而ES6
中规定Class
内部只有静态方法,没有静态属性,因此静态属性只能这样写,例:
class Foo {}
Foo.prop = 1;
5.通过extends
关键字实现继承,例:
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
}
子类必须在constructor
中调用super()
,即父类的构造函数。因为子类自己的this
对象必须先通过父类的构造函数完成塑造,如果不调用super
子类就得不到this
对象。这点ES6
的继承和ES5
的继承完全不同,ES5
中是先创造子类的this,然后再将父类的方法添加到this
上面。
知识点13:Promise
Promise是异步编程的一种解决方案,有了它就可以避免层层嵌套的回调函数。Promise是一个容器,通常保存着一个异步操作的结果。
1.生成Promise实例。例:
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
其中resolve
和reject
是两个由js
引擎提供的函数,分别用于将状态从“进行中”变为“成功”或“失败”,调用它们并不会终结Promise的参数函数的执行。
2.待实例生成后,用then分别指定两种状态的回调函数:
promise.then(function(value) {
// success
}, function(error) {
// failure
});
3.还可以用catch()
指定发生错误时的回调函数,用finally()
指定不管Promise
对象最后状态如何都会执行的操作。例:
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
4.Promise.all()
用于将多个Promise
实例包装成一个新的Promise
实例,多个请求会并发执行,只有每个状态都变成fulfilled(成功)
才会变成fulfilled
,只要有一个rejected(失败)
则合起来状态变成rejected
。例:
const p = Promise.all([p1, p2, p3]);
知识点14:async函数
ES2017引入async使得异步操作更加方便,async函数完全可以看作多个异步操作包装成的一个Promise 对象。async表示函数里有异步操作,await表示后面的表达式需要等待结果。例:
async function getStockPriceByName(name) {
const symbol = await getStockSymbol(name);
const stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName('goog').then(
function (result) {console.log(result);});
async
函数返回一个Promise
对象,因此可以使用then
添加回调函数,async
内部返回的值会成为then
的参数。该Promise
对象必须等到内部所有await
命令后面的Promise
对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。也就是说,只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
函数执行时一旦遇到await
就会先返回,等异步操作完成再接着执行函数体内后面的语句。如果多个异步操作不存在继发关系,最好用Promise.all()
让它们同时触发,缩短程序执行时间。
任何一个await
语句后面的Promise
对象变为reject
状态,整个async
函数都会中断执行,因此可以将await
放在try...catch
里面。
知识点15:ES6模块
历史上js
一直没有模块体系,对于开发大型复杂的项目形成了巨大障碍。社区制定了一些模块加载方案,主要有CommonJS
和AMD
两种。
ES6
使用export
命令与import
实现了模块功能,完全可以取代上述两种规范。其设计思想是静态化,使得编译时就能确定模块的依赖关系,而之前的两种都是运行时加载。
1.export命令常见用法有:
export var year = 1958; //输出一个变量
export { firstName, lastName, year }; //输出一组变量
export function multiply(x, y) {}; //输出函数
export {n as m}; //重命名为m
export default function(){}; //export default为模块指定默认输出,只能使用一次
export * from 'circle'; //输出别的模块的所有属性和方法
2.import命令会提升到整个模块头部首先执行,from后指定的位置可以是相对路径或绝对路径,后缀可以省略,常见用法有:
import { stat, exists, readFile } from 'fs'; //大括号中变量名需与模块对外的接口名称一致
import { lastName as surname } from './profile.js'; //重命名
import * as circle from './circle'; //用星号指定一个对象所有输出值都加载在这个对象上面
import customName from './export-default'; //用任意名字(不使用大括号)指向默认输出而无需知道原模块输出的函数名
3.浏览器加载ES6
使用type="module"
,会异步加载等到页面渲染完再执行模块脚本,等同于打开defer
属性。例:
<script type="module" src="./foo.js"></script>
4.ES6
模块与CommonJS
模块完全不同,CommonJS
模块输出的是一个值的拷贝,ES6
模块输出的是值的引用。另外,Node
要求ES6
模块采用.mjs
后缀文件名,require不能加载只有import
能加载。
知识点16:编码规范
(参考Airbnb公司:github.com/airbnb/javas)