ES6 - 基础笔记梳理

332 阅读9分钟

备战秋招,复习基础。如有错误,欢迎批评指正,共同进步!

变量声明

let 变量只在声明的块或子块中可用,不在全局对象上创造属性。不提升 → 出现暂存性死区 → 只要进入当前作用域,所要使用的变量就已经存在,但是不可获取,只有等到声明变量的那一行,才可以获取和使用变量

var 全局或整个封闭函数

const 块级作用域,且不能重新赋值 → 只读引用 → 但可以重新定义对象属性。const obj={"a":"b"}; → a只读b可写

do表达式,使获取块级作用域的返回值

let x = do {
    let t = f();
    t * t + 1;
};

扩展运算符

把数组变为参数序列

\begin{cases}
合并 & [...a,...b]\\
解构\\
返回多个值\\
将字符串转为数组 & [...'hello'] → ["h","e","l","l","o"]
\end{cases}

箭头函数

[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函数。

对象的扩展

_proto_属性

读取或设置当前对象的prototype对象 最好使用object.setPrototypeOf()object.getPrototypeOf()

symbol

第七种数据类型 表示独一无二的值。 [symbol]s=symbol();

symbol.for('现有参数') → 使用同一个symbol值,登记机制

Set和Map

Set

类似于数组,但所有成员值唯一,没有重复。

  • set转数组:array = Array.from(set);

  • 数组转set:set = new Set(Array) 返回set

  • 去除数组重复成员:[...new Set(Array)]返回数组

两个NaN是相等的

两个对象总是不相等的

操作方法:

方法 返回值 含义
---------- 基础操作 ----------
set.size 返回成员总数
add(a) 返回set 添加元素
delete(a) 返回布尔 是否删除成功
has(a) 返回布尔 是否存在该值
clear()
---------- 遍历操作 ----------
keys() 返回键名 set没有键名!
values() 返回键值 与keys行为一致
entries() 返回键值对 键名键值相同,如:['red','red']
forEach(键值,键名[,集合]) 没有返回值 使用回调函数遍历每个成员
---------- 其他操作 ----------
for (let x of set) 循环遍历
new Set[...a,...b] 并集
new Set([...a].filter(x=>b.hax(x))) 交集
new Set([...a].filter(x=>!b.hax(x))) 差集

遍历器:set.prototype[symbol.iterator] === set.prototype.values

WeakSet

成员只能是对象。

弱引用,不可遍历!

方法:add() delete() has() → 没有size和foeEach!

Map

键值对集合。任何类型的值都可用当作键!

  • map转数组:[...map][[a,1],[b,2]]

    [...map.keys()][a,b]

    [...map.values()][1,2]

  • 数组转map:map = new Map(array)

  • map转对象:如果map的所有键都是字符串,则可用转对象

    function srMapToObj(strMap){
        let obj = Object.create(null);
        for (let [k,v] of strMap){
            obj[k] = v;
        }
        return obj;
    }
    const myMap = new Map().set('yes',true).set('no',false);
    strMapToObj(myMap);  //{yes:true,no:false}
  • 对象转map:
    function objToStrMap(obj){
        let strMap = new Map();
        for (let k of Object.keys(obj)){
            strMap.set(k,obj[k]);
        }
        return strMap;
    }
    objToStrMap({yes:true,no:false});
  • map转json:
    1. map键名都是字符串,转为对象json
        function strMapToJson(strMap){
            return JSON.stringify(strMapToObj(strMap));
        }
    
    2. map键名有非字符串,转为数组json
        function mapToArrayJson(map){
            return JSON.stringify([...map]);
        }
        let myMap = newMap().set(true,7).set({foo:3},['abc']);
        mapToArrayJson(myMap); // '[[true,7],[{"foo":3},["abc"]]]'
  • json转map
    1. 所有键名都是字符串
        function jsonToStrMap(jsonStr){
            return objToStrMap(JSON.parse(jsonStr));
        }
    
    2. 整个json就是一个数字,且每个数字成员本身是一个具有2个成员的数组。是数组转json的逆操作。
        function jsonToMap(jsonStr){
            return new Map(JSON.parse(jsonStr));
        }
        jsonToMap('[[true,7],[{"foo":3},["abc"]]]') // Map {true =>7, Object {foo:3} => ['abc']}
方法 返回值 备注
---------- 基础操作 ----------
map.size 返回成员总数
map.set(key,value) 返回map key实际是与内存地址绑定的
get(key) 返回value
has(key) 返回布尔 是否存在该值
delete(key)
clear()
---------- 遍历操作 ----------
keys() 返回键名
values() 返回键值
entries() 返回所有成员
forEach() 遍历Map的所有成员

Map的遍历顺序就是插入顺序

遍历器:map[symbol.iterator] === set.entries

Map本身没有map和filter操作(set有)

WeakMap

只接收对象作为键名(null除外)

对象不计入垃圾回收机制(用于键所对应的对象可能会消失的场景)

没有key() value() entries() size() clear() forEach()!

应用场景:以DOM节点作为键名

Proxy

修改某些操作的默认行为,即“元编程”。

Reflect

获得语言的内部方法/修改某些Object返回结果

Promise

资料参考:BAT前端经典面试问题:史上最最最详细的手写Promise教程

一个容器,保存着某个未来才会结束的事件。

  1. 对象状态不受外界影响 Pending进行中 Fulfilled已成功 Rejected已失败
  2. 一旦状态改变就不会再变,任何时候都可以得到这个结果。(事件:一旦错过就监听不到了)
var promise = new Promise(function(resolve,reject){
    // some code...
    if (异步操作成功) {
        resolve(value);
    }else{
        reject(value);
    }
});

用then方法指定回调函数,将在当前脚本所有同步任务执行完成后才会执行.then方法返回的是新的Promise实例,因此可以链式!

promise.then(function(value){...},function(value){...});
  • 异步加载图片
function loadImageAsync(url){
    return new Promise(function(resolve,reject){
        var image = new Image();
        image.onload = function(){
            resolve(image);
        };
        image.onerror = function(){
            reject(new Error('Could not load image at '+url));
        };
        image.src = url;
    });
}
  • 用Promise实现AJAX
var getJSON = function(url){
    var promise = new Promise(function(resolve, reject){
        var client = new XMLHttpRequest();
        client.open("GET",url);
        client.onreadystatechange = handler;
        client.responseType = 'json';
        client.setRequestHeader("Accept","application/json");
        client.send();
        
        function handler(){
            if (this.readyState !==4){
                return;
            }
            if (this.status === 200){
                resolve(this.response);
            }else{
                reject(new Error(this.statusText));
            }
        };
    });
    return promise;
};

getJSON("/posts.json").then(function(json){
    console.log('Contents:'+json);
}, function(error){
    console.error('出错了',error);
});

Promise.all

将多个Promise实例包装成一个新的Promise实例。

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

状态:

  1. 只有p1 p2 p3的状态都Fulfilled,p才会Fulfilled。此时p1 p2 p3的返回值组成一个数组,传给p的回调函数。
  2. 只要p1 p2 p3有一个被Rejected,p就会Rejected。此时第一个被Rejected的实例的返回值会传递给p的回调函数。

手写实现

把所有要执行的函数都放进一个数组里面,然后数组以次以同步的形式一个一个执行,执行结束后能够接着执行then里面的东西。

Promise.all = arr => {
    let aResult = [];    //用于存放每次执行后返回结果
    return new _Promise(function (resolve, reject) {
      let i = 0;
      next();    //开始逐次执行数组中的函数
      function next() {
        arr[i].then(function (res) {
          aResult.push(res);    //执行后返回的结果放入数组中
          i++;
          if (i == arr.length) {    //如果函数数组中的函数都执行完,便把结果数组传给then
            resolve(aResult);
          } else {
            next();
          }
        })
      }
    })
  };

Promise.race()

状态:只要p1 p2 p3中有一个实例改变状态,P就会改变状态。率先改变状态的Promise实例的返回值传给P的回调参数。

Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(resolve,reject)
    };
  })
}

Iterator遍历器

一种机制/接口。用途:

  1. 为各种数据结构提供一个统一的、简便的访问接口
  2. 使数据结构的成员能够按某种次序排列
  3. 供for...of循环
let iter = arr[Symbol.iterator]();

for...in循环读取键名 for...of循环读取键值

To be continue...

Generator

状态机/遍历对象生成函数

  1. function命令与函数名之间有一个*
  2. 函数体内部使用yield语句定义(暂停)状态(调用next方法时才执行)
  3. 返回一个指向内部状态的指针对象

yield*

后跟一个遍历器对象,调用另一个generator。或跟一个数组,表示遍历数组内部。

Generator函数

var f = yield readFile();   →异步两阶段的分界线

To be continue...

其他方法

Thunk函数:自动执行Generator函数的方法。把参数放到一个临时函数中,再把临时函数传入函数体。

CO模块:用于Generator函数的自动执行。

async函数:是Generator函数的语法糖。将 * 替换为async,yield替换为await。内置执行器 asyncReadFile(),返回Promise对象。

Class

类和模块的内部默认使用严格模式。

constructor方法:创建和初始化由class创建的对象。

创建子类:extends 但不能继承常规(非可构造)对象,应该 用setPrototypeOf();

调用超类(原型):super 子类的构造必须执行一次super();

Mix-ins:以超类作为输入,以继承该类的子类作为输出。

Class只是一个语法糖,让对象原型的写法更清晰。

Class Point {...}
Point === Point.prototype.constructor;//true
typeof(Point);//function

Module模块

CommonJS模块

ES6前只要使用的模块加载方案。是对象,输入时必须查找对象属性。

//CommonJS模块
let { stat, exists, readFile } = require('fs');

// 等价于
let _fs = require('fs');
let stat = _fs.stat;
let exist = _fs.exist;
let readFile = _fs.readFile;

实质是整体加载fs模块,即加载js的所有方法,生成一个对象,然后再从对象上读取方法。

运行时加载:只有运行时才能得到这个对象,无法在编译时静态优化。

ES6模块

不是对象,是通过export命令显式指定输出的代码,在通过import命令输入。

//ES6模块
import {stat, exists, readFile } from 'fs';

实质是从fs模块加载3个方法,而不加载其他方法。

编译时加载:ES6可以在编译时完成加载模块。但ES6模块本身无法被引用,因为不是对象。

自动采用严格模式!

顶层this指向undefined!

export命令和import命令

  • export:规定模块的对外接口,使外部能够读取到模块内部的某个变量。
//写法1
export var m = 1;

//写法2
var m = 1;
export {m};

//写法3
var n = 1;
export {n as m};

//function 写法1
export function f(){};

//function 写法2
function f(){};
export {f};

export输出的接口与值是动态绑定的关系,即,通过该接口可以取到模块内部实时的值。

export命令可以出现在模块任何位置!

  • import:其他JS文件通过import命令加载模块。
import {lastName as surname} from './profile';

//整体加载
import * as othername from './profile';

import命令具有提升效果!会提升到整个模块的头部并首先执行。

import会执行所加载的模块。

  • 默认输出 export default
export default function test(){// 只能使用一次,一个模块只有一个默认输出
    ...
}

var a = 1;
export default a;

//以下错误!
export default var a = 1;

import customName from 'test';// 不需要大括号,只可能对应一个方法。名字任意。

ES6模块与CommonJS模块的差异

参考:百度阿里网易大疆等大小厂前端校招面筋|掘金技术征文

区别 CommonJS es6
加载原理 第一次加载模块就会执行整个模块,再次用到时,不会执行该模块,而是到缓存中取值。 不会缓存运行结果,动态的去被加载的模块中取值,并且变量总是绑定其所在模块。
输出 值的拷贝(模块中值的改变不会影响已经加载的值) 值的引用(静态分析,动态引用,原来模块值改变会改变加载的值)
加载方式 运行时加载(加载整个模块,即模块中的所有接口) 编译时加载(只加载需要的接口)
this指向 指向当前模块 指向undefined
循环加载 只输出已经执行的部分,还未执行的部分不会输出 遇到模块加载命令import时不会去执行模块,而是生成一个动态的只读引用,等到真正用到时再去模块中取值。只要引用存在,代码就能执行。