过年在家闲着没事,来看看ES6,选了阮一峰大大的《ES6 标准入门》这本书,了解一下新的js规范。这里做一下读书笔记。
目前各大浏览器的自新版本应该都支持ES6了,并且Node.js对ES6的支持度比浏览器还高,通过Node可以体验更多ES6的特性。
Babel是个ES6转码器,可以将ES6代码转换为ES5代码,从而在现有环境执行。也就是说,你可以用ES6的方式编写程序,又不用担心现有环境是否支持。
还有个Traceur转码器也是一样的效果。
基本用法: let用来都声明变量,类似var,但是所生命的变量,只在let命令所在的代码块中有效。for循环计数器的i,就很适合用let命令,这样i只在for循环内有效。
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
let不详var那样会发生“变量提升”现象,所以变量一定要先声明后使用,否则会报错。
变量提升简单来说,就是自动把变量的定义提前解析,这样,即使先使用,后定义变量,程序也可以正常运行。但是这里要注意,变量提升,只是提前声明了变量,并没有赋值。见下例:
console.log(foo); // 输出undefined
console.log(bar); // 报错ReferenceError
var foo = 2;
let bar = 2;
var出来的foo变量提升了,但是提升只是定义,并不赋值,所以是undefined;而let bar 则不存在变量提升,会直接报错
关于变量/函数提升
函数只有声明形式才能提升。匿名函数赋值不能提升。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。在语法上成为“暂时性死区(TDZ)”
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
若在let声明变量前使用typeof,也会报错。完全不声明反而不会报错。
let不允许重复声明
let实际上为js新增了块级作用域。
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
上边的函数有两个代码块,都声明了n,运行后输出5,这说明外层代码块不受内层代码块的影响,如果这里用var,最后输出的n就是10。
const 声明一个只读的常量,一旦声明,就要立即初始化复制,然后常量的值不能改变。
const的作用域跟let相同: 只在声明所在的块级作用域内有效。并且没有变量提升,一定要先声明后使用。且不可重复声明
const声明的对象,只是指向对象的地址不变,对象本身是可变的。但不能重新赋值
const声明的数组,可以用push等方法,但是不能重新赋值
从ES6开始,全局变量将逐步与全局对象的属性脱钩。let,const,class声明的全局变量,不再属于全局对象的属性:
var a = 1
window.a // 1
let b = 1;
window.b // undefined
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
如:
// ES5 赋值
var a = 1;
var b = 2;
var c = 3;
//ES6 解构 赋值
var [a,b,c] = [1,2,3];
//只要等号两边模式相同,左边的变量就会被赋予对应的值。
若解构不成功,变量的值就等于undefined
其实还有不完全解构:
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
对象的解构,要变量名相同:
var { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
var { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined
字符串解构:
const [a, b, c, d, e] = 'hello';
//a-e分别是 h,e,l,l,o
数值/布尔值解构:
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
函数参数解构:
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
解构的用途:
1.交换变量的值: [x,y] = [y,x]
2.函数返回多个值。
3.函数参数的定义
4.提取json数据:
var jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
repeat() : 返回一个新字符串,表示将原字符串重复n次。
padStart() : 从头部补全字符串
padEnd() : 从尾部补全字符串
Number.isFinite()用来检查一个数值是否为有限的(finite)。
Number.isNaN()用来检查一个值是否为NaN。
ES6将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。Number.parseInt(), Number.parseFloat()
Number.isInteger()用来判断一个值是否为整数。
ES6在Number对象上面,新增一个极小的常量Number.EPSILON。为浮点数计算设置误差范围,因为我们知道js浮点数计算是不精确的。但是如果这个误差能够小于Number.EPSILON,我们就可以认为得到了正确结果。因此,Number.EPSILON的实质是一个可以接受的误差范围。
Number.isSafeInteger()则是用来判断一个整数是否落在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值。
Math.trunc方法用于去除一个数的小数部分,返回整数部分。对于非数值,Math.trunc内部使用Number方法将其先转为数值。
Math.sign方法用来判断一个数到底是正数、负数、还是零。
Math.cbrt方法用于计算一个数的立方根。对于非数值,Math.cbrt方法内部也是先使用Number方法将其转为数值。
Math.hypot方法返回所有参数的平方和的平方根。如果参数不是数值,Math.hypot方法会将其转为数值。只要有一个参数无法转为数值,就会返回NaN。
对数方法若干。
三角函数方法若干。
新增指数运算符:
ES7新增了一个指数运算符(**),目前Babel转码器已经支持。2 ** 3 // 8
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和
Map)。实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。
// NodeList对象
let ps = document.querySelectorAll('p');
Array.from(ps).forEach(function (p) {
console.log(p);
});
// arguments对象
function foo() {
var args = Array.from(arguments);
// ...
}
上面代码中,querySelectorAll方法返回的是一个类似数组的对象,只有将这个对象转为真正的数组,才能使用forEach方法。
Array.of方法用于将一组值,转换为数组。
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方
法,会修改当前数组。
Array.prototype.copyWithin(target, start = 0, end = this.length)
它接受三个参数。
target(必需):从该位置开始替换数据。
start(可选):从该位置开始读取数据,默认为0。如果为负值,表示倒数。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
这三个参数都应该是数值,如果不是,会自动转为数值。
[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]
//上面代码表示将从3号位直到数组结束的成员(4和5),复制到从0号位开始的位置,结果覆盖了原来的1和2。
数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值
为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
fill方法使用给定值,填充一个数组。fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
//上面代码表示,fill方法从1号位开始,向原数组填充7,到2号位之前结束。
ES6提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以
用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。该方法属于ES7,但Babel转码器
已经支持。没有该方法之前,我们通常使用数组的indexOf方法,检查是否包含某个值。但indexof不够语义化,而且内部使用===容易导致误判
数组的空位指,数组的某一个位置没有任何值。比如,Array构造函数返回的数组都是空位。Array(3) // [, , ,]
,Array(3)返回一个具有3个空位的数组。
ES6则是明确将空位转为undefined。
Array.from方法会将数组的空位,转为undefined,也就是说,这个方法不会忽略空位。
在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。如:
function log(x, y) {
y = y || 'World';
console.log(x, y);
}
ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
ES6引入rest参数(形式为“…变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将
多余的参数放入数组中。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
扩展运算符(spread)是三个点(…)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
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)这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序
列。
扩展运算符的应用:
//合并数组新方法:
// ES5
[1, 2].concat(more)
// ES6
[1, 2, ...more]
//字符串转数组:
[...'hello']
// [ "h", "e", "l", "l", "o" ]
函数的name属性,返回函数的函数名。
ES6允许使用“箭头”(=>)定义函数。 var 函数名 = (参数) => return;
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;
};
箭头函数可以与变量解构结合使用。
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)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作Generator函数。
尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
函数调用自身,称为递归。如果尾调用自身,就称为尾递归。递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用
帧,所以永远不会发生“栈溢出”错误。
ES6允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
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!";
}
};
CommonJS模块输出变量,就非常合适使用简洁写法。
var ms = {};
function getItem (key) {
return key in ms ? ms[key] : null;
}
function setItem (key, value) {
ms[key] = value;
}
function clear () {
ms = {};
}
module.exports = { getItem, setItem, clear };
// 等同于
module.exports = {
getItem: getItem,
setItem: setItem,
clear:
ES5中,只有方括号法才能放表达式,ES6允许在字面量定义对象时,用表达式作为对象的属性名:
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
obj.foo // true
obj.abc // 123
表达式还可以用于定义方法名:
let obj = {
['h'+'ello']() {
return 'hi';
}
};
obj.hello() // hi
函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。
ES5比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不
等于自身,以及+0等于-0。JavaScript缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
ES6提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与
严格比较运算符(===)的行为基本一致。
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(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会直接返回该参数。如果该参数不是对象,则会先转成对象,然后返回。由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。
注意,Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
这等同于使用Object.assign方法。
ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin模式),新
方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就
是ES6引入Symbol的原因。
ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值
(Boolean)、字符串(String)、数值(Number)、对象(Object)。
P107