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,很大程度上解决了这个困扰。