JavaScript ES6 新增特性整理 - 2.对ES5内置对象的扩展

 2016年09月20日    121     声明


在 ES6 语言标准中,对一些原有对象进行了规范和功能扩展。如:对 ArrayStringMath等对象添加了更多的类方法和实例方法,使对象功能更加强大;而RegExp对象不仅增加了更多的匹配模式,还将String对象的一些模式匹配方法通过通用RegExp对象方法实现,使语言更加规范;还对ObjectFunction扩展了一些定义和使用方式,让使用更加方便。

  1. 对数组(Array)对象的扩展
  2. 对字符串(String)对象的扩展
  3. Math对象新增的函数
  4. Number对象的扩展
  5. RegExp对象的扩展
  6. Object对象的扩展
  7. 函数(Function)的扩展

1. 对数组(Array)对象的扩展

Array对象在 ES6 中扩展了两个类方法,还增加了一些实例方法。除些之外,还实现了Symbol.iterator协议,用于支持for…of循环。

1.1 类方法

  • Array.of() - 将传入参数构建为一个数组并返回
  • Array.from() - 将类数组对象或可遍历对象转换为数组并返回

Array.of()的使用:

Array.of(1);         // [1]
Array.of(1, 2, 3);   // [1, 2, 3]
Array.of(undefined); // [undefined]

Array.from() 的使用:

// 将类数组对象(arguments)转换成数组
(function () {
    var args = Array.from(arguments);
    return args;
})(1, 2, 3);                        // [1, 2, 3]

// 将可迭代对象(Set)转换成数组
Array.from(new Set(["foo", window]));  // ["foo", window]

// Map 对象转换成数组
var m = new Map([[1, 2], [2, 4], [4, 8]]);
Array.from(m);                  // [[1, 2], [2, 4], [4, 8]]  

// 字符串对象既是类数组又是可迭代对象
Array.from("foo");              // ["f", "o", "o"]

// 使用 map 函数转换数组元素
Array.from([1, 2, 3], x => x + x);   // [2, 4, 6]

// 生成一个数字序列
Array.from({length:5}, (v, k) => k); // [0, 1, 2, 3, 4]

1.2 实例方法

  • Array.prototype.fill() - 将指定区间的元素全部替换为某一元素
  • Array.prototype.find() - 按条件(传入方法)查找数组中是否有某一元素,有则返回其值,无则返回undefined
  • Array.prototype.findIndex() - 按条件(传入方法)查找数组中是否有某一元素,有则返回其索引,无则返回-1
  • Array.prototype.entries() - 返回一个Array Iterator对象,该对象是一个包含数组中每个索引的键值对
  • Array.prototype.keys() - 返回一个数组索引的迭代器
  • Array.prototype.copyWithin() - 拷贝数组的部分元素到同一数组的另一位置,并返回该数组
// fill() 方法
[1, 2, 3].fill(4)            // [4, 4, 4]
[1, 2, 3].fill(4, 1)         // [1, 4, 4]
[1, 2, 3].fill(4, 1, 2)      // [1, 4, 3]

// find() 方法
var inventory = [
  {name: 'apples', quantity: 2},
  {name: 'bananas', quantity: 0},
  {name: 'cherries', quantity: 5}
];

function findCherries(fruit) { 
  return fruit.name === 'cherries';
}

console.log(inventory.find(findCherries)); // { name: 'cherries', quantity: 5 }

// findIndex() 方法
function isPrime(element, index, array) {
  var start = 2;
  while (start <= Math.sqrt(element)) {
    if (element % start++ < 1) return false;
  }
  return (element > 1);
}

console.log( [4, 6, 8, 12].findIndex(isPrime) ); // -1, 没找到质数元素
console.log( [4, 6, 7, 12].findIndex(isPrime) ); // 2

// entries() 方法
var arr = ["a", "b", "c"];
var eArr = arr.entries();

console.log(eArr.next().value); // [0, "a"]
console.log(eArr.next().value); // [1, "b"]
console.log(eArr.next().value); // [2, "c"]

// keys() 方法
var arr = ["a", "b"];
var iterator = arr.keys();

console.log(iterator.next()); // { value: 0, done: false }
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

// copyWithin() 方法
[1, 2, 3, 4, 5].copyWithin(-2);
// [1, 2, 3, 1, 2]
[1, 2, 3, 4, 5].copyWithin(0, 3);
// [4, 5, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(0, 3, 4);
// [4, 2, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(0, -2, -1); 
// [4, 2, 3, 4, 5]


2. 对字符串(String)对象的扩展

ES6 中增加了一些新的字符串特性,如:模板字符串、多行字符串和字符串插补特性。除这些特性外,String对象本身也增加了一些方法。

2.1 新增字符串特性

模板字符串

模板字符串使用``号代替普通字符串的单引号和双引号,模板字符串除了具有普通字符串的功能外,还支持多行字符串、表达式插补、标签函数等功能。

多行字符串

// 在普通字符串,像下面这样使用多行字符串
console.log("string text line 1\n"+
"string text line 2");
// "string text line 1
// string text line 2"

// 而在模板字符串中
console.log(`string text line 1
string text line 2`);
// "string text line 1
// string text line 2"

表达式插补

// 在普通字符串使用表达式
var a = 5;
var b = 10;
console.log("a and b is " + (a + b));

// 而在模板字符串中可以直接插入表达式
console.log(`a and b is ${a + b}");

标签模板字符串

标签模板字符串是模板字符串的一种高级用法,它允许你通过标签函数修改模板字符串的输出:

var a = 5;
var b = 10;

function tag(strings, ...values) {
  console.log(strings[0]); // "Hello "
  console.log(strings[1]); // " world "
  console.log(values[0]);  // 15
  console.log(values[1]);  // 50

  return "itbilu.com";
}

tag`Hello ${ a + b } world ${ a * b}`;
// "itbilu.com"

模板字符串的详细介绍,请参考:ES6 模板字符串


Unicode转义序列

在ES6之前,我们可以使用字符串字面量或十六进制转义序列来表示字符串中的字符:

// 使用字面量
'foo'
"bar"

// 使用十六进制转义序列
'\xA9' // "©"

在ES6 中增加了Unicode转义序列表示方式。Unicode转义序列在\u之后接四个字符(0000FFFF)用于表示一个字节,每个字符串字符用一个或两个字节来表示。

'\u00A9' // "©"


2.2 新增String类方法

String类在ES6中扩展了两个方法:fromCodePoint()raw(),这两个方法作用如下:

  • String.fromCodePoint() - 通过指定Unicode码,返回原始字符串
  • String.raw() - 标签函数,用于返回模板字符串的原始字面量值
// fromCodePoint 方法的使用
String.fromCodePoint(42);       // "*"
String.fromCodePoint(65, 90);   // "AZ"

String.fromCodePoint('_');      // RangeError
String.fromCodePoint(Infinity); // RangeError

// raw 方法的使用
let name = "Bob";
String.raw `Hi\n${name}!`;   // "Hi\\nBob!"

String.raw({raw: "test"}, 0, 1, 2); // "t0e1s2t" 


2.3 新增String实例方法

ES6 还扩展了一些String实例方法,这些方法在所有字符串实例中使用。

  • String.prototype.codePointAt() - 返回指定位置字符的Unicode值
  • String.prototype.startsWith(),String.prototype.endsWith() - 判断字符串是否以指定的子字符串开头/结尾
  • String.prototype.includes() - 判断字符串中是否包含指定的子字符串
  • String.prototype.repeat() - 将字符串重复指定次数后返回
// codePointAt 的使用
'itbilu.com'.codePointAt(1);  // 116

// startsWith,endsWith 的使用
var str = "To be, or not to be, that is the question.";
str.startsWith("To be");  // true
str.startsWith("not to be");  // false
str.endsWith('question.');  //true

// includes 的使用
str.includes('To be');  // true

// repeat 的使用
"abc".repeat(2);  // abcabc


3. Math对象新增的函数

Math对象在JavaScript中被实现为一个静态类,该类主要提供数学中相关方法。

在ES6 中,Math对象增加了以下方法:

数字计算相关

  • Math.imul() - 乘法运算
  • Math.clz32() - 整数转为32位无符号形式的二进制后,开头的0的个数
  • Math.fround() - 将任意的数字转换为离它最近的单精度浮点数
  • Math.sign() - 判断数字的符号,正数、负数或零
  • Math.cbrt() - 计算立方根
  • Math.trunc() - 数字的小数部分去掉,只留整数部分
  • Math.hypot() - 返回它的所有参数的平方和的平方根
// imul 的使用
Math.imul(3, 4); // 12

// clz32 的使用
Math.clz32(1);   // 31
Math.clz32(1000);  // 22 

// fround 的使用
Math.fround(0);     // 0
Math.fround(1);     // 1
Math.fround(1.337); // 1.3370000123977661

// sign 的使用
Math.sign(3)     //  1
Math.sign(-3)    // -1
Math.sign("-3")  // -1
Math.sign(0)     //  0
Math.sign(-0)    // -0

// cbrt 的使用
Math.cbrt(27)    // 3
Math.cbrt("27")  // 3
Math.cbrt("-27") // -3

// trunc 的使用
Math.trunc(42.84)    // 42
Math.trunc(0.123)    //  0
Math.trunc(-0.123)   // -0

// hypot 的使用
Math.hypot(3, 4)        // 5
Math.hypot(3, 4, 5)     // 7.0710678118654755
Math.hypot()            // 0


数学函数

  • Math.log10() - 返回一个数字以 10 为底的对数
  • Math.log10() - 返回一个数字以 10 为底的对数
  • Math.log1p() - 返回一个数字加1后的自然对数(底为E), 既log(x+1)
  • Math.expm1() - 返回Ex-1,其中x为函数参数
// log10 的使用
Math.log10(10)   // 1
Math.log10(100)  // 2
Math.log10("100")// 2

// log2 的使用
Math.log2(2)     // 1
Math.log2(1024)  // 10
Math.log2(1)     // 0

// log1p 的使用
Math.log1p(Math.E-1)  // 1
Math.log1p(0)         // 0
Math.log1p("0")       // 0

// expm1 的使用
Math.expm1(1)     // 1.7182818284590453
Math.expm1(-38)   // -1
Math.expm1("-38") // -1


三角函数

  • Math.sinh() - 返回指定数值(角度)的双曲正弦函数
  • Math.cosh() - 返回指定数值(角度)的双曲余弦函数
  • Math.tanh() - 返回指定数值(角度)的双曲正切函数
  • Math.asinh() - 返回指定数值(角度)双曲正弦值
  • Math.acosh() - 返回指定数值(角度)双曲余弦值
  • Math.atanh() - 返回指定数值(角度)双曲正切值
// sinh 的使用
Math.sinh(0)      // 0
Math.sinh(1)      // 1.1752011936438014
Math.sinh("-1")   // -1.1752011936438014

// cosh 的使用
Math.cosh(0);  // 1
Math.cosh(1);  // 1.5430806348152437
Math.cosh(-1); // 1.5430806348152437

// tanh 的使用
Math.tanh(0);        // 0
Math.tanh(Infinity); // 1
Math.tanh(1);        // 0.7615941559557649

// asinh 的使用
Math.asinh(1);  // 0.881373587019543
Math.asinh(0);  // 0

// acosh 的使用
Math.acosh(-1); // NaN
Math.acosh(0);  // NaN
Math.acosh(0.5) // NaN
Math.acosh(1);  // 0

// atanh 的使用
Math.atanh(-2);  // NaN
Math.atanh(-1);  // -Infinity
Math.atanh(0);   // 0
Math.atanh(0.5); // 0.5493061443340548


4. Number对象的扩展

Number是经过封装的用于处理数字值的对象。在ES6 中,将一些原本用于数字处理的全局对象中的方法扩展到了Number对象中,如:isNaN()parseInt()parseFloat()。此外,还向Number添加了一些属性:


4.1 对象方法

  • Number.isNaN() - 检测传入的值是否是NaN,该方法比全局函数 isNaN()更可靠
  • Number.isFinite() - 检测传入的值是否是一个有穷数
  • Number.isInteger() - 检测传入的值是否是一个整数
  • Number.parseInt() - 将传入的字符串参数转换为整数,该方法与全局对象中的方法parseInt()相同
  • Number.parseFloat() - 将传入的字符串参数转换为浮点数,该方法与全局对象中的方法parseFloat()相同
  • Number.isSafeInteger() - 检测传入的值是否是一个安全的整数(位于253-1) 和 253-1 之间的整数)
// isNaN 的使用
Number.isNaN(NaN);        // true
Number.isNaN(Number.NaN); // true
Number.isNaN(0 / 0)       // true

// isFinite 的使用
Number.isFinite(Infinity);  // false
Number.isFinite(NaN);       // false
Number.isFinite(0);         // true
Number.isFinite(2e64);      // true

// isInteger 的使用
Number.isInteger(0);         // true
Number.isInteger(1);         // true
Number.isInteger(-100000);   // true
Number.isInteger(0.1);       // false
Number.isInteger(Math.PI);   // false

// isSafeInteger 的使用
Number.isSafeInteger(3);             // true
Number.isSafeInteger(Math.pow(2, 53));  // false
Number.isSafeInteger(Math.pow(2, 53) - 1); // true


4.2 对象属性

Number对象中扩展的类方法相对应,Number对象中还扩展了3个辅助属性,这些属性同样是静态属性,不需要实例化对象即可使用。

  • Number.EPSILON - 代表了不同的两个Number之间的最小的差,其值接近于2.2204460492503130808472633361816E-16或2-52
  • Number.MAX_SAFE_INTEGER - JavaScript中最小的安全整数(253-1)
  • Number.MIN_SAFE_INTEGER - JavaScript中最小的安全整数(-253-1)


5. RegExp对象的扩展

RegExp是一个构造函数,用于创建一个正则表达式对象,创建的对象可以根据特定的匹配模式进行文本匹配。

5.1 构造函数

在ES5中,RegExp构造函数有以下两种使用方式:

// 1. 第一个参数为字符串,第二个参数是匹配模式
var regex = new RegExp('xyz', 'i');

// 2. 参数是一个正则表示式
var regex = new RegExp(/xyz/i);

而使用以下格式时,会报错:

var regex = new RegExp(/xyz/, 'i');

ES6 对RegExp构造函数进行了扩展,当第一个参数为正则表达式而第二个标志参数存在时(如上例),new RegExp(/ab+c/, 'i')不再抛出TypeError异常。


5.2 字符串实例方法

在字符串对象中,有4个可以使用正则表达式的方法:match()replace()search()split()。ES6将这4个方法进行了规范,在语言内部会调用RegExp对象上的方法:

  • String.prototype.match调用RegExp.prototype[Symbol.match]
  • String.prototype.replace调用RegExp.prototype[Symbol.replace]
  • String.prototype.search调用RegExp.prototype[Symbol.search]
  • String.prototype.split调用RegExp.prototype[Symbol.split]


5.3 y修饰符与sticky属性

y修饰符是ES6 中新增的一种匹配模式,其与g匹配模式类似,但其下一次匹配是从上一次匹配成功的下一位置(lastIndex)开始。

RegExp.prototype.sticky属性是与y匹配模式相对应的一个属性,用于检测是否是粘滞匹配模式。

var text = "First line\nsecond line";
var regex = /(\S+) line\n?/y;

var match = regex.exec(text);
console.log(match[1]);         // "First"
console.log(regex.lastIndex);  // 11
console.log(regex.sticky);     // true


5.4 u修饰符

u同样是ES6 中新增的一种匹配模式,用于识别Unicode字符。

var text = "Образец text на русском языке";
var regex = /[\u0400-\u04FF]+/g;

var match = regex.exec(text);
console.log(match[1]);  // prints "Образец"
console.log(regex.lastIndex);  // prints "7"


6. Object对象的扩展

6.1 标准化__proto__属性

ES6之前,由于执引擎的不同,__proto__属性可能存在或不存在。ES6 进行了规范化,将__proto__属性做为一个对象的内部属性,该属性与[[Prototype]]指向同一个值(其构造函数的prototype属性),通常是对象的原型。

// 声明一个函数作为构造函数
function Employee() {
  /* 初始化实例 */
}

// 创建一个Employee实例
var fred = new Employee();

// 测试相等性
fred.__proto__ === Employee.prototype; // true


6.2 类方法

ES6 还对Object对象增加了以下类方法:

  • Object.is() - 比较两个对象是否相同
  • Object.setPrototypeOf() - 设置对象的原型
  • Object.assign() - 拷贝多个源对象的可枚举属性到目标对象并返回目标对象
  • Object.getOwnPropertySymbols() - 以数组的形式返回对象自身所有的symbol属性键
// is 方法的使用
Object.is('foo', 'foo');     // true
Object.is(window, window);   // true

Object.is('foo', 'bar');     // false
Object.is([], []);           // false

var test = { a: 1 };
Object.is(test, test);       // true

// 将对象原型设置为 null
Object.setPrototypeOf({}, null);

// assign 方法的使用
var obj = { a: 1 };
var copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }

// getOwnPropertySymbols 方法的使用
var obj = {};
var a = Symbol("a");
var b = Symbol.for("b");

obj[a] = "localSymbol";
obj[b] = "globalSymbol";

var objectSymbols = Object.getOwnPropertySymbols(obj);

console.log(objectSymbols.length); // 2
console.log(objectSymbols)         // [Symbol(a), Symbol(b)]
console.log(objectSymbols[0])      // Symbol(a)


6.3 其它扩展

简写

ES6 提供了更简洁的对象赋值方式:

var foo = 'bar';
var baz = {foo};
baz // {foo: "bar"}

// 等同于
var baz = {foo: foo};

简写方式同样适用在函数中的对象赋值:

function f(x, y) {
  return {x, y};
}

f(1, 2) // {x: 1, y: 2}

// 等同于
function f(x, y) {
  return {x: x, y: y};
}

ES6 还提供了更多对象简写方式:

var birth = '1986/08/11';

var Person = {
  name: '张三',
  birth, //等同于birth: birth
  // 等同于hello: function ()...
  hello() { console.log('我的名字是', this.name); }
};

属性名表达式

在ES6 之前,我们可以像下面这样为对象添加了一个属性:

var obj = {};
// 方法一
obj['foo'] = '一个属性';
// 方法二
obj['b'+'ar'] = '另一个属性';

而使用字面量的方式定义对象,只能使用第一种方法:

var obj = {
  foo: '一个属性',
  bar: '另一个属性'
};

ES6 对属性名定义进行了扩展,支持表达式的写法,在定义时将属性名表达式放在方括号内即可:

var fooKey = 'foo';
var obj = {
  [fooKey]: '一个属性',
  ['b'+'ar']: '另一个属性'
};


7. 函数(Function)的扩展

ES6 对函数参数进行了扩展,可以支持剩余参数默认值参数。除了对函数参数的扩展外,ES6 中还新增了两类函数:箭头函数生成器函数

7.1 剩余参数

剩余(reset)参数是将函数的最后一个参数以...参数名的形式提供。在函数调用时,剩余参数会成为一个数组,数组中的元素都是传递给该函数的剩余的参数值。

function f(a, b, ...theArgs) {
  console.log(theArgs);  // [3, 4, 5]
}

f(1, 2, 3, 4, 5);

注意:剩余参数只能做为最后一个参数提供,后面不能再有其它参数

剩余参数与arguments对象有以下区别:

  • 剩余参数只包含没有提供形参的实参;而arguments包含了提供给函数的所有实参
  • arguments不是一个真正的数组;而剩余参数是一个Array对象


7.2 默认值参数

默认值参数又称做默认函数参数,是指如果一个形参没有传入对应的实参或者传入了undefined,则该形参会被赋予一个默认值。

在JavaScript中,参数的默认值是undefined。有时,我们可能需要设置一个不同的默认值,这时可以使用默认值参数。

如,在下面的乘法计算中,如果参数b未提供,那么其值是undefined,直接进行乘法运算的值会是NaN。所以需要对b的值进行判断,并将其值赋为1

function multiply(a, b) {
  b = typeof b !== 'undefined' ?  b : 1;

  return a*b;
}

multiply(5); // 5

而如果使用默认值参数,我们就可以省去这些判断:

function multiply(a, b = 1) {
  return a*b;
}

multiply(5); // 5

默认值参数同样可以使用解构赋值

function f([x, y] = [1, 2], {z: z} = {z: 3}) { 
  return x + y + z; 
}

f(); // 6


7.3 箭头函数

箭头函数(Arrow function)是一个简写形式的函数表达式,它使用箭头(=>)来定义函数。箭头函数拥有词法作用域的this值,也就是说它不会新产生自己作用域下的thisargumentssupernew.target等对象。另外,箭头函数总是匿名的,也就不能用做构造函数。

箭头函数书写简单,可以使语法更为简洁:

var a = [
  "itbilu.com",
  "nodejs.org",
  "cnodejs.org"
];

var a2 = a.map(function(s){ return s.length });

// 使用箭头函数代码量会相对减少
var a3 = a.map( s => s.length );

更多关于箭头函数的介绍请参考:ES6 中的箭头函数


7.4 生成器函数

function*(function关键字后面跟一个*号)用于声明一个生成器(generator)函数,除定义方式不同外其内部还要使用yield关键字来定义函数状态,该函数会返回一个Generator对象。

除了使用function*表达式定义generator函数外,还可以GeneratorFunction构造函数定义。

生成器函数相当于一个状态机,是一种可以从执行中退出,并在之后重新进入的函数。生成器的环境(绑定的变量)会在每次执行后被保存,下次进入时可继续使用。

function* idMaker(){
  var index = 0;
  while(index < 3)
    yield index++;
}

var gen = idMaker();

console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next().value); // undefined
// ...

yield表达式与执行状态控制

调用一个生成器函数时其主体并不会马上执行,而是会返回一个迭代器(iterator)对象。当迭代器的next()方法被调用时,生成器函数的会被执行至第一个yield表达式。

每个next()方法会返回一个对象,该对象会包含以下两个属性:

  • done - 布尔值,表示是否已全部执行过完成
  • value - yield表达式返回的值
 // next() 方法返回的对象
{ value: 2, 
  done: false 
}

yield*表达式与对象遍历

除了使用yield定义执行状态外,还可以使用yield*将执行状态委派至一个可迭代对象,或另一个生成器函数。yield*会将对其后的对象进行遍历,每调用next()方法一次会返回对象中的一个元素。

通过yield*将执行状态委派至一个可迭代对象:

function * gen(){
  yield* ["a", "b", "c"];
}

var g = gen();

g.next();  // { value:"a", done:false }
g.next();  // { value:"b", done:false }
g.next();  // { value:"c", done:false }

通过yield*将执行状态委派至另一个生成器函数:

function* anotherGenerator(i) {
  yield i + 1;
  yield i + 2;
  yield i + 3;
}

function* generator(i){
  yield i;
  yield* anotherGenerator(i);
  yield i + 10;
}

var gen = generator(10);

console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20