👇 内容速览 👇
ES6 新增了let
和const
,它们声明的变量,都处于“块级作用域”。并且不存在“变量提升”,不允许重复声明。
同时,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
。
①Set 常用方法
// 实例化一个setconst set = new Set([1, 2, 3, 4]);// 遍历setfor (let item of set) {console.log(item);}// 添加元素,返回Set本身set.add(5).add(6);// Set大小console.log(set.size);// 检查元素存在console.log(set.has(0));// 删除指定元素,返回boollet 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
函数是 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 }
通俗的解释下为什么会有这种输出:
done
为true
。难点:在于为什么最后的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:三个状态、两个过程、一个方法
pending
、fulfilled
、rejected
pending
->fulfilled
pending
->rejected
then
:Promise
本质上只有一个方法,catch
和all
方法都是基于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); // => 1return 2; // 包装成 Promise.resolve(2)}).then(res => {console.log(res); // => 2});
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 class 的 new 实例和 es5 的 new 实例有什么区别?
在ES6
中(和ES5
相比),class
的new
实例有以下特点:
class
的构造参数必须是new
来调用,不可以将其作为普通函数执行es6
的class
不存在变量提升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...ofconsole.log("ES5 :");for (let _ in es5) {console.log(_);}// es6:不可枚举console.log("ES6 :");for (let _ in es6) {console.log(_);}
参考/推荐:《JavaScript 创建对象—从 es5 到 es6》
他可以实现 js 中的“元编程”:在目标对象之前架设拦截,可以过滤和修改外部的访问。
它支持多达 13 种拦截操作,例如下面代码展示的set
和get
方法,分别可以在设置对象属性和访问对象属性时候进行拦截。
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》
目前 js 社区有 4 种模块管理规范:AMD、CMD、CommonJS 和 EsModule。 ES Module 是原生实现的模块化方案,与 CommonJS 有以下几个区别:
require(${path}/xx.js)
,后者目前不支持,但是已有提案:import(xxx)
require/exports
来执行的