ES6學(xué)習(xí)筆記

  • 前言:這是學(xué)習(xí)阮一峰老師的《ECMAScript6 入門》所做的筆記。開源書籍鏈接地址http://es6.ruanyifeng.com

let 和 const 命令

let

類似于var怖喻,用來聲明變量,但是所聲明的變量喧兄,只在let命令所在的代碼塊內(nèi)有效勋桶。

{
  let a = 10;
  var b = 1;
}

a // ReferenceError: a is not defined.
b // 1

for循環(huán)的計數(shù)器,就很合適使用let命令露乏。

for (let i = 0; i < 10; i++) {
  // ...
}

let不存在變量提升

// var 的情況
console.log(foo); // 輸出undefined
var foo = 2;

// let 的情況
console.log(bar); // 報錯ReferenceError
let bar = 2;

只要塊級作用域內(nèi)存在let命令车海,它所聲明的變量就“綁定”(binding)這個區(qū)域笛园,不再受外部的影響。(暫時性死區(qū))

var tmp = 123;

if (true) {
  tmp = 'abc'; // 報錯ReferenceError
  let tmp;
}

示例:

if (true) {
  // TDZ開始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ結(jié)束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}

不允許重復(fù)聲明

// 報錯
function () {
  let a = 10;
  var a = 1;
}

// 報錯
function () {
  let a = 10;
  let a = 1;
}

ES6 的塊級作用域

let實(shí)際上為 JavaScript 新增了塊級作用域侍芝。

function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}

上面的函數(shù)有兩個代碼塊研铆,都聲明了變量n,運(yùn)行后輸出5竭贩。這表示外層代碼塊不受內(nèi)層代碼塊的影響蚜印。如果兩次都使用var定義變量n,最后輸出的值才是10留量。

ES6 允許塊級作用域的任意嵌套窄赋。
{{{{{let insane = 'Hello World'}}}}};

上面代碼使用了一個五層的塊級作用域。外層作用域無法讀取內(nèi)層作用域的變量楼熄。

{{{{
  {let insane = 'Hello World'}
  console.log(insane); // 報錯
}}}};

內(nèi)層作用域可以定義外層作用域的同名變量忆绰。

{{{{
  let insane = 'Hello World';
  {let insane = 'Hello World'}
}}}};

塊級作用域的出現(xiàn),實(shí)際上使得獲得廣泛應(yīng)用的立即執(zhí)行函數(shù)表達(dá)式(IIFE)不再必要了可岂。

// IIFE 寫法
(function () {
  var tmp = ...;
  ...
}());

// 塊級作用域?qū)懛?{
  let tmp = ...;
  ...
}

const

const聲明一個只讀的常量错敢。一旦聲明,常量的值就不能改變缕粹。

const PI = 3.1415;
PI // 3.1415

PI = 3; // 報錯TypeError: Assignment to constant variable.

const聲明的變量不得改變值稚茅,這意味著,const一旦聲明變量平斩,就必須立即初始化亚享,不能留到以后賦值。
const foo; 報錯// SyntaxError: Missing initializer in const declaration

const的作用域與let命令相同:只在聲明所在的塊級作用域內(nèi)有效绘面。

if (true) {
  const MAX = 5;
}

MAX // 報錯Uncaught ReferenceError: MAX is not defined

const命令聲明的常量也是不提升欺税,同樣存在暫時性死區(qū),只能在聲明的位置后面使用揭璃。

if (true) {
  console.log(MAX); // 報錯ReferenceError
  const MAX = 5;
}

const聲明的常量晚凿,也與let一樣不可重復(fù)聲明。

var message = "Hello!";
let age = 25;

// 以下兩行都會報錯
const message = "Goodbye!";
const age = 30;

ES6申明變量的6種方法
ES5 只有兩種聲明變量的方法:var命令和function命令瘦馍。ES6除了添加let和const命令歼秽,后面還會提到,另外兩種聲明變量的方法:import命令和class命令情组。所以哲银,ES6 一共有6種聲明變量的方法扛吞。

變量的解構(gòu)賦值

數(shù)組的解構(gòu)賦值

let [a, b, c] = [1, 2, 3];

本質(zhì)上,這種寫法屬于“模式匹配”荆责,只要等號兩邊的模式相同,左邊的變量就會被賦予對應(yīng)的值亚脆。下面是一些使用嵌套數(shù)組進(jìn)行解構(gòu)的例子做院。

let [foo,[[bar],baz]] = [1,[[2],3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

如果解構(gòu)不成功,變量的值就等于undefined濒持。

let [foo] = []; // foo -> undefined
let [bar, foo] = [1]; // foo -> undefined

不完全解構(gòu)键耕,即等號左邊的模式,只匹配一部分的等號右邊的數(shù)組柑营。這種情況下屈雄,解構(gòu)依然可以成功。

let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

如果等號的右邊不是數(shù)組(或者嚴(yán)格地說官套,不是可遍歷的結(jié)構(gòu)酒奶,參見《Iterator》一章),那么將會報錯奶赔。

// 報錯
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

解構(gòu)賦值允許指定默認(rèn)值

let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

注意惋嚎,ES6 內(nèi)部使用嚴(yán)格相等運(yùn)算符(===),判斷一個位置是否有值站刑。所以另伍,如果一個數(shù)組成員不嚴(yán)格等于undefined瓢颅,默認(rèn)值是不會生效的砌创。

let [x = 1] = [undefined];
x // 1

let [x = 1] = [null];
x // null

默認(rèn)值可以引用解構(gòu)賦值的其他變量,但該變量必須已經(jīng)聲明逻恐。

let [x = 1, y = x] = [];     // x=1; y=1
let [x = 1, y = x] = [2];    // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = [];     // ReferenceError

上面最后一個表達(dá)式之所以會報錯因悲,是因?yàn)閤用到默認(rèn)值y時堕汞,y還沒有聲明。

對象的解構(gòu)賦值

let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

對象的解構(gòu)與數(shù)組有一個重要的不同囤捻。數(shù)組的元素是按次序排列的臼朗,變量的取值由它的位置決定;而對象的屬性沒有次序蝎土,變量必須與屬性同名视哑,才能取到正確的值。

let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined

如果變量名與屬性名不一致誊涯,必須寫成下面這樣挡毅。

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

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined

上面代碼中,foo是匹配的模式暴构,baz才是變量跪呈。真正被賦值的是變量baz段磨,而不是模式foo。

與數(shù)組一樣耗绿,解構(gòu)也可以用于嵌套結(jié)構(gòu)的對象苹支。

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

注意,這時p是模式误阻,不是變量债蜜,因此不會被賦值。如果p也要作為變量賦值究反,可以寫成下面這樣寻定。

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]

嵌套賦值的例子:

let obj = {};
let arr = [];

({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });

obj // {prop:123}
arr // [true]

對象的解構(gòu)也可以指定默認(rèn)值

var {x = 3} = {};
x // 3

var {x, y = 5} = {x: 1};
x // 1
y // 5

var {x: y = 3} = {};
y // 3

var {x: y = 3} = {x: 5};
y // 5

var {x = 3} = {x: undefined};
x // 3

var {x = 3} = {x: null};
x // null

字符串的解構(gòu)賦值

字符串也可以解構(gòu)賦值。這是因?yàn)榇藭r精耐,字符串被轉(zhuǎn)換成了一個類似數(shù)組的對象狼速。

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

類似數(shù)組的對象都有一個length屬性,因此還可以對這個屬性解構(gòu)賦值卦停。

let {length : len} = 'hello';
len // 5

數(shù)值和布爾值的解構(gòu)賦值

解構(gòu)賦值時向胡,如果等號右邊是數(shù)值和布爾值,則會先轉(zhuǎn)為對象沫浆。

let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

上面代碼中捷枯,數(shù)值和布爾值的包裝對象都有toString屬性,因此變量s都能取到值专执。

函數(shù)參數(shù)的解構(gòu)賦值

function add([x, y]){
  return x + y;
}

add([1, 2]); // 3

函數(shù)參數(shù)的解構(gòu)也可以使用默認(rèn)值淮捆。

function move({x = 0, y = 0} = {}) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

解構(gòu)賦值用途

交換變量的值

let x = 1;
let y = 2;

[x, y] = [y, x];

從函數(shù)返回多個值

// 返回一個數(shù)組
function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

// 返回一個對象
function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();

函數(shù)參數(shù)的定義

// 參數(shù)是一組有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 參數(shù)是一組無次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});

提取JSON數(shù)據(jù)

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id, status, number);    // 42, "OK", [867, 5309]

函數(shù)參數(shù)的默認(rèn)值

jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
}) {
  // ... do stuff
};

遍歷Map結(jié)構(gòu)

var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world

如果只想獲取鍵名,或者只想獲取鍵值本股,可以寫成下面這樣攀痊。

// 獲取鍵名
for (let [key] of map) {
  // ...
}

// 獲取鍵值
for (let [,value] of map) {
  // ...
}

輸入模塊的指定方法
加載模塊時,往往需要指定輸入哪些方法拄显。解構(gòu)賦值使得輸入語句非常清晰苟径。
const { SourceMapConsumer, SourceNode } = require("source-map");

字符串的擴(kuò)展

字符的 Unicode 表示法

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

codePointAt
ES6提供了codePointAt方法,能夠正確處理4個字節(jié)儲存的字符躬审,返回一個字符的碼點(diǎn)棘街。

var s = '??a';
s.codePointAt(0) // 134071
s.codePointAt(1) // 57271
s.codePointAt(2) // 97

codePointAt方法返回的是碼點(diǎn)的十進(jìn)制值,如果想要十六進(jìn)制的值承边,可以使用toString方法轉(zhuǎn)換一下遭殉。

var s = '??a';
s.codePointAt(0).toString(16) // "20bb7"
s.codePointAt(2).toString(16) // "61"

你可能注意到了,codePointAt方法的參數(shù)博助,仍然是不正確的险污。比如,上面代碼中,字符a在字符串s的正確位置序號應(yīng)該是1蛔糯,但是必須向codePointAt方法傳入2拯腮。解決這個問題的一個辦法是使用for...of循環(huán),因?yàn)樗鼤_識別32位的UTF-16字符蚁飒。

var s = '??a';
for (let ch of s) {
  console.log(ch.codePointAt(0).toString(16));
}
// 20bb7
// 61

codePointAt方法是測試一個字符由兩個字節(jié)還是由四個字節(jié)組成的最簡單方法动壤。

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

String.fromCodePoint()
用于從碼點(diǎn)返回對應(yīng)字符,在作用上飒箭,正好與codePointAt方法相反狼电。

String.fromCodePoint(0x20BB7)
// "??"
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
// true

字符串的遍歷器接口
ES6為字符串添加了遍歷器接口,使得字符串可以被for...of循環(huán)遍歷弦蹂。

for (let codePoint of 'foo') {
  console.log(codePoint)
}
// "f"
// "o"
// "o"

除了遍歷字符串,這個遍歷器最大的優(yōu)點(diǎn)是可以識別大于0xFFFF的碼點(diǎn)强窖,傳統(tǒng)的for循環(huán)無法識別這樣的碼點(diǎn)凸椿。

var text = String.fromCodePoint(0x20BB7);

for (let i = 0; i < text.length; i++) {
  console.log(text[i]);
}
// " "
// " "

for (let i of text) {
  console.log(i);
}
// "??"

上面代碼中,字符串text只有一個字符翅溺,但是for循環(huán)會認(rèn)為它包含兩個字符(都不可打幽月),而for...of循環(huán)會正確識別出這一個字符咙崎。

at()
ES5對字符串對象提供charAt方法优幸,返回字符串給定位置的字符。該方法不能識別碼點(diǎn)大于0xFFFF的字符褪猛。

'abc'.charAt(0) // "a"
'??'.charAt(0) // "\uD842"

'abc'.at(0) // "a"
'??'.at(0) // "??"

normalize()
ES6提供字符串實(shí)例的normalize()方法网杆,用來將字符的不同表示方法統(tǒng)一為同樣的形式,這稱為Unicode正規(guī)化伊滋。

'\u01D1'.normalize() === '\u004F\u030C'.normalize()   // true

includes(), startsWith(), endsWith()
傳統(tǒng)上碳却,JavaScript只有indexOf方法,可以用來確定一個字符串是否包含在另一個字符串中笑旺。ES6又提供了三種新方法昼浦。
includes():返回布爾值,表示是否找到了參數(shù)字符串筒主。
startsWith():返回布爾值关噪,表示參數(shù)字符串是否在源字符串的頭部。
endsWith():返回布爾值乌妙,表示參數(shù)字符串是否在源字符串的尾部使兔。

var s = 'Hello world!';
s.includes('o') // true
s.startsWith('Hello') // true
s.endsWith('!') // true

這三個方法都支持第二個參數(shù),表示開始搜索的位置冠胯。

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

repeat()
repeat方法返回一個新字符串火诸,表示將原字符串重復(fù)n次。

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

參數(shù)如果是小數(shù)荠察,會被取整置蜀。

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

如果repeat的參數(shù)是負(fù)數(shù)或者Infinity奈搜,會報錯。

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

但是盯荤,如果參數(shù)是0到-1之間的小數(shù)馋吗,則等同于0,這是因?yàn)闀冗M(jìn)行取整運(yùn)算秋秤。0到-1之間的小數(shù)宏粤,取整以后等于-0,repeat視同為0灼卢。

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

參數(shù)NaN等同于0绍哎。

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

如果repeat的參數(shù)是字符串,則會先轉(zhuǎn)換成數(shù)字鞋真。

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

padStart()崇堰,padEnd()
ES2017 引入了字符串補(bǔ)全長度的功能。如果某個字符串不夠指定長度涩咖,會在頭部或尾部補(bǔ)全海诲。padStart()用于頭部補(bǔ)全,padEnd()用于尾部補(bǔ)全檩互。

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

上面代碼中特幔,padStart和padEnd一共接受兩個參數(shù),第一個參數(shù)用來指定字符串的最小長度闸昨,第二個參數(shù)是用來補(bǔ)全的字符串蚯斯。

如果原字符串的長度,等于或大于指定的最小長度零院,則返回原字符串溉跃。

'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'

如果用來補(bǔ)全的字符串與原字符串,兩者的長度之和超過了指定的最小長度告抄,則會截去超出位數(shù)的補(bǔ)全字符串撰茎。

'abc'.padStart(10, '0123456789')  // '0123456abc'

如果省略第二個參數(shù),默認(rèn)使用空格補(bǔ)全長度打洼。

'x'.padStart(4) // '   x'
'x'.padEnd(4) // 'x   '

padStart的常見用途是為數(shù)值補(bǔ)全指定位數(shù)龄糊。下面代碼生成10位的數(shù)值字符串。

'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"

另一個用途是提示字符串格式募疮。

'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
模板字符串

傳統(tǒng)的JavaScript語言炫惩,輸出模板通常是這樣寫的。

$('#result').append(
  'There are <b>' + basket.count + '</b> ' +
  'items in your basket, ' +
  '<em>' + basket.onSale +
  '</em> are on sale!'
);

上面這種寫法相當(dāng)繁瑣不方便阿浓,ES6引入了模板字符串解決這個問題他嚷。

$('#result').append(`
  There are <b>${basket.count}</b> items
   in your basket, <em>${basket.onSale}</em>
  are on sale!
`);

模板字符串(template string)是增強(qiáng)版的字符串,用反引號(`)標(biāo)識。它可以當(dāng)作普通字符串使用筋蓖,也可以用來定義多行字符串卸耘,或者在字符串中嵌入變量。

// 普通字符串
`In JavaScript '\n' is a line-feed.`

// 多行字符串
`In JavaScript this is
 not legal.`

console.log(`string text line 1
string text line 2`);

// 字符串中嵌入變量
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

模板字符串中嵌入變量粘咖,需要將變量名寫在${}之中蚣抗。

function authorize(user, action) {
  if (!user.hasPrivilege(action)) {
    throw new Error(
      // 傳統(tǒng)寫法為
      // 'User '
      // + user.name
      // + ' is not authorized to do '
      // + action
      // + '.'
      `User ${user.name} is not authorized to do ${action}.`);
  }
}

大括號內(nèi)部可以放入任意的JavaScript表達(dá)式,可以進(jìn)行運(yùn)算瓮下,以及引用對象屬性翰铡。

var x = 1;
var y = 2;
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"

`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"

var obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// 3

模板字符串之中還能調(diào)用函數(shù)。

function fn() {
  return "Hello World";
}

`foo ${fn()} bar`   // foo Hello World bar

如果模板字符串中的變量沒有聲明讽坏,將報錯锭魔。

// 變量place沒有聲明
var msg = `Hello, ${place}`;  // 報錯

如果大括號內(nèi)部是一個字符串,將會原樣輸出路呜。

`Hello ${'World'}`  // "Hello World"

String.raw()
String.raw方法赂毯,往往用來充當(dāng)模板字符串的處理函數(shù),返回一個斜杠都被轉(zhuǎn)義(即斜杠前面再加一個斜杠)的字符串拣宰,對應(yīng)于替換變量后的模板字符串。

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

String.raw`Hi\u000A!`;
// 'Hi\\u000A!'

如果原字符串的斜杠已經(jīng)轉(zhuǎn)義烦感,那么String.raw不會做任何處理巡社。

String.raw`Hi\\n`  // "Hi\\n"

正則的擴(kuò)展

RegExp 構(gòu)造函數(shù)
如果RegExp構(gòu)造函數(shù)第一個參數(shù)是一個正則對象,那么可以使用第二個參數(shù)指定修飾符手趣。而且晌该,返回的正則表達(dá)式會忽略原有的正則表達(dá)式的修飾符,只使用新指定的修飾符绿渣。

new RegExp(/abc/ig, 'i').flags  // "i"

上面代碼中朝群,原有正則對象的修飾符是ig,它會被第二個參數(shù)i覆蓋中符。

字符串的正則方法
字符串對象共有4個方法姜胖,可以使用正則表達(dá)式:match()、replace()淀散、search()和split()右莱。
// match(): 在字符串內(nèi)檢索指定的值

var str="1 plus 2 equal 3"
str.match(/\d+/g)  // 1,2,3

// replace(): 在字符串中用一些字符替換另一些字符,或替換一個與正則表達(dá)式匹配的子串档插。
例 執(zhí)行一個全局替換, 忽略大小寫:

var str="Mr Blue has a blue house";
str.replace(/blue/gi, "red");  // Mr red has a red house

// search(): 檢索字符串中指定的子字符串慢蜓,或檢索與正則表達(dá)式相匹配的子字符串。如果沒有找到任何匹配的子串郭膛,則返回 -1晨抡。

var str="Visit W3CSchool!";
str.search("W3CSchool"); // 6

// split(): 把一個字符串分割成字符串?dāng)?shù)組。

"hello".split("")   // "h", "e", "l", "l", "o"
"2:3:4:5".split(":")    // "2", "3", "4", "5"

ES6 將這4個方法,在語言內(nèi)部全部調(diào)用RegExp的實(shí)例方法耘柱,從而做到所有與正則相關(guān)的方法如捅,全都定義在RegExp對象上。

String.prototype.match 調(diào)用 RegExp.prototype[Symbol.match]
String.prototype.replace 調(diào)用 RegExp.prototype[Symbol.replace]
String.prototype.search 調(diào)用 RegExp.prototype[Symbol.search]
String.prototype.split 調(diào)用 RegExp.prototype[Symbol.split]

u修飾符

/\u{61}/.test('a') // false
/\u{61}/u.test('a') // true
/\u{20BB7}/u.test('??') // true

上面代碼表示帆谍,如果不加u修飾符伪朽,正則表達(dá)式無法識別\u{61}這種表示法,只會認(rèn)為這匹配61個連續(xù)的u汛蝙。

y修飾符
y修飾符的作用與g修飾符類似烈涮,也是全局匹配,后一次匹配都從上一次匹配成功的下一個位置開始窖剑。不同之處在于坚洽,g修飾符只要剩余位置中存在匹配就可,而y修飾符確保匹配必須從剩余的第一個位置開始西土,這也就是“粘連”的涵義讶舰。

var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;

r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]

r1.exec(s) // ["aa"]
r2.exec(s) // null

實(shí)際上,y修飾符號隱含了頭部匹配的標(biāo)志^需了。

單單一個y修飾符對match方法跳昼,只能返回第一個匹配,必須與g修飾符聯(lián)用肋乍,才能返回所有匹配鹅颊。

'a1a2a3'.match(/a\d/y) // ["a1"]
'a1a2a3'.match(/a\d/gy) // ["a1", "a2", "a3"]

sticky 屬性
與y修飾符相匹配,ES6 的正則對象多了sticky屬性墓造,表示是否設(shè)置了y修飾符堪伍。

var r = /hello\d/y;
r.sticky // true

flags屬性
ES6 為正則表達(dá)式新增了flags屬性,會返回正則表達(dá)式的修飾符觅闽。

// ES5 的 source 屬性
// 返回正則表達(dá)式的正文
/abc/ig.source
// "abc"

// ES6 的 flags 屬性
// 返回正則表達(dá)式的修飾符
/abc/ig.flags
// 'gi'

s修飾符
引入/s修飾符帝雇,使得.可以匹配任意單個字符。

/foo.bar/s.test('foo\nbar') // true

這被稱為dotAll模式蛉拙,即點(diǎn)(dot)代表一切字符尸闸。所以,正則表達(dá)式還引入了一個dotAll屬性刘离,返回一個布爾值室叉,表示該正則表達(dá)式是否處在dotAll模式。

const re = /foo.bar/s;
// 另一種寫法
// const re = new RegExp('foo.bar', 's');

re.test('foo\nbar') // true
re.dotAll // true
re.flags // 's'

后行斷言
JavaScript 語言的正則表達(dá)式硫惕,只支持先行斷言(lookahead)和先行否定斷言(negative lookahead)茧痕,不支持后行斷言(lookbehind)和后行否定斷言(negative lookbehind)。

"先行斷言"指的是恼除,x只有在y前面才匹配踪旷,必須寫成/x(?=y)/曼氛。比如,只匹配百分號之前的數(shù)字令野,要寫成/\d+(?=%)/舀患。

"先行否定斷言"指的是,x只有不在y前面才匹配气破,必須寫成/x(?!y)/聊浅。比如,只匹配不在百分號之前的數(shù)字现使,要寫成/\d+(?!%)/低匙。

"后行斷言"正好與"先行斷言"相反,x只有在y后面才匹配碳锈,必須寫成/(?<=y)x/顽冶。比如,只匹配美元符號之后的數(shù)字售碳,要寫成/(?<=\$)\d+/强重。

”后行否定斷言“則與”先行否定斷言“相反,x只有不在y后面才匹配贸人,必須寫成/(?<!y)x/间景。比如,只匹配不在美元符號后面的數(shù)字艺智,要寫成/(?<!\$)\d+/拱燃。

Unicode屬性類
目前,有一個提案力惯,引入了一種新的類的寫法\p{...}和\P{...},允許正則表達(dá)式匹配符合 Unicode 某種屬性的所有字符召嘶。

\P{…}是\p{…}的反向匹配父晶,即匹配不滿足條件的字符。

const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // true

上面代碼中弄跌,\p{Script=Greek}指定匹配一個希臘文字母甲喝,所以匹配π成功。

Unicode 屬性類要指定屬性名和屬性值铛只。
\p{UnicodePropertyName=UnicodePropertyValue}

對于某些屬性埠胖,可以只寫屬性名。
\p{UnicodePropertyName}

// 匹配所有數(shù)字
const regex = /^\p{Number}+$/u;
regex.test('231???') // true
regex.test('???') // true
regex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ') // true

// 匹配各種文字的所有字母淳玩,等同于 Unicode 版的 \w
[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]

// 匹配各種文字的所有非字母的字符直撤,等同于 Unicode 版的 \W
[^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]

// 匹配所有的箭頭字符
const regexArrows = /^\p{Block=Arrows}+$/u;
regexArrows.test('←↑→↓??↖↗↘↙?????????????') // true

具名組匹配
正則表達(dá)式使用圓括號進(jìn)行組匹配。
const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;

上面代碼中蜕着,正則表達(dá)式里面有三組圓括號谋竖。使用exec方法红柱,就可以將這三組匹配結(jié)果提取出來。

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31

組匹配的一個問題是蓖乘,每一組的匹配含義不容易看出來锤悄,而且只能用數(shù)字序號引用,要是組的順序變了嘉抒,引用的時候就必須修改序號零聚。

現(xiàn)在有一個“具名組匹配”(Named Capture Groups)的提案,允許為每一個組匹配指定一個名字些侍,既便于閱讀代碼隶症,又便于引用。

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31

上面代碼中娩梨,“具名組匹配”在圓括號內(nèi)部沿腰,模式的頭部添加“問號 + 尖括號 + 組名”(?<year>),然后就可以在exec方法返回結(jié)果的groups屬性上引用該組名狈定。同時颂龙,數(shù)字序號(matchObj[1])依然有效。

如果具名組沒有匹配纽什,那么對應(yīng)的groups對象屬性會是undefined措嵌。

const RE_OPT_A = /^(?<as>a+)?$/;
const matchObj = RE_OPT_A.exec('');

matchObj.groups.as // undefined
'as' in matchObj.groups // true

上面代碼中,具名組as沒有找到匹配芦缰,那么matchObj.groups.as屬性值就是undefined企巢,并且as這個鍵名在groups是始終存在的。

解構(gòu)賦值和替換
有了具名組匹配以后让蕾,可以使用解構(gòu)賦值直接從匹配結(jié)果上為變量賦值浪规。

let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
one  // foo
two  // bar

字符串替換時,使用$<組名>引用具名組探孝。

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;

'2015-01-02'.replace(re, '$<day>/$<month>/$<year>')
// '02/01/2015'

上面代碼中笋婿,replace方法的第二個參數(shù)是一個字符串,而不是正則表達(dá)式顿颅。

replace方法的第二個參數(shù)也可以是函數(shù)缸濒,該函數(shù)的參數(shù)序列如下。

'2015-01-02'.replace(re, (
   matched, // 整個匹配結(jié)果 2015-01-02
   capture1, // 第一個組匹配 2015
   capture2, // 第二個組匹配 01
   capture3, // 第三個組匹配 02
   position, // 匹配開始的位置 0
   S, // 原字符串 2015-01-05
   groups // 具名組構(gòu)成的一個對象 {year, month, day}
 ) => {
 let {day, month, year} = args[args.length - 1];
 return `${day}/${month}/${year}`;
});

具名組匹配在原來的基礎(chǔ)上粱腻,新增了最后一個函數(shù)參數(shù):具名組構(gòu)成的一個對象庇配。函數(shù)內(nèi)部可以直接對這個對象進(jìn)行解構(gòu)賦值。

引用
如果要在正則表達(dá)式內(nèi)部引用某個“具名組匹配”绍些,可以使用\k<組名>的寫法捞慌。

const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false

數(shù)字引用(\1)依然有效。

const RE_TWICE = /^(?<word>[a-z]+)!\1$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false

這兩種引用語法還可以同時使用柬批。

const RE_TWICE = /^(?<word>[a-z]+)!\k<word>!\1$/;
RE_TWICE.test('abc!abc!abc') // true
RE_TWICE.test('abc!abc!ab') // false

數(shù)值的擴(kuò)展

二進(jìn)制和八進(jìn)制表示法
ES6 提供了二進(jìn)制和八進(jìn)制數(shù)值的新的寫法卿闹,分別用前綴0b(或0B)和0o(或0O)表示揭糕。

0b111110111 === 503 // true
0o767 === 503 // true

如果要將0b和0o前綴的字符串?dāng)?shù)值轉(zhuǎn)為十進(jìn)制,要使用Number方法锻霎。

Number('0b111')  // 7
Number('0o10')  // 8

Number.isFinite(), Number.isNaN()
ES6 在Number對象上著角,新提供了Number.isFinite()和Number.isNaN()兩個方法。

Number.isFinite()用來檢查一個數(shù)值是否為有限的(finite)旋恼。

Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false

Number.isNaN()用來檢查一個值是否為NaN吏口。

Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true'/0) // true
Number.isNaN('true'/'true') // true

Number.parseInt(), Number.parseFloat()
ES6 將全局方法parseInt()和parseFloat(),移植到Number對象上面冰更,行為完全保持不變产徊。

Number.parseInt('12.34') // 12
Number.parseFloat('123.45aaa') // 123.45

這樣做的目的,是逐步減少全局性方法蜀细,使得語言逐步模塊化舟铜。

Number.isInteger()
Number.isInteger()用來判斷一個值是否為整數(shù)。需要注意的是奠衔,在 JavaScript 內(nèi)部谆刨,整數(shù)和浮點(diǎn)數(shù)是同樣的儲存方法,所以3和3.0被視為同一個值归斤。

Number.isInteger(25) // true
Number.isInteger(25.0) // true
Number.isInteger(25.1) // false
Number.isInteger("15") // false
Number.isInteger(true) // false

Number.EPSILON
ES6在Number對象上面痊夭,新增一個極小的常量Number.EPSILON。
引入一個這么小的量的目的脏里,在于為浮點(diǎn)數(shù)計算她我,設(shè)置一個誤差范圍。我們知道浮點(diǎn)數(shù)計算是不精確的迫横。

0.1 + 0.2
// 0.30000000000000004

0.1 + 0.2 - 0.3
// 5.551115123125783e-17

5.551115123125783e-17.toFixed(20)
// '0.00000000000000005551'

但是如果這個誤差能夠小于Number.EPSILON番舆,我們就可以認(rèn)為得到了正確結(jié)果。

5.551115123125783e-17 < Number.EPSILON
// true

因此矾踱,Number.EPSILON的實(shí)質(zhì)是一個可以接受的誤差范圍合蔽。

function withinErrorMargin (left, right) {
  return Math.abs(left - right) < Number.EPSILON;
}
withinErrorMargin(0.1 + 0.2, 0.3)
// true
withinErrorMargin(0.2 + 0.2, 0.3)
// false

上面的代碼為浮點(diǎn)數(shù)運(yùn)算,部署了一個誤差檢查函數(shù)介返。

安全整數(shù)和Number.isSafeInteger()
JavaScript能夠準(zhǔn)確表示的整數(shù)范圍在-253到253之間(不含兩個端點(diǎn)),超過這個范圍沃斤,無法精確表示這個值圣蝎。
Number.isSafeInteger()則是用來判斷一個整數(shù)是否落在這個范圍之內(nèi)。

Number.isSafeInteger('a') // false
Number.isSafeInteger(null) // false
Number.isSafeInteger(NaN) // false
Number.isSafeInteger(Infinity) // false
Number.isSafeInteger(-Infinity) // false

Number.isSafeInteger(3) // true
Number.isSafeInteger(1.2) // false
Number.isSafeInteger(9007199254740990) // true
Number.isSafeInteger(9007199254740992) // false

Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false
Number.isSafeInteger(Number.MIN_SAFE_INTEGER) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false
Math對象的擴(kuò)展

ES6在Math對象上新增了17個與數(shù)學(xué)相關(guān)的方法衡瓶。所有這些方法都是靜態(tài)方法徘公,只能在Math對象上調(diào)用涣澡。

Math.trunc()
Math.trunc方法用于去除一個數(shù)的小數(shù)部分咙咽,返回整數(shù)部分。

Math.trunc(4.1) // 4
Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc(-4.9) // -4
Math.trunc(-0.1234) // -0

對于非數(shù)值嚷炉,Math.trunc內(nèi)部使用Number方法將其先轉(zhuǎn)為數(shù)值。

Math.trunc('123.456')
// 123

對于空值和無法截取整數(shù)的值等太,返回NaN捂齐。

Math.trunc(NaN);      // NaN
Math.trunc('foo');    // NaN
Math.trunc();         // NaN

Math.sign()
Math.sign方法用來判斷一個數(shù)到底是正數(shù)、負(fù)數(shù)缩抡、還是零奠宜。對于非數(shù)值,會先將其轉(zhuǎn)換為數(shù)值瞻想。

它會返回五種值:
參數(shù)為正數(shù)压真,返回+1;
參數(shù)為負(fù)數(shù)蘑险,返回-1滴肿;
參數(shù)為0,返回0佃迄;
參數(shù)為-0泼差,返回-0;
其他值,返回NaN和屎。

Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN
Math.sign('9'); // +1
Math.sign('foo'); // NaN
Math.sign();      // NaN

Math.cbrt
Math.cbrt方法用于計算一個數(shù)的立方根拴驮。

Math.cbrt(-1) // -1
Math.cbrt(0)  // 0
Math.cbrt(1)  // 1
Math.cbrt(2)  // 1.2599210498948734

對于非數(shù)值,Math.cbrt方法內(nèi)部也是先使用Number方法將其轉(zhuǎn)為數(shù)值柴信。

Math.cbrt('8') // 2
Math.cbrt('hello') // NaN

Math.clz32
JavaScript的整數(shù)使用32位二進(jìn)制形式表示套啤,Math.clz32方法返回一個數(shù)的32位無符號整數(shù)形式有多少個前導(dǎo)0。

Math.clz32(0) // 32
Math.clz32(1) // 31
Math.clz32(1000) // 22
Math.clz32(0b01000000000000000000000000000000) // 1
Math.clz32(0b00100000000000000000000000000000) // 2

上面代碼中随常,0的二進(jìn)制形式全為0潜沦,所以有32個前導(dǎo)0;1的二進(jìn)制形式是0b1绪氛,只占1位唆鸡,所以32位之中有31個前導(dǎo)0;1000的二進(jìn)制形式是0b1111101000枣察,一共有10位争占,所以32位之中有22個前導(dǎo)0。

Math.imul()
Math.imul方法返回兩個數(shù)以32位帶符號整數(shù)形式相乘的結(jié)果序目,返回的也是一個32位的帶符號整數(shù)臂痕。

Math.imul(2, 4)   // 8
Math.imul(-1, 8)  // -8
Math.imul(-2, -2) // 4

Math.fround()
Math.fround方法返回一個數(shù)的單精度浮點(diǎn)數(shù)形式。

Math.fround(0)     // 0
Math.fround(1)     // 1
Math.fround(1.337) // 1.3370000123977661
Math.fround(1.5)   // 1.5
Math.fround(NaN)   // NaN

對于整數(shù)來說猿涨,Math.fround方法返回結(jié)果不會有任何不同握童,區(qū)別主要是那些無法用64個二進(jìn)制位精確表示的小數(shù)。這時叛赚,Math.fround方法會返回最接近這個小數(shù)的單精度浮點(diǎn)數(shù)澡绩。

Math.hypot()
Math.hypot方法返回所有參數(shù)的平方和的平方根稽揭。

Math.hypot(3, 4);        // 5
Math.hypot(3, 4, 5);     // 7.0710678118654755
Math.hypot();            // 0
Math.hypot(NaN);         // NaN
Math.hypot(3, 4, 'foo'); // NaN
Math.hypot(3, 4, '5');   // 7.0710678118654755
Math.hypot(-3);          // 3

上面代碼中,3的平方加上4的平方肥卡,等于5的平方溪掀。

雙曲函數(shù)方法
ES6新增了6個雙曲函數(shù)方法。

Math.sinh(x) 返回x的雙曲正弦(hyperbolic sine)
Math.cosh(x) 返回x的雙曲余弦(hyperbolic cosine)
Math.tanh(x) 返回x的雙曲正切(hyperbolic tangent)
Math.asinh(x) 返回x的反雙曲正弦(inverse hyperbolic sine)
Math.acosh(x) 返回x的反雙曲余弦(inverse hyperbolic cosine)
Math.atanh(x) 返回x的反雙曲正切(inverse hyperbolic tangent)

Math.signbit()
Math.signbit()方法判斷一個數(shù)的符號位是否設(shè)置了召调。

Math.signbit(2) //false
Math.signbit(-2) //true
Math.signbit(0) //false
Math.signbit(-0) //true

如果參數(shù)是NaN膨桥,返回false
如果參數(shù)是-0,返回true
如果參數(shù)是負(fù)值唠叛,返回true
其他情況返回false


指數(shù)運(yùn)算符
ES2016 新增了一個指數(shù)運(yùn)算符(**)只嚣。

2 ** 2 // 4   -> 2*2 = 4
2 ** 3 // 8   -> 2*2*2 = 8

指數(shù)運(yùn)算符可以與等號結(jié)合,形成一個新的賦值運(yùn)算符(**=)艺沼。

let a = 1.5;
a **= 2;
// 等同于 a = a * a;

let b = 4;
b **= 3;
// 等同于 b = b * b * b;

Integer數(shù)據(jù)類型
avaScript 所有數(shù)字都保存成64位浮點(diǎn)數(shù)册舞,這決定了整數(shù)的精確程度只能到53個二進(jìn)制位。大于這個范圍的整數(shù)障般,JavaScript 是無法精確表示的调鲸,這使得 JavaScript 不適合進(jìn)行科學(xué)和金融方面的精確計算。

現(xiàn)在有一個提案挽荡,引入了新的數(shù)據(jù)類型 Interger(整數(shù))藐石,來解決這個問題。整數(shù)類型的數(shù)據(jù)只用來表示整數(shù)定拟,沒有位數(shù)的限制于微,任何位數(shù)的整數(shù)都可以精確表示。

為了與 Number 類型區(qū)別青自,Integer 類型的數(shù)據(jù)必須使用后綴n表示株依。

1n + 2n // 3n

二進(jìn)制、八進(jìn)制延窜、十六進(jìn)制的表示法恋腕,都要加上后綴n。

0b1101n // 二進(jìn)制
0o777n // 八進(jìn)制
0xFFn // 十六進(jìn)制

typeof運(yùn)算符對于 Integer 類型的數(shù)據(jù)返回integer逆瑞。

typeof 123n   // 'integer'

函數(shù)的擴(kuò)展

函數(shù)參數(shù)的默認(rèn)值

ES6 之前荠藤,不能直接為函數(shù)的參數(shù)指定默認(rèn)值,只能采用變通的方法获高。

function log(x, y) {
  y = y || 'World';
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World

這種寫法的缺點(diǎn)在于哈肖,如果參數(shù)y賦值了,但是對應(yīng)的布爾值為false谋减,則該賦值不起作用。就像上面代碼的最后一行扫沼,參數(shù)y等于空字符出爹,結(jié)果被改為默認(rèn)值庄吼。

ES6 允許為函數(shù)的參數(shù)設(shè)置默認(rèn)值,即直接寫在參數(shù)定義的后面严就。

function log(x,y = 'World') {
    console.log(x,y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

可以看到总寻,ES6 的寫法比 ES5 簡潔許多,而且非常自然梢为。下面是另一個例子渐行。

function point(x = 0, y = 0) {
    this.x = x;
    this.y = y;
}

var p = new Point();
p // { x: 0, y: 0 }

參數(shù)變量是默認(rèn)聲明的,所以不能用let或const再次聲明铸董。

function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

使用參數(shù)默認(rèn)值時祟印,函數(shù)不能有同名參數(shù)。

function foo(x, x, y = 1) {
  // ...
}
// 報錯 SyntaxError: Duplicate parameter name not allowed in this context

與解構(gòu)賦值默認(rèn)值結(jié)合使用
參數(shù)默認(rèn)值可以與解構(gòu)賦值的默認(rèn)值粟害,結(jié)合起來使用蕴忆。

function foo({x, y = 5}) {
    console.log(x,y);
}
foo({})  // undefined, 5
foo({x: 1})  // 1, 5
foo({x: 1, y: 2})  // 1, 2
foo()  // TypeError: Cannot read property 'x' of undefined

上面代碼使用了對象的解構(gòu)賦值默認(rèn)值,而沒有使用函數(shù)參數(shù)的默認(rèn)值悲幅。只有當(dāng)函數(shù)foo的參數(shù)是一個對象時套鹅,變量x和y才會通過解構(gòu)賦值而生成。如果函數(shù)foo調(diào)用時參數(shù)不是對象汰具,變量x和y就不會生成卓鹿,從而報錯。

下面是另一個對象的解構(gòu)賦值默認(rèn)值的例子留荔。

function fetch(url, { body = '', method = 'GET', headers = {} }) {
  console.log(method);
}

fetch('http://example.com', {})  // "GET"

fetch('http://example.com')  // 報錯

上面代碼中吟孙,如果函數(shù)fetch的第二個參數(shù)是一個對象,就可以為它的三個屬性設(shè)置默認(rèn)值存谎。

上面的寫法不能省略第二個參數(shù)拔疚,如果結(jié)合函數(shù)參數(shù)的默認(rèn)值,就可以省略第二個參數(shù)既荚。這時稚失,就出現(xiàn)了雙重默認(rèn)值。

function fetch(url, { method = 'GET'} = {}) {
    console.log(method);
}
fetch('http://example.com')  // GET

下面兩種寫法有什么差別:

// 寫法一
function m1({x = 0, y = 0} = {}) {
  return [x, y];
}

// 寫法二
function m2({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

上面兩種寫法都對函數(shù)的參數(shù)設(shè)定了默認(rèn)值恰聘,區(qū)別是寫法一函數(shù)參數(shù)的默認(rèn)值是空對象句各,但是設(shè)置了對象解構(gòu)賦值的默認(rèn)值;寫法二函數(shù)參數(shù)的默認(rèn)值是一個有具體屬性的對象晴叨,但是沒有設(shè)置對象解構(gòu)賦值的默認(rèn)值凿宾。

// 函數(shù)沒有參數(shù)的情況
m1() // [0, 0]
m2() // [0, 0]

// x和y都有值的情況
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]

// x有值,y無值的情況
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]

// x和y都無值的情況
m1({}) // [0, 0];
m2({}) // [undefined, undefined]

m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]

函數(shù)的length屬性
指定了默認(rèn)值以后兼蕊,函數(shù)的length屬性初厚,將返回沒有指定默認(rèn)值的參數(shù)個數(shù)。也就是說,指定了默認(rèn)值后产禾,length屬性將失真排作。

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2

這是因?yàn)閘ength屬性的含義是,該函數(shù)預(yù)期傳入的參數(shù)個數(shù)亚情。某個參數(shù)指定默認(rèn)值以后妄痪,預(yù)期傳入的參數(shù)個數(shù)就不包括這個參數(shù)了。同理楞件,rest 參數(shù)也不會計入length屬性衫生。

(function(...args) {}).length // 0

如果設(shè)置了默認(rèn)值的參數(shù)不是尾參數(shù),那么length屬性也不再計入后面的參數(shù)土浸。

(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1

作用域
一旦設(shè)置了參數(shù)的默認(rèn)值罪针,函數(shù)進(jìn)行聲明初始化時,參數(shù)會形成一個單獨(dú)的作用域(context)栅迄。等到初始化結(jié)束站故,這個作用域就會消失。這種語法行為毅舆,在不設(shè)置參數(shù)默認(rèn)值時西篓,是不會出現(xiàn)的。

var x = 1;

function f(x, y = x) {
  console.log(y);
}

f(2) // 2

上面代碼中憋活,參數(shù)y的默認(rèn)值等于變量x岂津。調(diào)用函數(shù)f時,參數(shù)形成一個單獨(dú)的作用域悦即。在這個作用域里面吮成,默認(rèn)值變量x指向第一個參數(shù)x,而不是全局變量x辜梳,所以輸出是2粱甫。

let x = 1;

function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // 1

上面代碼中,函數(shù)f調(diào)用時作瞄,參數(shù)y = x形成一個單獨(dú)的作用域茶宵。這個作用域里面,變量x本身沒有定義宗挥,所以指向外層的全局變量x乌庶。函數(shù)調(diào)用時,函數(shù)體內(nèi)部的局部變量x影響不到默認(rèn)值變量x契耿。

如果此時瞒大,全局變量x不存在,就會報錯搪桂。

function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // ReferenceError: x is not defined

rest參數(shù)

ES6 引入 rest 參數(shù)(形式為...變量名)透敌,用于獲取函數(shù)的多余參數(shù),這樣就不需要使用arguments對象了。rest 參數(shù)搭配的變量是一個數(shù)組酗电,該變量將多余的參數(shù)放入數(shù)組中淌山。

function add(...values) {
    let sum = 0;
    for(var val of values) {
        sum += val;
    }
    return sum;
}

add(2, 5, 3);  // 10

下面是一個 rest 參數(shù)代替arguments變量的例子。

// arguments變量的寫法
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}

// rest參數(shù)的寫法
const sortNumbers = (...numbers) => numbers.sort();

rest 參數(shù)中的變量代表一個數(shù)組顾瞻,所以數(shù)組特有的方法都可以用于這個變量。下面是一個利用 rest 參數(shù)改寫數(shù)組push方法的例子德绿。

function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
    console.log(item);
  });
}

var a = [];
push(a, 1, 2, 3)

rest 參數(shù)之后不能再有其他參數(shù)(即只能是最后一個參數(shù))荷荤,否則會報錯。

// 報錯
function f(a, ...b, c) {
  // ...
}

函數(shù)的length屬性移稳,不包括 rest 參數(shù)蕴纳。

(function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1

嚴(yán)格模式

從 ES5 開始,函數(shù)內(nèi)部可以設(shè)定為嚴(yán)格模式个粱。

function doSomething(a, b) {
  'use strict';
  // code
}

ES2016 做了一點(diǎn)修改古毛,規(guī)定只要函數(shù)參數(shù)使用了默認(rèn)值、解構(gòu)賦值都许、或者擴(kuò)展運(yùn)算符稻薇,那么函數(shù)內(nèi)部就不能顯式設(shè)定為嚴(yán)格模式,否則會報錯胶征。

兩種方法可以規(guī)避這種限制塞椎。第一種是設(shè)定全局性的嚴(yán)格模式,這是合法的睛低。

'use strict';

function doSomething(a, b = a) {
  // code
}

第二種是把函數(shù)包在一個無參數(shù)的立即執(zhí)行函數(shù)里面案狠。

const doSomething = (function () {
  'use strict';
  return function(value = 42) {
    return value;
  };
}());

name屬性

函數(shù)的 name 屬性,返回該函數(shù)的函數(shù)名钱雷。

function foo() {}
foo.name // "foo"

如果將一個匿名函數(shù)賦值給一個變量骂铁,ES5 的name屬性,會返回空字符串罩抗,而 ES6 的name屬性會返回實(shí)際的函數(shù)名拉庵。

var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f"

如果將一個具名函數(shù)賦值給一個變量,則 ES5 和 ES6 的name屬性都返回這個具名函數(shù)原本的名字澄暮。

const bar = function baz() {};

// ES5
bar.name // "baz"

// ES6
bar.name // "baz"

箭頭函數(shù)

ES6 允許使用"箭頭"( => )定義函數(shù)名段。

var f = v => v;
上面的箭頭函數(shù)等同于:

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

如果箭頭函數(shù)不需要參數(shù)或需要多個參數(shù),就用一個圓括號()代表參數(shù)部分

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

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

如果箭頭函數(shù)的代碼塊部分多于一條語句泣懊,就要使用大括號將它們括起來伸辟,并且使用return語句返回。

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

由于大括號被解釋為代碼塊馍刮,所以如果箭頭函數(shù)直接返回一個對象信夫,必須在對象外面加上括號。

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

箭頭函數(shù)可以與變量解構(gòu)結(jié)合使用

const full = ({ first, last }) => first + ' ' + last;

// 等同于
function full(person) {
    return person.first + ' ' + person.last;
}

箭頭函數(shù)使得表達(dá)更加簡潔

const isEven = n => n % 2 == 0;
const square = n => n * n;

箭頭函數(shù)的一個用處是簡化回調(diào)函數(shù)

// 正常函數(shù)寫法
[1,2,3].map(function(x){
    return x * x;
});

// 箭頭函數(shù)寫法
[1,2,3].map(x => x * x);

另一個例子:

// 正常函數(shù)寫法
var result = values.sort(function(a, b) {
    return a - b;
});

//箭頭函數(shù)寫法
var result = values.sort((a,b) => a - b);

下面是 rest 參數(shù)與箭頭函數(shù)結(jié)合的例子:

const numbers = (...nums) => nums;
numbers(1,2,3,4,5)  // [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail];
headAndTail(1,2,3,4,5)  // [1,[2,3,4,5]]

嵌套的箭頭函數(shù)
箭頭函數(shù)內(nèi)部,還可以再使用箭頭函數(shù)静稻。下面是一個ES5語法的多重嵌套函數(shù)警没。

function insert(value) {
  return {into: function (array) {
    return {after: function (afterValue) {
      array.splice(array.indexOf(afterValue) + 1, 0, value);
      return array;
    }};
  }};
}

insert(2).into([1, 3]).after(1); //[1, 2, 3]

上面這個函數(shù),可以使用箭頭函數(shù)改寫振湾。

let insert = (value) => ({into:(array) => ({after: (afterValue) => {
    array.splice(array.indexOf(afterValue) + 1, 0, value);
    return array;
}})})

insert(2).into([1,3]).after(1);  // [1,2,3]

綁定this

箭頭函數(shù)可以綁定this對象杀迹,大大減少了顯式綁定this對象的寫法(call、apply押搪、bind)树酪。但是,箭頭函數(shù)并不適用于所有場合大州,所以ES7提出了“函數(shù)綁定”(function bind)運(yùn)算符续语,用來取代call、apply厦画、bind調(diào)用疮茄。雖然該語法還是ES7的一個提案,但是Babel轉(zhuǎn)碼器已經(jīng)支持根暑。

函數(shù)綁定運(yùn)算符是并排的兩個冒號(::)力试,雙冒號左邊是一個對象,右邊是一個函數(shù)排嫌。該運(yùn)算符會自動將左邊的對象懂版,作為上下文環(huán)境(即this對象),綁定到右邊的函數(shù)上面躏率。

foo::bar;
// 等同于
bar.bind(foo);

foo::bar(...argumengts);
// 等同于
bar.apply(foo,arguments);

尾調(diào)用優(yōu)化

尾調(diào)用是函數(shù)式編程的一個重要概念躯畴,本身非常簡單,一句話就能說清楚薇芝,是指某個函數(shù)的最后一步是調(diào)用另一個函數(shù)蓬抄。

function f(x) {
    return g(x);
}

上面代碼中,函數(shù)f的最后一步是調(diào)用函數(shù)g夯到,這就叫尾調(diào)用嚷缭。

以下三種情況,都不屬于尾調(diào)用耍贾。

// 情況一
function f(x){
  let y = g(x);
  return y;
}

// 情況二
function f(x){
  return g(x) + 1;
}

// 情況三
function f(x){
  g(x);
}

上面代碼中阅爽,情況一是調(diào)用函數(shù)g之后,還有賦值操作荐开,所以不屬于尾調(diào)用付翁,即使語義完全一樣。情況二也屬于調(diào)用后還有操作晃听,即使寫在一行內(nèi)百侧。情況三等同于下面的代碼砰识。

function f(x){
  g(x);
  return undefined;
}

尾調(diào)用不一定出現(xiàn)在函數(shù)尾部,只要是最后一步操作即可佣渴。

function f(x) {
  if (x > 0) {
    return m(x)
  }
  return n(x);
}

“尾調(diào)用優(yōu)化”(Tail call optimization)辫狼,即只保留內(nèi)層函數(shù)的調(diào)用幀。如果所有函數(shù)都是尾調(diào)用辛润,那么完全可以做到每次執(zhí)行時膨处,調(diào)用幀只有一項(xiàng),這將大大節(jié)省內(nèi)存砂竖。這就是“尾調(diào)用優(yōu)化”的意義灵迫。

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同于
function f() {
  return g(3);
}
f();

// 等同于
g(3);

尾遞歸
函數(shù)調(diào)用自身,稱為遞歸晦溪。如果尾調(diào)用自身,就稱為尾遞歸挣跋。

遞歸非常耗費(fèi)內(nèi)存三圆,因?yàn)樾枰瑫r保存成千上百個調(diào)用幀,很容易發(fā)生“棧溢出”錯誤(stack overflow)避咆。但對于尾遞歸來說舟肉,由于只存在一個調(diào)用幀,所以永遠(yuǎn)不會發(fā)生“棧溢出”錯誤查库。

function factorial(n) {
    if( n === 1 ) return 1;
    return n * factorial(n - 1);
}
factorial(5);

上面代碼是一個階乘函數(shù)路媚,計算n的階乘,最多需要保存n個調(diào)用記錄樊销,復(fù)雜度 O(n) 整慎。

如果改寫成尾遞歸,只保留一個調(diào)用記錄围苫,復(fù)雜度 O(1) 裤园。

function factorial(n, total) {
    if(n === 1) return total;
    return factorial(n - 1, n * total);
}
factorial(5, 1)  // 120

還有一個比較著名的例子,就是計算 Fibonacci 數(shù)列剂府,也能充分說明尾遞歸優(yōu)化的重要性拧揽。

非尾遞歸的 Fibonacci 數(shù)列實(shí)現(xiàn)如下。

function Fibonacci(n) {
    if(n <= 1) { return 1};
    return Fibonacci(n - 1) + Fibonacci(n - 2);
}
Fibonacci(10) // 89
Fibonacci(100)  // 堆棧溢出

尾遞歸優(yōu)化過的 Fibonacci 數(shù)列實(shí)現(xiàn)如下

function Fibonacci2(n, ac1 = 1, ac2 = 1) {
    if( n <= 1) { return ac2 };
    return Fibonacci2(n - 1, ac2, ac1 + ac2);
}
Fibonacci2(100)  // 573147844013817200000
Fibonacci2(1000)  // 7.0330367711422765e+208
Fibonacci2(10000)  // Infinity

由此可見腺占,“尾調(diào)用優(yōu)化”對遞歸操作意義重大淤袜,所以一些函數(shù)式編程語言將其寫入了語言規(guī)格。ES6 是如此衰伯,第一次明確規(guī)定铡羡,所有 ECMAScript 的實(shí)現(xiàn),都必須部署“尾調(diào)用優(yōu)化”意鲸。這就是說蓖墅,ES6 中只要使用尾遞歸库倘,就不會發(fā)生棧溢出,相對節(jié)省內(nèi)存论矾。

遞歸函數(shù)的改寫
尾遞歸的實(shí)現(xiàn)教翩,往往需要改寫遞歸函數(shù),確保最后一步只調(diào)用自身贪壳。做到這一點(diǎn)的方法饱亿,就是把所有用到的內(nèi)部變量改寫成函數(shù)的參數(shù)。比如上面的例子闰靴,階乘函數(shù) factorial 需要用到一個中間變量total彪笼,那就把這個中間變量改寫成函數(shù)的參數(shù)。這樣做的缺點(diǎn)就是不太直觀蚂且,第一眼很難看出來配猫,為什么計算5的階乘,需要傳入兩個參數(shù)5和1杏死?

兩個方法可以解決這個問題泵肄。方法一是在尾遞歸函數(shù)之外,再提供一個正常形式的函數(shù)淑翼。

function tailFactorial(n, total) {
  if (n === 1) return total;
  return tailFactorial(n - 1, n * total);
}

function factorial(n) {
  return tailFactorial(n, 1);
}

factorial(5) // 120

函數(shù)式編程有一個概念腐巢,叫做柯里化(currying),意思是將多參數(shù)的函數(shù)轉(zhuǎn)換成單參數(shù)的形式玄括。這里也可以使用柯里化冯丙。

function currying(fn, n) {
  return function (m) {
    return fn.call(this, m, n);
  };
}

function tailFactorial(n, total) {
  if (n === 1) return total;
  return tailFactorial(n - 1, n * total);
}

const factorial = currying(tailFactorial, 1);

factorial(5) // 120

上面代碼通過柯里化,將尾遞歸函數(shù)tailFactorial變?yōu)橹唤邮芤粋€參數(shù)的factorial遭京。

第二種方法就簡單多了胃惜,就是采用 ES6 的函數(shù)默認(rèn)值。

function factorial(n, total = 1) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5) // 120

上面代碼中哪雕,參數(shù)total有默認(rèn)值1蛹疯,所以調(diào)用時不用提供這個值。

數(shù)組的擴(kuò)展

擴(kuò)展運(yùn)算符

擴(kuò)展運(yùn)算符是三個點(diǎn)(...)热监,它好比rest參數(shù)的逆運(yùn)算捺弦,將一個數(shù)組轉(zhuǎn)為用逗號分隔的參數(shù)序列。

console.log(...[1, 2, 3])
// 1 2 3

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

該運(yùn)算符主要用于函數(shù)調(diào)用

function push(array, ...items) {
    array.push(...items);
}

function add(x, y) {
    return x + y;
}

var numbers = [4, 38];
add(...numbers)  // 42

上面代碼中孝扛,array.push(...items)和add(...numbers)這兩行列吼,都是函數(shù)的調(diào)用,它們的都使用了擴(kuò)展運(yùn)算符苦始。該運(yùn)算符將一個數(shù)組寞钥,變?yōu)閰?shù)序列。

擴(kuò)展運(yùn)算符與正常的函數(shù)參數(shù)可以結(jié)合使用陌选,非常靈活理郑。

function f(v, w, x, y, z) { }
var args = [0, 1];
f(-1, ...args, 2, ...[3]);

擴(kuò)展運(yùn)算符后面還可以放置表達(dá)式蹄溉。

const arr = [  
    ...(x > 0 ? ['a'] : []),
    'b'
];

如果擴(kuò)展運(yùn)算符后面是一個空數(shù)組,則不產(chǎn)生任何效果您炉。

[...[], 1]  // [1]

替代數(shù)組的 apply 方法
由于擴(kuò)展運(yùn)算符可以展開數(shù)組柒爵,所以不再需要 apply 方法,將數(shù)組轉(zhuǎn)為函數(shù)的參數(shù)了赚爵。

// ES5的寫法
function f(x, y, z) {
    ...
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6的寫法
function f(x, y, z) {
    ...
}
var args = [0, 1, 2];
f(...args);

下面是擴(kuò)展運(yùn)算符取代 apply 方法的一個實(shí)際的例子棉胀,應(yīng)用 Math.max 方法,簡化求出一個數(shù)組最大元素的寫法冀膝。

// ES5的寫法
Math.max.apply(null, [14, 3, 77])

// ES6的寫法
Math.max(...[14, 3, 77])

// 等同于
Math.max(14, 3, 77);

另一個例子是通過 push 函數(shù)唁奢,將一個數(shù)組添加到另一個數(shù)組的尾部。

// ES5的寫法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);

// ES6的寫法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);

上面代碼的 ES5 寫法中窝剖,push方法的參數(shù)不能是數(shù)組麻掸,所以只好通過apply方法變通使用push方法。有了擴(kuò)展運(yùn)算符赐纱,就可以直接將數(shù)組傳入push方法脊奋。

下面是另外一個例子。

// ES5
new (Date.bind.apply(Date, [null, 2015, 1, 1]))
// ES6
new Date(...[2015,1,1]);
擴(kuò)展運(yùn)算符的應(yīng)用

合并數(shù)組
擴(kuò)展運(yùn)算符提供了數(shù)組合并的新寫法千所。

// ES5
[1, 2].concat(more)
// ES6
[1, 2, ...more]

var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];

// ES5的合并數(shù)組
arr1.concat(arr2, arr3);
// ['a', 'b', 'c', 'd', 'e']

// ES6的合并數(shù)組
[...arr1, ...arr2, ...arr3]
// ['a', 'b', 'c', 'd', 'e']

與解構(gòu)賦值結(jié)合
擴(kuò)展運(yùn)算符可以與解構(gòu)賦值結(jié)合起來,用于生成數(shù)組蒜埋。

// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list

下面是另外一些例子淫痰。

const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]

const [first, ...rest] = [];
first // undefined
rest // []

const [first, ...rest] = ["foo"];
first // "foo"
rest // []

如果將擴(kuò)展運(yùn)算符用于數(shù)組賦值,只能放在參數(shù)的最后一位整份,否則會報錯待错。

const [...butLast, last] = [1, 2, 3, 4, 5];
// 報錯

const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 報錯

函數(shù)的返回值
JavaScript的函數(shù)只能返回一個值,如果需要返回多個值烈评,只能返回數(shù)組或?qū)ο蠡鸲怼U(kuò)展運(yùn)算符提供了解決這個問題的一種變通方法。

var dateFields = readDateFields(database);
var d = new Date(...dateFields);

上面代碼從數(shù)據(jù)庫取出一行數(shù)據(jù)讲冠,通過擴(kuò)展運(yùn)算符瓜客,直接將其傳入構(gòu)造函數(shù)Date。

字符串
擴(kuò)展運(yùn)算符還可以將字符串轉(zhuǎn)為真正的數(shù)組竿开。

[...'hello']
// ["h", "e", "l", "l", "o"]

實(shí)現(xiàn)了 Iterator 接口的對象
任何 Iterator 接口的對象谱仪,都可以用擴(kuò)展運(yùn)算符轉(zhuǎn)為真正的數(shù)組。

var nodeList = document.querySelectorAll('div');
var array = [...nodeList];

上面代碼中否彩,querySelectorAll方法返回的是一個nodeList對象疯攒。它不是數(shù)組,而是一個類似數(shù)組的對象列荔。這時敬尺,擴(kuò)展運(yùn)算符可以將其轉(zhuǎn)為真正的數(shù)組枚尼,原因就在于NodeList對象實(shí)現(xiàn)了 Iterator 。

Map 和 Set 結(jié)構(gòu)砂吞,Generator 函數(shù)
擴(kuò)展運(yùn)算符內(nèi)部調(diào)用的是數(shù)據(jù)結(jié)構(gòu)的 Iterator 接口署恍,因此只要具有 Iterator 接口的對象,都可以使用擴(kuò)展運(yùn)算符呜舒,比如 Map 結(jié)構(gòu)锭汛。

let map = new Map([ 
    [1, 'one'],
    [2, 'two'],
    [3, 'three'],
]);
let arr = [...map.keys()];  // [1, 2, 3]

Generator 函數(shù)運(yùn)行后,返回一個遍歷器對象袭蝗,因此也可以使用擴(kuò)展運(yùn)算符唤殴。

var go = function*(){
  yield 1;
  yield 2;
  yield 3;
};
[...go()] // [1, 2, 3]

上面代碼中,變量go是一個 Generator 函數(shù)到腥,執(zhí)行后返回的是一個遍歷器對象朵逝,對這個遍歷器對象執(zhí)行擴(kuò)展運(yùn)算符,就會將內(nèi)部遍歷得到的值乡范,轉(zhuǎn)為一個數(shù)組配名。

Array.from()

Array.from方法用于將兩類對象轉(zhuǎn)為真正的數(shù)組:類似數(shù)組的對象(array-likeobject)和可遍歷(iterable)的對象(包括ES6新增的數(shù)據(jù)結(jié)構(gòu)Set和Map)。

下面是一個類似數(shù)組的對象晋辆,Array.from將它轉(zhuǎn)為真正的數(shù)組渠脉。

let arrayLike = {
    '0':'a',
    '1':'b',
    '2':'c',
    length: 3
};

// ES5的寫法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的寫法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

如果參數(shù)是一個真正的數(shù)組,Array.from會返回一個一模一樣的新數(shù)組瓶佳。
Array.from([1, 2, 3]) // [1, 2, 3]

Array.from方法還支持類似數(shù)組的對象芋膘。所謂類似數(shù)組的對象持灰,本質(zhì)特征只有一點(diǎn)譬嚣,即必須有l(wèi)ength屬性狼钮。因此野芒,任何有l(wèi)ength屬性的對象蝇率,都可以通過Array.from方法轉(zhuǎn)為數(shù)組

Array.of()

Array.of方法用于將一組值蜕猫,轉(zhuǎn)換為數(shù)組祭阀。

Array.of(3, 11, 8)  // [3, 11, 8]
Array.of(3)  // [3]
Array.of(3).length  // 1

這個方法的主要目的惦界,是彌補(bǔ)數(shù)組構(gòu)造函數(shù)Array()的不足傻工。因?yàn)閰?shù)個數(shù)的不同霞溪,會導(dǎo)致Array()的行為有差異。

Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]

上面代碼中中捆,Array方法沒有參數(shù)威鹿、一個參數(shù)、三個參數(shù)時轨香,返回結(jié)果都不一樣忽你。只有當(dāng)參數(shù)個數(shù)不少于2個時,Array()才會返回由參數(shù)組成的新數(shù)組臂容。參數(shù)個數(shù)只有一個時科雳,實(shí)際上是指定數(shù)組的長度根蟹。

Array.of基本上可以用來替代Array()或new Array(),并且不存在由于參數(shù)不同而導(dǎo)致的重載糟秘。它的行為非常統(tǒng)一简逮。

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

Array.of總是返回參數(shù)值組成的數(shù)組。如果沒有參數(shù)尿赚,就返回一個空數(shù)組散庶。

數(shù)組實(shí)例的 copyWithin()

數(shù)組實(shí)例的 copyWithin 方法,在當(dāng)前數(shù)組內(nèi)部凌净,將指定位置的成員復(fù)制到其他位置(會覆蓋原有成員)悲龟,然后返回當(dāng)前數(shù)組。也就是說冰寻,使用這個方法须教,會修改當(dāng)前數(shù)組。

Array.prototype.copyWithin(target, start = 0, end = this.length)

它接受三個參數(shù):
target(必需):從該位置開始替換數(shù)據(jù)斩芭。
start(可選):從該位置開始讀取數(shù)據(jù)轻腺,默認(rèn)為0。如果為負(fù)值划乖,表示倒數(shù)贬养。
end(可選):到該位置前停止讀取數(shù)據(jù),默認(rèn)等于數(shù)組長度琴庵。如果為負(fù)值误算,表示倒數(shù)。

這三個參數(shù)都應(yīng)該是數(shù)值细卧,如果不是尉桩,會自動轉(zhuǎn)為數(shù)值筒占。

[1, 2, 3, 4, 5].copyWithin(0, 3)  // [4, 5, 3, 4, 5]

上面代碼表示將從3號位直到數(shù)據(jù)結(jié)束的成員(4和5)贪庙,復(fù)制到從0號位開始的位置,結(jié)果覆蓋了原來的1和2翰苫。

下面是更多例子

// 將3號位復(fù)制到0號位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]

// -2相當(dāng)于3號位止邮,-1相當(dāng)于4號位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]

// 將3號位復(fù)制到0號位
[].copyWithin.call({length: 5, 3: 1}, 0, 3)
// {0: 1, 3: 1, length: 5}

// 將2號位到數(shù)組結(jié)束,復(fù)制到0號位
var i32a = new Int32Array([1, 2, 3, 4, 5]);
i32a.copyWithin(0, 2);
// Int32Array [3, 4, 5, 4, 5]

// 對于沒有部署 TypedArray 的 copyWithin 方法的平臺
// 需要采用下面的寫法
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
// Int32Array [4, 2, 3, 4, 5]

數(shù)組實(shí)例的 find() 和 findIndex()

數(shù)組實(shí)例的 find 方法奏窑,用于找出第一個符合條件的數(shù)組成員导披。它的參數(shù)是一個回調(diào)函數(shù),所有數(shù)組成員依次執(zhí)行該回調(diào)函數(shù)埃唯,直到找出第一個返回值為 true 的成員撩匕,然后返回該成員。如果沒有符合條件的成員墨叛,則返回 undefined

[1, 4, -5, 10].find((n) => n < 0)  // -5

上面代碼找出數(shù)組中第一個小于0的成員止毕。

[1, 5, 10, 15].find(function(value, index, arr) {
    return value > 9;
})
// 10

上面代碼中模蜡,find 方法的回調(diào)函數(shù)可以接受三個參數(shù),依次為當(dāng)前的值扁凛、當(dāng)前的位置和原數(shù)組忍疾。

數(shù)組實(shí)例的 findIndex 方法的用法與 find 方法非常類似,返回第一個符合條件的數(shù)組成員的位置谨朝,如果所有成員都不符合條件卤妒,則返回-1。

[1, 5, 10, 15].findIndex(function(value, index, arr) {
    return value > 9;
})
// 2

這兩個方法都可以接受第二個參數(shù)字币,用來綁定回調(diào)函數(shù)的this對象则披。

另外,這兩個方法都可以發(fā)現(xiàn)NaN纬朝,彌補(bǔ)了數(shù)組的IndexOf方法的不足收叶。

[NaN].indexOf(NaN)
// -1

[NaN].findIndex(y => Object.is(NaN, y))
// 0

上面代碼中,indexOf方法無法識別數(shù)組的NaN成員共苛,但是findIndex方法可以借助Object.is方法做到判没。

數(shù)組實(shí)例的fill()

fill方法使用給定值,填充一個數(shù)組隅茎。

['a','b','c'].fill(7)
// [7, 7, 7]

new Array(3).fill(7)
// [7, 7, 7]

上面代碼表明澄峰,fill方法用于空數(shù)組的初始化非常方便。數(shù)組中已有的元素辟犀,會被全部抹去俏竞。

fill方法還可以接受第二個和第三個參數(shù),用于指定填充的起始位置和結(jié)束位置堂竟。

['a','b','c'].fill(7, 1, 2)
// ['a',7,'c']

數(shù)組實(shí)例的 entries(), keys() 和 values()

ES6 提供三個新的方法——entries()魂毁,keys()和values()——用于遍歷數(shù)組。它們都返回一個遍歷器對象(詳見《Iterator》一章)出嘹,可以用for...of循環(huán)進(jìn)行遍歷席楚,唯一的區(qū)別是keys()是對鍵名的遍歷、values()是對鍵值的遍歷税稼,entries()是對鍵值對的遍歷烦秩。

for(let index of ['a', 'b'].keys()) {
    console.log(index);
}
// 0
// 1

for(let elem of ['a', 'b'].values()) {
    console.log(elem);
}
// 'a'
// 'b'

for(let [index, elem] of ['a', 'b'].entries()) {
    console.log(index, elem);
}
// 0 "a"
// 1 "b"

數(shù)組實(shí)例的 includes()

Array.prototype.includes 方法返回一個布爾值,表示某個數(shù)組是否包含給定的值郎仆,與字符串的 includes 方法類似只祠。ES2016 引入了該方法。

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true

該方法的第二個參數(shù)表示搜索的起始位置扰肌,默認(rèn)為0抛寝。如果第二個參數(shù)為負(fù)數(shù),則表示倒數(shù)的位置,如果這時它大于數(shù)組長度(比如第二個參數(shù)為-4盗舰,但數(shù)組長度為3)猴凹,則會重置為從0開始。

[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true

數(shù)組的空位

數(shù)組的空位指岭皂,數(shù)組的某一個位置沒有任何值郊霎。比如,Array構(gòu)造函數(shù)返回的數(shù)組都是空位爷绘。
Array(3) // [, , ,]
上面代碼中书劝,Array(3)返回一個具有3個空位的數(shù)組。

注意土至,空位不是undefined购对,一個位置的值等于undefined,依然是有值的陶因÷獍空位是沒有任何值,in運(yùn)算符可以說明這一點(diǎn)楷扬。

0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false

上面代碼說明解幽,第一個數(shù)組的0號位置是有值的,第二個數(shù)組的0號位置沒有值烘苹。

ES5 對空位的處理躲株,已經(jīng)很不一致了,大多數(shù)情況下會忽略空位:

  • forEach(), filter(), every() 和some()都會跳過空位镣衡。

  • map()會跳過空位霜定,但會保留這個值

  • join()和toString()會將空位視為undefined,而undefined和null會被處理成空字符串廊鸥。

    // forEach方法
    [,'a'].forEach((x,i) => console.log(i)); // 1

    // filter方法
    ['a',,'b'].filter(x => true) // ['a','b']

    // every方法
    [,'a'].every(x => x==='a') // true

    // some方法
    [,'a'].some(x => x !== 'a') // false

    // map方法
    [,'a'].map(x => 1) // [,1]

    // join方法
    [,'a',undefined,null].join('#') // "#a##"

    // toString方法
    [,'a',undefined,null].toString() // ",a,,"

ES6 則是明確將空位轉(zhuǎn)為undefined望浩。

Array.from方法會將數(shù)組的空位,轉(zhuǎn)為undefined惰说,也就是說磨德,這個方法不會忽略空位。

Array.from(['a',,'b'])
// [ "a", undefined, "b" ]

擴(kuò)展運(yùn)算符(...)也會將空位轉(zhuǎn)為undefined助被。

[...['a',,'b']]
// [ "a", undefined, "b" ]

copyWithin()會連空位一起拷貝剖张。

    [,'a','b',,].copyWithin(2,0) // [,"a",,"a"]

fill()會將空位視為正常的數(shù)組位置切诀。

new Array(3).fill('a') // ["a","a","a"]

for...of循環(huán)也會遍歷空位揩环。

let arr = [, ,];
for (let i of arr) {
  console.log(1);
}
// 1
// 1

entries()、keys()幅虑、values()丰滑、find()和findIndex()會將空位處理成undefined。

// entries()
[...[,'a'].entries()] // [[0,undefined], [1,"a"]]

// keys()
[...[,'a'].keys()] // [0,1]

// values()
[...[,'a'].values()] // [undefined,"a"]

// find()
[,'a'].find(x => true) // undefined

// findIndex()
[,'a'].findIndex(x => true) // 0

由于空位的處理規(guī)則非常不統(tǒng)一,所以建議避免出現(xiàn)空位褒墨。

對象的擴(kuò)展

屬性的簡潔表示法

ES6 允許直接寫入變量和函數(shù)炫刷,作為對象的屬性和方法。這樣的書寫更加簡潔郁妈。

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

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

上面代碼表明浑玛,ES6 允許在對象之中,直接寫變量噩咪。這時顾彰,屬性名為變量名, 屬性值為變量的值。下面是另一個例子胃碾。

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

// 等同于

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

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

除了屬性簡寫涨享,方法也可以簡寫。

var o = {
  method() {
    return "Hello!";
  }
};

// 等同于

var o = {
  method: function() {
    return "Hello!";
  }
};

下面是一個實(shí)際的例子仆百。

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

這種寫法用于函數(shù)的返回值厕隧,將會非常方便。

function getPoint() {
    var x = 1;
    var y = 10;
    return {x, y};
}
getPoint();  // {x: 1, y: 10}

屬性名表達(dá)式

JavaScript語言定義對象的屬性俄周,有兩種方法吁讨。

// 方法一
obj.foo = true;

// 方法二
obj['a' + 'bc'] = 123;

上面代碼的方法一是直接用標(biāo)識符作為屬性名,方法二是用表達(dá)式作為屬性名峦朗,這時要將表達(dá)式放在方括號之內(nèi)挡爵。

但是,如果使用字面量方式定義對象(使用大括號)甚垦,在 ES5 中只能使用方法一(標(biāo)識符)定義屬性茶鹃。

var obj = {
  foo: true,
  abc: 123
};

ES6 允許字面量定義對象時,用方法二(表達(dá)式)作為對象的屬性名艰亮,即把表達(dá)式放在方括號內(nèi)闭翩。

let propKey = 'foo';

let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};

下面是另一個例子。

var lastWord = 'last word';

var a = {
  'first word': 'hello',
  [lastWord]: 'world'
};

a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"

表達(dá)式還可以用于定義方法名迄埃。

let obj = {
  ['h' + 'ello']() {
    return 'hi';
  }
};

obj.hello() // hi

注意疗韵,屬性名表達(dá)式與簡潔表示法,不能同時使用侄非,會報錯蕉汪。

// 報錯
var foo = 'bar';
var bar = 'abc';
var baz = { [foo] };

// 正確
var foo = 'bar';
var baz = { [foo]: 'abc'};

注意,屬性名表達(dá)式如果是一個對象逞怨,默認(rèn)情況下會自動將對象轉(zhuǎn)為字符串[object Object]者疤,這一點(diǎn)要特別小心。

const keyA = {a: 1};
const keyB = {b: 2};

const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB'
};

myObject // Object {[object Object]: "valueB"}

上面代碼中叠赦,[keyA]和[keyB]得到的都是[object Object]驹马,所以[keyB]會把[keyA]覆蓋掉,而myObject最后只有一個[object Object]屬性。

方法的 name 屬性

函數(shù)的name屬性糯累,返回函數(shù)名算利。對象方法也是函數(shù),因此也有name屬性泳姐。

const person = {
  sayName() {
    console.log('hello!');
  },
};

person.sayName.name   // "sayName"

上面代碼中效拭,方法的name屬性返回函數(shù)名(即方法名)。

如果對象的方法使用了取值函數(shù)(getter)和存值函數(shù)(setter)胖秒,則name屬性不是在該方法上面允耿,而是該方法的屬性的描述對象的get和set屬性上面,返回值是方法名前加上get和set扒怖。

const obj = {
  get foo() {},
  set foo(x) {}
};

obj.foo.name
// TypeError: Cannot read property 'name' of undefined

const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');

descriptor.get.name // "get foo"
descriptor.set.name // "set foo"

有兩種特殊情況:bind方法創(chuàng)造的函數(shù)较锡,name屬性返回bound加上原函數(shù)的名字;Function構(gòu)造函數(shù)創(chuàng)造的函數(shù)盗痒,name屬性返回anonymous蚂蕴。

(new Function()).name // "anonymous"

var doSomething = function() {
  // ...
};
doSomething.bind().name // "bound doSomething"

Object.is()

ES5比較兩個值是否相等,只有兩個運(yùn)算符:相等運(yùn)算符(==)和嚴(yán)格相等運(yùn)算符(===)俯邓。它們都有缺點(diǎn)骡楼,前者會自動轉(zhuǎn)換數(shù)據(jù)類型,后者的NaN不等于自身稽鞭,以及+0等于-0鸟整。JavaScript缺乏一種運(yùn)算,在所有環(huán)境中朦蕴,只要兩個值是一樣的篮条,它們就應(yīng)該相等。

ES6提出“Same-value equality”(同值相等)算法吩抓,用來解決這個問題涉茧。Object.is就是部署這個算法的新方法。它用來比較兩個值是否嚴(yán)格相等疹娶,與嚴(yán)格比較運(yùn)算符(===)的行為基本一致伴栓。

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

不同之處只有兩個:一是+0不等于-0,二是NaN等于自身雨饺。

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

Object.assign()

Object.assign方法用于對象的合并钳垮,將源對象(source)的所有可枚舉屬性,復(fù)制到目標(biāo)對象(target)额港。

var target = { a: 1 };

var source1 = { b: 2 };
var source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

Object.assign方法的第一個參數(shù)是目標(biāo)對象饺窿,后面的參數(shù)都是源對象。

如果目標(biāo)對象與源對象有同名屬性锹安,或多個源對象有同名屬性短荐,則后面的屬性會覆蓋前面的屬性。

var target = { a: 1, b: 1 };

var source1 = { b: 2, c: 2 };
var source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

Object.assign方法實(shí)行的是淺拷貝叹哭,而不是深拷貝忍宋。也就是說,如果源對象某個屬性的值是對象风罩,那么目標(biāo)對象拷貝得到的是這個對象的引用糠排。

var obj1 = {a: {b: 1}};
var obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2

上面代碼中,源對象obj1的a屬性的值是一個對象超升,Object.assign拷貝得到的是這個對象的引用入宦。這個對象的任何變化,都會反映到目標(biāo)對象上面室琢。

Object.assign 常見用途

為對象添加屬性

class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}

上面方法通過Object.assign方法乾闰,將x屬性和y屬性添加到Point類的對象實(shí)例。

為對象添加方法

Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});

// 等同于下面的寫法
SomeClass.prototype.someMethod = function (arg1, arg2) {
  ···
};
SomeClass.prototype.anotherMethod = function () {
  ···
};

上面代碼使用了對象屬性的簡潔表示法盈滴,直接將兩個函數(shù)放在大括號中涯肩,再使用assign方法添加到SomeClass.prototype之中。

克隆對象

function clone(origin) {
  return Object.assign({}, origin);
}

上面代碼將原始對象拷貝到一個空對象巢钓,就得到了原始對象的克隆病苗。

不過,采用這種方法克隆症汹,只能克隆原始對象自身的值硫朦,不能克隆它繼承的值。如果想要保持繼承鏈背镇,可以采用下面的代碼咬展。

function clone(origin) {
  let originProto = Object.getPrototypeOf(origin);
  return Object.assign(Object.create(originProto), origin);
}

未完不續(xù)...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瞒斩,隨后出現(xiàn)的幾起案子挚赊,更是在濱河造成了極大的恐慌,老刑警劉巖济瓢,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荠割,死亡現(xiàn)場離奇詭異,居然都是意外死亡旺矾,警方通過查閱死者的電腦和手機(jī)蔑鹦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箕宙,“玉大人嚎朽,你說我怎么就攤上這事〖砼粒” “怎么了哟忍?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵狡门,是天一觀的道長。 經(jīng)常有香客問我锅很,道長其馏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任爆安,我火速辦了婚禮叛复,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扔仓。我一直安慰自己褐奥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布翘簇。 她就那樣靜靜地躺著撬码,像睡著了一般。 火紅的嫁衣襯著肌膚如雪版保。 梳的紋絲不亂的頭發(fā)上耍群,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音找筝,去河邊找鬼蹈垢。 笑死,一個胖子當(dāng)著我的面吹牛袖裕,可吹牛的內(nèi)容都是我干的曹抬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼急鳄,長吁一口氣:“原來是場噩夢啊……” “哼谤民!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起疾宏,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤张足,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后坎藐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體为牍,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年岩馍,在試婚紗的時候發(fā)現(xiàn)自己被綠了碉咆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛀恩,死狀恐怖疫铜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情双谆,我是刑警寧澤壳咕,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布席揽,位于F島的核電站,受9級特大地震影響谓厘,放射性物質(zhì)發(fā)生泄漏幌羞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一庞呕、第九天 我趴在偏房一處隱蔽的房頂上張望新翎。 院中可真熱鬧程帕,春花似錦住练、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至岭埠,卻和暖如春盏混,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背惜论。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工许赃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人馆类。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓混聊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親乾巧。 傳聞我的和親對象是個殘疾皇子句喜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355