深入理解ES6(一)

《深入理解ES6》

1. 块级作用域绑定

var声明

函数作用域或全局作用域中使用var声明的变量,变量会被当作是在当前作用域顶部声明的变量


块级声明

声明在指定块的作用域之外无法访问的变量,其存在于:

  • 函数内部
  • 块中({}之间的区域)

let声明

  1. let声明的变量可以把变量的作用域限制在当前的代码块中。
  2. let声明的变量不会被提升,通常将let声明语句放在封闭代码块的顶部。
  3. let语句禁止重声明,否则会报错。(同一作用域不能用let重复定义已经存在的标识符)
// 同一作用域
var count = 30;  
let count = 40; // => 报错

// 不同作用域
var count = 30;  
if (condition) {  
    let count = 40; // => 正常,内部的count会遮蔽全局作用域中的count
}

const声明

  1. const声明的是常量,其值一旦被设定后不可再更改,且每个通过const声明的变量需要进行初始化赋值。
  2. const与let声明的都是块级标识符,其特点【作用域、不被提升、禁止重声明】类似。
  3. const声明不允许修改绑定,但可以修改值。如下:

用const声明对象后,可以修改该对象的属性值

const person = {  
    name: 'Nick'
};

person.name = 'Mark'; // => 正常

person = {  
    name: 'Mark';
}; // => 抛出语法错误

临时死区(TDZ)

JavaScript引擎在扫描代码发现变量声明时,遇到var声明则会将他们提升至作用域顶部,遇到let或const声明则会将声明放置TDZ中。

访问TDZ中的变量会触发运行时错误,只有执行过变量声明语句后,变量才会从TDZ中移除,然后可以被正常访问。

循环中的let const声明

  • let: 每次迭代循环都会创建一个新变量,并以之前迭代中同名变量的值将其初始化。
// 5 5 5 5 5 
for (var i = 0; i < 5; i++) {  
    setTimeout(function timer() {
        console.log(i);     
    }, 1000);
}

// 0 1 2 3 4
for (let i = 0; i < 5; i++) {  
    setTimeout(function timer() {
        console.log(i);     
    }, 1000);
}
  • const: 在普通的for循环中,若初始化变量时使用了const,且在后面的迭代中更改了这个变量的值会导致报错。若在后面的循环中不会修改该变量,则可以使用const声明。
var func = [];  
var object = {  
    a: 1,
    b: 2,
    c: 3
};

for (const key in object) {  
    func.push(function() {
        console.log(key);
    });
}

func.forEach(function(func) {  
    func();
});

2. 字符串和正则表达式

字符串操作

子串识别

在字符串中检测是否包含一段子字符串。

  • includes(): 在字符串中检测到制定子串则返回true,否则返回false
  • startsWith(): 在字符串的起始部分检测,返回值同includes
  • endsWith(): 在字符串的结束部分检测,返回值同includes

repeat()方法

字符串拷贝重复,repeat方法接收一个number类型的参数,表示字符串重复的次数

console.log('hello ES6'.repeat(2)); // => hello ES6hello ES6  

正则表达式修饰符

  • y修饰符 黏滞表达式:在字符串开始字符匹配时,它通知搜索从正则表达式的lastIndex属性开始进行,如果在指定位置没有成功匹配,则停止继续匹配,称之为黏滞行为。
let text = 'hello1 hello2 hello3';  
let stickyPattern = /hello\d\s?/y;

let stickyResult = stickyPattern.exec(text);  
console.log(stickyResult); // => ["hello1 ", index: 0, input: "hello1 hello2 hello3"]

stickyPattern.lastIndex = 1;  
stickyResult = stickyPattern.exec(text);  
console.log(stickyResult); // => null, 从'e'开始匹配,未能成功匹配到则就此终止  

只有调用exec()和test()这些正则表达式的方法时,才会涉及到lastIndex属性,调用字符串的方法则不会发生黏滞行为

黏滞表达式中,如果使用^字符来匹配字符串的开端,则只会从字符串的起始位置或多行模式下的首行进行匹配;当lastIndex为0时,如果正则表达式含有^,则是否使用黏滞表达式并无差别,如果lastIndex的值不为0,则该表达式永远不会匹配到正确结果。

检测正则中是否存在y修饰符:pattern.sticky,检查其是否有sticky属性。

  • 正则表达式复制
let re1 = /ab/i;  
let re2 = new RegExp(re1); // re2.toString() => "/ab/i"; re2是re1的一份拷贝  
let re3 = new RegExp(re1, "g"); // re3.toString() => "/ab/g"; 第一个参数是正则表达式,第二个参数修改其修饰符  

正则表达式文本

re2.source() // => "ab"

flags属性

re2.flags() // => "g"

模板字面量

写法:反撇号【 ` 】替代单双引号

lastIndex

  • 多行字符串 (多行、换行)

ES6之前的版本中,通常需要依靠数组或字符串拼接的方法来创建多行字符串。

^ let meaasge = Multiline
string
; // => Multiline
// string
pattern.sticky let name = 'Mickey';
let message = Hello ${name};
console.log(message); // => Hello Mickey
正则表达式复制 let name = Mickey;
let message = Hello, ${my name is ${name}};
console.log(message); // => Hello, My name is Mickey

let re1 = /ab/i;  
let re2 = new RegExp(re1); // re2.toString() => "/ab/i"; re2是re1的一份拷贝  
let re3 = new RegExp(re1, "g"); // re3.toString() => "/ab/g"; 第一个参数是正则表达式,第二个参数修改其修饰符  
let message = tagHello world!; // => 模板字面量Hello world的模板标签是 tag
re2.source() // => "ab"javascript function tag (stringArr, price, salePrice) {
let result = ''; for (let i = 0; i < stringArr.length; i++) { console.log(stringArr[i]); }
console.log(price); console.log(salePrice);

} let count = 10;
let price = 5;
let rate = 0.1;
tagHello ${count * price} world ${count * price * rate};
re2.flags() // => "g" let message1 = Multiline\nstring;
let message2 = String.rawMultiline\nstring;
console.log(message1); // => Multiline
// string console.log(message2); // => Multiline\nstring
Multiline
string
function makeReq(url, timeout = 2000, callback = function() {}) {
// ··· } Hello ${name} function mixArgs(first, second) {
console.log(first = arguments[0]); console.log(second = arguments[1]); first = 'c'; second = 'd'; console.log(first = arguments[0]); console.log(second = arguments[1]); }

mixArgs('a', 'b');

// --------------- // true // true // false // false // --------------- Mickey function mixArgs(first, second = 'b') {
console.log(arguments.length); console.log(first = arguments[0]); console.log(second = arguments[1]); first = 'c'; second = 'd'; console.log(first = arguments[0]); console.log(second = arguments[1]); }

mixArgs('a', 'b');

// --------------- // 2 // true // false // false // false // --------------- // arguments[1]的值为undefined,改变first和second的值并不会影响arguments对象,因此可以通过arguments对象将参数恢复为初始值。 Hello, ${ function getSum(first, second = first) {
return first + second; } getSum(1, 2); // => 3
getSum(1); // => 2
} function getSum(first = second, second) {
return first + second; } getSum(1, 2); // => 3
getSum(undefined, 1); // => second is not defined

// ----------- // 当first初始化时,second还未初始化,程序报错【临时死区】 // ----------- Hello world! function demo(array) {
for (let i = 0; i < arguments.length; i++) { console.log(arguments[i]); } } demo('1', '2', '3'); // => 1 2 3
Hello world function demo(...array) {
for (let i = 0; i < array.length; i++) { console.log(array[i]); } } demo('1', '2', '3'); // => 1 2 3
Hello ${count * price} world ${count * price * rate} function createPerson(name, age) {
return { name, age } } Multiline\nstring var person = {
name: 'MiKi', syName() { console.log(this.name); } }; Multiline\nstring Object.is(NaN, NaN); // ture
Object.is(-0, +0); // false

**交换两个变量的值**
var receiver = {};
Object.assgn(receiver,
{ type: 'js', name: 'es6', }, { type: 'css', }, ); console.log(receiver.type); // "css" 第二个源对象的type属性覆盖了第一个
console.log(receiver.name); // "es6"
左侧是一个解构模式,右侧是一个为交换过程创建的临时数组字面量。代码执行过程中,先解构临时数组,将b和a的值赋值到左侧数组的前两个位置。

**解构数组时,也可以添加默认值**

- 不定元素

在数组中,可以通过...语法将数组中的其余元素赋值给一个特定的变量。
let node = {
type: 'Identifier', name: 'foo', };

let { type, name } = node;

console.log(type); // "Identifier"
console.log(name); // "foo"

colors数组中的第一个元素被赋值给firstColor变量,其余元素被赋值给restColor数组

**注意:在被解构的数组中,不定元素必须为最后一个条目,否则会抛出异常**

#### 解构参数
let node = {
type: 'Identifier', name: 'foo', };

let type = 'Literal';
let name = 5;

({ type, name } = node); // 为变量赋值

console.log(type); // "Identifier"
console.log(name); // "foo"

**注意:**

① 如果解构赋值表达式的右值为null或undefined,则程序会报错,且调用setCookie函数时,不传入第三个参数程序也会抛出异常

② 若想将解构参数定为可选,那么就需要提供默认值

- 默认值
let node = {
type: 'Identifier', name: 'foo', };

let { type, name, value } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // undefined

let { type, name, value = true } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // true

### 7. Set集合与Map集合


### 8. 迭代器和生成器

#### 什么是迭代器

- 迭代器是一种特殊的对象,它包含有专门为迭代过程设计的专有接口。

- 所有的迭代器都有一个next()方法,每次调用next()方法都会返回一个结果对象。

- 返回的结果对象有两个属性: ① done,一个布尔类型的值,当没有更多可返回的数据时返回true,其他情况下全部返回false;② value,表示下一个将要返回的值
let node = {
type: 'Identifier', };

// 读取type的属性并将其值存在变量localType中;为localName赋默认值 let { type: localType, name: localName = 'default' } = node;

console.log(localType); // "Identifier"
console.log(localName); // "default"

#### 什么是生成器

- 生成器是一种返回迭代器的函数,通过function关键字后的星号(*)来表示,函数中会用到新的关键字 yield。

- yield关键字是ES6的新特性,可以通过它来指定调用迭代器的next()方法时的返回值和返回顺序。

- 生成器的调用过程和其他函数一样,最终返回的是创建好的迭代器。

- 在生成器函数中,每执行完一条yield语句后,函数会自动停止执行,直到再次调用迭代器的next()方法时才会继续执行下一条yield语句。

- 使用yield关键字可以返回任何值或表达式,可以通过生成器函数批量给迭代器添加元素。
let colors = [ 'red', 'yellow', 'blue' ];
let [ firstColor, , thirdColor ] = colors;

console.log(firstColor); // "red"
console.log(thirdColor); // "blue"

- yield关键字只可在生成器内部使用,在其他地方或函数中使用会导致程序抛错。
let colors = [ 'red', 'yellow', 'blue' ];
let firstColor = 'black';
let thirdColor = 'white';

let [ firstColor, , thirdColor ] = colors;

console.log(firstColor); // "red"
console.log(thirdColor); // "blue"`

**交换两个变量的值**

let a = 1;
let b = 2;

[ a, b ] = [ b, a];

左侧是一个解构模式,右侧是一个为交换过程创建的临时数组字面量。代码执行过程中,先解构临时数组,将b和a的值赋值到左侧数组的前两个位置。

**解构数组时,也可以添加默认值**

- 不定元素

在数组中,可以通过...语法将数组中的其余元素赋值给一个特定的变量。

let colors = [ 'red', 'green', 'blue' ];
let [ firstColor, ...restColor ] = colors;

console.log(firstColor); // "red"
console.log(restColor.length); // 2
console.log(restColor[0]); // "green"
console.log(restColor[1]); // "blue"

colors数组中的第一个元素被赋值给firstColor变量,其余元素被赋值给restColor数组

**注意:在被解构的数组中,不定元素必须为最后一个条目,否则会抛出异常**

#### 解构参数

function setCookie(type, name, { path, domain, expires }) {
// ... }

setCookie('js', 'time', {
path: '1', domain: '2', expires: 6000, });

// 实际js引擎操作 function setCookie(type, name, options) {
let { path, domain, expires } = options; // ... }

**注意:**

① 如果解构赋值表达式的右值为null或undefined,则程序会报错,且调用setCookie函数时,不传入第三个参数程序也会抛出异常

② 若想将解构参数定为可选,那么就需要提供默认值

- 默认值

function setCookie(type, name, { path, domain, expires } = {}) {
// ... }

### 7. Set集合与Map集合


### 8. 迭代器和生成器

#### 什么是迭代器

- 迭代器是一种特殊的对象,它包含有专门为迭代过程设计的专有接口。

- 所有的迭代器都有一个next()方法,每次调用next()方法都会返回一个结果对象。

- 返回的结果对象有两个属性: ① done,一个布尔类型的值,当没有更多可返回的数据时返回true,其他情况下全部返回false;② value,表示下一个将要返回的值

var iterator = createIterator([1, 2, 3]);

iterator.next(); // => {value: 1, done: false}
iterator.next(); // => {value: 2, done: false}
iterator.next(); // => {value: 3, done: false}
iterator.next(); // => {value: undefined, done: true}
// 之后所有的调用都会返回相同的内容 iterator.next(); // => {value: undefined, done: true}

#### 什么是生成器

- 生成器是一种返回迭代器的函数,通过function关键字后的星号(*)来表示,函数中会用到新的关键字 yield。

- yield关键字是ES6的新特性,可以通过它来指定调用迭代器的next()方法时的返回值和返回顺序。

- 生成器的调用过程和其他函数一样,最终返回的是创建好的迭代器。

- 在生成器函数中,每执行完一条yield语句后,函数会自动停止执行,直到再次调用迭代器的next()方法时才会继续执行下一条yield语句。

- 使用yield关键字可以返回任何值或表达式,可以通过生成器函数批量给迭代器添加元素。

// 循环中使用yield function *createIterator(items) {
for (let i = 0; i < items.length; i++) { yield items[i]; } }

let iterator = createIterator([1, 3, 5]);
iterator.next(); // => {value: 1, done: false}
iterator.next(); // => {value: 3, done: false}
iterator.next(); // => {value: 5, done: false}
iterator.next(); // => {value: undefined, done: true}
// 之后所有的调用都会返回相同的内容 iterator.next(); // => {value: undefined, done: true}

- yield关键字只可在生成器内部使用,在其他地方或函数中使用会导致程序抛错。

function *createIterator(items) {
items.forEach(function(item) { // => 语法错误,虽然在生成器函数内部,但其实际是嵌套在forEach回调函数中 yield item + 1; }); }

**生成器函数表达式**

let createIterator = function *(items) {
for (let i = 0; i < items.length; i++) { yield items[i]; } }

**生成器对象方法**

let o = {
*createIterator(items) { for (let i = 0; i < items.length; i++) { yield items[i]; } } };

#### 可迭代对象和for-of循环

**可迭代对象**

- 可迭代对象具有Symbol.iterator属性,所有的集合对象和字符串都是可迭代对象,这些对象中都有默认的迭代器。**for-of循环需要用到可迭代对象的这些功能**

**for-of循环**

- for-of循环每执行一次都会调用可迭代对象的next()方法,并将迭代器返回的结果对象的value属性存储在一个变量中。

- 循环将持续执行知道返回对象的done属性的值为true

- for-of只能用于可迭代对象

let values = [1, 2, 3];
for (let num of values) {
console.log(num); }

// 1 // 2 // 3

**访问默认迭代器**

- 可以通过Symbol.iterator来访问对象默认的迭代器

- 具有Symbol.iterator属性的对象都有默认的迭代器,可以用Symbol.iterator来检测对象是否为可迭代对象

#### 内建迭代器

**集合对象迭代器**

- ES6中有三种类型的集合对象:数组、Map集合、Set集合,这三种对象都内建了一下三种迭代器:

① entries() 返回一个迭代器,其值为多个键值对

若被遍历的对象是数组,则第一个表示的是数组的索引;如果是Set集合,则第一个和第二个元素都是值;如果是Map集合,则第一个元素是键名

② values() 返回一个迭代器,其值为集合的值

③ keys() 返回一个迭代器,其值为集合中所有的键名 

- 数组和Set集合的默认迭代器是values()方法,Map集合的默认迭代器是entries()方法

#### 高级迭代器功能

**给迭代器传递参数**

function *createIterator() {
let first = yield 1; let second = yield first + 2; // 4 + 2 yield second + 3; // 5 + 3 }

let iterator = createIterator();

iterator.next(); // => {value: 1, done: false}
iterator.next(4); // => {value: 6, done: false}
iterator.next(5); // => {value: 8, done: false}
iterator.next(); // => {value: undefined, done: true}
```

  • 第一次调用next()方法时,无论传入什么参数都会被丢弃

  • 在含参yield语句中,表达式右侧等价于第一次调用next()方法后的下一个返回值,左侧等价于第二次调用next()方法后,在函数继续执行前得到的返回值。

9. JS中的类

10. 改进的数组功能

13. 用模块封装代码

米奇

继续阅读此作者的更多文章