闲来无事,点开 es6 的文档看看,复习复习一些需要注意的地方,小做总结。
- 暂时性死区
// 1. let/const
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
// 2. 比较隐秘的死区
function bar(x = y, y = 2) {
// error
return [x, y];
}
- 函数结构默认参数
// 写法一, 对x和y分别设置默认值
function f1({ x = 0, y = 0 } = {}) {
return [x, y];
}
// 写法二, 对{x,y} 设置默认值
function f2({ x, y } = { x: 0, y: 0 }) {
return [x, y];
}
f1({}); // [0,0]
f2({}); // [undefined,undefined]
- 尾调用优化
/* -------------------------- 尾调用 -------------------------- */
// 形如:
function f(x) {
return g(x);
}
// 以下情况都不是
function f(x) {
let y = g(x);
return y;
}
function f(x) {
return g(x) + 1;
}
function f(x) {
g(x);
}
/* -------------------------- 优化 -------------------------- */
// 函数在内存中会有调用栈,当尾调用不再使用外层函数的变量时,就可以释放外层函数的调用帧,从而达到内存优化
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
/* -------------------------- 注意 -------------------------- */
function addOne(a) {
var one = 1;
function inner(b) {
return b + one; // 使用到了外层函数的one, 所以addOne的调用帧不会被删除
}
return inner(a);
}
- 尾递归
// 尾递归是基于尾调用的, 因为递归很容易造成栈溢出, 但是尾递归始终只有一个调用帧, 绝对安全可靠
// 差 O(n)
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(5); // 120
// 优 O(1)
function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
不难看出,从非递归转为递归主要就是把使用到的内部变量转移为函数的参数
ES6 的尾调用优化只在严格模式下开启, 因为正常模式下 arguments 和 caller 会跟踪调用栈, 尾调用优化调用栈被改写, 这两个变量失真;
严格模式会禁用这两个变量
- set & map
/* -------------------------- set -------------------------- */
// 特点就是不重复, 可以用来去重,取交集,并集,差集方便
let a = [1, 2, 2, 3],
b = [3, 4, 5],
c = [1, 4, 6];
[...new Set(a)]; // [1,2,3]
// 并
const res1 = [...new Set([...a, ...b])]; // [1,2,3,4,5]
let a1 = new Set(a),
b1 = new Set(b);
// 交
const res2 = [...new Set([...a1].filter(item => b1.has(item)))]; // [3]
// 差
const res1 = [...new Set([...a1].filter(item => !b1.has(item)))]; // [1,2]
/* -------------------------- map -------------------------- */
// 特点就是v=>v的映射关系, key不再拘泥于字符串, 可以是对象,函数等; Map 既能保存键值对,又能够记住键的原始插⼊顺序
let map = new Map([[null, 1], [Symbol('正确'), 'true'], [function a{}, 'log']])
// Map { null => 1, Symbol(正确) => 'true', [Function: a] => 'log' }
/* -------------------------- WeakSet&WeakMap -------------------------- */
// 相对于Set和Map都没有size和clear()方法了
// WeakSet的值必须是对象, WeakMap的键必须是对象
// 都是弱引用, 不计入垃圾回收机制, 会自动回收, 可以避免内存泄露
// 主要说一下WeakMap可以设置私有变量
let _counter = new WeakMap();
let _action = new WeakMap();
class Countdown {
constructor(counter, action) {
_counter.set(this, counter);
_action.set(this, action);
}
dec() {
let counter = _counter.get(this);
if (counter < 1) return;
counter--;
_counter.set(this, counter);
if (counter === 0) {
_action.get(this)();
}
}
}
let c = new Countdown(2, () => console.log('DONE'));
c.dec()
c.dec()
// DONE
- Proxy
顾名思义,代理,这里主要讲下 vue3 和 vue2 中的 Object.defineProperty 的区别。
proxy:
- 代理的是整个对象, 可以监听所有属性的变化, 包括新增和删除
- 有 13 中拦截
Object.defineProperty:
- 劫持对象的属性, 只能监听属性的改变, 无法监测属性的新增和删除以及数组的新增和删除
- 只有 get 和 set, get 收集依赖, set 触发更新
- class
ES5 的继承,实质是先创造子类的实例对象this
,然后再将父类的方法添加到this
上面(Parent.apply(this)
)。ES6 的继承机制完全不同,实质是先创造父类的实例对象this
(所以必须先调用super
方法),然后再用子类的构造函数修改this
。
在 react 中 super(props) 中的 props 也是必须的,但其实,即使不传 props,render 或其他方法也能访问到 this.props,因为 react 内部做了处理,必须传的原因是在 constructor 执行完毕前,拿不到 this.props
// React 内部 const instance = new YourComponent(props); instance.props = props;
Class 作为构造函数的语法糖,同时有 prototype 属性和__proto__属性,因此同时存在两条继承链。
class A {}
class B extends A {}
// 构造函数的原型链指向父函数的原型对象
B.prototype.__proto__ === A.prototype;
// 构造函数继承自父函数
B.__proto__ === A;
说起 class 不得不谈谈 React 中的 function 组件和 class 组件,react 在内部使用
isReactComponent
字段来检测是不是 class 组件,因为 class 都会继承 Componentclass Component {} Component.prototype.isReactComponent = {};