ECMAScript6发布于2015年,是下一代ECMAScript的标准。当前ES6的支持情况如下:
JavaScript原本由网景公司发明,它将其提交给标准化组织ECMA管理,ECMA发布语言标准后,将其命名为ECMAScript。可以认为我们常说的JavaScript是ECMAScript标准的实现。
本节介绍ES7(2016)版本引入的新特性。
判断数组是否包含指定的值。
指数操作符。
本节介绍ES8(2017)版本引入的新特性。
async用于声明一个函数是异步的。await用于等待一个异步函数执行完成。await只能出现在async函数的函数体中(不能是内嵌函数的函数体)。
async会导致一个函数的返回值变成Promise:
async function testAsync() {
return "hello async";
}
const result = testAsync();
console.log(result); // Promise { 'hello async' }
对于上面这个返回直接量的函数,async会直接利用Promise.resolve()将其封装为Promise。 如果函数没有返回值,则将undefined封装为Promise。
await用于等待一个表达式的结果:
本质上async就是Promise的语法糖。下面的例子,让代码变得简洁:
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
// Promise语法
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
// async/wait语法
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
要注意await后面的Promise可能会被Rejected,因此最好:
async function f() {
try {
await returnPromise();
} catch (err) {
console.log(err);
}
}
// 或者
async function f() {
await returnPromise().catch(function (err){
console.log(err);
});
}
返回所有属性的值。Object.keys()用于返回所有属性的名字。
返回所有属性键值对的数组。
字符串补白。
返回对象自身属性的描述符。
创建通用的原始二进制数据缓冲区,类似于ArrayBuffer。
提供SharedArrayBuffer上的原子操作。
ES引入了一系列的新语法,本章内容将一一介绍。
该关键字类似于var,用于声明一个变量。但是,let声明的变量的作用域仅仅在当前代码块。也就是说,let为JavaScript引入了块级作用域:
{
var user1 = 'meng';
let user0 = 'alex';
}
user1;
user0; // ReferenceError: user0 is not defined
let关键字适用于for循环中的计数器变量定义。
let变量不存在var变量那种所谓变量提升(Hoisting)的特性,也就是说,必须先声明后使用,否则会报ReferenceError错。
而且,只要在一个代码块中的任何地方声明了let varname ,则块外部声明的任何名为varname的变量都被屏蔽:
var varname = true;
{
varname = false; // ReferenceError: varname is not defined
typeof varname; // ReferenceError: varname is not defined 而不是返回undefined
let varname;
}
上例中,在let声明出现之前,变量varname不可用的现象称为temporal dead zone。
所谓全局对象,是指浏览器中的window/self变量、Node.js中的global变量。在全局作用域通过var声明的变量,自动成为全局对象的属性。
ES6规定,以var、function声明的全局变量,仍然是全局对象的属性,但是以let、const、class关键字声明的全局变量,不作为全局对象的属性。
let关键字为JavaScript引入了块级作用域,其特性如下:
有了块级作用域后,以防止污染命名空间为目的的立即调用函数表达式(IIFE)不再需要:
// IIFE风格
(function () {
var tmp = {};
}());
// 等价的块作用域风格
{
let tmp = {};
}
ES5规定,函数只能在全局作用域,或者函数作用域的最外层声明,不能定义在代码块中。但是,很多浏览器没有遵循此标准,仍然允许在代码块中声明函数。
ES6引入了块级作用域,在代码块中声明的函数,仅仅在块内可见,就好像加了let声明的变量一样。 但是,为了 避免兼容性问题,ES6允许在浏览器环境中运行的JavaScript不遵循此规定,而是:
由于不同浏览器的行为不同,应该避免在块级作用域声明函数,如果一定要声明,应该使用函数表达式语法:
{
// 使用这种语法,明确指定函数仅仅在块中可用
let func = function(){
}
// 这种语法,可能会导致提升,由于浏览器的差异可能导致意外行为
function func(){
}
}
该关键字用于声明一个常量,常量一旦声明,其值就不能改变。在声明常量式必须同时指定初始化式,否则会报错:
const PI; // SyntaxError: Missing initializer in const declaration
const变量在作用域上的特征、与全局对象的关系,与let变量一致。
对于对象、数组等类型,const实际上仅仅限定变量指针的不变性,这类似于Java中的常量。要实现C语言那样的常量,可以调用ES5引入的Object.freeze() 方法。
JavaScript语言采用了基于原型的继承机制:
// 构造函数,定义了一个类型
function Fruit( weight ) {
// 构造函数中,可以使用this访问正在构造的对象
this.weight = weight;
}
// 构造函数的prototype属性,即为类型的原型对象,每个实例都继承原型的属性、方法
Fruit.prototype.getWeight = function () {
return this.weight;
}
function Apple( weight ) {
this.weight = weight;
}
// 可以设置原型对象,达到继承的效果
Apple.prototype = new Fruit();
var apple = new Apple( 1.2 );
console.log( apple.getWeight() ); // 1.2
这种机制与Java、Python等基于类的继承机制差异很大,容易让JavaScript新手困惑。
ES6引入类似于传统OO语言的类型定义语法:
// 定义类型
class Fruit {
// 定义构造函数
constructor( weight ) {
this.weight = weight;
// 默认返回this,但是你可以返回任意对象
}
// 方法之间不需要逗号间隔,不需要function关键字
getWeight() {
return this.weight;
}
}
这种语法本质上仅仅是语法糖,并没有改变JavaScript的继承机制 :
console.log( typeof Fruit ); // 类型仍然是function // class块中的方法,仍然是声明在原型对象上的 console.log( Fruit.prototype.getWeight );
注意:
var f = new Fruit( 1.2 ); console.log( f.hasOwnProperty( 'weight' ) ); // true console.log( f.hasOwnProperty( 'getWeight' ) ); // false console.log( f.__proto__.hasOwnProperty( 'getWeight' ) ); // true
new Fruit(); // ReferenceError: Fruit is not defined
class Fruit {}
类似于函数,class也支持以表达式方式声明:
const Fruit = class F {
getClassName() {
return F.name;
}
};
console.log( new Fruit().getClassName() ); // F
上面的例子中,对外暴露出来的类名是Fruit,F仅仅可以在class内部使用,代表当前类,F可以省略。
可以定义一个立即构造的匿名类:
let apple = new class {
constructor( weight ) {
this.weight = weight;
}
}( 1.2 );
class块中定义的所有方法都对外部可见,尽管如此,我们可以利用Symbol的唯一性,来定义无法引用的私有方法:
const checkWeight = Symbol( 'checkWeight' );
class Fruit {
[checkWeight]() {
}
}
ES6允许定义类的继承关系,使用类似于Java的extends关键字:
// 使用extends关键字继承
class Apple extends Fruit {
constructor( weight ) {
// 调用父构造器
super( weight );
}
}
在构造子类的实例时,必须先创建父类的实例,并在其基础上添加新的属性/方法。 子类构造器必须调用父类的构造器,然后才能使用this关键字。
属性__proto__用于读写一个对象的原型对象。子类的__proto__等于父类,代表了构造函数之间的继承关系:
Apple.__proto__ === Fruit // true
子类的prototype属性的__proto__属性,等于父类的prototype属性,代表了原型对象之间的继承关系:
Apple.prototype.__proto__ === Fruit.prototype // true
子类实例的__proto__的__proto__属性,等于父类实例的__proto__属性。
此关键字可以:
class Fruit {}
class Apple extends Fruit {
constructor() {
super();
super.name = 'Apple';
console.log( super.name ); // undefined
console.log( this.name ); // Apple
}
}
new Apple();
在ES5中,无法扩展Boolean、Number、String、Array、Date、Function、RegExp、Error、Object等原生构造函数。这是因为子类型无法获得原生构造函数的内部属性。
ES5是先创建子类的this,然后再把父类的属性添加到此this上,而父类的内部属性无法获取。
ES6则是先创建父类的this,再基于子类构造函数修饰这个this,因此父类的所有行为都可以继承:
class NegIdxArray extends Array {
constructor() {
super( ...arguments );
}
}
var nia = new NegIdxArray();
nia[ 0 ] = 0;
console.log( nia.length ); // 打印1,行为正常
当继承Object时,需要注意,ES6禁止向Object()传递任何参数。
可以在class块内定义getter/setter,其行为与ES5保持一致:
class Fruit {
constructor( weight ) {
this._weight = weight;
}
get weight() {
return this._weight;
}
set weight( weight ) {
console.log( `Setting weight: ${weight}`);
this._weight = weight;
}
}
var f = new Fruit( 1.2 );
f.weight = 1.5; // Setting weight: 1.5
可以在class块内部定义生成器方法,例如下面的例子:
class Foo {
constructor( ...args ) {
this.args = args;
}
// 将Foo变为Iterable类型
* [Symbol.iterator]() {
for ( let arg of this.args ) {
yield arg;
}
}
}
可以在方法定义上添加static限定符,这样的方法不会被实例继承,但是可以被子类继承。静态方法只能通过类名调用:
class Fruit {
static newInstance() {
return new Fruit();
}
}
class Apple extends Fruit {
}
console.log( Fruit.newInstance() ); // Fruit {}
ES6规定,class内部仅仅包含方法的定义,不包括任何属性。要声明属性,可以:
ES7包含了静态属性语法的提案,现在可以通过Babel转换器得以支持,语法如下:
class ClassName {
static staticProp = 0;
instanceProp = 0;
}
ES6允许使用new.target这个表达式:
使用new.target,我们可以定义“抽象类”:
class Shape {
constructor() {
if ( new.target === Shape ) {
throw new Error( `Type Shape is abstract.` );
}
}
}
JavaScript一直没有模块体系,不能自然的把大型应用程序拆分为相互依赖的小文件,并通过简单的方法拼装。
在ES6之前,一些开源框架引入了模块化方案,例如CommonJS和AMD,两者分别用于服务器和浏览器。现在ES6在语言层面上添加了通用的模块化支持。
CommonJS、AMD之类的框架,都是在运行时完成模块依赖关系的处理、导入/导出变量的推导的,例如:
// CommonJS模块
let { stat, exists, readFile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat, exists = _fs.exists, readfile = _fs.readfile;
上述代码相当于整体的加载fs模块生成一个临时对象,然后再读取该对象的属性,导出为变量的。这是典型的运行时加载,因为临时对象必须在运行时才能创建。相比之下,ES6中的模块并非对象,采用的加载方式是静态加载,利用import 、export 关键字显式的导入/导出变量:
import { stat, exists, readFile } from 'fs';
如果要在浏览器中引用一个ES6模块,需要使用如下标签:
<script type="module" src="foo.js"></script>
注意:ES6模块动采用严格模式,不需要在模块头部显式指定。
该关键字用于规范模块的对外接口。一个模块是一个独立文件,其内部所有变量,外部无法读取。如果希望某个变量被外部访问,就必须使用export关键字来导出此变量,export语句可以位于模块作用域的任何位置,不能位于块、函数体内部。示例:
let name = 'Alex';
let age = 30;
// 导出方式一:
export let name;
// 导出方式二:导出多个变量。这种方式更好,在脚本尾部统一导出,容易看清导出的内容
export {name, age};
// 函数、类的导出语法类似
export function func(){}
export class cls{}
默认情况下,导出变量的名字与其在模块中的名字一致,但可以使用as关键字修改为任意名字:
export {
name as userName,
age as userAge,
age as userAge2 // 同一变量可以导出多次
};
导出的变量,与模块内对应变量是动态绑定的,在运行期间,模块内变量的值发生变化则导出变量也跟着变化:
export var num = 1; setTimeout( () => num = 2, 1000 ); // 在一秒后,任何导入num的脚本中num的值均为2
这一动态绑定的特性与CommonJS完全不同,CommonJS的导出变量不会动态更新。
该关键字用于输入其它模块提供的功能。一旦某个模块通过export暴露了某些变量,其它JS文件就可以通过import命令引用它们:
// 不需要模块的指定.js后缀
import {name, age} from './user';
import也支持as关键字:
import {name as userName} from './user';
import具有Hoisting效果,即import语句总是相当于在脚本最顶端执行。
使用通配符* 可以导入模块所有暴露的变量:
import * as user from './user'; console.log( user.name );
通配符导入,会自动忽略模块中的默认导出。
通过import命令导入变量时,你必须知道这些变量的导出名。模块作者可以指定一个变量为“默认导出”:
// 默认导出函数,可以是匿名函数
export default function () { }
// 默认导出变量
var name = 'Alex';
export default name;
// 默认导出类
export default class {}
这样,导入该模块的JS,可以用任意名字指代此默认导出:
import customName from './export-default';
进行默认导入时,变量名周围不能加大括号。
ES6模块,导出的是变量引用,而CommonJS导出的则是值的拷贝。
CommonJS导入的变量,与定义它的模块中的那个变量没有联动关系,各自独立变化。
ES6导入的变量,是一个“常指针”,不能对导入的变量进行重新赋值:
import { obj } from './lib';
obj.prop = 123; // OK
obj = {}; // TypeError
ES6的导入是在编译期处理的,这意味着,要导入什么模块在编译期就必须确定,不能依赖于运行时:
const path = './' + fileName; // CommonJS 运行时决定加载的模块 const myModual = require(path); // ES6 不支持: import * from path; // 语法错误:string literal expected
目前有一个提案,建议引入import() 函数,以支持运行时加载。该函数与CommonJS的require() 不同之处是,它是异步加载,返回一个Promise。
目前的浏览器、Nodejs都不支持ES6模块。除了Babel以外,还可以使用:
<script src="system.js"></script>
<script>
// import方法是异步的,返回一个Promise对象
// 这里的app.js可以使用ES6模块语法
System.import('./app.js');
</script>
ES6引入一种新语法,允许当赋值语句的左值、右值遵循相同的模式(Pattern)时,进行自动赋值。
let [a, b, c] = [ 1, 2, 3 ]; // 1 2 3 [ a, [ [ b ], c ] ] = [ 4, [ [ 5, 0 ], 6 ] ]; // 4 5 6 [ a, b, c ] = [ , , 9 ]; // 4 5 9 [ a, b ] = [ 1, 2, 3 ]; // 1 2 9
除了let变量,var、const变量也支持使用解构赋值。解构赋值可以部分成功,失败的变量自动赋值为undefined。
实际上,任何Iterator都可以作为数组解构的右值:
// Set结构
let [a, b, c] = new Set( [ 0, 0, 0 ] );
// 生成器函数
function* gnerator() {
let a = 0;
while ( true ) {
yield ++a;
}
}
[ a, b, c ] = gnerator()
console.log( [ a, b, c ] ); // [ 1, 2, 3 ]
前面提到过,结构赋值失败时,变量赋值为undefined(即使此变量赋值前是有值的)。ES6允许设置解构赋值的默认值,语法如下:
// 在变量名后面增加 = value let [a,b = 2,c = 3] = [ 1, null ];
判断Pattern对应位置是否有值时,ES6使用严格相等操作符===与undefined比较,因此上面的例子中,b的默认值2不会生效。
默认值可以指定为一个表达式,该表达式仅仅在需要时(Pattern对应位置为undefined)才会求值:
function defaultValue() {
console.log( 'This function won't be invoked' );
}
let [a = defaultValue()] = [ null ];
与适用于Iterator的方括号语法类似,对象语法也可以用于解构赋值:
const { a, b, c }={ a: 1, b: 2, c: 3 }
console.log( c ); // 3
方括号语法根据位置去匹配,而对象解构赋值使用属性名去匹配,位置顺序不重要。
如果变量名与属性名不一样,必须使用下面的语法:
const { propname:varname }={ propname: 1 };
console.log( varname );// 1
对象解构也可以设置默认值:const { propname:varname = 1 }={}
对已声明的变量进行解构赋值,可能会被JavaScript引擎当作代码块来解释进而导致语法错误:
var a, b, c;
{ a, b, c } = { a: 1, b: 2, c: 3 }; //SyntaxError: Unexpected token =
// 正确的写法如下:
({ a, b, c } = { a: 1, b: 2, c: 3 })
使用对象解构赋值,可能很方便的把对象的属性、方法赋值给多个变量,例如:
let { log, sin, cos } = Math;
字符串作为解构赋值的右值,可以把字符逐个赋值给变量:
let { a, b, c } = 'alex';
数字/布尔值作为解构赋值的右值时,引擎先将其转换为对象:
let {toString: s} = true;
s === Boolean.prototype.toString
函数的参数,可以作为解构赋值的左值:
function add( [x,y] ) {
return x + y;
}
console.log( add( [ 1, 2 ] ) ); //3
console.log( add( 1, 2 ) ); // 错误:调用必须使用解构语法
// 对象解构语法、默认值都是支持的
function add( { x, y = 1 } ) {
return x + y;
}
console.log(add( { x: 1 } )); // 2
函数的返回值,可以作为解构赋值的右值:
function pair() {
return [ -1, 1 ];
}
let [l,r ] = pair();
可以看到,上例中的用法实现了类似Python函数调用的多个返回值的效果。
合理使用解构赋值,可以让代码更加简洁,下面是一些应用场景举例:
| 交换变量的值:[x, y] = [y, x]; |
| 从函数返回多个值 |
| 为函数参数提供默认值:因为解构赋值的左值可以提供默认值 |
|
更丰富的参数风格: // 普通传参风格:
function func( a, b, c ){}
func( 1, 2, 3 );
// 允许以数组放入传入所有参数:
function func( [a,b,c] ){}
func( [1, 2, 3] );
// 允许类似于Python的关键字参数,实现乱序传参
function func( { a, b, c } ){}
func( { b: 2, a: 1, c: 3 } )
|
|
快速数据提取: 解构赋值可以使用非常简洁的代码从JSON中提取数据: let json = {
success: true,
message: 'OK',
object: {
name: 'Alex',
age: 30
}
};
let { object:{ name, age } } = json;
console.log( name, age ); // Alex 30
|
|
遍历map: 任何Iterator都可以使用for...of循环来迭代,配合解构赋值,可以很方便的获得map的键值: for ( let [key, value] of map ) {
console.log( key + " is " + value );
}
|
|
导入模块的指定成员: const { min, max } = require("module-stat");
|
展开运算符也是以... 作为前导(类似于函数的Rest参数),展开运算符用于把数组展开为若干个值,主要用于函数调用:
console.log( ...[ 1, 2, 3, 4, 5 ] ); // 1 2 3 4 5 console.log( [ 1, 2, 3, 4, 5 ] ); // [ 1, 2, 3, 4, 5 ]
展开运算符可以代替函数的apply方法。
展开运算符也可以用来合并数组:
var array = [ 1, 2, 3 ]; var array2 = [ 0, ...array ];
也可以和解构赋值联合使用(用在左值列表的结尾):
let [a, ...rest] = [ 1, 2, 3, 4, 5 ]; console.log( rest ); // [ 2, 3, 4, 5 ]
展开运算符还可以:
和数组类似,对象也支持使用展开操作符:
// 用于解构赋值,z = { a: 3, b: 4 }
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
// 取出对象z的可枚举属性,存放到对象n中。等同于使用Object.assign方法
let z = { a: 3, b: 4 };
let n = { ...z };
ES6允许定义对象属性时,指定一个变量:
let name = 'Alex';
let age = 30;
let user = { name, age };
// 另外一个例子
function func( x, y ) {
return { x, y };
}
这样,变量名就作为属性名,变量值则作为属性值。
和属性类似,方法也允许简写:
var user = {
getName(){
return this.name
},
// ES5的Getter/Setter类似于方法简写
get name(){
},
// 如果某个方法是生成器,则必须加*前缀
* generator(){yield 0;}
}
ES6允许在花括号语法中,用表达式来组成属性名、方法名:
var user = {
['name' + i]: 'Alex'
}
这种写法不能和属性名简写同时使用。 另外,作为属性名的表达式会自动转换为字符串。
ES6允许为函数参数提供默认值:function log( x, base = 10 ){}
使用默认值后,函数的length属性将返回没有指定默认值的参数个数(而不是全部参数个数)。
ES6可以将参数列表中剩余的所有参数暴露为一个数组:
function(a, b, ...theArgs) {
// 第三个及之后的参数,存放在theArgs变量中
}
Rest参数必须作为最后一个形参。
函数的name属性返回函数的名称,ES6将其纳入标准。
在全局作用域下,this指向全局对象:浏览器中的window/self或者Node.js中的global
在Node.js模块、ES6模块的定义中,this指向当前模块
在函数作用域下,如果:
该操作符是ES7提案的一部分,目前可以通过Babel支持它,用于绑定函数到目标对象并调用:
// 绑定
foo::bar // 等价于bar.bind(foo)
// 绑定并调用
function showThis() {
console.log( this );
}
""::showThis(); // 等价于showThis.bind("")()
// 将obj的foo方法绑定到obj,并返回绑定后函数
var method = obj::obj.foo;
// 上述代码等价于
var method = ::obj.foo;
在函数式编程领域,所谓尾调用(Tail Call)是指作为函数体最后一个步骤的函数调用:
function f( x ) {
return g( x );
}
// 下面三个都不是尾调用
function f( x ) {
let y = g( x );
return y;
}
function f( x ) {
return g( x ) + 1;
}
function f( x ) {
g( x );
// 这边相当于有一个 return undefined
}
在使用递归结构时,尾调用具有优化的空间:由于尾调用作为最后一步,因此可以安全的丢弃调用者的栈帧并将其作为尾调用函数的栈帧,这样栈长度就不会增长,也不会因为过深的递归导致太多的内存占用甚至栈溢出。ES6明确要求解释器优化尾递归调用。
要利用尾调用优化来提升程序性能,你需要注意调整自己的代码,例如下面的阶乘实现就是正面的例子:
function factorial( n, total ) {
if ( n === 1 ) return total;
// 尾递归调用
return factorial( n - 1, n * total );
}
这个例子用到了递归,但是代价却与循环差不多。
关键字 => 用于定义所谓箭头函数,类似于其它语言中的Lambda:
// 最后一个语句作为返回值
// 单个参数
var square = x => x ** 2;
// 多个参数
var add = ( a, b ) => a + b;
// 多个语句,必须使用花括号,最后一个语句return
var sayHello = who => {
var greetings = `Hello ${who}`;
console.log( greetings );
return true;
}
// 与解构赋值联用
(
( { name, age } ) => {
console.log( `${name} is ${age} years old` );
}
)( { name: 'Alex', age: 30 } );
关于箭头函数,需要注意:
JavaScript之前的版本支持\uxxxx 的Unicode字符表示法,最多支持0xFFFF之前的字符。ES6则引入\u{xxxxx} 这样的表示法,只要把代码点(Code point)放在花括号内,可以表示任意范围的Unicode字符:
"\u{41}\u{42}\u{43}" // abc
JavaScript中拼写HTML模板异常的繁琐,需要很多字符串拼接操作。ES6引入了新的模板字符串语法,它允许声明多行字符串,以反引号(` )限定:
var user = { name: 'Alex', age: 30 };
var html = `
<div>
<div>${user.name}</div>
<div>${user.age + 1}</div>
</div>
`;
模板字符串中的空白符会被原样保留,而且,模板中支持插入当前上下文中的变量,类似于Shell的变量替换或者Java的EL表达式。${} 内部可以包含任意表达式,例如函数调用、其它模板字符串。 如果模板字符串引用未声明的变量将会导致报错。
标签模板(tagged template)是模板字符串的一种高级应用。模板模板是紧跟着函数名之后的模板字符串,该函数会被调用,来处理模板字符串:
var name = 'Alex';
console.log`${name} Hello ${name}`
// 等价于
console.log( [ 'Hello ', '' ], name, name );
// 打印: [ '', ' Hello ', '' ] 'Alex' 'Alex'
从上面的例子可以看出,所谓标签模板本质上是函数调用表达式,将模板字符串解析为参数的规则如下:
标签模板的函数体,通常都执行这样的逻辑:
例如下面的代码,可以过滤用户输入中的不安全字符:
// 打印: [ '', ' Hello ', '' ] 'Alex' 'Alex'
// 这些变量来自用户输入
var name = 'Alex<script>';
var age = 30;
function escapeHTML( str ) {
var arg = String( str );
return arg.replace( /&/g, "&" ).replace( /</g, "<" ).replace( />/g, ">" );
}
function safehtml( literals, ...values ) {
var index = 0;
var result = '';
for ( let seg of literals ) {
result += seg;//原样加入
// 每个元素后面都跟着一个变量,除了最后一个元素
if ( index < values.length ) {
result += escapeHTML( values[ index++ ] );
}
}
return result;
}
// 这个模板需要被安全的插入到DOM
var result = safehtml`
<div>Hello ${name}, You are ${age} years old</div>
`;
console.log( result ); // <div>Hello Alex<script>, You are 30 years old</div>
标签模板也提供了一种直白的语法,来把其它语言的代码嵌入到JavaScript语言中,例如jsx函数可以把DOM字符串转换为React对象。
ES6引入新的二进制、八进制数值直接量语法:
var bin = 0b1011; bin = 0B1011; var oct = 0o777; oct = 0O777;
ES6允许正则式构造函数第二个参数指定的标记,覆盖第一个参数中内置的标记:
console.log( new RegExp( /abc/ig, 'i' ).flags ); // 打印i,第一个参数的ig被替换
该修饰符用于支持4字节表示的Unicode字符,以及支持\u{xxx}语法的字符串:
/\u{61}/.test('a') // false
/\u{61}/u.test('a') // true
该修饰符类似于g,也用于全局匹配,但是y限定后一次匹配必须从上一次匹配成功的下一个字符开始。
正则式对象的sticky属性指示该修饰符是否被设置。
数组的空位是没有任何值的索引点:
// 构造函数返回的数组都是空位 console.log( Array( 2 ) ); // [ , ]
注意:空位与undefined不同,后者是一个确实的值。要判断某个索引是否空位可以使用in运算符:
console.log( 0 in [] ); // false console.log( 0 in [ undefined ] ); // true
在ES5中:
ES6明确的把空位作为undefined看待。
ES6引入了一种新的数据类型,叫做Symbol,用于表示独一无二的值(类似于Java的枚举)。该类型与Undefined、Null、Boolean、String、Number、Object并列,成为第七种类型。
注意:
Symbol变量通过调用Symbol函数获得:
let name = Symbol();
// 可以入参一个字符串,但是不影响不相等性
let name = Symbol('name');
注意:
var sym = Symbol('name');
String(sym) // Symbol('name')
Boolean(sym) ; // true
现在对象的属性名,除了支持字符串类型之外,还可以支持Symbol类型:
let obj = {
[name]: 'Alex'
}
console.log( obj[ name ] ); // Alex
由于每个Symbol都不相等,因此将其作为属性名时,可以防止冲突。访问由Symbol标识的属性时,只能用方括号语法,不能用点号语法。
可以使用Symbol定义一组常量:
log.levels = {
DEBUG: Symbol( 'debug' ),
INFO: Symbol( 'info' ),
WARN: Symbol( 'warn' )
};
类似于数组,但是成员的值必须唯一,即没有重复值:
var set = new Set();
[ 1, 2, 3, '3' ].map( e => set.add( e ) );
console.log( set ); // Set { 1, 2, 3, '3' }
可以看到,判断元素相等时,使用的是精确相等(Object.is)。Set构造函数有几个变体:
let set = new Set( [ 1, 2, 3, 3 ] ); // 传入数组 console.log( set.size ); // 3
利用Set构造函数,可以给数组去重:[...new Set(array)]
与Set类似,但是成员只能是Object类型,该集合持有对象的弱引用,GC将无视这些引用。WeakSet没有size属性,也不能遍历。但是你可以调用其add/delete/has方法
JavaScript对象本质上就是一个Hash结构,可以作为Map使用。但是这种Map的Key类型只能是String/Symbol。
ES6引入的Map,支持任何类型的对象作为映射的键:
var map = new Map(); // 默认构造器
map = new Map( [
[ 'name', 'Alex' ],
[ 'age', 30 ],
[ 0 , 1 ]
] );
console.log( map.has( 'age' ) ); // true
Map在判断两个键是否相等时,使用类似于Object.is()判断。
类似于WeakSet,键是对象的弱引用。仅仅支持将对象作为键。你可以调用其get()、set()、has()、delete()方法,不支持遍历。
现在JavaScript中用来表示集合的结构包括:Array、Map、Set,它们都是可迭代(Iteratble)对象。所有可迭代对象都可以使用for ... of 循环,来遍历集合的全部成员。
任何对象,只要其具有名为Symbol.iterator 的方法,则被认为是可迭代的。对前述方法进行调用,会返回一个迭代器对象(Iterator),你可以简单的返回具有next()方法的简单对象,JavaScript引擎负责自动设置其原型为Generator.prototype:
const obj = {
[Symbol.iterator]: function () {
// 返回迭代器
return {
next: function () {
return {
value: 1,
done: false
};
}
};
}
};
for ( val of obj ) {
console.log( val ); // 无限打印
}
for..of循环遍历迭代器的过程如下:
除了必须的next()方法,迭代器对象还可以提供额外的两个方法。如果不提供,默认实现由Generator.prototype提供。这两个方法的说明如下:
function readLines( file ) {
return {
next() {
// 正常退出
if ( file.eof() ) {
file.close();
return { done: true };
}
},
// 中途退出,默认逻辑是简单的把入参设置到结果数据结构的value字段
return( v ) {
file.close();
return { value : v, done: true };
},
};
}
var g = function*() {
try {
yield;
}
catch ( e ) {
console.log( e ); // 1
}
};
var i = g();
i.next();
try {
// 注意不能用throw关键字抛出,该关键字无法影响生成器
i.throw( 1 ); // 这个错误在内部捕获
i.throw( 2 ); // 这个错误在外部捕获,因为第一个错误已经触发yield并导致生成器结束
}
catch ( e ) {
console.log( e ); // 2
}
以下场景下,解释器自动遍历目标对象的迭代器: 解构赋值、扩展运算符、yield* 、for...of 、Array.from 、Promise.all() 、Promise.race() 等等。
让Symbol.iterator返回一个生成器,可以简化代码:
let obj = {
// Symbol.iterator方法可以返回生成器
* [Symbol.iterator]() {
yield 1;
yield 2;
yield* [ 3, 4, 5 ];
}
};
console.log( [ ...obj ] ); // [ 1, 2, 3, 4, 5 ]
生成器是ES6提供的异步编程解决方案。它是一种特殊的函数,其声明语法如下:
// 要声明一个函数为生成器,必须在function后附加 * 号
function* generator() {
// 生成器函数体可以包含yield语句
yield 1;
yield 2;
return 3;
}
对生成器函数执行调用操作,你会得到一个迭代器对象,但是生成器的函数体不会 执行:
function* generator() {
yield 1;
yield 2;
return 3;
}
let iterable = generator(); // 此时generator的函数体并未执行
for ( e of iterable ) {
console.log( e ); // 1 2
}
每一个yield语句后面的表达式的值,将作为迭代器的元素,注意return语句后面的表达式不作为迭代器元素。关键字yield的本义就是“产出”。
你可以手工调用上述迭代器的next()方法:
let iterable = generator();
console.log( iterable.next() ); // { value: 1, done: false }
console.log( iterable.next() ); // { value: 2, done: false }
console.log( iterable.next() ); // { value: 3, done: true } return语句后的表达式作为value
从效果上来看,执行生成器函数体时,每当遇到yield就返回(暂停)一次。返回后,生成器函数体的内部状态仍然被保留。当再一次调用next()时,自动恢复内部状态,从上一次退出的那个点继续向下执行。这种行为特点与普通函数完全不同。
对生成器执行附带入参,可以为生成器的yield语句提供“返回值”,这里有一个特别容易误会的地方:
var generator = (function *() {
console.log( yield 1);
})();
generator.next( 2 ); // 什么都不打印
generator.next( 3 ); // 打印3
你可以通过next()调用来启动生成器,并让它在yield一个值(上面的1)后立即暂停。如果生成器当前正因为执行了yield语句而处于暂停中,你再一次调用next()可以让其恢复执行,这时你才可以为next()调用附带参数,此参数作为返回时yield语句的值。
上面的next(2)调用,导致生成器在标记红色的地方暂停:console.log( yield 1),打印语句根本不会执行,因此提供参数2没有任何意义。
function* numbers() {
yield* [ 4, 5, 6 ]
}
function* generator() {
yield* [ 1, 2, 3 ]; // 逐个yield数组
yield* numbers(); // 逐个yield生成器
}
// 展开操作符能够遍历迭代器
console.log( [ ...generator() ] ); // [ 1, 2, 3, 4, 5, 6 ]
yield语句可以产生一个值,但是该语句本身的返回值默认是undefined。你可以在next()调用是提供一个参数,这个参数自动成为yield的返回值。带参数的next()调用类似于Python语言生成器的send()方法,这种情况下生成器函数成为协程。
协程是不依赖于线程的、轻量级的异步编程技术,它通过几个例程(函数)相互协作来完成逻辑。下面是一个简单的例子:
// 服务器代理
var serverAgent = (function*() {
var clientToRequest = new Map();
while ( true ) {
// 1、服务器运行到yield,放弃控制权,暂停
// 4、服务器从暂停中恢复,从yield的返回值得到客户端ID,并异步发送Ajax请求
clientToRequest.set( yield, sendRequest() );
// 5、遍历客户端请求的列表,判断Ajax请求状态
for ( e of clientToRequest ) {
var cid = e[ 0 ];
var xhr = e[ 1 ];
if ( isDone( xhr ) ) {
// 6、如果Ajax请求已经完毕,则调用客户端处理,并放弃控制权
clients[ cid ].next( xhr.responseText );
}
}
}
})();
serverAgent.next(); // 0、启动服务器代理
// 很多客户端
var clients = [];
for ( i of [ 1, 2, 3, 4, 5 ] ) {
var client = (function*() {
// 3、客户端向服务器代理发送请求,附带自己的ID
// 7、客户端从yield返回,返回值为服务器代理提供的响应对象。异步处理的同步化表达
var response = yield serverAgent.next( i );
// 8、处理响应
console.log( response );
})();
clients.push( client );
client.next(); // 2、启动客户端
}
使用协程进行异步处理,代码要比回调简洁的多。
很多JavaScript框架都提供了Promise这种异步编程框架,终于现在可以在语言级别原生的支持了。比起传统的基于回调函数、事件的异步处理,Promise更加合理和强大。
一个Promise保存着一个未来才能完成的事件(通常是异步操作)的结果。Promise所关联的异步操作,其状态只能是Pending(正在进行)、Resolved(已经解决)、Rejected(已经失败)之一。只有异步操作的结果可以决定状态到底是哪一个。状态只能从Pending变为Resolved或者Rejected,而且只能改变一次。
Promise具有一些缺点:
Promise是一个构造函数,你可以调用它创建Promise:
// 创建者代码:
var promise = new Promise( function ( resolve, reject ) {
// 执行异步操作...
if ( succeed ) {
resolve( value ); // 将Promise置为已解决
}
else {
reject( error ); // 将Promise置为已失败
}
} );
一旦Promise被创建,通过构造函数传入的回调就会立即执行。此回调的两个参数resolve、reject,是引擎提供的函数,你可以调用它们而把Promise置为已解决、已失败状态之一。
要知道,构造函数由Promise的创建者调用,但是Promise肯定是提供给其它模块使用的。那么其它模块拿到这个Promise有什么意义呢?
其它模块可以调用它的then方法,注册当Promise状态改变后,需要执行的回调函数:
// 客户代码:
promise.then( function ( value ) {
// 成功时执行的回调
}, function ( error ) {
// 失败时执行的回调
} );
下面是一个简单的例子:
function timeout( ms ) {
return new Promise( ( resolve, reject ) => {
// 此回调会立即执行,因此下面的语句立即打印
console.log( 'Starting process...' );
// 异步的设置Promise为已解决状态
setTimeout( resolve, ms, 'Success' );
} );
}
// 客户代码
// 由于setTimeout是异步操作,它必然在then调用之后,才可能执行回调
timeout( 1000 ).then( ( value ) => {
// 异步操作完成时,打印Promise创建者提供的值
console.log( value );
} );
下面是一个基于Promise处理Ajax的例子:
var getJSON = function ( url ) {
var promise = new Promise( function ( resolve, reject ) {
var client = new XMLHttpRequest();
client.open( "GET", url );
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader( "Accept", "application/json" );
client.send();
function handler() {
if ( this.readyState !== 4 ) {
return;
}
if ( this.status === 200 ) {
resolve( this.response );
}
else {
reject( new Error( this.statusText ) );
}
};
} );
return promise;
};
// 客户代码
getJSON( "/posts.json" ).then( function ( json ) {
// 处理JSON
}, function ( error ) {
} );
总之,都是这样的套路:服务方执行异步操作,并给客户一个承诺,客户拿到承诺后注册期望的操作,服务方完成异步操作后,调用客户端注册的操作,完成处理。
Promise的创建者,可以向resolve函数传递另外一个Promise对象:
var p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('Failed')), 3000);
});
var p2 = new Promise(function (resolve, reject) {
// 向resolve传递另外一个Promise p1
setTimeout(() => resolve(p1), 100);
});
P2没Resolve为一个“依然未Resolve”的Promise,此时JS引擎不会简单的直接调用客户通过then注册的回调函数(即100ms后回调不会被调用)。而是另外的那个Promise(P1)的状态决定当前Promise(P2)的状态——如果P1处于Pending则P2会等待,直到P1变为Resolved/Rejected,才调用客户回调:
// 于3秒后失败,而不是100ms后成功 p2.then(result => console.log(result), error => console.log(error));
基于这种用法,可以很容易的管理多个异步操作,例如只在所有这些操作都成功的时候,才认为整个事务是成功的。
前面的例子已经使用过此方法,它的基本作用是注册当Promise状态改变后,需要执行的回调函数。该方法可以有两个入参,它们都必须是函数,第一个作为Promise变为Resolved时的回调,第二个可选,作为Promise变为Rejected时的回调。
then()方法的返回值是一个新的Promise实例,因此可以使用链式调用。链式调用时,前面then的回调函数的返回值,将会传递给后面then的回调函数:
new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'Succeeded');
}).then(result => {
console.log(result);
return 'OK';
}).then(result => console.log(result));
// 1秒后连续打印Succeeded、OK
如果前面then的回调函数的返回值是一个Promise,则后面then的回调函数直到此Promise状态改变后才被调用:
// getJSON函数返回一个Promise
getJSON("/post/1.json").then(function (post) {
// 继续调用getJSON,返回一个Promise
return getJSON(post.commentURL);
}).then(result=>{
// 此回调在第二次getJSON返回的Promised被Resolved时才调用
});
此方法等价于then(null, rejection) 调用,用于指定Rejected时的回调函数。链式调用时,前面then方法抛出的异常,也经由后面的catch方法捕获。
注意,当Promise状态已经变成Resolved后,再抛出异常是无效的:
var promise = new Promise(function (resolve, reject) {
resolve('OK');
throw new Error(); // 无效
});
promise.then(function (value) {
console.log(value)
}).catch(function (error) {
console.log(error)
});
// 打印OK
一般情况下,都使用catch方法,而不是then的第二回调。
该静态方法用于将多个Promise实例包装为单个新的Promise实例:var p = Promise.all([p1, p2, p3]) ,当:
该静态方法也是用于将多个Promise实例包装为单个新的Promise实例。但是,只要有一个成员Promise的状态发生改变,则整体Promise的状态也发生改变,并且与前面成员Promise的状态一致。
该静态方法用于把一个对象转换为Promise:
// 把jQuery生成的deferred对象转换为Promise
var p = Promise.resolve($.ajax('/data.json'));
该方法的参数,有四种情形:
let thenable = {
then: function(resolve, reject) {
resolve(true);
}
};
新建的Promise对象会立即调用这里的then方法
返回一个Rejected的Promise实例。
Promise的链式调用,不管以then还是catch结尾,其内部的异常都不会抛出到外部。为了确保未处理的异常能够抛出,可以在调用链的结尾添加一个done()调用。
该方法指定一个无论什么情况下,都会执行的回调。示例:
// 使用垫片库获得finally支持
require( 'promise.prototype.finally' ).shim();
var assert = require('assert');
// 使用finally:
Promise.resolve(42).finally(function () {
assert.equal(arguments.length, 0);
});
该对象提供以下功能:
|
语言内部功能相关方法 Object对象某些方法,例如Object.defineProperty(),现在同时在Reflect上部署,未来的新方法统一部署到Reflect上 注意,Reflect改进了Object某些方法的行为,例如defineProperty失败时,Object返回异常,而Reflect返回false |
|
与Proxy配合 Reflect提供了与Proxy的Handler一一对应的方法,调用这些方法,可以执行“默认行为” |
ES6引入了代理(Proxy),利用它可以实现类似于JDK动态代理、Python装饰器/元类的功能。
Proxy在被代理对象上设置一个拦截器,对此对象的访问必须经由此拦截器,拦截器可以改变方法调用、属性读写的行为。例如下面的拦截器改写了属性读写的默认行为:
let target = {};
// 改变属性读写的行为,打印调试信息
var proxy = new Proxy( target, {
get: function ( target, key, receiver ) {
console.log( `getting ${key}` );
return Reflect.get( target, key, receiver );
},
set: function ( target, key, value, receiver ) {
console.log( `setting ${key}` );
return Reflect.set( target, key, value, receiver );
}
} );
proxy.age = 1; // setting age
proxy.age++; // getting age, setting age
Proxy构造函数接受两个参数,第一个是被代理对象,第二个是处理器(Handler)。需要拦截的操作不同,则处理器写法也不同:
| Handler的键 | 拦截的操作 |
| get |
签名:get(target, propKey, receiver)。拦截对象属性、数组元素的读取操作 将对象的原型设置为提供了get处理器的代理,对象自动获得代理的get行为: var proxy = new Proxy( Array.prototype, {
// receiver为被代理的那个对象
get( target, propKey, receiver ) {
let index = Number( propKey );
if ( index < 0 ) {
propKey = String( receiver.length + index );
}
return receiver[ propKey ];
}
} );
var a = [ 1, 2, 3, 4, 5 ];
Object.setPrototypeOf( a, proxy );
console.log( a[ -1 ] ); // 5
|
| set | 签名:set(target, propKey, value, receiver)。拦截对象属性、数组元素的设置操作 |
| apply |
仅当代理的目标是函数时使用 示例: {
apply ( target, ctx, args ) {
return Reflect.apply( ...arguments );
}
}
|
| construct | 仅当代理目标是作为构造器的函数时使用 签名:construct(target, args) 拦截构造函数调用操作:new proxy(...args) |
| has | 签名:has(target, propKey)。拦截 prop in obj、hasOwnProperty操作,返回布尔值 |
| deleteProperty | 签名:deleteProperty(target, propKey)。拦截delete obj[prop]操作,返回布尔值 |
| ownKeys | 签名:ownKeys(target) 拦截:Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy) 返回对象自身所有属性的数组,注意Object.keys仅返回可枚举属性 |
| getOwnPropertyDescriptor | 签名:getOwnPropertyDescriptor(target, propKey) 拦截:Object.getOwnPropertyDescriptor(proxy, propKey),返回属性描述符 |
| defineProperty | 签名:defineProperty(target, propKey, propDesc) 拦截:Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回布尔值 |
| preventExtensions | 签名:preventExtensions(target)。拦截Object.preventExtensions(proxy),返回布尔值 |
| getPrototypeOf | 签名:getPrototypeOf(target)。拦截Object.getPrototypeOf(proxy),返回原型对象 |
| isExtensible | 签名:isExtensible(target)。返回布尔值 |
| setPrototypeOf | 签名:setPrototypeOf(proxy, proto)。拦截Object.setPrototypeOf(proxy, proto),返回布尔值 |
考虑到浏览器的兼容性问题,不能直接在其中运行ES6代码。即使你仅仅需要支持最新版本的现代浏览器,模ES6模块之类的特性也不被支持。
因此,必须要进行转码(transpile),将ES6语法转换为通用的ES5语法,并通过垫片(Polyfill)库来适配ES5中缺失的ES6 API。
这是一个广泛使用的JavaScript编译器,请参考:使用Babel进行JS转码
Google提供的JavaScript转码器。Traceur也允许直接在网页中使用ES6代码:
<!-- Traceur库 -->
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<!-- 这两个脚本用于支持浏览器环境 -->
<script src="https://google.github.io/traceur-compiler/bin/BrowserSystem.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
<!-- 注意type设置为module-->
<script type="module">
// 导入用户模块
import './Greeter.js';
// 可以包含任意ES6代码
</script>
Traceur可以对支持的ES6特性进行细粒度的控制:
<script>
// 创建一个全局变量,引用Traceur加载器
window.System = new traceur.runtime.BrowserTraceurLoader();
// 进行细粒度控制
var metadata = {
traceurOptions: {
experimental: true,
properTailCalls: true,
symbols: true,
arrayComprehension: true,
asyncFunctions: true,
asyncGenerators: exponentiation,
forOn: true,
generatorComprehension: true
}
};
// 加载自己的模块
System.import( './module.js', { metadata: metadata } ).catch( function ( ex ) {
// 加载失败时的处理
} );
</script>
| 属性/方法 | 说明 |
| is() |
该方法补充了既有的相等比较运算符:
当然,对于{} == {} 三种运算符都返回false,因为复杂对象比较的是地址 |
| assign() |
类似于Ext.apply,可以将1-N个源对象的属性覆盖到目标对象,声明在后面的源对象同名属性,优先级高 该方法进行的是浅拷贝 |
| __proto__ |
用于读取、设置当前对象的原型对象,作用类似于setPrototypeOf()/getPrototypeOf()静态方法 |
ES6支持多种遍历对象属性的方法:
| for...in 遍历对象自身定义和继承得到的可枚举属性,不包含Symbol属性 |
| Object.keys(obj) 返回对象自身定义的可枚举属性的名称的数组,不包含Symbol属性 |
| Object.values(obj) 返回对象自身定义的可枚举属性的值的数组,不包含Symbol属性 |
| Object.entries(obj) 类似上面,但是数组的元素是[key, value] |
| Object.getOwnPropertyNames(obj) 返回对象自身定义的全部属性的数组,不包含Symbol属性,包含不可枚举属性 |
| Reflect.ownKeys(obj) 返回自身所有属性的数组,包括不可能枚举属性,包括Symbol属性 |
| 方法 | 说明 |
| for() |
根据键查找已经通过Symbol.for登记的Symbol,如果不存在,则定义一个并登记、返回: let name = Symbol.for( 'name' ); Object.is( Symbol.for( 'name' ), name ); // true 注意,Symbol()调用生成的Symbol不参与查找,其值总是独特的: let name = Symbol( 'name' ); Object.is( Symbol.for( 'name' ), name ); // false |
| keyFor() | 返回一个已经登记的Symbol的键 |
| 方法 | 说明 |
| codePointAt() |
返回字符串目标位置上(以2字节为单位划分位置)的字符对应的代码点 在JavaScript内部,字符使用UTF-16表示(二字节),对于需要4字节粗存储的Unicode字符,会被认为是两个字符。charAt、charCodeAt不能正确的识别4字节的Unicode字符 codePointAt方法解决此问题,它可以正确处理4字节字符,并返回正确的10进制代码点的值: s.codePointAt(0).toString(16) // 转为16进制 使用for ... of循环可以获得字符串中所有字符的代码点: for (let c of str) {
c.codePointAt(0);
}
|
| fromCodePoint() | 该静态函数用于把代码点转换为字符 |
| includes() startsWith() endsWith() |
这些子串搜索函数补充了indexOf() 这三个函数都支持第二个参数,用于指定搜索的起始位置 |
| repeat() |
返回字符串重复N遍的副本 |
| padStart() padEnd() |
使用指定的字符串左侧/右侧补全字符串,确保它到达一定的长度 |
| raw() |
一个标签模板,处理字符串,将其中的反斜杠替换为双反斜杠: console.log( '\t' ); // 空白 console.log( String.raw`\t` ); // \t console.log( '\\t' ); // \t console.log( String.raw`\\t` ); // \\t |
| 方法 | 说明 |
| isFinite() |
判断一个数字是否为有限的: Number.isFinite(0.8); // true
Number.isFinite(15); // true
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('15'); // false
// 非数字一律返回false
Number.isFinite(NaN); // false
Number.isFinite(true); // false
Number.isFinite('foo'); // false
|
| isNaN() | 判断参数能否转换为数字 |
| parseFloat() | 解析为浮点数 |
| parseFloat() | 解析为整数 |
| isInteger() |
判断是否整数,由于JavaScript以相同的方式存储整数和浮点数,因此6.0和6被作为同一个值看待: Number.isInteger(6.0) // true Number.isInteger(6.1) // false |
| isSafeInteger() | 判断是否安全整数,即在-2^53到2^53之间,超过此范围的整数,其值无法精确表示 |
| 方法 | 说明 |
| trunc() | 取整数,不进行舍入 |
| sign() | 判断值的正负 |
| cbrt() | 求立方根 |
| clz32() | JavaScript中整数使用4字节表示,该函数判断整数的二进制形式,具有多少个前导的0 |
| imul() | 返回两个整数以32位带符号整数的方式的乘积 |
| fround() | 返回一个树的单精度浮点数形式 |
| hypot() | 返回所有参数平方和的平方根 |
| expm1(x) | 计算e^x - 1 |
| log1p(x) | 计算 1+ x的自然对数,即Math.log(1 + x) |
| log10(x) | 计算以10为底的对数 |
| log2(x) | 计算以2为底的对数 |
| sinh(x) cosh(x) tanh(x) asinh(x) acosh(x) atanh(x) |
双曲正弦 双曲余弦 双曲正切 反双曲正弦 反双曲余弦 反双曲正切 |
| 方法 | 说明 |
| from() |
用于将两类对转换为真正的数组:
|
| of() | 将一组对象转换为数组:Array.of( 'Hello', 'World' ); // [ 'Hello', 'World' ] |
| copyWithin() |
从数组内部替换元素: /** * 4 开始替换的索引 * 1 从什么索引开始拷贝元素 * 2 拷贝几个元素 */ [ 0, 1, 2, 3, 4, 5, 6 ].copyWithin( 4, 1,2 ) // [ 0, 1, 2, 3, 1, 5, 6 ] |
| find() | 返回第一个匹配回调的成员:[ -1, 0, 1, 2, 3 ].find( n => n > 0 ) |
| findIndex() | 返回第一个匹配回调的成员的索引,该函数与find()都接收第二个参数,绑定回调的this |
| fill() | 使用指定的值填充数组 |
| entries() |
返回数组键值对的迭代器: for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
|
| keys() | 返回索引数字的迭代器 |
| values() | 返回数组元素的迭代器 |
| includes() | 判断数组是否包含指定的元素 |
| 属性/方法 | 说明 |
| size | 返回成员总数 |
| add() | 添加一个元素,返回集合本身 |
| delete() | 删除一个元素,返回是否成功 |
| has() | 判断是否包含某个成员 |
| clear() | 清除所有成员 |
| keys() values() | 返回遍历值的迭代器,遍历顺序与插入顺序一致 |
| entries() | 返回条目的迭代器,其key/value一直 |
| forEach() | 对每个成员执行一个回调 |
| filter() | 返回通过回调的元素构成的子集 |
| 属性/方法 | 说明 |
| size | 返回成员总数 |
| set() | 设置键值对,返回Map本身 |
| get() | 根据键获得值,如果找不到key则返回undefined |
| delete() | 删除一个键,返回是否成功 |
| has() | 判断是否包含某个键 |
| clear() | 清除所有成员 |
| keys() | 返回遍历键的迭代器,遍历顺序与插入顺序一致 |
| values() | 返回遍历值的迭代器,遍历顺序与插入顺序一致 |
| entries() | 返回条目的迭代器,条目是[key,value]数组 |
| forEach() | 对每个成员执行一个回调 |
Leave a Reply