【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理) 中

简介: 【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理) 中

1.10 Set和Map数据结构

1.10.1 Set

介绍:

Set数据结构类似数组,但所有成员的值唯一

Set本身为一个构造函数,用来生成Set数据结构,使用add方法来添加新成员。

let a = new Set();
[1,2,2,1,3,4,5,4,5].forEach(x=>a.add(x));
for(let k of a){
    console.log(k)
};
// 1 2 3 4 5

基础使用

let a = new Set([1,2,3,3,4]);
[...a]; // [1,2,3,4]
a.size; // 4
// 数组去重
[...new Set([1,2,3,4,4,4])];// [1,2,3,4]

注意

  • Set中添加值的时候,不会类型转换,即5'5'是不同的。
[...new Set([5,'5'])]; // [5, "5"]

属性和方法

  • 属性:
  • Set.prototype.constructor:构造函数,默认就是Set函数。
  • Set.prototype.size:返回Set实例的成员总数。
  • 操作方法:
  • add(value):添加某个值,返回 Set 结构本身。
  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • has(value):返回一个布尔值,表示该值是否为Set的成员。
  • clear():清除所有成员,没有返回值。
let a = new Set();
a.add(1).add(2); // a => Set(2) {1, 2}
a.has(2);        // true
a.has(3);        // false
a.delete(2);     // true  a => Set(1) {1}
a.clear();       // a => Set(0) {}

数组去重

let a = new Set([1,2,3,3,3,3]);

1.10.2 Set的应用

数组去重

// 方法1
[...new Set([1,2,3,4,4,4])]; // [1,2,3,4]
// 方法2
Array.from(new Set([1,2,3,4,4,4]));    // [1,2,3,4]

遍历和过滤

let a = new Set([1,2,3,4]);
// map 遍历操作
let b = new Set([...a].map(x =>x*2));// b => Set(4) {2,4,6,8}
// filter 过滤操作
let c = new Set([...a].filter(x =>(x%2) == 0)); // b => Set(2) {2,4}

获取并集、交集和差集

let a = new Set([1,2,3]);
let b = new Set([4,3,2]);
// 并集
let c1 = new Set([...a, ...b]);  // Set {1,2,3,4}
// 交集
let c2 = new Set([...a].filter(x => b.has(x))); // set {2,3}
// 差集
let c3 = new Set([...a].filter(x => !b.has(x))); // set {1}
  • 遍历方法:
  • keys():返回键名的遍历器。
  • values():返回键值的遍历器。
  • entries():返回键值对的遍历器。
  • forEach():使用回调函数遍历每个成员

Set遍历顺序是插入顺序,当保存多个回调函数,只需按照顺序调用。但由于Set结构没有键名只有键值,所以keys()values()是返回结果相同。

let a = new Set(['a','b','c']);
for(let i of a.keys()){console.log(i)};   // 'a' 'b' 'c'
for(let i of a.values()){console.log(i)}; // 'a' 'b' 'c'
for(let i of a.entries()){console.log(i)}; 
// ['a','a'] ['b','b'] ['c','c']

并且 还可以使用for...of直接遍历Set

let a = new Set(['a','b','c']);
for(let k of a){console.log(k)};   // 'a' 'b' 'c'

forEach与数组相同,对每个成员执行操作,且无返回值。

let a = new Set(['a','b','c']);
a.forEach((v,k) => console.log(k + ' : ' + v));

1.10.3 Map

由于传统的JavaScript对象只能用字符串当做键,给开发带来很大限制,ES6增加Map数据结构,使得各种类型的值(包括对象)都可以作为键。

Map结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。 基础使用

let a = new Map();
let b = {name: 'leo' };
a.set(b,'my name'); // 添加值
a.get(b);           // 获取值
a.size;      // 获取总数
a.has(b);    // 查询是否存在
a.delete(b); // 删除一个值
a.clear();   // 清空所有成员 无返回

注意

  • 传入数组作为参数,指定键值对的数组
let a = new Map([
    ['name','leo'],
    ['age',18]
])
  • 如果对同一个键多次赋值,后面的值将覆盖前面的值
let a = new Map();
a.set(1,'aaa').set(1,'bbb');
a.get(1); // 'bbb'
  • 如果读取一个未知的键,则返回undefined
new Map().get('abcdef'); // undefined
  • 同样的值的两个实例,在 Map 结构中被视为两个键。
let a = new Map();
let a1 = ['aaa'];
let a2 = ['aaa'];
a.set(a1,111).set(a2,222);
a.get(a1); // 111
a.get(a2); // 222

遍历方法: Map 的遍历顺序就是插入顺序。

  • keys():返回键名的遍历器。
  • values():返回键值的遍历器。
  • entries():返回所有成员的遍历器。
  • forEach():遍历 Map 的所有成员。
let a = new Map([
    ['name','leo'],
    ['age',18]
])
for (let i of a.keys()){...};
for (let i of a.values()){...};
for (let i of a.entries()){...};
a.forEach((v,k,m)=>{
    console.log(`key:${k},value:${v},map:${m}`)
})

将Map结构转成数组结构

let a = new Map([
    ['name','leo'],
    ['age',18]
])
let a1 = [...a.keys()];   // a1 => ["name", "age"]
let a2 = [...a.values()]; // a2 =>  ["leo", 18]
let a3 = [...a.entries()];// a3 => [['name','leo'], ['age',18]]

1.10.4 Map与其他数据结构互相转换

  • Map 转 数组
let a = new Map().set(true,1).set({f:2},['abc']);
[...a]; // [[true:1], [ {f:2},['abc'] ]]
  • 数组 转 Map
let a = [ ['name','leo'], [1, 'hi' ]]
let b = new Map(a);
  • Map 转 对象 如果所有 Map 的键都是字符串,它可以无损地转为对象。
    如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。
function fun(s) {
  let obj = Object.create(null);
  for (let [k,v] of s) {
    obj[k] = v;
  }
  return obj;
}
const a = new Map().set('yes', true).set('no', false);
fun(a)
// { yes: true, no: false }
  • 对象 转 Map
function fun(obj) {
  let a = new Map();
  for (let k of Object.keys(obj)) {
    a.set(k, obj[k]);
  }
  return a;
}
fun({yes: true, no: false})
// Map {"yes" => true, "no" => false}
  • Map 转 JSON
    (1)Map键名都是字符串,转为对象JSON:
function fun (s) {
    let obj = Object.create(null);
    for (let [k,v] of s) {
        obj[k] = v;
    }
    return JSON.stringify(obj)
}
let a = new Map().set('yes', true).set('no', false);
fun(a);
// '{"yes":true,"no":false}'

(2)Map键名有非字符串,转为数组JSON:

function fun (map) {
  return JSON.stringify([...map]);
}
let a = new Map().set(true, 7).set({foo: 3}, ['abc']);
fun(a)
// '[[true,7],[{"foo":3},["abc"]]]'
  • JSON 转 Map
    (1)所有键名都是字符串:
function fun (s) {
  let strMap = new Map();
  for (let k of Object.keys(s)) {
    strMap.set(k, s[k]);
  }
  return strMap;
  return JSON.parse(strMap);
}
fun('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}

(2)整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组:

function fun2(s) {
  return new Map(JSON.parse(s));
}
fun2('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}

1.11 Proxy

proxy 用于修改某些操作的默认行为,可以理解为一种拦截外界对目标对象访问的一种机制,从而对外界的访问进行过滤和修改,即代理某些操作,也称“代理器”。

1.11.1 基础使用

proxy实例化需要传入两个参数,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

let p = new Proxy(target, handler);
let a = new Proxy({}, {
    get: function (target, handler){
        return 'leo';
    }
})
a.name; // leo
a.age;  // leo
a.abcd; // leo

上述a实例中,在第二个参数中定义了get方法,来拦截外界访问,并且get方法接收两个参数,分别是目标对象所要访问的属性,所以不管外部访问对象中任何属性都会执行get方法返回leo

注意

  • 只能使用Proxy实例的对象才能使用这些操作。
  • 如果handler没有设置拦截,则直接返回原对象。
let target = {};
let handler = {};
let p = new Proxy(target, handler);
p.a = 'leo'; 
target.a;  // 'leo'

同个拦截器函数,设置多个拦截操作

let p = new Proxy(function(a, b){
    return a + b;
},{
    get:function(){
        return 'get方法';
    },
    apply:function(){
        return 'apply方法';
    }
})

Proxy支持的13种拦截操作

13种拦截操作的详细介绍:打开阮一峰老师的链接

  • get(target, propKey, receiver): 拦截对象属性的读取,比如proxy.foo和proxy['foo']。
  • set(target, propKey, value, receiver): 拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
  • has(target, propKey): 拦截propKey in proxy的操作,返回一个布尔值。
  • deleteProperty(target, propKey): 拦截delete proxy[propKey]的操作,返回一个布尔值。
  • ownKeys(target): 拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey): 拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc): 拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target): 拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target): 拦截Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target): 拦截Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto): 拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • apply(target, object, args): 拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
  • construct(target, args): 拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。

1.11.2 取消Proxy实例

使用Proxy.revocale方法取消Proxy实例。

let a = {};
let b = {};
let {proxy, revoke} = Proxy.revocale(a, b);
proxy.name = 'leo';  // 'leo'
revoeke();
proxy.name;  // TypeError: Revoked  

1.11.3 实现 Web服务的客户端

const service = createWebService('http://le.com/data');
service.employees().than(json =>{
    const employees = JSON.parse(json);
})
function createWebService(url){
    return new Proxy({}, {
        get(target, propKey, receiver{
            return () => httpGet(url+'/'+propKey);
        })
    })
}

1.12 Promise对象

1.12.1 概念

主要用途:解决异步编程带来的回调地狱问题

Promise简单理解一个容器,存放着某个未来才会结束的事件(通常是一个异步操作)的结果。通过Promise对象来获取异步操作消息,处理各种异步操作。

Promise对象2特点

  • 对象的状态不受外界影响

Promise对象代表一个异步操作,有三种状态:pending(进行中)fulfilled(已成功)rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果

Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

注意,为了行文方便,本章后面的resolved统一只指fulfilled状态,不包含rejected状态。

Promise缺点

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

1.12.2 基本使用

Promise为一个构造函数,需要用new来实例化。

let p = new Promise(function (resolve, reject){
   if(/*异步操作成功*/){
       resolve(value);
   } else {
       reject(error);
   }
})

Promise接收一个函数作为参数,该函数两个参数resolvereject,有JS引擎提供。

  • resolve作用是将Promise的状态从pending变成resolved,在异步操作成功时调用,返回异步操作的结果,作为参数传递出去。
  • reject作用是将Promise的状态从pending变成rejected,在异步操作失败时报错,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

p.then(function(val){
    // success...
},function(err){
    // error...
})

几个例子来理解

  • 当一段时间过后,Promise状态便成为resolved触发then方法绑定的回调函数。
function timeout (s){
    return new Promise((resolve, reject){
        setTimeout(result,ms, 'done');
    })
}
timeout(100).then(val => {
    console.log(val);
})
  • Promise新建后立刻执行。
let p = new Promise(function(resolve, reject){
    console.log(1);
    resolve();
})
p.then(()=>{
    console.log(2);
})
console.log(3);
// 1
// 3
// 2 

异步加载图片

function f(url){
    return new Promise(function(resolve, reject){
        const img = new Image ();
        img.onload = function(){
            resolve(img);
        }
        img.onerror = function(){
            reject(new Error(
                'Could not load image at ' + url
            ));
        }
        img.src = url;
    })
}

resolve函数和reject函数的参数为resolve函数或reject函数

p1的状态决定了p2的状态,所以p2要等待p1的结果再执行回调函数。

const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})
p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail

调用resolvereject不会结束Promise参数函数的执行,除了return:

new Promise((resolve, reject){
    resolve(1);
    console.log(2);
}).then(r => {
    console.log(3);
})
// 2
// 1
new Promise((resolve, reject){
    return resolve(1);
    console.log(2);
})
// 1

1.12.3 Promise.prototype.then()

作用是为Promise添加状态改变时的回调函数,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

then方法返回一个新Promise实例,与原来Promise实例不同,因此可以使用链式写法,上一个then的结果作为下一个then的参数。

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});

1.12.4 Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 处理 getJSON 和 前一个回调函数运行时发生的错误
  console.log('发生错误!', error);
});

如果 Promise 状态已经变成resolved,再抛出错误是无效的。

const p = new Promise(function(resolve, reject) {
  resolve('ok');
  throw new Error('test');
});
p
  .then(function(value) { console.log(value) })
  .catch(function(error) { console.log(error) });
// ok

promise抛出一个错误,就被catch方法指定的回调函数捕获,下面三种写法相同。

// 写法一
const p = new Promise(function(resolve, reject) {
  throw new Error('test');
});
p.catch(function(error) {
  console.log(error);
});
// Error: test
// 写法二
const p = new Promise(function(resolve, reject) {
  try {
    throw new Error('test');
  } catch(e) {
    reject(e);
  }
});
p.catch(function(error) {
  console.log(error);
});
// 写法三
const p = new Promise(function(resolve, reject) {
  reject(new Error('test'));
});
p.catch(function(error) {
  console.log(error);
});

一般来说,不要在then方法里面定义Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });
// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

1.12.5 Promise.prototype.finally()

finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

finally不接收任何参数,与状态无关,本质上是then方法的特例。

promise
.finally(() => {
  // 语句
});
// 等同于
promise
.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);

上面代码中,如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。

finally方法总是会返回原来的值。

// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})
// resolve 的值是 2
Promise.resolve(2).finally(() => {})
// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})
// reject 的值是 3
Promise.reject(3).finally(() => {})

1.12.6 Promise.all()

用于将多个 Promise 实例,包装成一个新的 Promise 实例,参数可以不是数组,但必须是Iterator接口,且返回的每个成员都是Promise实例。

const p = Promise.all([p1, p2, p3]);

p的状态由p1p2p3决定,分成两种情况。

  1. 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
  2. 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
// 生成一个Promise对象的数组
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
  // ...
}).catch(function(reason){
  // ...
});

上面代码中,promises是包含 6 个 Promise 实例的数组,只有这 6 个实例的状态都变成fulfilled,或者其中有一个变为rejected,才会调用Promise.all方法后面的回调函数。

注意:如果Promise的参数中定义了catch方法,则rejected后不会触发Promise.all()catch方法,因为参数中的catch方法执行完后也会变成resolved,当Promise.all()方法参数的实例都是resolved时就会调用Promise.all()then方法。

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]

如果参数里面都没有catch方法,就会调用Promise.all()的catch方法。

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 报错了

1.12.7 Promise.race()

Promise.all方法类似,也是将多个Promise实例包装成一个新的Promise实例。

const p = Promise.race([p1, p2, p3]);

Promise.all方法区别在于,Promise.race方法是p1, p2, p3中只要一个参数先改变状态,就会把这个参数的返回值传给p的回调函数。

1.12.8 Promise.resolve()

将现有对象转换成 Promise 对象。

const p = Promise.resolve($.ajax('/whatever.json'));

1.12.9 Promise.reject()

返回一个rejected状态的Promise实例。

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
  console.log(s)
});
// 出错了

注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。

const thenable = {
  then(resolve, reject) {
    reject('出错了');
  }
};
Promise.reject(thenable)
.catch(e => {
  console.log(e === thenable)
})
// true

1.13 Iterator和 for...of循环

1.13.1 Iterator遍历器概念

Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator三个作用

  • 为各种数据结构,提供一个统一的、简便的访问接口;
  • 使得数据结构的成员能够按某种次序排列;
  • Iterator 接口主要供ES6新增的for...of消费;

1.13.2 Iterator遍历过程

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含valuedone两个属性的对象。

  • value属性是当前成员的值;
  • done属性是一个布尔值,表示遍历是否结束;

模拟next方法返回值:

let f = function (arr){
    var nextIndex = 0;
    return {
        next:function(){
            return nextIndex < arr.length ?
            {value: arr[nextIndex++], done: false}:
            {value: undefined, done: true}
        }
    }
}
let a = f(['a', 'b']);
a.next(); // { value: "a", done: false }
a.next(); // { value: "b", done: false }
a.next(); // { value: undefined, done: true }

1.13.3 默认Iterator接口

若数据可遍历,即一种数据部署了Iterator接口。

ES6中默认的Iterator接口部署在数据结构的Symbol.iterator属性,即如果一个数据结构具有Symbol.iterator属性,就可以认为是可遍历

Symbol.iterator属性本身是函数,是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内(参见《Symbol》一章)。

原生具有Iterator接口的数据结构有

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

1.13.4 Iterator使用场景

  • (1)解构赋值
    对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
let a = new Set().add('a').add('b').add('c');
let [x, y] = a;       // x = 'a'  y = 'b'
let [a1, ...a2] = a;  // a1 = 'a' a2 = ['b','c']
  • (2)扩展运算符
    扩展运算符(...)也会调用默认的 Iterator 接口。
let a = 'hello';
[...a];            //  ['h','e','l','l','o']
let a = ['b', 'c'];
['a', ...a, 'd'];  // ['a', 'b', 'c', 'd']
  • (2)yield*
    yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
let a = function*(){
    yield 1;
    yield* [2,3,4];
    yield 5;
}
let b = a();
b.next() // { value: 1, done: false }
b.next() // { value: 2, done: false }
b.next() // { value: 3, done: false }
b.next() // { value: 4, done: false }
b.next() // { value: 5, done: false }
b.next() // { value: undefined, done: true }
  • (4)其他场合
    由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。
  • for...of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]])
  • Promise.all()
  • Promise.race()

1.13.5 for...of循环

只要数据结构部署了Symbol.iterator属性,即具有 iterator 接口,可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterato方法。

使用场景

for...of可以使用在数组SetMap结构类数组对象Genetator对象字符串

  • 数组
    for...of循环可以代替数组实例的forEach方法。
let a = ['a', 'b', 'c'];
for (let k of a){console.log(k)}; // a b c
a.forEach((ele, index)=>{
    console.log(ele);    // a b c
    console.log(index);  // 0 1 2 
})

for...in对比,for...in只能获取对象键名,不能直接获取键值,而for...of允许直接获取键值。

let a = ['a', 'b', 'c'];
for (let k of a){console.log(k)};  // a b c
for (let k in a){console.log(k)};  // 0 1 2
  • Set和Map
    可以使用数组作为变量,如for (let [k,v] of b){...}
let a = new Set(['a', 'b', 'c']);
for (let k of a){console.log(k)}; // a b c
let b = new Map();
b.set('name','leo');
b.set('age', 18);
b.set('aaa','bbb');
for (let [k,v] of b){console.log(k + ":" + v)};
// name:leo
// age:18
// aaa:bbb
  • 类数组对象
// 字符串
let a = 'hello';
for (let k of a ){console.log(k)}; // h e l l o
// DOM NodeList对象
let b = document.querySelectorAll('p');
for (let k of b ){
    k.classList.add('test');
}
// arguments对象
function f(){
    for (let k of arguments){
        console.log(k);
    }
}
f('a','b'); // a b
  • 对象
    普通对象不能直接使用for...of会报错,要部署Iterator才能使用。
let a = {a:'aa',b:'bb',c:'cc'};
for (let k in a){console.log(k)}; // a b c
for (let k of a){console>log(k)}; // TypeError

1.13.6 跳出for...of

使用break来实现。

for (let k of a){
    if(k>100)
        break;
    console.log(k);
}

1.14 Generator函数和应用

1.14.1 基本概念

Generator函数是一种异步编程解决方案。

原理

执行Genenrator函数会返回一个遍历器对象,依次遍历Generator函数内部的每一个状态。

Generator函数是一个普通函数,有以下两个特征:

  • function关键字与函数名之间有个星号;
  • 函数体内使用yield表达式,定义不同状态;

通过调用next方法,将指针移向下一个状态,直到遇到下一个yield表达式(或return语句)为止。简单理解,Generator函数分段执行,yield表达式是暂停执行的标记,而next恢复执行。

function * f (){
    yield 'hi';
    yield 'leo';
    return 'ending';
}
let a = f();
a.next();  // {value: 'hi', done : false}
a.next();  // {value: 'leo', done : false}
a.next();  // {value: 'ending', done : true}
a.next();  // {value: undefined, done : false}

1.14.2 yield表达式

yield表达式是暂停标志,遍历器对象的next方法的运行逻辑如下:

  1. 遇到yield就暂停执行,将这个yield后的表达式的值,作为返回对象的value属性值。
  2. 下次调用next往下执行,直到遇到下一个yield
  3. 直到函数结束或者return为止,并返回return语句后面表达式的值,作为返回对象的value属性值。
  4. 如果该函数没有return语句,则返回对象的valueundefined

注意:

  • yield只能用在Generator函数里使用,其他地方使用会报错。
// 错误1
(function(){
    yiled 1;  // SyntaxError: Unexpected number
})()
// 错误2  forEach参数是个普通函数
let a = [1, [[2, 3], 4], [5, 6]];
let f = function * (i){
    i.forEach(function(m){
        if(typeof m !== 'number'){
            yield * f (m);
        }else{
            yield m;
        }
    })
}
for (let k of f(a)){
    console.log(k)
}
  • yield表达式如果用于另一个表达式之中,必须放在圆括号内。
function * a (){
    console.log('a' + yield);     //  SyntaxErro
    console.log('a' + yield 123); //  SyntaxErro
    console.log('a' + (yield));     //  ok
    console.log('a' + (yield 123)); //  ok
}
  • yield表达式用做函数参数或放在表达式右边,可以不加括号
function * a (){
    f(yield 'a', yield 'b');    //  ok
    lei i = yield;              //  ok
}

1.14.3 next方法

yield本身没有返回值,或者是总返回undefinednext方法可带一个参数,作为上一个yield表达式的返回值。

function * f (){
    for (let k = 0; true; k++){
        let a = yield k;
        if(a){k = -1};
    }
}
let g =f();
g.next();    // {value: 0, done: false}
g.next();    // {value: 1, done: false}
g.next(true);    // {value: 0, done: false}

这一特点,可以让Generator函数开始执行之后,可以从外部向内部注入不同值,从而调整函数行为。

function * f(x){
    let y = 2 * (yield (x+1));
    let z = yield (y/3);
    return (x + y + z);
}
let a = f(5);
a.next();   // {value : 6 ,done : false}
a.next();   // {value : NaN ,done : false}  
a.next();   // {value : NaN ,done : true}
// NaN因为yeild返回的是对象 和数字计算会NaN
let b = f(5);
b.next();     // {value : 6 ,done : false}
b.next(12);   // {value : 8 ,done : false}
b.next(13);   // {value : 42 ,done : false}
// x 5 y 24 z 13

1.14.4 for...of循环

for...of循环会自动遍历,不用调用next方法,需要注意的是,for...of遇到next返回值的done属性为true就会终止,return返回的不包括在for...of循环中。

function * f(){
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    return 5;
}
for (let k of f()){
    console.log(k);
}
// 1 2 3 4  没有 5 

1.14.5 Generator.prototype.throw()

throw方法用来向函数外抛出错误,并且在Generator函数体内捕获。

let f = function * (){
    try { yield }
    catch (e) { console.log('内部捕获', e) }
}
let a = f();
a.next();
try{
    a.throw('a');
    a.throw('b');
}catch(e){
    console.log('外部捕获',e);
}
// 内部捕获 a
// 外部捕获 b

1.14.6 Generator.prototype.return()

return方法用来返回给定的值,并结束遍历Generator函数,如果return方法没有参数,则返回值的value属性为undefined

function * f(){
    yield 1;
    yield 2;
    yield 3;
}
let g = f();
g.next();          // {value : 1, done : false}
g.return('leo');   // {value : 'leo', done " true}
g.next();          // {value : undefined, done : true}

1.14.7 next()/throw()/return()共同点

相同点就是都是用来恢复Generator函数的执行,并且使用不同语句替换yield表达式。

  • next()yield表达式替换成一个值。
let f = function * (x,y){
    let r = yield x + y;
    return r;
}
let g = f(1, 2); 
g.next();   // {value : 3, done : false}
g.next(1);  // {value : 1, done : true}
// 相当于把 let r = yield x + y;
// 替换成 let r = 1;
  • throw()yield表达式替换成一个throw语句。
g.throw(new Error('报错'));  // Uncaught Error:报错
// 相当于将 let r = yield x + y
// 替换成 let r = throw(new Error('报错'));
  • next()yield表达式替换成一个return语句。
g.return(2); // {value: 2, done: true}
// 相当于将 let r = yield x + y
// 替换成 let r = return 2;

1.14.8 yield* 表达式

用于在一个Generator中执行另一个Generator函数,如果没有使用yield*会没有效果。

function * a(){
    yield 1;
    yield 2;
}
function * b(){
    yield 3;
    yield * a();
    yield 4;
}
// 等同于
function * b(){
    yield 3;
    yield 1;
    yield 2;
    yield 4;
}
for(let k of b()){console.log(k)}
// 3
// 1
// 2
// 4

1.14.9 应用场景

  1. 控制流管理
    解决回调地狱:
// 使用前
f1(function(v1){
    f2(function(v2){
        f3(function(v3){
            // ... more and more
        })
    })
})
// 使用Promise 
Promise.resolve(f1)
    .then(f2)
    .then(f3)
    .then(function(v4){
        // ...
    },function (err){
        // ...
    }).done();
// 使用Generator
function * f (v1){
    try{
        let v2 = yield f1(v1);
        let v3 = yield f1(v2);
        let v4 = yield f1(v3);
        // ...
    }catch(err){
        // console.log(err)
    }
}
function g (task){
    let obj = task.next(task.value);
  // 如果Generator函数未结束,就继续调用
  if(!obj.done){
      task.value = obj.value;
      g(task);
  }
}
g( f(initValue) );
  1. 异步编程的使用 在真实的异步任务封装的情况:
let fetch = require('node-fetch');
function * f(){
    let url = 'http://www.baidu.com';
    let res = yield fetch(url);
    console.log(res.bio);
}
// 执行该函数
let g = f();
let result = g.next();
// 由于fetch返回的是Promise对象,所以用then
result.value.then(function(data){
    return data.json();
}).then(function(data){
    g.next(data);
})

1.15 Class语法和继承

1.15.1 介绍

ES6中的class可以看作只是一个语法糖,绝大部分功能都可以用ES5实现,并且,类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式

// ES5
function P (x,y){
    this.x = x;
    this.y = y;
}
P.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};
var a = new P(1, 2);
// ES6
class P {
    constructor(x, y){
        this.x = x;
        this.y = y;
    }
    toString(){
        return '(' + this.x + ', ' + this.y + ')';
    }
}
let a = new P(1, 2);

值得注意: ES6的的所有方法都是定义在prototype属性上,调用类的实例的方法,其实就是调用原型上的方法。

class P {
    constructor(){ ... }
    toString(){ ... }
    toNumber(){ ... }
}
// 等同于
P.prototyoe = {
    constructor(){ ... },
    toString(){ ... },
    toNumber(){ ... }
}
let a = new P();
a.constructor === P.prototype.constructor; // true

类的属性名可以使用表达式

let name = 'leo';
class P {
    constructor (){ ... }
    [name](){ ... }
}

Class不存在变量提升: ES6中的类不存在变量提升,与ES5完全不同:

new P ();   // ReferenceError
class P{...};

Class的name属性

name属性总是返回紧跟在class后的类名。

class P {}
P.name;  // 'P'

1.15.2 constructor()方法

constructor()是类的默认方法,通过new实例化时自动调用执行,一个类必须有constructor()方法,否则一个空的constructor()会默认添加。

constructor()方法默认返回实例对象(即this)。

class P { ... }
// 等同于
class P {
    constructor(){ ... }
}

1.15.3 类的实例对象

与ES5一样,ES6的类必须使用new命令实例化,否则报错。

class P { ... }
let a = P (1,2);     // 报错
let b = new P(1, 2); // 正确

与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

class P {
    constructor(x, y){
        this.x = x;
        this.y = y;
    }
    toString(){
        return '(' + this.x + ', ' + this.y + ')';
    }
}
var point = new Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false 
point.__proto__.hasOwnProperty('toString') // true
// toString是原型对象的属性(因为定义在Point类上)

1.15.4 Class表达式

与函数一样,类也可以使用表达式来定义,使用表达式来作为类的名字,而class后跟的名字,用来指代当前类,只能再Class内部使用。

let a = class P{
    get(){
        return P.name;
    }
}
let b = new a();
b.get(); // P
P.name;  // ReferenceError: P is not defined

如果类的内部没用到的话,可以省略P,也就是可以写成下面的形式。

let a = class { ... }

1.15.5 私有方法和私有属性

由于ES6不提供,只能变通来实现:

  • 1.使用命名加以区别,如变量名前添加_,但是不保险,外面也可以调用到。
class P {
    // 公有方法
    f1 (x) {
        this._x(x);
    }
    // 私有方法
    _x (x){
        return this.y = x;
    }
}
  • 2.将私有方法移除模块,再在类内部调用call方法。
class P {
    f1 (x){
        f2.call(this, x);
    }
}
function f2 (x){
    return this.y = x;
}
  • 3.使用Symbol为私有方法命名。
const a1 = Symbol('a1');
const a2 = Symbol('a2');
export default class P{
    // 公有方法
    f1 (x){
        this[a1](x);
    }
    // 私有方法
    [a1](x){
        return this[a2] = x;
    }
}

1.15.6 this指向问题

类内部方法的this默认指向类的实例,但单独使用该方法可能报错,因为this指向的问题。

class P{
    leoDo(thing = 'any'){
        this.print(`Leo do ${thing}`)
    }
    print(text){
        console.log(text);
    }
}
let a = new P();
let { leoDo } = a;
leoDo(); // TypeError: Cannot read property 'print' of undefined
// 问题出在 单独使用leoDo时,this指向调用的环境,
// 但是leoDo中的this是指向P类的实例,所以报错

解决方法

  • 1.在类里面绑定this
class P {
    constructor(){
        this.name = this.name.bind(this);
    }
}
  • 2.使用箭头函数
class P{
    constructor(){
        this.name = (name = 'leo' )=>{
            this.print(`my name is ${name}`)
        }
    }
}

1.15.7 Class的getter和setter

使用getset关键词对属性设置取值函数和存值函数,拦截属性的存取行为。

class P {
    constructor (){ ... }
    get f (){
        return 'getter';
    }
    set f (val) {
        console.log('setter: ' + val);
    }
}
let a = new P();
a.f = 100;   // setter : 100
a.f;          // getter

1.15.8 Class的generator方法

只要在方法之前加个(*)即可。

class P {
    constructor (...args){
        this.args = args;
    }
    *[Symbol.iterator](){
        for (let arg of this.args){
            yield arg;
        }
    }
}
for (let k of new P('aa', 'bb')){
    console.log(k);
}
// 'aa'
// 'bb'

1.15.9 Class的静态方法

由于类相当于实例的原型,所有类中定义的方法都会被实例继承,若不想被继承,只要加上static关键字,只能通过类来调用,即“静态方法”。

class P (){
    static f1 (){ return 'aaa' };
}
P.f1();    // 'aa'
let a = new P();
a.f1();    // TypeError: a.f1 is not a function

如果静态方法包含this关键字,则this指向类,而不是实例。

class P {
    static f1 (){
        this.f2();
    }
    static f2 (){
        console.log('aaa');
    }
    f2(){
        console.log('bbb');
    }
}
P.f2();  // 'aaa'

并且静态方法可以被子类继承,或者super对象中调用。

class P{
    static f1(){ return 'leo' };
}
class Q extends P { ... };
Q.f1();  // 'leo'
class R extends P {
    static f2(){
        return super.f1() + ',too';
    }
}
R.f2();  // 'leo , too'

1.15.10 Class的静态属性和实例属性

ES6中明确规定,Class内部只有静态方法没有静态属性,所以只能通过下面实现。

// 正确写法
class P {}
P.a1 = 1;
P.a1;      // 1
// 无效写法
class P {
    a1: 2,          // 无效
    static a1 : 2,  // 无效
}
P.a1;      // undefined

新提案来规定实例属性和静态属性的新写法

  • 1.类的实例属性
    类的实例属性可以用等式,写入类的定义中。
class P {
    prop = 100;   // prop为P的实例属性 可直接读取
    constructor(){
        console.log(this.prop); // 100
    }
}

有了新写法后,就可以不再contructor方法里定义。

为了可读性的目的,对于那些在constructor里面已经定义的实例属性,新写法允许直接列出

// 之前写法:
class RouctCounter extends React.Component {
    constructor(prop){
        super(prop);
        this.state = {
            count : 0
        }
    }
}
// 新写法
class RouctCounter extends React.Component {
    state;
    constructor(prop){
        super(prop);
        this.state = {
            count : 0
        }
    }
}
  • 2.类的静态属性
    只要在实例属性前面加上static关键字就可以。
class P {
    static prop = 100;
    constructor(){console.log(this.prop)}; // 100
}

新写法方便静态属性的表达。

// old 
class P  { .... }
P.a = 1;
// new 
class P {
    static a = 1;
}

1.15.11 Class的继承

主要通过extends关键字实现,继承父类的所有属性和方法,通过super关键字来新建父类构造函数的this对象。

class P { ... }
class Q extends P { ... }
class P { 
    constructor(x, y){
        // ...
    }
    f1 (){ ... }
}
class Q extends P {
    constructor(a, b, c){
        super(x, y);  // 调用父类 constructor(x, y)
        this.color = color ;
    }
    f2 (){
        return this.color + ' ' + super.f1(); 
        // 调用父类的f1()方法
    }
}

子类必须在constructor()调用super()否则报错,并且只有super方法才能调用父类实例,还有就是,父类的静态方法,子类也可以继承到

class P { 
    constructor(x, y){
        this.x = x;
        this.y = y;
    }
    static fun(){
        console.log('hello leo')
    }
}
// 关键点1 调用super
class Q extends P {
    constructor(){ ... }
}
let a = new Q(); // ReferenceError 因为Q没有调用super
// 关键点2 调用super
class R extends P {
    constructor (x, y. z){
        this.z = z; // ReferenceError 没调用super不能使用
        super(x, y);
        this.z = z; // 正确
    }
}
// 关键点3 子类继承父类静态方法
R.hello(); // 'hello leo'

super关键字

既可以当函数使用,还可以当对象使用。

  • 1.当函数调用,代表父类的构造函数,但必须执行一次。
class P {... };
class R extends P {
    constructor(){
        super();
    }
}
  • 2.当对象调用,指向原型对象,在静态方法中指向父类。
class P {
    f (){ return 2 };
}
class R extends P {
    constructor (){
        super();
        console.log(super.f()); // 2
    }
}
let a = new R()

注意super指向父类原型对象,所以定义在父类实例的方法和属性,是无法通过super调用的,但是通过调用super方法可以把内部this指向当前实例,就可以访问到。

class P {
    constructor(){
        this.a = 1;
    }
    print(){
        console.log(this.a);
    }
}
class R extends P {
    get f (){
        return super.a;
    }
}
let b = new R();
b.a; // undefined 因为a是父类P实例的属性
// 先调用super就可以访问
class Q extends P {
    constructor(){
        super();   // 将内部this指向当前实例
        return super.a;
    }
}
let c = new Q();
c.a; // 1
// 情况3
class J extends P {
    constructor(){
        super();
        this.a = 3;
    }
    g(){
        super.print();
    }
}
let c = new J();
c.g(); // 3  由于执行了super()后 this指向当前实例


目录
相关文章
|
2天前
|
JavaScript 小程序 Java
ES6使用实践总结
ES6使用实践总结
16 0
|
2天前
|
JavaScript 前端开发
|
10月前
|
自然语言处理 前端开发 Java
【ES系列八】——问题整理与总结
前端时间在团队中进行了ES的分享活动,只是将大概的内容进行了宏观的分享,以及自己在做的过程中的一些思路,但是在最后的答疑环节,伙伴们提出了一些问题,今天就针对于这些问题进行了一个简单的总结,希望能够带给读者朋友一些帮助。
|
11月前
|
缓存 JavaScript 前端开发
「万字进阶」深入浅出 Commonjs 和 Es Module
全方位介绍模块化 Commonjs 和 Es Module
「万字进阶」深入浅出 Commonjs 和 Es Module
|
11月前
|
Docker 容器
es应用笔记1-es部署
es应用笔记1-es部署
83 0
|
11月前
|
前端开发 JavaScript
每天3分钟,重学ES6-ES12系列文章汇总
每天3分钟,重学ES6-ES12系列文章汇总
40 0
|
存储 监控 前端开发
ES6 从入门到精通 # 23:ES6 的模块化实现(完结)
ES6 从入门到精通 # 23:ES6 的模块化实现(完结)
120 0
ES6 从入门到精通 # 23:ES6 的模块化实现(完结)
|
JavaScript 前端开发 数据库
再不学ES6你就out了 —— 一文搞懂ES6
ES6, 全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版 虽然15年就有正式版本了,但是国内普遍商用是在2018年之后去了,甚至到现在有很多前端仍然搞不懂ES6
142 0
再不学ES6你就out了 —— 一文搞懂ES6
|
JSON JavaScript 数据格式
ES6学习记录
ES6学习记录
89 0