ES6對各類基本類型都作了擴展,內容有些多,本章節挑選比較重要的擴展說明。html
傳統上,JavaScript只有indexOf
方法,能夠用來肯定一個字符串是否包含在另外一個字符串中。ES6又提供了三種新方法。es6
includes()
:返回布爾值,表示是否找到了參數字符串。startsWith()
:返回布爾值,表示參數字符串是否在原字符串的頭部。endsWith()
:返回布爾值,表示參數字符串是否在原字符串的尾部。var s = 'Hello world!'; s.startsWith('Hello') // true s.endsWith('!') // true s.includes('o') // true
這三個方法都支持第二個參數,表示開始搜索的位置。正則表達式
var s = 'Hello world!'; s.startsWith('world', 6) // true s.endsWith('Hello', 5) // true s.includes('Hello', 6) // false
上面代碼表示,使用第二個參數n時,endsWith
的行爲與其餘兩個方法有所不一樣。它針對前n個字符,而其餘兩個方法針對從第n個位置直到字符串結束。數組
repeat方法返回一個新字符串,表示將原字符串重複n次。app
'x'.repeat(3) // "xxx" 'hello'.repeat(2) // "hellohello" 'na'.repeat(0) // ""
參數若是是小數,會被取整。模塊化
'na'.repeat(2.9) // "nana"
若是repeat的參數是負數或者Infinity,會報錯。函數
'na'.repeat(Infinity) // RangeError 'na'.repeat(-1) // RangeError
可是,若是參數是0到-1之間的小數,則等同於0,這是由於會先進行取整運算。0到-1之間的小數,取整之後等於-0,repeat視同爲0。工具
'na'.repeat(-0.9) // ""
參數NaN等同於0。優化
'na'.repeat(NaN) // ""
若是repeat的參數是字符串,則會先轉換成數字。this
'na'.repeat('na') // "" 'na'.repeat('3') // "nanana"
這個功能應該是最值得介紹的了,由於有了這個,咱們能夠拋棄以前用 +
號拼接字符串了。
傳統的JavaScript語言,輸出模板一般是這樣寫的。
$('#result').append( 'There are <b>' + basket.count + '</b> ' + 'items in your basket, ' + '<em>' + basket.onSale + '</em> are on sale!' );
上面這種寫法至關繁瑣不方便,並且改動麻煩,ES6引入了模板字符串解決這個問題。
$('#result').append(` There are <b>${basket.count}</b> items in your basket, <em>${basket.onSale}</em> are on sale! `);
模板字符串(template string)是加強版的字符串,用反引號(`)標識。它能夠看成普通字符串使用,也能夠用來定義多行字符串,或者在字符串中嵌入變量。
// 普通字符串 `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}?`
上面代碼中的模板字符串,都是用反引號表示。若是在模板字符串中須要使用反引號,則前面要用反斜槓轉義。
var greeting = `\`Yo\` World!`;
若是使用模板字符串表示多行字符串,全部的空格和縮進都會被保留在輸出之中。
$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `);
上面代碼中,全部模板字符串的空格和換行,都是被保留的,好比<ul>標籤前面會有一個換行。若是你不想要這個換行,可使用trim方法消除它。
$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `.trim());
模板字符串中嵌入變量,須要將變量名寫在${}之中。
function authorize(user, action) { if (!user.hasPrivilege(action)) { //傳統寫法爲 //return "str:" + a + "XXXX"; return `str: ${a} XXXX`); } }
大括號內部能夠放入任意的JavaScript表達式,能夠進行運算,以及引用對象屬性。
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"
模板字符串之中還能調用函數。
function fn() { return "Hello World"; } `foo ${fn()} bar` // foo Hello World bar
若是大括號中的值不是字符串,將按照通常的規則轉爲字符串。好比,大括號中是一個對象,將默認調用對象的toString
方法。
若是模板字符串中的變量沒有聲明,將報錯。
// 變量place沒有聲明 var msg = `Hello, ${place}`; // 報錯
因爲模板字符串的大括號內部,就是執行JavaScript代碼,所以若是大括號內部是一個字符串,將會原樣輸出。
`Hello ${'World'}` // "Hello World"
模板字符串甚至還能嵌套。
const tmpl = addrs => ` <table> ${addrs.map(addr => ` <tr><td>${addr.first}</td></tr> <tr><td>${addr.last}</td></tr> `).join('')} </table> `; //上面代碼中,模板字符串的變量之中,又嵌入了另外一個模板字符串,使用方法以下。 const data = [ { first: '<Jane>', last: 'Bond' }, { first: 'Lars', last: '<Croft>' }, ]; console.log(tmpl(data)); // <table> // // <tr><td><Jane></td></tr> // <tr><td>Bond</td></tr> // // <tr><td>Lars</td></tr> // <tr><td><Croft></td></tr> // // </table>
ES6對字符串還有許多擴展,例如 對 字符Unicode表示的擴充以及爲字符串提供了遍歷方法(for ... of
)
詳情請點擊 http://es6.ruanyifeng.com/#do...
在 ES5 中,RegExp構造函數的參數有兩種狀況。
第一種狀況是,參數是字符串,這時第二個參數表示正則表達式的修飾符(flag)。
var regex = new RegExp('xyz', 'i'); // 等價於 var regex = /xyz/i;
第二種狀況是,參數是一個正則表示式,這時會返回一個原有正則表達式的拷貝。
var regex = new RegExp(/xyz/i); // 等價於 var regex = /xyz/i;
可是,ES5 不容許此時使用第二個參數添加修飾符,不然會報錯。
var regex = new RegExp(/xyz/, 'i'); // Uncaught TypeError: Cannot supply flags when constructing one RegExp from another
ES6 改變了這種行爲。若是RegExp構造函數第一個參數是一個正則對象,那麼可使用第二個參數指定修飾符。並且,返回的正則表達式會忽略原有的正則表達式的修飾符,只使用新指定的修飾符。
new RegExp(/abc/ig, 'i').flags // "i"
上面代碼中,原有正則對象的修飾符是ig,它會被第二個參數i覆蓋。
ES6 對正則表達式添加了u修飾符,含義爲「Unicode模式」,用來正確處理大於uFFFF的 Unicode 字符。也就是說,會正確處理四個字節的 UTF-16 編碼。
/^\uD83D/u.test('\uD83D\uDC2A') // false /^\uD83D/.test('\uD83D\uDC2A') // true
上面代碼中,\uD83D\uDC2A
是一個四個字節的 UTF-16 編碼,表明一個字符。可是,ES5 不支持四個字節的 UTF-16 編碼,會將其識別爲兩個字符,致使第二行代碼結果爲true。加了u
修飾符之後,ES6 就會識別其爲一個字符,因此第一行代碼結果爲false。
除了u修飾符,ES6 還爲正則表達式添加了y修飾符,叫作「粘連」(sticky)修飾符。
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
上面代碼有兩個正則表達式,一個使用g
修飾符,另外一個使用y
修飾符。這兩個正則表達式各執行了兩次,第一次執行的時候,二者行爲相同,剩餘字符串都是_aa_a
。因爲g
修飾沒有位置要求,因此第二次執行會返回結果,而y
修飾符要求匹配必須從頭部開始,因此返回null。
若是改一下正則表達式,保證每次都能頭部匹配,y
修飾符就會返回結果了。
var s = 'aaa_aa_a'; var r = /a+_/y; r.exec(s) // ["aaa_"] r.exec(s) // ["aa_"]
ES6 在Number對象上,新提供了Number.isFinite()和Number.isNaN()兩個方法。
Number.isFinite()
用來檢查一個數值是否爲有限的(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
ES5 能夠經過下面的代碼,部署Number.isFinite
方法。
(function (global) { var global_isFinite = global.isFinite; Object.defineProperty(Number, 'isFinite', { value: function isFinite(value) { return typeof value === 'number' && global_isFinite(value); }, configurable: true, enumerable: false, writable: true }); })(this);
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
ES5 經過下面的代碼,部署Number.isNaN()
。
(function (global) { var global_isNaN = global.isNaN; Object.defineProperty(Number, 'isNaN', { value: function isNaN(value) { return typeof value === 'number' && global_isNaN(value); }, configurable: true, enumerable: false, writable: true }); })(this);
它們與傳統的全局方法isFinite()
和isNaN()
的區別在於,傳統方法先調用Number()
將非數值的值轉爲數值,再進行判斷,而這兩個新方法只對數值有效,Number.isFinite()
對於非數值一概返回false, Number.isNaN()
只有對於NaN才返回true,非NaN一概返回false。
isFinite(25) // true isFinite("25") // true Number.isFinite(25) // true Number.isFinite("25") // false isNaN(NaN) // true isNaN("NaN") // true Number.isNaN(NaN) // true Number.isNaN("NaN") // false Number.isNaN(1) // false
ES6 將全局方法parseInt()和parseFloat(),移植到Number對象上面,行爲徹底保持不變。
// ES5的寫法 parseInt('12.34') // 12 parseFloat('123.45#') // 123.45 // ES6的寫法 Number.parseInt('12.34') // 12 Number.parseFloat('123.45#') // 123.45
這樣作的目的,是逐步減小全局性方法,使得語言逐步模塊化。
Number.parseInt === parseInt // true Number.parseFloat === parseFloat // true
Number.isInteger()用來判斷一個值是否爲整數。須要注意的是,在 JavaScript 內部,整數和浮點數是一樣的儲存方法,因此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
ES5 能夠經過下面的代碼,部署Number.isInteger()
。
(function (global) { var floor = Math.floor, isFinite = global.isFinite; Object.defineProperty(Number, 'isInteger', { value: function isInteger(value) { return typeof value === 'number' && isFinite(value) && floor(value) === value; }, configurable: true, enumerable: false, writable: true }); })(this);
基本用法
ES6 以前,不能直接爲函數的參數指定默認值,只能採用變通的方法。
function log(x, y) { y = y || 'World'; console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello World
上面代碼檢查函數log的參數y有沒有賦值,若是沒有,則指定默認值爲World。這種寫法的缺點在於,若是參數y賦值了,可是對應的布爾值爲false,則該賦值不起做用。就像上面代碼的最後一行,參數y等於空字符,結果被改成默認值。
爲了不這個問題,一般須要先判斷一下參數y是否被賦值,若是沒有,再等於默認值。
if (typeof y === 'undefined') { y = 'World'; }
ES6 容許爲函數的參數設置默認值,即直接寫在參數定義的後面。
function log(x, y = 'World') { console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello
能夠看到,ES6 的寫法比 ES5 簡潔許多,並且很是天然。
除了簡潔,ES6 的寫法還有兩個好處:首先,閱讀代碼的人,能夠馬上意識到哪些參數是能夠省略的,不用查看函數體或文檔;其次,有利於未來的代碼優化,即便將來的版本在對外接口中,完全拿掉這個參數,也不會致使之前的代碼沒法運行。
參數變量是默認聲明的,因此不能用let或const再次聲明。
function foo(x = 5) { let x = 1; // error const x = 2; // error }
上面代碼中,參數變量x是默認聲明的,在函數體中,不能用let或const再次聲明,不然會報錯。
使用參數默認值時,函數不能有同名參數。
// 不報錯 function foo(x, x, y) { // ... } // 報錯 function foo(x, x, y = 1) { // ... } // SyntaxError: Duplicate parameter name not allowed in this context
另外,一個容易忽略的地方是,參數默認值不是傳值的,而是每次都從新計算默認值表達式的值。也就是說,參數默認值是惰性求值的。
let x = 99; function foo(p = x + 1) { console.log(p); } foo() // 100 x = 100; foo() // 101
上面代碼中,參數p的默認值是x + 1。這時,每次調用函數foo,都會從新計算x + 1,而不是默認p等於 100。
參數默認值的位置
一般狀況下,定義了默認值的參數,應該是函數的尾參數。由於這樣比較容易看出來,到底省略了哪些參數。若是非尾部的參數設置默認值,實際上這個參數是無法省略的。
// 例一 function f(x = 1, y) { return [x, y]; } f() // [1, undefined] f(2) // [2, undefined]) f(, 1) // 報錯 f(undefined, 1) // [1, 1] // 例二 function f(x, y = 5, z) { return [x, y, z]; } f() // [undefined, 5, undefined] f(1) // [1, 5, undefined] f(1, ,2) // 報錯 f(1, undefined, 2) // [1, 5, 2]
上面代碼中,有默認值的參數都不是尾參數。這時,沒法只省略該參數,而不省略它後面的參數,除非顯式輸入undefined。
若是傳入undefined,將觸發該參數等於默認值,null則沒有這個效果。
function foo(x = 5, y = 6) { console.log(x, y); } foo(undefined, null) // 5 null
上面代碼中,x參數對應undefined,結果觸發了默認值,y參數等於null,就沒有觸發默認值。
函數的 length 屬性
指定了默認值之後,函數的length屬性,將返回沒有指定默認值的參數個數。也就是說,指定了默認值後,length屬性將失真。
(function (a) {}).length // 1 (function (a = 5) {}).length // 0 (function (a, b, c = 5) {}).length // 2
上面代碼中,length屬性的返回值,等於函數的參數個數減去指定了默認值的參數個數。好比,上面最後一個函數,定義了3個參數,其中有一個參數c指定了默認值,所以length屬性等於3減去1,最後獲得2。
這是由於length屬性的含義是,該函數預期傳入的參數個數。某個參數指定默認值之後,預期傳入的參數個數就不包括這個參數了。同理,後文的 rest 參數也不會計入length屬性。
(function(...args) {}).length // 0
若是設置了默認值的參數不是尾參數,那麼length屬性也再也不計入後面的參數了。
(function (a = 0, b, c) {}).length // 0 (function (a, b = 1, c) {}).length // 1
做用域
一旦設置了參數的默認值,函數進行聲明初始化時,參數會造成一個單獨的做用域(context)。等到初始化結束,這個做用域就會消失。這種語法行爲,在不設置參數默認值時,是不會出現的。
var x = 1; function f(x, y = x) { console.log(y); } f(2) // 2
上面代碼中,參數y的默認值等於變量x。調用函數f時,參數造成一個單獨的做用域。在這個做用域裏面,默認值變量x指向第一個參數x,而不是全局變量x,因此輸出是2。
再看下面的例子。
let x = 1; function f(y = x) { let x = 2; console.log(y); } f() // 1
上面代碼中,函數f調用時,參數y = x造成一個單獨的做用域。這個做用域裏面,變量x自己沒有定義,因此指向外層的全局變量x。函數調用時,函數體內部的局部變量x影響不到默認值變量x。
若是此時,全局變量x不存在,就會報錯。
function f(y = x) { let x = 2; console.log(y); } f() // ReferenceError: x is not defined
下面這樣寫,也會報錯。
var x = 1; function foo(x = x) { // ... } foo() // ReferenceError: x is not defined
上面代碼中,參數x = x造成一個單獨做用域。實際執行的是let x = x,因爲暫時性死區的緣由,這行代碼會報錯」x 未定義「。
ES6 引入 rest 參數(形式爲...變量名),用於獲取函數的多餘參數,這樣就不須要使用arguments對象了。rest 參數搭配的變量是一個數組,該變量將多餘的參數放入數組中。
function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; } add(2, 5, 3) // 10
上面代碼的add函數是一個求和函數,利用 rest 參數,能夠向該函數傳入任意數目的參數。
下面是一個 rest 參數代替arguments變量的例子。
// arguments變量的寫法 function sortNumbers() { return Array.prototype.slice.call(arguments).sort(); } // rest參數的寫法 const sortNumbers = (...numbers) => numbers.sort();
上面代碼的兩種寫法,比較後能夠發現,rest 參數的寫法更天然也更簡潔。
rest 參數中的變量表明一個數組,因此數組特有的方法均可以用於這個變量。下面是一個利用 rest 參數改寫數組push方法的例子。
function push(array, ...items) { items.forEach(function(item) { array.push(item); console.log(item); }); } var a = []; push(a, 1, 2, 3)
注意,rest 參數以後不能再有其餘參數(即只能是最後一個參數),不然會報錯。
// 報錯 function f(a, ...b, c) { // ... }
函數的length屬性,不包括 rest 參數。
(function(a) {}).length // 1 (function(...a) {}).length // 0 (function(a, ...b) {}).length // 1
基本用法
ES6 容許使用「箭頭」(=>)定義函數。
var f = v => v;
上面的箭頭函數等同於:
var f = function(v) { return v; };
若是箭頭函數不須要參數或須要多個參數,就使用一個圓括號表明參數部分。
var f = () => 5; // 等同於 var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同於 var sum = function(num1, num2) { return num1 + num2; };
若是箭頭函數的代碼塊部分多於一條語句,就要使用大括號將它們括起來,而且使用return語句返回。
var sum = (num1, num2) => { return num1 + num2; }
因爲大括號被解釋爲代碼塊,因此若是箭頭函數直接返回一個對象,必須在對象外面加上括號,不然會報錯。
// 報錯 let getTempItem = id => { id: id, name: "Temp" }; // 不報錯 let getTempItem = id => ({ id: id, name: "Temp" });
若是箭頭函數只有一行語句,且不須要返回值,能夠採用下面的寫法,就不用寫大括號了。
let fn = () => void doesNotReturn();
箭頭函數能夠與變量解構結合使用。
const full = ({ first, last }) => first + ' ' + last; // 等同於 function full(person) { return person.first + ' ' + person.last; }
箭頭函數使得表達更加簡潔。
const isEven = n => n % 2 == 0; const square = n => n * n;
上面代碼只用了兩行,就定義了兩個簡單的工具函數。若是不用箭頭函數,可能就要佔用多行,並且還不如如今這樣寫醒目。
箭頭函數的一個用處是簡化回調函數。
// 正常函數寫法 [1,2,3].map(function (x) { return x * x; }); // 箭頭函數寫法 [1,2,3].map(x => x * x);
另外一個例子是
// 正常函數寫法 var result = values.sort(function (a, b) { return a - b; }); // 箭頭函數寫法 var result = values.sort((a, b) => a - b); 下面是 rest 參數與箭頭函數結合的例子。 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]]
使用注意點
箭頭函數有幾個使用注意點。
(1)函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。
(2)不能夠看成構造函數,也就是說,不可使用new命令,不然會拋出一個錯誤。
(3)不可使用arguments對象,該對象在函數體內不存在。若是要用,能夠用 rest 參數代替。
(4)不可使用yield命令,所以箭頭函數不能用做 Generator 函數。
上面四點中,第一點尤爲值得注意。this對象的指向是可變的,可是在箭頭函數中,它是固定的。
function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } var id = 21; foo.call({ id: 42 }); // id: 42
上面代碼中,setTimeout的參數是一個箭頭函數,這個箭頭函數的定義生效是在foo函數生成時,而它的真正執行要等到100毫秒後。若是是普通函數,執行時this應該指向全局對象window,這時應該輸出21。可是,箭頭函數致使this老是指向函數定義生效時所在的對象(本例是{id: 42}),因此輸出的是42。
箭頭函數可讓setTimeout裏面的this,綁定定義時所在的做用域,而不是指向運行時所在的做用域。下面是另外一個例子。
function Timer() { this.s1 = 0; this.s2 = 0; // 箭頭函數 setInterval(() => this.s1++, 1000); // 普通函數 setInterval(function () { this.s2++; }, 1000); } var timer = new Timer(); setTimeout(() => console.log('s1: ', timer.s1), 3100); setTimeout(() => console.log('s2: ', timer.s2), 3100); // s1: 3 // s2: 0
上面代碼中,Timer函數內部設置了兩個定時器,分別使用了箭頭函數和普通函數。前者的this綁定定義時所在的做用域(即Timer函數),後者的this指向運行時所在的做用域(即全局對象)。因此,3100毫秒以後,timer.s1被更新了3次,而timer.s2一次都沒更新。
箭頭函數可讓this指向固定化,這種特性頗有利於封裝回調函數。下面是一個例子,DOM 事件的回調函數封裝在一個對象裏面。
var handler = { id: '123456', init: function() { document.addEventListener('click', event => this.doSomething(event.type), false); }, doSomething: function(type) { console.log('Handling ' + type + ' for ' + this.id); } };
上面代碼的init方法中,使用了箭頭函數,這致使這個箭頭函數裏面的this,老是指向handler對象。不然,回調函數運行時,this.doSomething這一行會報錯,由於此時this指向document對象。
this指向的固定化,並非由於箭頭函數內部有綁定this的機制,實際緣由是箭頭函數根本沒有本身的this,致使內部的this就是外層代碼塊的this。正是由於它沒有this,因此也就不能用做構造函數。
因此,箭頭函數轉成 ES5 的代碼以下。
// ES6 function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } // ES5 function foo() { var _this = this; setTimeout(function () { console.log('id:', _this.id); }, 100); }
上面代碼中,轉換後的ES5版本清楚地說明了,箭頭函數裏面根本沒有本身的this,而是引用外層的this。
除了this,arguments在箭頭函數之中也是不存在的,它是指向外層函數的對應變量。
function foo() { setTimeout(() => { console.log('args:', arguments); }, 100); setTimeout(function() { console.log('args:', arguments); }, 100); } foo(2, 4, 6, 8) // args: [2, 4, 6, 8]
上面代碼中,箭頭函數內部的變量arguments,實際上是函數foo的arguments變量。
另外,因爲箭頭函數沒有本身的this,因此固然也就不能用call()、apply()、bind()這些方法去改變this的指向。
(function() { return [ (() => this.x).bind({ x: 'inner' })() ]; }).call({ x: 'outer' }); // ['outer']
上面代碼中,箭頭函數沒有本身的this,因此bind方法無效,內部的this指向外部的this。
長期以來,JavaScript 語言的this對象一直是一個使人頭痛的問題,在對象方法中使用this,必須很是當心。箭頭函數」綁定」this,很大程度上解決了這個困擾。