1. 首页

看完阮一峰老师的ECMAScript 6、我总结了那么多笔记

globalThis 顶层对象

JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。


var a = 2; window.a // 2

ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
JS中文网 – 全球极客挚爱的技术成长平台 www.javascriptC.com
一个致力于帮助开发者用代码改变世界为使命的平台,每天都可以在这里找到技术世界的头条内容


var a = 2; window.a // 2 let b = 3 window.b //undefined
  • 浏览器里面,顶层对象是window,但 NodeWeb Worker 没有window
  • 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self
  • Node 里面,顶层对象是global,但其他环境都不支持。

ES2020 在语言标准的层面,引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this

Destructuring

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构

Array Destructuring


let [a, , b, ...c] = [1, 2, 3, 4, 5] a // 1 b // 3 c // [4,5] //error let [a] = 1 //不具备 Iterator 接口 let [a, b = 2, c = 3] = [1, , undefined] a //1, b //2, c //3 let [x = 1, y = x] = []; // x=1; y=1 let [x = y, y = 1] = []; // ReferenceError: y is not defined //交换位置 let x = 1, y = 2; [y, x] = [x, y] x // 2 y // 1

Object Destructuring

Object的解构与Array有一个重要的不同。Array的元素是按次序排列的,变量的取值由它的位置决定;而Objectprop没有次序,变量必须与prop同名,才能取到正确的value


let { foo, baz } = { foo: 'aaa', bar: 'bbb' }; foo // 'aaa' baz // undefined //解构对象方法 let { log, sin, cos } = Math; const { log } = console;

以上说明,对象的解构赋值是下面形式的简写


let { foo: baz } = { foo: 'aaa', bar: 'bbb' }; baz // "aaa" foo // error: foo is not defined let obj = { p: [ 'Hello', { y: 'World' } ] }; let { p: [x, { y }] } = obj; p // p is not defined // p是模式,不参与赋值 x // "Hello" y // "World" let { p, p: [x, { y }] } = obj; x // "Hello" y // "World" p // ["Hello", {y: "World"}]

Tip:对象的解构赋值可以取到继承的属性。


const obj1 = {}; const obj2 = { foo: 'bar' }; Object.setPrototypeOf(obj1, obj2); const { foo } = obj1; foo // "bar"

设置默认值时,默认值生效的条件是,对象的属性值严格等于undefined

var { message: msg = 'Something went wrong', say } = { say() { console.log(`say`) } };
msg // Something went wrong
say // function say(){}
// JavaScript中文:www.javascriptc.com – 全球极客挚爱的技术成长平台

Symbol

表示独一无二的值。它是 JavaScript 语言的第七种数据类型.前六种是:undefinednull、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)


let sy = Symbol('a') let sy1 = Symbol('a') sy === sy1 // false sy.toString() === sy1.toString() // true // ES2019 提供 description sy.description === sy1.description // true let s1 = Symbol.for('foo'); let s2 = Symbol.for('foo'); s1 === s2 // true

Symbol.for()Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("foo")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("foo")30 次,会返回 30 个不同的 Symbol 值。

Symbol.iterator

对象的Symbol.iterator属性,指向该对象的默认遍历器方法。这个着重记了一下


const myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable] // [1, 2, 3] // ...的底层是for...of

对象进行for...of循环时,会调用Symbol.iterator方法,返回该对象的默认遍历器,


class Collection { *[Symbol.iterator]() { let i = 0; while(this[i] !== undefined) { yield this[i]; ++i; } } } let myCollection = new Collection(); myCollection[0] = 1; myCollection[1] = 2; for(let value of myCollection) { console.log(value); } // 1 // 2

Symbol.toStringTag

对象的Symbol.toStringTag属性,指向一个方法。在该对象上面调用Object.prototype.toString方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制[object Object][object Array]object后面的那个字符串。


({[Symbol.toStringTag]: 'Foo'}.toString()) // "[object Foo]" class Collection { get [Symbol.toStringTag]() { return 'xxx'; } } let x = new Collection(); Object.prototype.toString.call(x) // "[object xxx]"

Set && Map

ES6 提供了新的数据结构 Set, Map

Set成员的值都是唯一的,没有重复的值,Set内的元素是强类型,会进行类型检查。


let set = new Set([1, true, '1', 'true', 1]) // Set(4) {1, true, "1", "true"}

Set处理并集(Union)、交集(Intersect)和差集(Difference)


let a = new Set([1, 2, 3]); let b = new Set([4, 3, 2]); // 并集 let union = new Set([...a, ...b]); // Set {1, 2, 3, 4} // 交集 let intersect = new Set([...a].filter(x => b.has(x))); // set {2, 3} // (a 相对于 b 的)差集 let difference = new Set([...a].filter(x => !b.has(x))); // Set {1}

Map类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。


const map = new Map([ ['name', '张三'], ['title', 'Author'] ]); map.size // 2 map.has('name') // true map.get('name') // "张三" map.has('title') // true map.get('title') // "Author"

数据间的转换

Map To Array


const myMap = new Map() .set(true, 7) .set({foo: 3}, ['abc']); [...myMap] // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]

Array To Map


new Map([ [true, 7], [{foo: 3}, ['abc']] ]) // Map { // true => 7, // Object {foo: 3} => ['abc'] // }

Map To Object


function strMapToObj(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 }

Object To Map


let obj = {"a":1, "b":2}; let map = new Map(Object.entries(obj));

Map To JSON


function strMapToJson(strMap) { return JSON.stringify(strMapToObj(strMap)); } let myMap = new Map().set('yes', true).set('no', false); strMapToJson(myMap) // '{"yes":true,"no":false}'

JSON To Map


function jsonToStrMap(jsonStr) { return objToStrMap(JSON.parse(jsonStr)); } jsonToStrMap('{"yes": true, "no": false}') // Map {'yes' => true, 'no' => false}

WeakSet && WeakMap 所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakSet && WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。

Proxy

概述

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

Object.defineProperty是使用的数据劫持:直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。数据劫持最典型的应用 —–> 双向的数据绑定(一个常用的面试题),

  • Vue 2.x 利用 Object.defineProperty(),并且把内部解耦为 Observer, Dep, 并使用 Watcher 相连
  • Vue3.x 版本之后改用 Proxy 进行实现

ProxyObject.defineProperty的对比

  • Object.defineProperty
    • 只能监听对象(Object),不能监听数组的变化,无法触发push, pop, shift, unshift,splice, sort, reverse
    • 必须遍历对象的每个属性
    • 只能劫持当前对象属性,如果想深度劫持,必须深层遍历嵌套的对象
    "use strict"
    let obj = {};
    let value = 1
    Object.defineProperty(obj, 'listenA', {
      writable: true,  //可修改
      enumerable: true,  // 可枚举   for...in...   Object.keys()
      configurable: true,  // 可配置,可删除
      get: () => value,
      set: val => {
        console.log(`set obj.listenA .. ${val}`);
        value = val
      },
    });
    obj.listenA = 2 //set obj.listenA .. 2
    console.log(obj.listenA)  // 2
    复制代码
    
  • Proxy
    • 可以直接监听对象而非属性
    • 可以直接监听数组的变化
    // 代理整个对象
    let proxyObj = new Proxy({}, {
      get: (target, key, receiver) => {
        console.log(`getting ${key}!`);
        return target[key];
      },
      set: (target, key, value, receiver) => {
        console.log(target, key, value, receiver);
        return target[key] = value;
      },
    });
    proxyObj.val = 1;    // {} val 1 {}
    proxyObj.val;       // getting val!
    
    //代理数组
    let proxyArr = new Proxy([], {
      get: (target, key, receiver) => {
        console.log(`getting ${key}!`);
        return target[key];
      },
      set: (target, key, value, receiver) => {
        console.log(target, key, value, receiver);
        return (target[key] = value);
      },
    });
    proxyArr[0] = 1; //  {} val 1 {}
    console.log(proxyArr[0]); //  getting val!  // 1
    console.log(proxyArr); // [1]
    复制代码
    

Reflect

Reflect翻译过来是反射的意思,与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。有一下几个作用

  • Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。
  • 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

// 老写法 try { Object.defineProperty(target, property, attributes); // success } catch (e) { // failure } // 新写法 if (Reflect.defineProperty(target, property, attributes)) { // 成功返回true // success } else { // failure }
  • Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。

// 老写法 'assign' in Object // true // 新写法 Reflect.has(Object, 'assign') // true
  • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

Proxy(target, { set: function(target, name, value, receiver) { var success = Reflect.set(target, name, value, receiver); if (success) { console.log('property ' + name + ' on ' + target + ' set to ' + value); } return success; } });

async function

ES2017 标准引入了 async 函数,使得异步操作变得更加方便,由于async函数返回的是Promise对象,可以作为await命令的参数。


async function timeout(ms) { await new Promise((resolve) => { setTimeout(resolve, ms); }); } async function asyncPrint(value, ms) { await timeout(ms); console.log(value); } asyncPrint('hello world', 50);

返回 Promise 对象


async function f() { return 'hello world'; } f().then(v => console.log(v)) // "hello world"

async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到


async function f() { throw new Error('出错了'); } f().then( v => console.log('resolve', v), e => console.log('reject', e) ) //reject Error: 出错了

Promise 对象的状态变化

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。


async function getTitle(url) { let response = await fetch(url); let html = await response.text(); return html.match(/<title>([\s\S]+)<\/title>/i)[1]; } getTitle('http://localhost:8080/').then(console.log(123))

await 命令

正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。


async function f() { // 等同于 // return 123; return await 123; } f().then(v => console.log(v)) // 123

任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。


async function f() { await Promise.reject('出错了'); await Promise.resolve('hello world'); // 不会执行 }

如果希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。


async function f() { try { await Promise.reject('出错了'); } catch(e) { } return await Promise.resolve('hello world'); } f() .then(v => console.log(v)) // hello world

async 函数的实现原理

Generator 函数和自动执行器,包装在一个函数里


async function fn(args) { // ... } // 等同于 function fn(args) { return spawn(function* () { // ... }); } function spawn(genF) { return new Promise(function(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); }

严格模式

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";

严格模式主要有以下限制。

  • 变量必须声明后再使用
  • 函数的参数不能有同名属性,否则报错
  • 不能使用with语句
  • 不能对只读属性赋值,否则报错
  • 不能使用前缀 0 表示八进制数,否则报错
  • 不能删除不可删除的属性,否则报错
  • 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
  • eval不会在它的外层作用域引入变量
  • evalarguments不能被重新赋值
  • arguments不会自动反映函数参数的变化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全局对象
  • 不能使用fn.callerfn.arguments获取函数调用的堆栈
  • 增加了保留字(比如protected、staticinterface

export && import

export && import来进行模块的导出导入。


// 写法一 export var m = 1; // 写法二 var m = 1; export {m}; // 写法三 var n = 1; export {n as m}; // 报错 function f() {} export f; // 正确 export function f() {}; // 正确 function f() {} export {f}; import 'lodash'; import 'lodash'; //加载了两次lodash,但是只会执行一次。 export { foo, bar } from 'my_module'; //当前模块不能直接使用foo和bar // 接口改名 export { foo as myFoo } from 'my_module'; // 整体输出 export * from 'my_module'; //默认接口 export { default } from 'foo'; export { default as es6 } from './someModule';

结束语

以上是小编学习完阮一峰老师的ES6之后的心得,希望大家指正😜

文章推荐

作者:前端Up主
链接:https://juejin.im/post/6883014651110506510

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:看完阮一峰老师的ECMAScript 6、我总结了那么多笔记

链接:https://www.javascriptc.com/4617.html

« 075. 颜色分类
海贼王 One Piece,一起康康Vue版本号中的彩蛋»
Flutter 中文教程资源

相关推荐

QR code