LearningJS ES6

ES2016学习

BabelJS

  • Learn ES2015
  • 阮一峰 ES6标准入门

Babel使用

# 转码结果输出到标准输出
$ babel example.js
# 转码结果写入一个文件
# --out-file 或 -o 参数指定输出文件
$ babel example.js --out-file compiled.js
# 或者
$ babel example.js -o compiled.js
# 整个目录转码
# --out-dir 或 -d 参数指定输出目录
$ babel src --out-dir lib
# 或者$ babel src -d lib
# -s 参数生成source map文件
$ babel src -d lib -s

上面代码是在全局环境下,进行Babel转码。这意味着,如果项目要运行,全局环境必须有Babel,也就是说项目产生了对环境的依赖。另一方面,这样做也无法支持不同项目使用不同版本的Babel。

一个解决办法是将babel-cli安装在项目之中。
# 安装
$ npm install --save-dev babel-cli

然后,改写package.json

{ 
   // ... 
  "devDependencies": {
     "babel-cli": "^6.0.0"
   },
   "scripts": {
     "build": "babel src -d lib"
   },
}

转码的时候,就执行下面的命令。

$ npm run build

ES6基本语法

let命令

  • ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
  • 不存在变量提升:let像var那样会发生“变量提升”现象。所以,变量一定要在声明后使用,否则报错。
  • 暂时性死区:只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
  • 不允许重复声明:let不允许在相同作用域内,重复声明同一个变量。

变量的解构赋值

1、数组
  • 只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。
//经典的
function* fibs() {
    var a = 0;
    var b = 1;
    while (true) {
      yield a;
      [a, b] = [b, a + b];
    }
}
var [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5
  • 解构赋值允许指定默认值(默认值生效的条件是,对象的属性值严格等于undefined)
var [foo = true] = [];
foo // true
2、对象
  • 对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
var { 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'

也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

3、字符串的解构赋值
  • 字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
  • 类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
4、数值和布尔值的解构赋值
  • 解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象。由于undefinednull无法转为对象,所以对它们进行解构赋值,都会报错
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
5、函数参数的解构
  • 函数参数的解构赋值
[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]
  • 函数参数的解构也可以使用默认值。
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]
[1, undefined, 3].map((x = 'yes') => x);
// [ 1, 'yes', 3 ]
6、解构赋值的用途
  • 交换变量的值
[x, y] = [y, x];
  • 遍历Map结构
    任何部署了Iterator接口的对象,都可以用for...of
    循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便。
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) { 
  // ...
}

字符串的扩展

基本函数
 //能够正确处理4个字节储存的字符,返回一个字符的码点
 String.codePointAt()

 //用于从码点返回对应字符
 String.fromCodePoint() 

 //字符串的遍历器接口
 for (let codePoint of 'foo') {
  console.log(codePoint)
 }
 //可以识别Unicode编号大于0xFFFF的字符,返回正确的字符
 'abc'.at(0) // "a"
 ''.at(0) // ""

 //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

 //repeat方法返回一个新字符串,表示将原字符串重复n次。
 'x'.repeat(3) // "xxx"

 //ES7中的padStart(),padEnd()padStart和padEnd一共接受两个参数,第一个参数用来指定字符串的最小长度,第二个数是用来补全的字符串。
 'x'.padStart(5, 'ab') // 'ababx'
 'x'.padStart(4, 'ab') // 'abax'
 'x'.padEnd(5, 'ab') // 'xabab'
 'x'.padEnd(4, 'ab') // 'xaba'
模板字符串
  • 模板标签
    模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。
    标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。
var a = 5;
var b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于tag(['Hello ', ' world ', ''], 15, 50);
function tag(stringArr, ...values){
   // ...
}

tag函数的第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推。
“标签模板”的一个重要应用,就是过滤HTML字符串,防止用户输入恶意内容。

var message = SaferHTML`

${sender} has sent you a message.

`; function SaferHTML(templateData) { var s = templateData[0]; for (var i = 1; i < arguments.length; i++) { var arg = String(arguments[i]); s += arg.replace(/&/g, "&") .replace(//g, ">"); s += templateData[i]; } return s; }
  • String.raw()
    用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字符串

数组的扩展

Array.from()

  • Array.from
    方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)
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']

值得提醒的是,扩展运算符(...)也可以将某些数据结构转为数组

// arguments对象
function foo() { 
   var args = [...arguments];
}
// NodeList对象
[...document.querySelectorAll('div')]

扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。Array.from方法则是还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。
Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

Array.from(arrayLike, x => x * x);
// 等同于Array.from(arrayLike).map(x => x * x);
Array.from([1, 2, 3], (x) => x * x)// [1, 4, 9]

如果map函数里面用到了this关键字,还可以传入Array.from的第三个参数,用来绑定this。

  • Array.of
    Array.of方法用于将一组值,转换为数组。
 Array.of(3, 11, 8) // [3,11,8]
 Array.of(3) // [3]
 Array.of(3).length // 1

 //Array.of方法可以用下面的代码模拟实现。
 function ArrayOf(){ 
   return [].slice.call(arguments);
 }
  • copyWithin()
    在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
//Array.prototype.copyWithin(target, start = 0, end = this.length)
[1, 2, 3, 4, 5].copyWithin(0, 3)// [4, 5, 3, 4, 5]
  • find()和findIndex()
    数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员
[1, 4, -5, 10].find((n) => n < 0)// -5

数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

[1, 5, 10, 15].findIndex(function(value, index, arr) { 
    return value > 9;
})
 // 2

这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。

  • fill
['a', 'b', 'c'].fill(7)// [7, 7, 7
]new Array(3).fill(7)// [7, 7, 7]
  • 数组实例的entries(),keys()和values()
    ES6提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是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"
  • 数组实例的includes()
    Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的include方法类似
[1, 2, 3].includes(2);  // true
[1, 2, 3].includes(4);  // false
[1, 2, NaN].includes(NaN); // true

函数的扩展

1、函数参数的默认值

  • 实例1
function Point(x = 0, y = 0) { 
    this.x = x; 
    this.y = y;
}
var p = new Point();
p // { x: 0, y: 0 }
  • 再请问下面两种写法有什么差别?
// 写法一
function m1({x = 0, y = 0} = {}) {
     return [x, y];
}
// 写法二
function m2({x, y} = { x: 0, y: 0 }) { 
    return [x, y];
}

上面两种写法都对函数的参数设定了默认值,区别是写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值

// 函数没有参数的情况
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]

2、rest参数

ES6引入rest参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

3、扩展运算符

扩展运算符(spread)是三个点(...)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数列。

console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)// 1 2 3 4 5
[...document.querySelectorAll('div')]// [
,
,
]

对象的扩展

1、Object.is()就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

2、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方法的第一个参数是目标对象,后面的参数都是源对象。

3、属性的遍历

(1)for...in
for...in
循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。
(2)Object.keys(obj)
Object.keys
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames
返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols
返回一个数组,包含对象自身的所有Symbol属性。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys
返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举。、
以上的5种方法遍历对象的属性,都遵守同样的属性遍历的次序规则。

首先遍历所有属性名为数值的属性,按照数字排序。
其次遍历所有属性名为字符串的属性,按照生成时间排序。
最后遍历所有属性名为Symbol值的属性,按照生成时间排序。

Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]

proto属性,Object.setPrototypeOf(),Object.getPrototypeOf()

1、proto属性,该属性没有写入ES6的正文,而是写入了附录,原因是proto前后的双下划线,说明它本质上是一个内部属性,而不是一个正式的对外的API,只是由于浏览器广泛支持,才被加入了ES6。标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
2、Object.setPrototypeOf()
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对象。

function Rectangle() {}
var rec = new Rectangle();
Object.getPrototypeOf(rec) === Rectangle.prototype
//true

Set和Map数据结构

1、Set

Set实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。

add(value):添加某个值,返回Set结构本身。
delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
has(value):返回一个布尔值,表示该值是否为Set的成员。
clear():清除所有成员,没有返回值。

Set结构的实例有四个遍历方法,可以用于遍历成员。

keys():返回键名的遍历器
values():返回键值的遍历器
entries():返回键值对的遍历器
forEach():使用回调函数遍历每个成员

2、Map

1、属性和操作方法

(1)size属性返回Map结构的成员总数。
(2)set(key, value)set方法返回的是Map本身,因此可以采用链式写法。
(3)get(key)
(4)has(key)
(5)delete(key)
(6)clear()

2、遍历方法

Map原生提供三个遍历器生成函数和一个遍历方法。
keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。
forEach():遍历Map的所有成员。

//注意
map[Symbol.iterator] === map.entries 

Iterator和for...of循环

你可能感兴趣的:(LearningJS ES6)