ES6 入门
本文参考阮一峰老师的《es6 入门》
前言
如今,距离 ES6 的正式发布时间已经过去了 3 年,随着时间的推移,各大浏览器对 ES6 的支持度已经越来越高了,超过 90%的 ES6 语法特性都实现。掌握 ES6 已经是一名前端工程师必备的能力。
ES6 let 和 const 命令
let 命令
基本用法
ES6 新增了let
命令,用来声明变量,但是let
所声明的变量只能在自己的代码块内有效,也就是说,原本没有块级作用域的javascript
现在也有了块级作用域。
{
let a = 1;
}
console.log(a); //a is not defined
复制代码
正因为产生块级作用域这个特点,let
十分适用于 for 循环
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
复制代码
上面代码中,变量i
是 let
声明的,当前的 i
只在本轮循环有效,所以每一次循环的 i
其实都是一个新的变量,所以最后输出的是 6。
如果使用var
命令,最后输出的是 10
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
复制代码
不存在变量提升
let
不像 var
那样会发生“变量提升”现象。所以,变量一定要在声明后使用,否则报错。
console.log(a); // 输出 undefined
console.log(b); // 报错 ReferenceError
var a = 2;
let b = 2;
复制代码
上面代码中,变量a
用 var
命令声明,会发生变量提升,即脚本开始运行时,变量 a
已经存在了,但是没有值,所以会输出undefined
。变量 b
用let
命令声明,不会发生变量提升。这表示在声明它之前,变量b
是不存在的,这时如果用到它,就会抛出一个错误。
暂时性死区
只要块级作用域内存在 let
命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
ES6 明确规定,如果区块中存在 let
和 const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用 let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 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
命令声明变量 tmp
之前,都属于变量 tmp
的“死区”。
ES6 规定暂时性死区和 let、const
语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
不允许重复声明
let
不允许在相同作用域内,重复声明同一个变量。
// 报错
function () {
let a = 10;
var a = 1;
}
// 报错
function () {
let a = 10;
let a = 1;
}
复制代码
因此,不能在函数内部重新声明参数。
do 表达式
本质上,块级作用域是一个语句,将多个操作封装在一起,没有返回值。
{
let t = f();
t = t * t + 1;
}
复制代码
上面代码中,块级作用域将两个语句封装在一起。但是,在块级作用域以外,没有办法得到 t 的值,因为块级作用域不返回值,除非t
是全局变量。
现在有一个提案,使得块级作用域可以变为表达式,也就是说可以返回值,办法就是在块级作用域之前加上do
,使它变为do
表达式。
let x = do {
let t = f();
t * t + 1;
};
复制代码
上面代码中,变量x
会得到整个块级作用域的返回值。
const 命令
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
复制代码
上面代码表明改变常量的值会报错。
const
命令大致特点和let
相似,不再一一列举
ES6 数组的扩展
Array.from()
这个方法是 Array 构造器的静态方法
作用:将把类数组对象转成真正的数组。
格式:
Array.from(类数组对象)
Array.from(类数组对象,function(item,index){
return;//
})
复制代码
代码如下
var lis = document.getElementsByTagName("li")
var rs = Array.from(lis)
console.log(Array.isArry(rs))//true
复制代码
Array.of()
作用:将一组值转换为数组。与 Array.from 功能相似,理解用来创建数组。 主要目的是弥补构造器 Array()的不足
var arr1 = new Array.of(3) //[3]
var arr2 = new Array.of("3")//['3']
复制代码
find 和 findIndex
find:用于找出第一个符合条件的数组元素。找不到则是 undefined
。注意,它是不会返回多 个,只找一个,找到了就返回。如果你要把所有的满足条件的数组元素素都找出来,你应该用filter()
findIndex
:返回第一个符合条件的数组元素的索引。找不到则是-1;
格式:
arr.find(function(item,index){
return 条件;//
})
复制代码
var arr = [
{name:"zs1",score:90}
{name:"zs2",score:90}
{name:"zs3",score:90}
]
var rs = Array.find(item =>item.name=="zs1")// {name:"zs1",score:90}
复制代码
includes
作用:判断元素是否在数组中存在。返回值是 true|false
代码如下:
var myarr = [1,2,3]
myarr.includes(1)//true
myarr.includes(0)//flase
复制代码
fill
作用:给数组填充指定值。fill
方法用于空数组的初始化非常方便。已有数据会被覆盖。 fill
方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置
格式:
arr.fill(值)
arr.fill(值,起点,终点) 包括起点,不包括终点
复制代码
var arr = new Array(5)
arr.fill("*") //["*","*","*","*","*"]
复制代码
数组的扩展运算符
功能:把数据结构转成数组。
代码如下:
var arr1 = [1,2,3]
var arr2 = [...arr1] //[1,2,3]
复制代码
函数的扩展
函数参数的默认值
基本用法
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
复制代码
箭头函数
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;
};
复制代码
箭头函数使得表达更加简洁。如果不用箭头函数,可能就要占用多行,而且还不如现在这样写醒目。
箭头函数的一个用处是简化回调函数。
// 正常函数写法
[1,2,3].map(function (x) {
return x * x;
});
// 箭头函数写法
[1,2,3].map(x => x * x);
复制代码
注意
(1)函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
(3)不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 Rest 参数代替。
(4)不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
ES6 对象的扩展
属性的简洁表示法
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!";
}
};
复制代码
Object.is()
比较两个值是否相等
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
复制代码
Object.assign()
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
var str = { a: 1, b: 1 };
var str1 = { b: 2, c: 2 };
var str2 = { c: 3 };
Object.assign(str, str1, str2);
str // {a:1, b:2, c:3}
复制代码
var v1 = 'abc';
var v2 = true;
var v3 = 10;
var obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
复制代码
上面代码中,v1、v2、v3分别是字符串、布尔值和数值,结果只有字符串合入目标对象(以字符数组的形式),数值和布尔值都会被忽略。这是因为只有字符串的包装对象,会产生可枚举属性。
Object.getOwnProertyDescriptor();
获取一个对象中某个属性的详细信息。
var a = 1
console.log(object.getOwnProertyDescriptor(window,"a")
复制代码
getOwnPropertyNames()
获取自的属性,以数组的格式返回的。
var obj = {
name:xiaoli
age:22
}
console.log(object.getOwnPropertyNames(obj))//["name","age"]
复制代码
Object.keys()
var obj = {
name:xiaoli
age:22
}
console.log(object.keys(obj))//["name","age"]
复制代码
使用Object.getOwnPropertyNames()和Object.keys()都可以得到一个对象的属性名,属性名是放在一个数组中的。
对于对象的遍历目前有三种方式:
for in
Object.keys()
Object.getOwnPropertyNames()
for in : 会输出自身以及原型链上可枚举的属性。
Object.keys() : 用来获取对象自身可枚举的属性键
Object.getOwnPropertyNames() : 用来获取对象自身的全部属性名
__proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()
(1)__proto__属性
隐式原型属性,用来设计对象的隐式原型
(2)Object.setPrototypeOf()
Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的prototype对象。它是ES6正式推荐的设置原型对象的方法。
// 格式
Object.setPrototypeOf(object, prototype)
// 用法
var o = Object.setPrototypeOf({}, null);
复制代码
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);
proto.y = 20;
proto.z = 40;
obj.x // 10
obj.y // 20
obj.z // 40
复制代码
(3)Object.getPrototypeOf()
该方法与setPrototypeOf方法配套,用于读取一个对象的prototype对象。
Object.getPrototypeOf(obj);
复制代码
ES6 变量的解构赋值
数组的解构赋值
基本用法
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
以前,为变量赋值,只能直接指定值。
var a = 1;
var b = 2;
var c = 3;
复制代码
ES6 允许写成下面这样。
var [a, b, c] = [1, 2, 3];
复制代码
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。
默认值
解构赋值允许指定默认值。
var [f = true] = [];
f // true
[x, y = 'b'] = ['a']; // x='a', y='b'
[x, y = 'b'] = ['a', undefined]; // x='a', y='b'
复制代码
注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined
,默认值是不会生效的。
对象的解构赋值
解构不仅可以用于数组,还可以用于对象。
var { f, b } = { f: "aaa", b: "bbb" };
f // "aaa"
b // "bbb"
复制代码
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
var { b, f } = { f: "aaa", b: "bbb" };
f // "aaa"
b // "bbb"
var { baz } = { f: "aaa", b: "bbb" };
baz // undefined
复制代码
字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
复制代码
类似数组的对象都有一个length
属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
复制代码
函数参数的解构赋值
函数的参数也可以使用解构赋值。
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
复制代码
上面代码中,函数add
的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x
和y
。对于函数内部的代码来说,它们能感受到的参数就是x
和y
。
ES6 字符串的扩展
repeat()
repeat
方法返回一个新字符串,表示将原字符串重复n
次。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'a'.repeat(0) // ""
复制代码
参数如果是小数,会被取整。
'a'.repeat(2.9) // "aa"
复制代码
如果repeat
的参数是负数或者Infinity
,会报错。
'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError
复制代码
trim()
除去字符串空格的。
trim 左右空格都是去掉
trimLeft 左空格去掉
trimRight 右空格去掉
var str = " ab ab abc "
str.trim = "ab ab abc"
str.trimLeft = "ab ab abc "
str.trimRight = " ab ab abc"
复制代码
includes
var str = "abc"
str.includes("a")//true
str.includes("e")//flase
复制代码
startsWith
var str = "abc def"
str.startsWith("abc")//true
str.startsWith("abcd")//false
复制代码
padStart
var str = "abc def"
str.padStart(15,"*")
//"********abc def"
复制代码
padEnd
var s = 4
s.toFixed(2)
"4.00"
s.toFixed(2).padStart
"04.00"
复制代码
模板字符串
传统的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)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
模板字符串中嵌入变量,需要将变量名写在${}之中。
function authorize(user, action) {
if (!user.hasPrivilege(action)) {
throw new Error(
// 传统写法为
// 'User '
// + user.name
// + ' is not authorized to do '
// + action
// + '.'
`User ${user.name} is not authorized to do ${action}.`);
}
}
复制代码
ES6 数值的扩展
Number.parseInt(), Number.parseFloat()
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.isInteger()
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
复制代码
Number.EPSILON
ES6 在Number
对象上面,新增一个极小的常量Number.EPSILON
。
Number.EPSILON
// 2.220446049250313e-16
Number.EPSILON.toFixed(20)
// '0.00000000000000022204'
复制代码
引入一个这么小的量的目的,在于为浮点数计算,设置一个误差范围。我们知道浮点数计算是不精确的。
Math 对象的扩展
ES6 在Math
对象上新增了 17 个与数学相关的方法。所有这些方法都是静态方法,只能在 Math 对象上调用。
Math.trunc()
Math.trunc
方法用于去除一个数的小数部分,返回整数部分。
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
复制代码
Math.cbrt()
Math.cbrt
方法用于计算一个数的立方根。
Math.cbrt(-1) // -1
Math.cbrt(0) // 0
Math.cbrt(1) // 1
Math.cbrt(2) // 1.2599210498948734
复制代码
Math.clz32()
JavaScript 的整数使用 32 位二进制形式表示,Math.clz32
方法返回一个数的 32 位无符号整数形式有多少个前导 0。
Math.clz32(0) // 32
Math.clz32(1) // 31
Math.clz32(1000) // 22
Math.clz32(0b01000000000000000000000000000000) // 1
Math.clz32(0b00100000000000000000000000000000) // 2
复制代码
Math.imul()
Math.imul
方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
Math.imul(2, 4) // 8
Math.imul(-1, 8) // -8
Math.imul(-2, -2) // 4
复制代码
Math.fround()
Math.fround
方法返回一个数的单精度浮点数形式。
Math.fround(1.337) // 1.3370000123977661
复制代码
对于整数来说,Math.fround 方法返回结果不会有任何不同,区别主要是那些无法用 64 个二进制位精确表示的小数。这时,Math.fround 方法会返回最接近这个小数的单精度浮点数。
Math.hypot()
Math.hypot
方法返回所有参数的平方和的平方根。
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 的平方。
如果参数不是数值,Math.hypot 方法会将其转为数值。只要有一个参数无法转为数值,就会返回 NaN。
ES6 set和map数据结构
Set
基本用法 ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set本身是一个构造函数,用来生成Set数据结构。
var s = new Set();
[2, 3, 5, 4, 5, 2, 2].map(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4
复制代码
上面代码通过add方法向Set结构加入成员,结果表明Set结构不会添加重复的值。
Set
函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。
// 例一
var set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 例二
var items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
// 例三
function divs () {
return [...document.querySelectorAll('div')];
}
var set = new Set(divs());
set.size // 56
// 类似于
divs().forEach(div => set.add(div));
set.size // 56
复制代码
上面代码中,例一和例二都是Set函数接受数组作为参数,例三是接受类似数组的对象作为参数。
// 去除数组的重复成员
[...new Set(array)]
复制代码
class
从形式上,向主流的面向对象的语言靠拢。
我们之前写对象方式可以用 Class 用进行优化
前面我们都是创建构造器,然后去new构造器,构造器就相当于一个类,在ES6中,就可以使用class来创建对象了。
1,class创建对象 格式:
class 类名{
constructor(参数){
this.属性 = 参数;
}
method(){
}
}
复制代码
注意:
1.class 是关键字,后面紧跟类名,类名首字母大写,采取的是大驼峰命名法则。类名之后是
2.在{}中,不能直接写语句,只能写方法,方法不需要使用关键字
3.方法和方法之间没有逗号。不是键值对
class NBAPlayer{
constructor(name,age,height){
this.name = name;
this.age = age;
this.height = height;
}
say(){
console.log("`我是${this.name},我今年${this.age}岁,身高${this.height}`")
}
}
var p1 = new NBAPlayer("库里","25","190")
复制代码
使用extends实现继承
格式:
class 子类 extend 父类 {
constructor (参数){
super(参数)
this.属性=值
}
}
复制代码
注意:
使用 extends 关键字来实现继承,在子类中的构造器 constructor 中,必须要显式调用父类的 super 方法,如果不调用,则 this 不可用
class NBAPlayer{
constructor(name,age,height){
this.name = name;
this.age = age;
this.height = height;
}
say(){
console.log("`我是${this.name},我今年${this.age}岁,身高${this.height}`")
}
}
class MVP extends NBAPlayer{
constructor(name,age,height,year){
super(name,age,height)
this.year = year;
}
showMvp(){
console.log("`我是${this.name},是${this.year}年的MVP`")
}
}
var p1 = new MVP("库里","25","190",2016)
复制代码
类的静态方法 static
直接通过类名来访问的方法就是静态方法。如:Math.abs();这里的 abs()是静态方法。 Array.isArray();isArray()是静态方法. 在方法名前加 static 就可以了
后记
花一天的时间总结了阮一峰老师的《es6 入门》,其中还是有很多不懂的地方,还要继续学习。