Node.js 4.0 (二)

Generator

Generator 函数有多种理解角度。从语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

执行 Generator 函数会返回一个遍历器对象,也就是说, Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

形式上, Generator 函数是一个普通函数,但是有两个特征。一是,function 命令与函数名之间有一个星号;二是,函数体内部使用 yield 语句,定义不同的内部状态( yield 语句在英语里的意思就是“产出”)。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
var hw = helloWorldGenerator();

上面代码定义了一个 Generator 函数 helloWorldGenerator ,它内部有两个 yield 语句 “hello” 和 “world” ,即该函数有三个状态:hello,world 和 return 语句(结束执行)。

然后, Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,遍历器对象(Iterator Object)。

next 方法

  调用遍历器对象的 next 方法,使得指针移向下一个状态。也就是说,每次调用 next 方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个 yield 语句(或 return 语句)为止。换言之, Generator 函数是分段执行的,yield 语句是暂停执行的标记,而 next 方法可以恢复执行。

hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }

上面代码一共调用了四次 next 方法。

  第一次调用, Generator 函数开始执行,直到遇到第一个 yield 语句为止。next 方法返回一个对象,它的 value 属性就是当前 yield 语句的值 hello,done 属性的值 false,表示遍历还没有结束。

  第二次调用,Generator 函数从上次 yield 语句停下的地方,一直执行到下一个 yield 语句。next 方法返回的对象的 value 属性就是当前 yield 语句的值 world,done 属性的值 false,表示遍历还没有结束。

  第三次调用,Generator 函数从上次 yield 语句停下的地方,一直执行到 return 语句(如果没有 return 语句,就执行到函数结束)。next 方法返回的对象的 value 属性,就是紧跟在 return 语句后面的表达式的值(如果没有 return 语句,则 value 属性的值为 undefined ), done 属性的值 true,表示遍历已经结束。

  第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的 value 属性为 undefined,done 属性为 true 。以后再调用 next 方法,返回的都是这个值。

  总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的 next 方法,就会返回一个有着 value 和 done 两个属性的对象。 value 属性表示当前的内部状态的值,是 yield 语句后面那个表达式的值; done 属性是一个布尔值,表示是否遍历结束。

yield* 语句

用来在一个 Generator 函数里面执行另一个 Generator 函数,我们需要用 yield* 语句。

如果 yield 命令后面跟的是一个遍历器对象,需要在 yield 命令后面加上星号,表明它返回的是一个遍历器对象。这被称为 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

运行结果就是使用一个遍历器,遍历了多个 Generator 函数,有递归的效果。

Promise

所谓 Promise,就是一个对象,用来传递异步操作的消息。

Promise 对象有以下两个特点。

(1)对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending (进行中)、Resolved (已完成,又称 ulfilled )和 Rejected (已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected 。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件 (Event) 完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

基本用法

ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。

下面代码创造了一个 Promise 实例。

var promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject 。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve 函数的作用是,将 Promise 对象的状态从“未完成”变为“成功”(即从 Pending 变为 Resolved ),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去; reject 函数的作用是,将 Promise 对象的状态从“未完成”变为“失败”(即从 Pending 变为 Rejected ),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise 实例生成以后,可以用 then 方法分别指定 Resolved 状态和 Reject 状态的回调函数。

promise.then(function(value) {
  // success
}, function(value) {
  // failure
});

then 方法可以接受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为 Resolved 时调用,第二个回调函数是 Promise 对象的状态变为 Reject 时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受 Promise 对象传出的值作为参数。

Promise.prototype.then() 与 catch()

因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回 promises , 所以它们可以被链式调用—一种被称为 composition 的操作.

Promise.prototype.then()

then() 方法返回一个 Promise。它有两个参数,分别为 Promise 在 success 和 failure 情况下的回调函数。

p.then(onFulfilled, onRejected);
p.then(function(value) {
   // 满足
  }, function(reason) {
  // 拒绝
});

一个 Function , 当 Promise 为 fulfilled 时调用. 该函数有一个参数, 为肯定结果 value;为 rejected 时调用. 该函数有一个参数, 为否定原因 reason 。

因为 then 方法返回一个 Promise ,你可以轻易地链式调用 then 。

var p2 = new Promise(function(resolve, reject) {
  resolve(1);
});

p2.then(function(value) {
  console.log(value); // 1
  return value + 1;
}).then(function(value) {
  console.log(value); // 2
});

Promise.prototype.catch()

catch() 方法只处理 Promise 被拒绝的情况,并返回一个 Promise 。该方法的行为和调用 Promise.prototype.then(undefined, onRejected) 相同。

语法
p.catch(onRejected);

p.catch(function(reason) {
   // 拒绝
});

onRejected 当 Promise 被拒绝时调用的 Function 。该函数调用时会传入一个参数:拒绝原因。

示例:使用catch方法
var p1 = new Promise(function(resolve, reject) {
  resolve("成功");
});

p1.then(function(value) {
  console.log(value); // "成功!"
  throw "哦,不!";
}).catch(function(e) {
  console.log(e); // "哦,不!"
});

catch 方法主要作用于处理 promise 组合。

Promise.all()

Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

var p = Promise.all([p1,p2,p3]);

  上面代码中, Promise.all 方法接受一个数组作为参数, p1、p2、p3 都是 Promise 对象的实例,如果不是,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。( romise.all 方法的参数不一定是数组,但是必须具有 iterator 接口,且返回的每个成员都是 Promise 实例。)

p 的状态由 p1、p2、p3 决定,分成两种情况。

(1)只有 p1、p2、p3 的状态都变成 fulfilled ,p 的状态才会变成 fulfilled,此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数。

(2)只要 p1、p2、p3 之中有一个被 rejected,p 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 p 的回调函数。

下面是一个具体的例子。

// 生成一个Promise对象的数组
var promise = Promise.resolve(3);
Promise.all([true, promise])
       .then(values => {
           console.log(values); // [true, 3]
        });

Promise.race()

race 函数返回一个 Promise ,这个 Promise 根据传入的 Promise 中的第一个确定状态–不管是接受还是拒绝–的状态而确定状态。

Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

var p1 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 500, "one"); 
});
var p2 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 100, "two"); 
});
Promise.race([p1, p2]).then(function(value) {
  console.log(value); // "two"
  // Both resolve, but p2 is faster
});

上面代码中,只要 p1、p2 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。

Prommise.reject()

Promise.reject(reason) 方法也会返回一个新的 Promise 实例,该实例的状态为 rejected 。 Promise.reject 方法的参数 reason ,会被传递给实例的回调函数。

使用静态的 Promise.reject() 方法

Promise.reject("Testing static reject").then(function(reason) {
  // 未被调用
}, function(reason) {
  console.log(reason); // "测试静态拒绝"
});

Promise.reject(new Error("fail")).then(function(error) {
  // 未被调用
}, function(error) {
  console.log(error); // 堆栈跟踪
});

Promise.resolve()

Promise.resolve(value) 方法返回一个以给定值 resolve 掉的 Promise 对象。但如果这个值是 thenable 的(就是说带有 then 方法),返回的 promise 会“追随”这个 thenable 的对象,接收它的最终状态(指 resolved/rejected/pendding/settled );否则这个被返回的 promise 对象会以这个值被 fulfilled 。

语法
Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(thenable);
value 用来 resolve 待返回的 promise 对象的参数。既可以是一个 Promise 对象也可以是一个 thenable 。

静态方法 Promise.resolve 返回一个 Promise 对象,这个 Promise 对象是被 resolve 的。

示例
使用静态方法 Promise.resolve

Promise.resolve("Success").then(function(value) {
  console.log(value); // "Success"
}, function(value) {
  // not called
});

以一个数组进行 resolve

var p = Promise.resolve([1,2,3]);
p.then(function(v) {
  console.log(v[0]); // 1
});

resolve 另一个 Promise 对象

var original = Promise.resolve(true);
var cast = Promise.resolve(original);
cast.then(function(v) {
  console.log(v); // true
});

Symbol

  ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值。它是JavaScript 语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

  Symbol 值通过 Symbol 函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

let s = Symbol();
typeof s
// "symbol"

  注意, Symbol 函数前不能使用 new 命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。

Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

Symbol 函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol 函数的返回值是不相等的。

var s1 = Symbol("foo");
var s2 = Symbol("foo");
console.log(s1 === s2); // false

Symbol 值不能与其他类型的值进行运算,会报错。

但是,Symbol 值可以显式转为字符串。

symbol 属性名

 由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。

var mySymbol = Symbol();
// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
var a = {
  [mySymbol]: 'Hello!'
};
// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
console.log(a[mySymbol]); // "Hello!"

上面代码通过方括号结构和 Object.defineProperty ,将对象的属性名指定为一个 Symbol 值。

Symbol 值作为对象属性名时,不能用点运算符。

在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。

let s = Symbol();
let obj = {
  [s](arg) { ... }
};

Symbol 类型还可以用于定义一组常量,保证这组常量的值都是不相等的。

Symbol 值作为属性名时,该属性还是公开属性,不是私有属性。

属性名遍历

Symbol 作为属性名,该属性不会出现在 for…in、for…of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames() 返回。但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols 方法,可以获取指定对象的所有 Symbol 属性名。

Object.getOwnPropertySymbols 方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

var obj = {};
var a = Symbol('a');
var b = Symbol('b');

obj[a] = 'Hello';
obj[b] = 'World';
var objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols);
// [Symbol(a), Symbol(b)]

Symbol.for()

使用给定的 key 搜索现有符号,如果找到则返回符号。否则将得到一个新的使用给定的 key 在全局符号注册表中创建的符号。

  有时,我们希望重新使用同一个 Symbol 值,Symbol.for 方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。

var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');
console.log(s1 === s2); // true

上面代码中,s1 和 s2 都是 Symbol 值,但是它们都是同样参数的 Symbol.for 方法生成的,所以实际上是同一个值。

  Symbol.for() 与 Symbol() 这两种写法,都会生成新的 Symbol 。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。 Symbol.for() 不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的 key 是否已经存在,如果不存在才会新建一个值。比如,如果你调用 Symbol.for(“cat”) 30 次,每次都会返回同一个 Symbol 值,但是调用 Symbol(“cat”) 30 次,会返回 30 个不同的 Symbol 值。

Symbol.for("bar") === Symbol.for("bar")
// true
Symbol("bar") === Symbol("bar")
// false

上面代码中,由于 Symbol() 写法没有登记机制,所以每次调用都会返回一个不同的值。

Symbol.keyFor()

为给定符号从全局符号注册表中检索一个共享符号键。

Symbol.keyFor 方法返回一个已登记的 Symbol 类型值的 key。

var s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

var s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

上面代码中,变量 s2 属于未登记的 Symbol 值,所以返回 undefined

箭头函数

箭头函数就是个是简写形式的函数表达式,并且它拥有词法作用域的 this 值.箭头函数总是匿名的.

ES6允许使用“箭头”(=>)定义函数。

var f = v => v;

//等同于:

var f = function(v) {
  return v;
};

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

var f = () => 5;
// 等同于
var f = function (){ return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用 return 语句返回。

var sum = (num1, num2) => { return num1 + num2; }

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。

var getTempItem = id => ({ id: id, name: "Temp" });

箭头函数的一个用处是简化回调函数。

// 正常函数写法
[1,2,3].map(function (x) {
  return x * x;
});

// 箭头函数写法
[1,2,3].map(x => x * x);

另一个例子是

// 正常函数写法
var result = values.sort(function(a, b) {
  return a - b;
});

// 箭头函数写法
var result = values.sort((a, b) => a - b);

如果不用箭头函数,可能就要占用多行,而且还不如现在这样写醒目。

使用注意点

(1)函数体内的 this 对象,绑定定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 Rest 参数代替。

(4)不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。

this 对象的指向是可变的,但是在箭头函数中,它是固定的。

function Timer () {
  this.seconds = 0
  setInterval(() => this.seconds++, 1000)
}
var timer = new Timer()
setTimeout(() => console.log(timer.seconds), 3100)
// 3

上面代码中,Timer 函数内部的 setInterval- 调用了 _this.seconds- 属性,通过箭头函数将 _this 绑定在 Timer 的实例对象。否则,输出结果是 0 ,而不是 3 。

模板字符串

模板字符串允许嵌入表达式,并且支持多行字符串和字符串插补特性。

  模板字符串使用反引号 ( ) 来代替普通字符串中的用双引号和单引号。模板字符串可以包含特定语法 (${expression}) 的占位符。占位符中的表达式和周围的文本会一起传递给一个默认函数,该函数负责将所有的部分连接起来,如果一个模板字符串由表达式开头,则该字符串被称为带标签的模板字符串,该表达式通常是一个函数,它会在模板字符串处理后被调用,在输出最终结果前,你都可以在通过该函数对模板字符串来进行操作处理。

语法
string text

`string text line 1
 string text line 2`

`string text ${expression} string text`

tag `string text ${expression} string text`

多行字符串

在新行中插入的任何字符都是模板字符串中的一部分,使用普通字符串,你可以通过以下的方式获得多行字符串:

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("Fifteen is " + (a + b) + " and\nnot " + (2 * a + b) + ".");
// "Fifteen is 15 and
// not 20."

现在通过模板字符串,我们可以使用一种更优雅的方式来表示:

var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and\nnot ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."

带标签的模板字符串

模板字符串的一种更高级的形式称为带标签的模板字符串。它允许您通过标签函数修改模板字符串的输出。标签函数的第一个参数是一个包含了字符串字面值的数组(在本例中分别为“Hello”和“world”);第二个参数,在第一个参数后的每一个参数,都是已经被处理好的替换表达式(在这里分别为“15”和“50”)。 最后,标签函数返回处理好的字符串。在后面的示例中,标签函数的名称可以为任意的合法标示符。

var a = 5;
var b = 10;

function tag(strings) {
  console.log(arguments);    // { '0': [ 'Hello ', ' world ', '' ], '1': 15, '2': 50 }
  console.log(strings[0]);   // "Hello "
  console.log(strings[1]);   // " world "
  console.log(arguments[1]);  // 15
  console.log(arguments[2]);  // 50

  return "Hubwiz!";
}

console.log(tag`Hello ${ a + b } world ${ a * b}`);
// "Hubwiz!"

我们打印出arguments可以看到 处理的参数为

{ '0': [ 'Hello ', ' world ', '' ], '1': 15, '2': 50 }

我们已有的Hello和world参数放到一个数组中,后续处理的参数依次生成。

原始字符串

在标签函数的第一个参数中,存在一个特殊的属性 raw ,我们可以通过它来访问模板字符串的原始字符串。

function tag(strings, values) {
  console.log(strings.raw[0]); 
  // "string text line 1 \\n string text line 2"
}

tag`string text line 1 \n string text line 2`;

另外,使用 String.raw() 方法创建原始字符串和使用默认模板函数和字符串连接创建是一样的。

String.raw`Hi\n${2+3}!`;
// "Hi\\n5!"

安全性
由于模板字符串能够访问变量和函数,因此不能由不受信任的用户来构造。

"use strict"
let a = 10;
console.warn(`${a+=20}`); // "30"
console.warn(a); // 30

字符串扩展

字符的 Unicode 表示法

JavaScript 允许采用 \uxxxx 形式表示一个字符,其中 “xxxx” 表示字符的码点。

"\u0061"   // "a"

但是,这种表示法只限于 \u0000——\uFFFF 之间的字符。超出这个范围的字符,必须用两个双字节的形式表达。

"\uD842\uDFB7"  // "𠮷"
"\u20BB7"  // " 7"

  上面代码表示,如果直接在 “\u” 后面跟上超过 0xFFFF 的数值(比如 \u20BB7 ), JavaScript 会理解成 “\u20BB+7” 。由于 \u20BB 是一个不可打印字符,所以只会显示一个空格,后面跟着一个 7 。

ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。

"\u{20BB7}"
// "𠮷"
"\u{41}\u{42}\u{43}"
// "ABC"
let hello = 123;
hell\u{6F} // 123
'\u{1F680}' === '\uD83D\uDE80'
// true

上面代码中,最后一个例子表明,大括号表示法与四字节的 UTF-16 编码是等价的。

有了这种表示法之后, JavaScript 共有 6 种方法可以表示一个字符。

'\z' === 'z'  // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

codePointAt() 字节

codePointAt 方法,能够正确处理 4 个字节储存的字符,返回一个字符的码点。

var s = "𠮷a";

s.codePointAt(0) // 134071
s.codePointAt(1) // 57271

s.charCodeAt(2) // 97

  codePointAt 方法的参数,是字符在字符串中的位置(从 0 开始)。上面代码中, JavaScript 将 “𠮷a” 视为三个字符, codePointAt 方法在第一个字符上,正确地识别了 “𠮷”,返回了它的十进制码点 134071 (即十六进制的 20BB7 )。在第二个字符(即 “𠮷” 的后两个字节)和第三个字符 “a” 上, codePointAt 方法的结果与 charCodeAt 方法相同。

  总之, codePointAt 方法会正确返回四字节的 UTF-16 字符的码点。对于那些两个字节储存的常规字符,它的返回结果与 charCodeAt 方法相同。

codePointAt 方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。

function is32Bit(c) {
  return c.codePointAt(0) > 0xFFFF;
}
is32Bit("𠮷") // true
is32Bit("a") // false

String.fromCodePoint()

 ES5 提供 String.fromCharCode 方法,用于从码点返回对应字符,但是这个方法不能识别辅助平面的字符(编号大于 0xFFFF )。

String.fromCharCode(0x20BB7)
// "ஷ"

上面代码中, String.fromCharCode 不能识别大于 0xFFFF 的码点,所以 0x20BB7 就发生了溢出,最高位 2 被舍弃了,最后返回码点 U+0BB7 对应的字符,而不是码点 U+20BB7 对应的字符。

  ES6 提供了 String.fromCodePoint 方法,可以识别 0xFFFF 的字符,弥补了 String.fromCharCode 方法的不足。在作用上,正好与 codePointAt 方法相反。

String.fromCodePoint(0x20BB7)
// "𠮷"

注意,fromCodePoint 方法定义在 String 对象上,而 codePointAt 方法定义在字符串的实例对象上。

includes(), startsWidth(), endsWidth()

传统上,JavaScript 只有 indexOf 方法,可以用来确定一个字符串是否包含在另一个字符串中。

ES6又提供了三种新方法。

includes():返回布尔值,表示是否找到了参数字符串。

startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。

endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。

var s = 'Hello world!';

s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true

这三个方法都支持第二个参数,表示开始搜索的位置。

var s = 'Hello world!';

s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false

上面代码表示,使用第二个参数 n 时, endsWith 的行为与其他两个方法有所不同。它针对前 n 个字符,而其他两个方法针对从第 n 个位置直到字符串结束。

repeat() 重复次数

repeat 方法返回一个新字符串,表示将原字符串重复 n 次。

'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""

参数如果是小数,会被取整。

'na'.repeat(2.9) // "nana"

如果 repeat 的参数是负数或者 Infinity ,会报错。

'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError

但是,如果参数是 0 到 -1 之间的小数,则等同于 0 ,这是因为会先进行取整运算。0 到 -1 之间的小数,取整以后等于 -0 , repeat 视同为 0 。

'na'.repeat(-0.9) // ""

参数 NaN 等同于 0 。

'na'.repeat(NaN) // ""

如果 repeat 的参数是字符串,则会先转换成数字。

'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"