- 前言:這是學(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ù)...