原文链接(格式更好):2-3 ES6 详解
const
基础
用于定义常量的,初始化时必须赋值
特点:
- 不允许重复赋值
-
- 对于基础类型后续的值不可更改
- 对于复杂类型后续的引用地址不可更改,但其中的值可以更改
- 有块级作用域
- 存在暂时性死区
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
特点
- 没有自己的 this,它的 this 取决于定义时上下文
-
- 所以就不能用于构造函数
- 没有内置的 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)新增的数组和对象方法有很多,以下是一些常用的方法:
数组方法:
ES6(ECMAScript 2015)新增的数组些常用的方法:
Array.from(): 用于将类数组对象或可遍历对象转为真正的数组。Array.of(): 用于将一组值转为数组。
-
Array.of('foo', 2, 'bar', true);// ["foo", 2, "bar", true]
copyWithin(): 用于在当前数组内部,将指定位置的成员复制到其他位置。fill(): 用于填充数组。find(): 返回数组中第一个满足条件的元素。findIndex(): 返回数组中第一个满足条件的元素的下标。includes(): 检索数组是否包含某个值,返回布尔值。entries(): 帮助数组遍历每一个key值与value值。keys(): 帮助数组遍历所有的key值。values(): 帮助数组遍历所有的value值。
对象方法:
官方文档:Object - JavaScript | MDN
ES6(ECMAScript 2015)新增的对象常用的方法:
Object.assign(): 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。Object.keys(): 返回一个由给定对象的自身可枚举属性组成的数组。Object.getOwnPropertyNames(): 返回一个由指定对象的所有自身属性(包括不可枚举属性但不包括Symbol值作为名称的属性)的属性名(包括不可枚举属性)组成的数组。Object.getOwnPropertySymbols(): 返回一个由指定对象的所有自身Symbol属性值的属性名组成的Array。Object.is(): 用来检测两个值是否是同一个对象,或者检测一个值是否是null和1. `Object.setPrototypeOf(): 设置一个对象的内部[[Prototype]]链接。Object.values(): 返回一个由给定对象的所有自身属性的值组成的数组。Object.entries(): 返回一个由给定对象的所有自身属性的键值对组成的数组。Object.get(): 获取对象的属性值。Object.set(): 设置对象的属性值并返回该对象。Object.create(): 使用给定的原型和可选的属性描述符创建一个新的对象。Object.defineProperty(): 在对象上定义一个新的属性,或修改一个对象的现有属性,并返回这个对象。Object.defineProperties(): 在一个对象上定义一个直接以该对象为基础的对象的新属性和/或修改对象的现有属性的性质。Object.freeze(): 防止对象的属性被更改,并防止对象被扩展。Object.seal(): 防止对象的属性被删除,并防止新的属性被添加到对象中。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 就会自己进行尾调用优化,优化如下:
// 递归调用不会保留任何状态或局部变量,因此可以重用当前的调用栈帧,而无需创建新的栈帧
// 即 重复使用一个栈帧