2-3 ES6 详解

123 阅读9分钟

原文链接(格式更好):2-3 ES6 详解

const

基础

用于定义常量的,初始化时必须赋值

特点:

  1. 不允许重复赋值
    1. 对于基础类型后续的值不可更改
    2. 对于复杂类型后续的引用地址不可更改,但其中的值可以更改
  1. 有块级作用域
  2. 存在暂时性死区
const LIMIT = 10;
const OBJ_MAP = {
  x: 10,
  y: 20,
};
const OBJ_ARRAY = [1, 2, 3];

// LIMIT = 20; // Uncaught TypeError: Assignment to constant variable.
OBJ_MAP.z = 30; // {"x":10,"y":20,"z":30}
OBJ_ARRAY.push(4); // [1, 2, 3, 4]
OBJ_MAP = { a: 10 }; // Uncaught TypeError: Assignment to constant variable.
OBJ_ARRAY = [9]; // Uncaught TypeError: Assignment to constant variable.

面试点

1. 在 ES5 中,如何模拟一个常量的[不允许重复赋值]

var NUMBER = 10;
NUMBER = 20; // 不报错,代码有效的

// 模拟操作 - Object.defineProperty(...)

// 无报错版
Object.defineProperty(window, "NUMBER2", {
  value: 10,
});

console.log("[ NUMBER2 ] >", NUMBER2); // 10

NUMBER2 = 20; // 不报错

console.log("[ NUMBER2 ] >", NUMBER2); // 10

// 有报错版
Object.defineProperty(window, "NUMBER3", {
  get() {
    return 10;
  },
  set() {
    throw new Error("Assignment to constant variable");
  },
});

console.log("[ NUMBER3 ] >", NUMBER3); // 10

NUMBER3 = 20; // 报错: Uncaught Error: Assignment to constant variable

console.log("[ NUMBER3 ] >", NUMBER3); // 10

2. 在 ES5 中,如何模拟块级作用域

if (true) {
  var xsdw = 10;
}
console.log("[ xsdw ] >", xsdw); // 10

if (true) {
  const ghnm = 10;
}
console.log("[ ghnm ] >", ghnm); // 报错:Uncaught ReferenceError: ghnm is not defined

// 模拟操作 - IFEE
if (true) {
  (function () {
    var iws = 10;
  })();
}
console.log("[ iws ] >", iws); // 报错:Uncaught ReferenceError: iws is not defined

3. 对于复杂类型,是用 const 还是 let?

答案:const

对于复杂类型,用 const 后将不允许对其重新覆盖(引用地址不可变),但不会限制值的更改

一句话:能用 const 的坚决不用 let

4. 如何对复杂类型常量化

考察点:Object.freeze(obj)

官方文档:Object.freeze() - JavaScript | MDN

被冻结的对象不能再被更改:不能添加新的属性,不能移除现有的属性,不能更改它们的可枚举性、可配置性、可写性或值,对象的原型也不能被重新指定

缺点:只能冻结根层属性,对内嵌的复杂类型无法冻结

解决方案:针对多层嵌套的复杂类型,手动循环冻结

function deepFreeze(_obj) {
  const _freeze = Object.freeze(_obj);

  Object.keys(_freeze).forEach((key) => {
    if (_freeze[key] instanceof Object) {
      _freeze[key] = deepFreeze(_freeze[key]);
    }
  });

  return _freeze;
}

解构

解开结构

对象解构:{}

数组解构: []

const obj = { name: 'lisi', age: 25 }
const { name, age } = obj
// name: 'lisi'
// age: 25

const { name, ...obj2 } = obj // 剩余解构:剩余的以对象形式的放到 obj2 内,不再包含原型链
// name: 'lisi'
// obj2: { age: 25 }

const arr = [100, 200, 300]
const [a, b, c] = arr
// a: 100
// b: 200
// c: 300

const [a, ...b] = arr // 剩余解构:剩余的以数组形式的放到 b 内,不再包含原型链
// a: 100
// b: [200, 300]

解构操作的本质算是取值的语法糖

const { name, age } = obj
// 等价于
const name = obj.name
const age = obj.age

const { name, ...obj2 } = obj
// 等价于
const name = obj.name
const obj2 = Object.create({})
obj2.age = obj.age
// 更多属性...

const [a, b, c] = arr
// 等价于
const a = arr[0]
const b = arr[1]
const c = arr[2]

const [a, ...b] = arr
// 等价于
const a = arr[0]
const b = arr.slice(1)

数组解构能很方便的进行变量交换

// 以前的逻辑
let hj = 1;
let jh = 2;
let _c = 0;
_c = hj;
hj = jh;
jh = _c;

// 运用数组解构后
let kj = 1;
let jk = 2;
[kj, jk] = [jk, kj];

箭头函数

基础

// 普通函数
function add(x, y) {
 return x + y
}
const add = function(x, y) {
 return x + y
}

// 箭头函数
const add = (x, y) => {
 return x + y
}
// 等价于
const add = (x, y) => x + y

特点

  1. 没有自己的 this,它的 this 取决于定义时上下文
    1. 所以就不能用于构造函数
  1. 没有内置的 arguments

class

基础

常用语法

// class 声明
class Person {
  x: 10 // 属性
  getX(){} // 方法
}

// class 表达式
const Person2 = class {
  x: 10 // 属性
  getX(){} // 方法
}

完整语法

// class 声明:不允许再次声明已经存在的类;不可以提升
class name [extends] {
  // class body
  [key]: value // 属性,将作为实例本身的属性
  [key]() {} // 方法,,将作为实例原型上的方法,实例本身不具备该的方法
	static [key]: any // 静态[属性/方法](不可枚举),作为类本身的[属性/方法]
}

  
// class 表达式:可以是命名的,也可以是匿名的;允许你重新定义类
const MyClass = class [className] [extends otherClassName] {
  // class body
  [key]: value // 属性,将作为实例本身的属性
  [key]() {} // 方法,,将作为实例原型上的方法,实例本身不具备该的方法
	static [key]: any // 静态[属性/方法](不可枚举),作为类本身的[属性/方法]
}

场景模拟

// function 时代
// 构造函数声明
function Course(teacher, course) {
  // 作为实例的属性
  this.teacher = teacher;
  this.course = course;
  this.age = 29
}
// 作为实例原型链上的方法
Course.prototype.getCourse = function () {
  return this.teacher + ":" + this.course;
};
// 作为构造函数的方法
Course.getXX = function() {}

// 实例 1
const course1 = new Course("xx", "ES6");
// 实例 2
const course2 = new Course("yy", "ES7");
console.log("[ course1.getCourse() ] >", course1.getCourse()); // xx:ES6
console.log("[ course2.getCourse() ] >", course2.getCourse()); // yy:ES7

// class 时代
// 类声明
class Course {
  constructor(teacher, course) {
    // 作为实例的属性
    this.teacher = teacher;
    this.course = course;
  }

  // 作为实例的属性
  age = 29

  // 作为实例原型链上的方法
  getCourse() {
    return this.teacher + ":" + this.course;
  }

  // 作为类的方法
  static getXX() {}
}
// 实例 1
const course3 = new Course("xx", "ES6");
// 实例 2
const course4 = new Course("yy", "ES7");
console.log("[ course3.getCourse() ] >", course3.getCourse()); // xx:ES6
console.log("[ course4.getCourse() ] >", course4.getCourse()); // yy:ES7

使用场景

适配器模式,封装核心

 class Utils {
  constructor(core) {
    this._main = core;
    this._name = "my_utils_name";
  }

  get name() {
    return this._name || this._main.name;
  }

  get core() {
    return this._main;
  }
}

// 封装了下 lodash
const utils = new Utils(_);

面试点

1. class 的类型是什么?

typeof class 为 'function'

Promise

处理异步的一种方式

// Promise 的 A+ 标准源码
const PENDING = "PENDING";
const REJECTED = "REJECTED";
const FULFILLED = "FULFILLED";

class myPromise {
  value = null;
  error = null;
  status = PENDING;

  onFulfilledCallbacks = [];
  onRejectedCallbacks = [];

  constructor(executor) {
    const resolve = (value) => {
      this.value = value;
      this.status = FULFILLED;
      this.onFulfilledCallbacks.forEach((fn) => fn());
    };

    const reject = (error) => {
      this.error = error;
      this.status = REJECTED;
      this.onRejectedCallbacks.forEach((fn) => fn());
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then = (onFulfilled, onRejected) => {
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (error) => {
            throw error;
          };

    const promise2 = new myPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        }, 0);
      }
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        }, 0);
      }

      if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          }, 0)
        );

        this.onRejectedCallbacks.push(
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          }, 0)
        );
      }
    });

    return promise2;
  };
}

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError("循环引用"));
  }

  let called = false;

  if ((typeof x === "object" && x !== null) || typeof x === "function") {
    try {
      let then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;

            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;

            reject(r);
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;

      reject(e);
    }
  } else {
    resolve(x);
  }
}

new myPromise((resolve, reject) => {
  resolve(1);
}).then((res) => {
  console.log(res);
});

ES6(ECMAScript 2015)新增的数组和对象方法有很多,以下是一些常用的方法:

数组方法:

官方文档:Array - JavaScript | MDN

ES6(ECMAScript 2015)新增的数组些常用的方法:

  1. Array.from(): 用于将类数组对象或可遍历对象转为真正的数组。
  2. Array.of(): 用于将一组值转为数组。
    1. Array.of('foo', 2, 'bar', true);// ["foo", 2, "bar", true]
  1. copyWithin(): 用于在当前数组内部,将指定位置的成员复制到其他位置。
  2. fill(): 用于填充数组。
  3. find(): 返回数组中第一个满足条件的元素。
  4. findIndex(): 返回数组中第一个满足条件的元素的下标。
  5. includes(): 检索数组是否包含某个值,返回布尔值。
  6. entries(): 帮助数组遍历每一个key值与value值。
  7. keys(): 帮助数组遍历所有的key值。
  8. values(): 帮助数组遍历所有的value值。

对象方法:

官方文档:Object - JavaScript | MDN

ES6(ECMAScript 2015)新增的对象常用的方法:

  1. Object.assign(): 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。
  2. Object.keys(): 返回一个由给定对象的自身可枚举属性组成的数组。
  3. Object.getOwnPropertyNames(): 返回一个由指定对象的所有自身属性(包括不可枚举属性但不包括Symbol值作为名称的属性)的属性名(包括不可枚举属性)组成的数组。
  4. Object.getOwnPropertySymbols(): 返回一个由指定对象的所有自身Symbol属性值的属性名组成的Array。
  5. Object.is(): 用来检测两个值是否是同一个对象,或者检测一个值是否是null和1. `
  6. Object.setPrototypeOf(): 设置一个对象的内部[[Prototype]]链接。
  7. Object.values(): 返回一个由给定对象的所有自身属性的值组成的数组。
  8. Object.entries(): 返回一个由给定对象的所有自身属性的键值对组成的数组。
  9. Object.get(): 获取对象的属性值。
  10. Object.set(): 设置对象的属性值并返回该对象。
  11. Object.create(): 使用给定的原型和可选的属性描述符创建一个新的对象。
  12. Object.defineProperty(): 在对象上定义一个新的属性,或修改一个对象的现有属性,并返回这个对象。
  13. Object.defineProperties(): 在一个对象上定义一个直接以该对象为基础的对象的新属性和/或修改对象的现有属性的性质。
  14. Object.freeze(): 防止对象的属性被更改,并防止对象被扩展。
  15. Object.seal(): 防止对象的属性被删除,并防止新的属性被添加到对象中。
  16. Object.preventExtensions(): 防止新属性添加到对象中,但允许现有属性的修改。

扩展

尾调用

一个函数执行结果作为另一个函数的返回值

function A(x) {
  return x + 1
}
function B(number) {
  return A(number)
}
const num = B(1)
// 一个函数执行结果(A)作为另一个函数(B)的返回值(return)

尾调用场景

// 斐波那契函数 - 递归尾调用
function fib(n) {
  if (n < 2) {
    return n;
  } else {
    return fib(n - 1) + fib(n - 2); // 递归 + 计算
  }
}

// 由于是[递归 + 计算],调用时会存在 压栈 的情况
// fib 每次调用时,都会进栈,等 fib 执行完毕再后出栈,但由于是递归调用的,如果 n 很大
// 则栈中存在大量的 fib,则形成了 压栈
// 改成如下形式则可以优化

function fib(n) {
  return fibImpl(0, 1, n)
}
function fibImpl(a, b, n) {
  if(a === 0) {
    return a
  }
  return fibImpl(b, a + b, n - 1) // 自递归,JS 会进行尾调用优化
}

// 尾调用优化
// 如果是单纯的自递归,没有计算的话,JS 就会自己进行尾调用优化,优化如下:
// 递归调用不会保留任何状态或局部变量,因此可以重用当前的调用栈帧,而无需创建新的栈帧
// 即 重复使用一个栈帧