zoukankan      html  css  js  c++  java
  • JS

    友情提示:本文仅mark几个常用的新特性,详细请参见:ES6入门 - ryf

    碎片

    var VS let VS const

    • var:声明全局变量,
    • let:声明块级变量,即局部变量
    • const:声明常量,块级作用域,不可修改且必须初始化

    将一个对象彻底冻结为常量的方法

    var constantize = (obj) => {
      // 冻结对象本身 
      Object.freeze(obj); 
      // 冻结对象的属性
      Object.keys(obj).forEach( (key, i) => {
        if ( typeof obj[key] === 'object' ) {
          constantize( obj[key] );
        }
      });
    };  

    ES6声明变量的方法,除上述外,还支持:class、import、function。

    Number.isNaN() & Number.isFinite()  

    该两者仅对数值有效:

    • Number.isNaN():判断一个数值是否为NaN,利用NaN是唯一不等于自身的值,用于isNaN()判断
    • Number.isFinite():表示某个数值是否为正常的数值(即,非infinity),Infinity、-Infinity、NaN和undefined返回false,其余均返回true

    注意与传统的全局方法isFinite()和isNaN()的区别,传统方法先调用Number()将非数值的值转为数值,再进行判断。

    同理,Number.parseInt(), Number.parseFloat()优先使用。

    新增Number.EPSILON表示极小量,表示 1 与大于 1 的最小浮点数之间的差。

    function isTrueWithinErrorMargin (left, right) {
      return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
    }
    

    新增Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER分别表示安全的极大值和极小值。

    Symbol

    JS 的第7种数据类型,独一无二的特性:

    • 用于扩展对象属性名
    • 定义常量

    提供几个常用方法

    • Symbol()
    • Symbol.for()
    • Symbol.keyfor()

    ${}

    模版字符串(template string)语法,配合反引号``使用

    • 换行
    • 表达式嵌入:占位符(使用 <%...%> 放置 JavaScript 代码,使用 <%= ... %> 输出 JavaScript 表达式。)
    • 支持嵌套

    标签模版

    函数调用的一种特殊形式:fun`xxx`

    可过滤 HTML 字符串,防止用户输入恶意内容(特殊字符转义)

    function toSaferHTML(templateData) {
      let s = templateData[0];
      for (let i = 1; i < arguments.length; i++) {
        let arg = String(arguments[i]);
    
        // Escape special characters in the substitution.
        s += arg.replace(/&/g, "&")
                .replace(/</g, "<")
                .replace(/>/g, ">");
    
        // Don't escape special characters in the template.
        s += templateData[i];
      }
      return s;
    }
    
    let sender = '<script>alert("abc")</script>'; // 恶意代码
    let message = toSaferHTML`<p>${sender} has sent you a message.</p>`;
    // <p><script>alert("abc")</script> has sent you a message.</p>
    

    注:模板处理函数的第一个参数(模板字符串数组),还有一个 raw 属性,用于保存转义后的原字符串。

    支持多语言处理。

    ...

    扩展运算符,基于 for...of,将一个数组转为参数序列,或将实现了 Iterator 接口的对象转化为真正的数组。

    • 取代apply()方法
    • 复制数组(深拷贝)或合并数组(浅拷贝)
    • 配合解构赋值:扩展运算符可以识别四字节的Unicode字符的长度

    注意,没有实现 Iterator 接口的对象可以使用Array.from 

    • 类似数组的对象:(1)DOM 操作返回的 NodeList 集合;(2)函数内部的arguments对象
    • 可遍历的对象

    提供2种字符串长度方法

    [1]. [...str].length
    [2]. Array.from(str).length 

    Object.assign

    将源对象(source)的所有可枚举属性,复制到目标对象(target)。(浅拷贝)

    • undefined和null不能作为第一个参数
    • 只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)

    使用场景

    • 为对象添加属性/方法
    • 克隆/合并对象
    • 为属性指定默认值

    Object.getOwnPropertyDescriptor

    获取对象属性的描述对象,其中属性enumerable表示可枚举性。以下只对enumerable=true的对象有效

    • for...in循环:只遍历对象自身的和继承的可枚举的属性
    • Object.keys():返回对象自身的所有可枚举的属性的键名
    • JSON.stringify():只串行化对象自身的可枚举的属性
    • Object.assign(): 忽略enumerablefalse的属性,只拷贝对象自身的可枚举的属性

    Object.getOwnPropertyDescriptors

    基于Object.getOwnPropertyDescriptor实现,返回指定对象所有自身属性(非继承属性)的描述对象。

    • 解决Object.assign()无法正确拷贝get属性和set属性的问题
    • 配合Object.create方法,将对象属性克隆到一个新对象(浅拷贝)
    • 实现 Mixin(混入)模式
    //克隆 方法1
    const clone = Object.create(
      Object.getPrototypeOf(obj),
      Object.getOwnPropertyDescriptors(obj)
    ); 
    //方法2
    const clone2 = Object.assign(
      Object.create(Object.getPrototypeOf(obj)),
      obj
    );
    

    属性遍历

    • for...in
    • Object.keys()
    • Object.getOwnPropertyNames():返回一个数组,包含对象自身的所有属性的键名
    • Object.getOwnPropertySymbols():返回一个数组,包含对象自身的所有 Symbol 属性的键名
    • Reflect.ownKeys():返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举

    建议,尽量不要用for...in循环,而用Object.keys()代替。

    此外,for...in只能获得对象的键名,不能直接获取键值,而for...of允许遍历获得键值。

    Iterator & for...of

    为不同的数据结构提供统一的访问机制,任何数据结构只要部署了Iterator接口:

    • 支持遍历(for...of)操作
    • 使用扩展运算符,将其转为数组

    本质是:数据结构部署Symbol.iterator属性

    遍历器接口(Iterable)、指针对象(Iterator)和next方法返回值的模版描述如下

    interface Iterable {
      [Symbol.iterator]() : Iterator,
    }
    
    interface Iterator {
      next(value?: any) : IterationResult,
    }
    
    interface IterationResult {
      value: any,
      done: boolean,
    }
    

    原生具备 Iterator 接口的数据结构:

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

    默认调用遍历器的场景

    • for...of
    • 解构赋值
    • ...
    • yield*:其后面跟一个可遍历的结构,默认调用该结构的遍历器接口
    • Array.from(),Promise.all/race()

    扩展应用

    • String

    将遍历器转换为数组,提供2种方法:

    [...str.matchAll(regex)]
    Array.from(str.matchAll(regex));
    

    其中,matchAll() 用于一次性取出所有匹配结果,返回一个遍历器。  

    Set & Map

    ES6在原有的集合数据结构(数组Array和对象Object)的基础上,新增MapSet

    • Set:类似数组,值唯一
    • Map:类似(键值对集合的)对象,将"字符串-值"结构的Object扩展到"值-值"结构的Map
    • WeakSet:不可遍历,成员只能是对象
    • WeakMap:不可遍历

    很重要:遍历顺序就是插入顺序。支持遍历 for...of 和 forEach。

    其中,forEach 方法的

    • 第一个参数回调函数的参数依次为:(value, key, map)
    • 第二个参数用于绑定this,指向某个对象

    Set

    [1]. 数组去重

    function dedupe(array) {
      return Array.from(new Set(array));
    }
    

    [2]. 交并差集运算

    若想改变Set本身,提供如下2种方法

    // 方法一:利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构
    let set = new Set([1, 2, 3]);
    set = new Set([...set].map(val => val * 2));
    // 方法二:利用Array.from方法
    let set = new Set([1, 2, 3]);
    set = new Set(Array.from(set, val => val * 2));
    

    Map

    关于Map与其他数据结构的转换,可参见:http://es6.ruanyifeng.com/#docs/set-map

    解构赋值

    从数组和对象中提取值,对变量进行赋值:(模式匹配)

    • 只要等号右边的值不是对象或数组,就先将其转为对象
    • 实现了Iterator接口的数据结构,可以采用数组形式的解构赋值
    • 由于undefined和null无法转为对象,所以对其解构赋值,会报错
    • 解构赋值尽量不适用圆括号()

    支持默认值,前提是对象的属性值/数组成员严格等于undefined。若数组成员是null,默认值不会生效。

    除数组和对象,字符串也支持解构赋值。 

    应用场景

    • 交换变量的值
    • 从函数返回多个值
    • 函数参数的定义、默认值
    • 提取json数据
    • 利用for...of遍历map结构

    [1]. 在函数形参使用解构赋值

    // 写法一
    function m1({x = 0, y = 0} = {}) {
      return [x, y];
    }
    // 写法二
    function m2({x, y} = { x: 0, y: 0 }) {
      return [x, y];
    }
    
    • 写法一:函数参数的默认值是空对象,但是设置了对象解构赋值的默认值
    • 写法二:函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值

    推荐写法一,因为在函数体中实际使用的左边的x和y。 

    箭头函数

    箭头函数可以让this指向固定化,总是指向函数定义生效时所在的作用域,而不是指向运行时所在的作用域,这种特性很有利于封装回调函数。

    s1 = 0;  s2 = 0;
    function Timer() {
      this.s1 = 0;  this.s2 = 0;
      // 箭头函数
      setInterval(() => this.s1++, 1000);
      // 普通函数
      setInterval(function () {
        this.s2++;
      }, 1000);
    }
    
    var timer = new Timer();
    setTimeout(() => console.log('s1: ', timer.s1), 3200);  // 3
    setTimeout(() => console.log('s2: ', timer.s2), 3200);  // 0
    setTimeout(() => console.log('s11: ', this.s1), 3200);  // 0
    setTimeout(() => console.log('s22: ', this.s2), 3200);  // 3
    

    箭头函数自动绑定this,可以减少对this的显式绑定(callapplybind)。

    ::双冒号运算符(函数绑定运算符),可以用来取代callapplybind调用。

    foo::bar(...arguments);  等同于  bar.apply(foo, arguments);  

    注意点

    • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
    • 不可以当作构造函数,即:不可以使用new命令 
    • 不可以使用yield命令,即:箭头函数不能用作 Generator 函数
    • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替

    尾调用 & 尾递归

    尾调用:某个函数的最后一步是调用另一个函数

    function f(x){
      return g(x); //(1)return (2)无其他计算
    }
    

    尾递归基于尾调用,相对节省内存,不会发生栈溢出。

    相关示例,可参考:阶乘或Fibonacci 数列.

    注意,尾调用优化,仅在严格模式下有效。

    Proxy & Reflect

    Proxy:修改某些操作的默认行为,可以进行数据验证

    关于 Proxy 支持的拦截操作,具体参见:http://es6.ruanyifeng.com/#docs/proxy

    其中,apply用于拦截如下操作:

    • 函数调用
    • call、apply
    • Reflect.apply

    Reflect:将Object对象上的方法迁移到Reflect对象上

    关于Reflect对象的方法与Proxy对象的方法一一对应,具体参见:http://es6.ruanyifeng.com/#docs/reflect

    建议用 Reflect.xxx 代替 Object.xxx

    综上,Proxy 对象和 Reflect 对象联合使用,前者拦截操作,后者完成默认行为。

    Promise对象

    引出

    • 回调地狱(callback hell)
    • 多个异步回调难以维护和控制的问题

    设计思想:所有异步任务都返回一个 Promise 实例。(异步操作同步化)

    Promise 实质上是一个构造函数。

    var p = new Promise(f1);
    p.then(f2);
    

    回调函数f1完成后,执行回调函数f2。(添加状态改变时的回调函数通过then()方法)

    Promise 对象通过自身的状态,来控制异步操作。

    • 异步操作未完成(pending)
    • 异步操作成功(fulfilled)
    • 异步操作失败(rejected)

    同时,只有异步操作的结果才会改变其状态,Promise 实例的状态变化只可能发生一次:

    • 异步操作成功,Promise 实例传回一个值(value),状态变为 fulfilled
    • 异步操作失败,Promise 实例抛出一个错误(error),状态变为 rejected
    // resolve 和 reject 均由 JavaScript 引擎提供,无需自己实现
    var p = new Promise(function (resolve, reject) {
      // ...
      if (/* 异步操作成功 */){
        resolve(value);
      } else { /* 异步操作失败 */
        reject(new Error());
      }
    });
    • resolve:将Promise实例的状态从“未完成”变为“成功”(pending-->fulfilled),在异步操作成功时调用,并将异步操作的结果作为参数传出
    • reject:将Promise实例的状态从“未完成”变为“失败”(pending-->rejected),在异步操作失败时调用,并将异步操作的错误作为参数传出

    注意:

    • Promise 的回调函数属于异步任务,会在同步任务之后执行。
    • Promise 的回调函数不是正常的异步任务,而是微任务(microtask) 

    正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。所以,微任务的执行时间一定早于正常任务。

    setTimeout(function() {
      console.log(1);
    }, 0);
    
    new Promise(function (resolve, reject) {
      resolve(2);
    }).then(console.log);
    
    console.log(3);
    
    //  3 2 1
    

    Promise对象还有其他特性(可以看作是缺点):

    • Promise 对象新建后就会立即执行,无法取消Promise 
    • Promise 内部的错误不会影响到 Promise 外部的代码(Promise 会吃掉错误),也可以通过设置回调函数将错误信息抛出
    • 代码冗余,all then()...

    静态方法

    • Promise.all():与
    • Promise.race():或
    • Promise.resolve():将现有对象转为立即resolved的Promise对象
    • Promise.reject():返回一个新的Promise实例,该实例的状态为rejected,回调立即执行

    Promise.resolve()与Promise.reject()略有不同,Promise.reject()会将参数原封不动地传出。

    新的Promise.try()用于统一管理同步和异步代码,统一用promise.catch()捕获所有同步和异步的错误

    Promise.try(database.users.get({id: userId}))
      .then(...)
      .catch(...)

    参考Promise对象ES6 - Promise - ryfeng

    Generator函数

    遍历器对象生成函数,可以暂停函数执行,返回任意表达式的值

    • function*:
    • yield:产出,暂停标志

    调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针,可以通过next()依次遍历Generator函数内部的每一个状态(异步操作的容器)。

    • 异步操作同步化表达
    • 为任意对象部署 Iterator 接口
    • 作为数据结构,提供类似数组的接口
    • 控制流管理:项目拆分成任务,任务拆分成步骤,依次执行
    • 状态机(容器)
    • 协程(coroutine)

    注意,返回的遍历器对象,其Symbol.iterator属性是其自身

    function* gen(){...}
    var g = gen();
    g[Symbol.iterator] === g
    

    遍历器对象是Generator函数的实例,继承其原型上的方法,但是this对象无法访问。若想访问:

    // 将遍历器对象绑定到Generator函数的原型
    var gen = Gen.call(Gen.prototype);  

    若想应用new命令,对外封装一层即可

    function F() {
      return Gen.call(Gen.prototype);
    }
    // f即遍历器对象
    F f = new F();
    

    for...of

    支持自动遍历Generator函数时生成的Iterator对象。

    注意,不会遍历到return语句,扩展运算符、解构赋值和Array.from()亦是。

    利用for...of循环,可以写出遍历任意对象(object)的方法。通过Generator函数为对象加上这个接口

    function* objectEntries() {
      let propKeys = Object.keys(this);
    
      for (let propKey of propKeys) {
        yield [propKey, this[propKey]];
      }
    }
    
    let obj= { first: 'Jane', last: 'Doe' };
    obj[Symbol.iterator] = objectEntries;
    
    for (let [key, value] of objectEntries(obj)) {
      console.log(`${key}: ${value}`);
    }
    

    重点理解下述3个原型方法:

    Generator.prototype.next()

    • next方法可以带一个参数,该参数重写上一个yield表达式的返回值(yield表达式默认无返回值或undefined)
    • 第一次执行next方法,等同于启动执行Generator函数的内部代码

    Generator.prototype.throw()

    • Generator函数体内或外抛出的错误gen.throw(),会优先被Generator体内的try...catch捕获
    • 注意遍历器对象的throw()方法和throw命令的不同
    • throw方法抛出的错误要被内部try...catch捕获,前提是必须至少执行过一次next方法,,否则只能被外部try...catch捕获
    • throw方法被捕获后,会附带执行一次next方法,返回下一条yield表达式
    • Generator函数体内的错误未捕获到,会中断Generator函数体内的后续代码 

    Generator.prototype.return()

    • 返回给定的值,终结执行Generator函数
    • 优先级低于finally代码块
    next(): 将yield表达式替换成一个值
    throw(): 将yield表达式替换成一个throw语句
    return(): 将yield表达式替换成一个return语句  

    yield*

    在一个Generator函数A里面执行另一个Generator函数B。

    场景:递归

    若B中有return语句,通过以下形式获取返回值

    var value = yield* B()
    

    异步应用

    异步调用方式

    • 发布/订阅(事件监听)
    • 回调函数
    • Promise对象
    • Generator函数:协程

    协程

    Generator函数是协程在ES6的实现,可以理解为Generator函数是协程的实例

    最大特点就是可以交出函数的执行权(暂停函数执行和恢复执行)

    • 函数体内、外的数据交换
    • 错误处理机制

    自动执行机制:接收和交还程序的执行权(当异步操作有了结果,自动交回执行权)

    • 回调函数:将异步操作包装成 Thunk 函数,在回调函数里面交回执行权
    • Promise对象:将异步操作包装成 Promise 对象,用then方法交回执行权

    Thunk函数

    自动执行Generator函数的一种方法。

    • yield:将程序的执行权移出 Generator 函数
    • thunk:将执行权交还给 Generator 函数

    懒执行,传名调用的实现策略,用临时函数(Thunk函数)替换某个表达式。在JavaScript中,是将多参数(某个参数是回调函数)函数fn,替换成一个只接受回调函数作为参数的单参数函数。

    // Thunk函数转换器
    const Thunk = function(fn) {
      return function (...args) {
        return function (callback) {
          return fn.call(this, ...args, callback);
        }
      };
    };  

    提供Thunk函数转换工具:Thunkify 模块

    无需编写Generator函数的自动执行器,而且其检查机制,确保回调函数只运行一次。使用前提是Generator函数的yield命令后面,只能是Thunk函数。

    // 引入
    var thunkify = require('thunkify');
    // 转换
    var thunkFun= thunkify(fn);

    co模块

    自动执行Generator函数的另一种方法,基于Promise对象的自动执行器。

    co函数接收Generator函数作为参数,返回Promise对象,支持then方法执行回调函数。

    // 引入
    var co = require('co');
    // 自动执行
    co(gen).then(...);
    

    本质上封装了两种自动执行器(Thunk 函数和 Promise 对象),使用 co 的前提条件是,Generator函数的yield命令后面,只能是 Thunk 函数或 Promise 对象。 

    而且,co函数支持并发操作:把并发的操作放在数组或对象里,跟在yield命令后面

    参考 Generator - ruanyifeng

    以上,使Generator支持异步操作,即yield命令后是异步方法,则:该方法只能返回一个Thunk函数或者一个Promise对象。

    async函数

    • async:表示函数里有异步操作
    • await:表示紧跟在后面的表达式需要等待结果

    Generator函数的语法糖:对Generator函数和自动执行器的封装

    相比Generator函数:( *和yield --> async和await )

    • 内置(自动)执行器
    • 立即返回Promise对象,支持then方法执行回调函数。
    • 语义清晰,适应性广

    async函数可以看作是将多个异步操作封装成一个Promise对象,await命令是内部then()方法的语法糖。

    关于3者的比较,可参见:async函数-5

    务必注意,不能在普通函数中使用await。但是 esm 模块加载器支持顶层await,即await命令可以不放在async函数里面,直接使用

    // 顶层 await 的写法
    const res = await fetch('google.com');
    console.log(await res.text()); 

    常见形式:

    // 函数声明
    async function foo() {}
    // 函数表达式
    const foo = async function () {};
    // 箭头函数
    const foo = async () => {};
    
    // 对象的方法
    let obj = { 
      async foo() {} 
    };
    obj.foo().then(...)
    
    // Class 的方法
    class Storage {
      constructor() {
        this.cachePromise = caches.open('avatars');
      }
      async getAvatar(name) {
        const cache = await this.cachePromise;
        return cache.match(`/avatars/${name}.jpg`);
      }
    }
    const storage = new Storage();
    storage.getAvatar('jake').then(…);

    错误处理机制

    async函数内部抛出错误,会导致返回的Promise对象变为reject状态,同时中断async函数的执行,若不中断:

    • try...catch
    • await Promise(...).catch()

    抛出的错误对象会被catch方法回调函数接收到。

    并发

    互不影响相互独立的2个异步操作同时执行

    let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    

    场景:并发拉数据

    async function pullDataFeomUrl(urls) {
      // 并发读取远程URL
      const textPromises = urls.map(async url => {
        const response = await fetch(url);
        return response.text();
      });
    
      // 按次序输出
      for (const textPromise of textPromises) {
        console.log(await textPromise);
      }
    }

    异步遍历器(Async Iterator

    es2018引入,为异步操作提供原生的遍历器接口:异步返回value和done

    特点:异步遍历器的next()方法返回一个Promise对象。

    类似对象的同步遍历器部署Symbol.iterator属性,支持for...of,对象的异步遍历器部署Symbol.asynciterator属性,支持for await...of。

    即,可遍历对象的Symbol.asynciterator属性返回一个异步Generator函数

    const asyncIterator = asyncIterable[Symbol.asyncIterator]();  

    注意,for await...of循环也可以用于同步遍历器。

    异步遍历器重要的是:可以接近相同的方式处理同步操作和异步操作。

    // 同步Generator函数
    function* map(iterable, func) {
      const iter = iterable[Symbol.iterator]();
      while (true) {
        const {value, done} = iter.next();
        if (done) break;
        yield func(value);
      }
    }
    
    // 异步Generator函数
    async function* map(iterable, func) {
      const iter = iterable[Symbol.asyncIterator]();
      while (true) {
        const {value, done} = await iter.next();
        if (done) break;
        yield func(value);
      }
    }

    异步Generator函数

    async函数与Generator函数的结合

    • await:用于将外部操作产生的值输入函数内部
    • yield:用于将函数内部的值输出

    Generator函数返回同步遍历器对象,异步Generator函数返回异步遍历器对象。

  • 相关阅读:
    Android杂谈ubuntu系统下adb连接小米2
    Android UI设计ListView的页脚(footer)的使用
    Android杂谈关于Android的nodpi,xhdpi,hdpi,mdpi,ldpi
    ”该证书已被签发机构吊销“错误解决方案
    Android杂谈RelativeLayout中的baseline是什么?
    ubuntu下git更改默认编辑器
    Android UI设计ListView Item的OnItemLongClickListener同时监听两种事件
    Android UI设计ListView的item选中效果
    Ubuntu下ssh服务器文件操作命令
    ubuntu下emacs的配置(cedit,ecb)
  • 原文地址:https://www.cnblogs.com/wjcx-sqh/p/9363610.html
Copyright © 2011-2022 走看看