专栏首页YuanXinES6重难点整理

ES6重难点整理

? 内容速览 ?

  • let 和 const
  • Set 和 Map
  • Generator 和 yield
  • Promise、async/await 介绍
  • Proxy 代理器

let 和 const

ES6 新增了letconst,它们声明的变量,都处于“块级作用域”。并且不存在“变量提升”,不允许重复声明。

同时,const声明的变量所指向的内存地址保存的数据不得改变:

  • 对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。
  • 对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),不能保证指向的数据结构不可变。

如果要保证指向的数据结构也不可变,需要自行封装:

/**
 * 冻结对象
 * @param {Object} obj
 * @return {Object}
 */
function constantize(obj) {
    if (Object.isFrozen(obj)) {
        return obj;
    }

    Reflect.ownKeys(obj).forEach(key => {
        // 如果属性是对象,递归冻结
        typeof obj[key] === "object" && (obj[key] = constantize(obj[key]));
    });

    return Object.freeze(obj);
}

/********测试代码 **********/

const obj = {
    a: 1,
    b: {
        c: 2,
        d: {
            a: 1
        }
    },
    d: [1, 2]
};

const fronzenObj = constantize(obj);
try {
    fronzenObj.d = [];
    fronzenObj.b.c = 3;
} catch (error) {
    console.log(error.message);
}

Set 和 Map

题目:解释下SetMap

  • Set 元素不允许重复
  • Map 类似对象,但是它的键(key)可以是任意数据类型

①Set 常用方法

// 实例化一个set
const set = new Set([1, 2, 3, 4]);

// 遍历set
for (let item of set) {
    console.log(item);
}

// 添加元素,返回Set本身
set.add(5).add(6);

// Set大小
console.log(set.size);

// 检查元素存在
console.log(set.has(0));

// 删除指定元素,返回bool
let success = set.delete(1);
console.log(success);

set.clear();

其他遍历方法:由于没有键名,values()keys()返回同样结果。

for (let item of set.keys()) {
    console.log(item);
}

for (let item of set.values()) {
    console.log(item);
}

for (let item of set.entries()) {
    console.log(item);
}

②Map 常用方法

Map 接口基本和 Set 一致。不同的是增加新元素的 API 是:set(key, value)

const map = new Map();

// 以任意对象为 Key 值
// 这里以 Date 对象为例
let key = new Date();
map.set(key, "today");

console.log(map.get(key));

Generator 与 yield

generator函数是 es6 提供的新特性,它的最大特点是:控制函数的执行。让我们从网上最火的一个例子来看:

function* foo(x) {
    var y = 2 * (yield x + 1);
    var z = yield y / 3;
    return x + y + z;
}

var b = foo(5);
b.next(); // { value:6, done:false }
b.next(12); // { value:8, done:false }
b.next(13); // { value:42, done:true }

通俗的解释下为什么会有这种输出:

  1. 给函数 foo 传入参数 5,但由于它是 generator,所以执行到第一个 yield 前就停止了。
  2. 第一次调用 next(),这次传入的参数会被忽略暂停**。
  3. 第二次调用 next(12),传入的参数会被当作上一个 yield 表达式的返回值。因此,y = 2 * 12 = 24。执行到第二个 yield,返回其后的表达式的值 24 / 3 = 8。然后函数在此处暂停。
  4. 第三次调用 next(13),没有 yield,只剩 return 了,按照正常函数那样返回 return 的表达式的值,并且donetrue

难点:在于为什么最后的value是 42 呢?

首先,x的值是刚开始调用 foo 函数传入的 5。而最后传入的 13 被当作第二个 yield 的返回值,所以z的值是 13。对于y的值,我们在前面第三步中已经计算出来了,就是 24。

所以,x + y + z = 5 + 24 + 13 = 42

看懂了上面的分析,再看下面这段代码就很好理解了:

function* foo(x) {
    var y = 2 * (yield x + 1);
    var z = yield y / 3;
    return x + y + z;
}

var a = foo(5);
a.next(); // Object{value:6, done:false}
a.next(); // Object{value:NaN, done:false}
a.next(); // Object{value:NaN, done:true}

只有第一次调用 next 函数的时候,输出的 value 是 6。其他时候由于没有给 next 传入参数,因此 yield 的返回值都是undefined,进行运算后自然是NaN

Promise 介绍

简单归纳下 Promise:三个状态、两个过程、一个方法

  • 三个状态:pendingfulfilledrejected
  • 两个过程(单向不可逆):
    • pending->fulfilled
    • pending->rejected
  • 一个方法thenPromise本质上只有一个方法,catchall方法都是基于then方法实现的。

请看下面这段代码:

// 构造 Promise 时候, 内部函数立即执行
new Promise((resolve, reject) => {
    console.log("new Promise");
    resolve("success");
});
console.log("finifsh");

//  then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装
Promise.resolve(1)
    .then(res => {
        console.log(res); // => 1
        return 2; // 包装成 Promise.resolve(2)
    })
    .then(res => {
        console.log(res); // => 2
    });

async/await 介绍

async函数返回一个Promise对象,可以使用then方法添加回调函数。

当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

这也是它最受欢迎的地方:能让异步代码写起来像同步代码,并且方便控制顺序

可以利用它实现一个sleep函数阻塞进程:

function sleep(millisecond) {
    return new Promise(resolve => {
        setTimeout(() => resolve, millisecond);
    });
}

/**
 * 以下是测试代码
 */
async function test() {
    console.log("start");
    await sleep(1000); // 睡眠1秒
    console.log("end");
}

test(); // 执行测试函数

虽然方便,但是它也不能取代Promise,尤其是我们可以很方便地用Promise.all()来实现并发,而async/await只能实现串行。

function sleep(second) {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(Math.random());
            resolve();
        }, second);
    });
}

async function chuanXingDemo() {
    await sleep(1000);
    await sleep(1000);
    await sleep(1000);
}

async function bingXingDemo() {
    var tasks = [];
    for (let i = 0; i < 3; ++i) {
        tasks.push(sleep(1000));
    }

    await Promise.all(tasks);
}

运行bingXingDemo(),几乎同时输出,它是并发执行;运行chuanXingDemo(),每个输出间隔 1s,它是串行执行。

ES6 对象和 ES5 对象

题目:es6 class 的 new 实例和 es5 的 new 实例有什么区别?

ES6中(和ES5相比),classnew实例有以下特点:

  • class的构造参数必须是new来调用,不可以将其作为普通函数执行
  • es6class不存在变量提升
  • 最重要的是:es6 内部方法不可以枚举。es5 的prototype上的方法可以枚举。

为此我做了以下测试代码进行验证:

console.log(ES5Class()); // es5:可以直接作为函数运行
// console.log(new ES6Class()) // 会报错:不存在变量提升

function ES5Class() {
    console.log("hello");
}

ES5Class.prototype.func = function() {
    console.log("Hello world");
};

class ES6Class {
    constructor() {}
    func() {
        console.log("Hello world");
    }
}

let es5 = new ES5Class();
let es6 = new ES6Class();

// 推荐在循环对象属性的时候,使用for...in
// 在遍历数组的时候的时候,使用for...of
console.log("ES5 :");
for (let _ in es5) {
    console.log(_);
}

// es6:不可枚举
console.log("ES6 :");
for (let _ in es6) {
    console.log(_);
}

参考/推荐《JavaScript 创建对象—从 es5 到 es6》

Proxy 代理器

他可以实现 js 中的“元编程”:在目标对象之前架设拦截,可以过滤和修改外部的访问。

它支持多达 13 种拦截操作,例如下面代码展示的setget方法,分别可以在设置对象属性和访问对象属性时候进行拦截。

const handler = {
    // receiver 指向 proxy 实例
    get(target, property, receiver) {
        console.log(`GET: target is ${target}, property is ${property}`);
        return Reflect.get(target, property, receiver);
    },
    set(target, property, value, receiver) {
        console.log(`SET: target is ${target}, property is ${property}`);
        return Reflect.set(target, property, value);
    }
};

const obj = { a: 1, b: { c: 0, d: { e: -1 } } };
const newObj = new Proxy(obj, handler);

/**
 * 以下是测试代码
 */

newObj.a; // output: GET...
newObj.b.c; // output: GET...

newObj.a = 123; // output: SET...
newObj.b.c = -1; // output: GET...

运行这段代码,会发现最后一行的输出是 GET ...。也就是说它触发的是get拦截器,而不是期望的set拦截器。这是因为对于对象的深层属性,需要专门对其设置 Proxy

更多请见《阮一峰 ES6 入门:Proxy》

EsModule 和 CommonJS 的比较

目前 js 社区有 4 种模块管理规范:AMD、CMD、CommonJS 和 EsModule。 ES Module 是原生实现的模块化方案,与 CommonJS 有以下几个区别:

  • CommonJS 支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案:import(xxx)
  • CommonJS 是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
  • commonJs 输出的是值的浅拷贝,esModule 输出值的引用
  • ES Module 会编译成 require/exports 来执行的

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 从Web开发的角度聊聊MVC、MVP和MVVM

    M、V 是这三种架构模式中的共同含有的部分,M 是 Model 的缩写,代表“数据模型”;V 是 View 的缩写,代表“视图”。

    心谭博客
  • NodeJS模块研究 - child_process

    掌握 nodejs 的 child_process 模块能够极大提高 nodejs 的开发能力,例如主从进程来优化 CPU 计算的问题,多进程开发等等。本文从以...

    心谭博客
  • 批量修改log中的提交信息

    事情的起因是这样的:迷恋的谷歌的我最近申请了一个新的 google 邮箱。然后果断在 github 上更新了邮箱地址,并且删除了之前的 163 等国内邮箱。

    心谭博客
  • 快速掌握JavaScript面试基础知识(三)

    根据StackOverflow 调查, 自 2014 年一来,JavaScript 是最流行的编程语言。当然,这也在情理之中,毕竟 1/3 的开发工作都需要一些...

    Fundebug
  • 深入理解JavaScript的事件循环(Event Loop)

    在两个环境下的Event Loop实现是不一样的,在浏览器中基于 规范 来实现,不同浏览器可能有小小区别。在Node中基于 libuv 这个库来实现

    书童小二
  • jquery实现表格动态添加

    //点击追加触发 $(function(){ $("#button").click(function(){ var div_ = $("#sel").val()...

    yaohong
  • 百度地图BMap API的应用实例

    前几天,帮朋友做了几款地图API接口调研,推荐他使用百度BMap和谷歌GMap API,后来又直接交由我来替他做

    阳光岛主
  • javaWeb技术第一篇之HTML

    <font color="设置颜色" size="设置大小" face="设置字体">

    海仔
  • <col>标签

    <col> 标签定义表格中一个或多个列的属性值。通常在<colgroup>元素内。

    Html5知典
  • 【HTML打印】HTML直接调用window下的打印机并执行打印任务(简单打印任务生成)

    我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite...

    IT小马哥

扫码关注云+社区

领取腾讯云代金券