跳到主要内容

ES6

简介

  • ES6 是 ECMA 为 JavaScript 制定的第 6 个标准版本
  • ECMAscript 2015 是在 2015 年 6 月发布 ES6 的第一个版本。以此类推,ECMAscript 2016 是 ES6 的第二个版本,也叫 ES7、ES2016。
  • ES6 是一个泛指,含义是 5.1 版本以后的 JavaScript 下一代标准。

let 和 const

let

用来声明变量,只在let命令所在的代码块内有效,即块级作用域。不存在变量提升,不允许重复声明

function varTest() {
var a = 1;
if (true) {
var a = 2;
console.log(a); // 2
}
console.log(a); // 2
}

function letTest() {
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 1;
// let b = 2; // SyntaxError: Identifier 'b' has already been declared
if (true) {
let b = 2;
console.log(b); // 2
}
console.log(b); // 1
}

letTest()的 if 语句中,可以再次声明变量 b,是因为变量 b 只在这个 if 语句中有效。如果在 if 语句中使用var声明变量 b,会报错。

let 很适合在 for 循环时声明索引变量

暂时性死区:在使用 let 或 const 声明变量之前,该变量都不可用

{
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 1;
}

const

const声明一个只读的常量,必须初始化赋值。一旦声明,常量的值就不能改变,只在声明所在的块级作用域内有效。 不存在变量提升,不允许重复声明。复杂类型(数组、对象等)指针指向的地址不能更改,内部数据可以更改

const a = '123';
a = '234'; // TypeError: Assignment to constant letiable

const arr = [1, 2, 3];
arr.push(4);
console.log(arr); // [1,2,3,4]
arr = [];
console.log(arr); // 改变数组的指向会出错 Uncaught TypeError: Assignment to constant letiable
注意

let 和 const 声明的全局变量不属于顶层对象的属性,只存在于块级作用域中

let a = 1;
const b = 2;
console.log(window.a); // undefined
console.log(window.b); // undefined

https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/133

var

var 声明的变量是全局变量,在函数中声明属于局部变量

var a = 1;
function fn() {
var a = 2;
}
fn();
console.log(a); // 1

在函数中不使用 var,该变量是全局的

var a = 1;
function fn() {
a = 2;
}
fn();
console.log(a); // 2

var 声明变量存在变量提升

console.log(a); // undefined
var a = 1;

// 在编辑阶段,变成如下形式:
var a;
console.log(a);
a = 1;

可以重复声明变量,后面的会覆盖前面的

var b = 1;
var b = 2;
console.log(b); // 2

模板字符串

模板字符串是增强版的字符串,用反引号\标识,嵌入的变量名写在${}之中。

基础使用

  1. 基本的字符串格式化
const name = 'world';

// ES5
console.log('hello' + name);

// ES6
console.log(`hello${name}`);
  1. 多行字符串拼接
let say = `<div>
<p>hello, world</p>
</div>`;

标记模版

标记模版(Tagged templates),使用函数解析模版文字。标签函数的第一个参数是一个字符串值数组,其余参数和表达式相关。

在 React 项目中,常用CSS in JS方案管理样式,如EmotionStyled-components。它们使用了标记模版,常见写法:

const Button = styled.button`
background: transparent;
border-radius: 3px;
border: 2px solid #bf4f74;
color: #bf4f74;
margin: 0 1em;
padding: 0.25em 1em;
`;

示例:

function myTag(strings, personExp, ageExp) {
console.log('strings', strings); // [ 'That ', ' is a ', '.' ]
const str0 = strings[0];
const str1 = strings[1];
const str2 = strings[2];

const ageStr = ageExp < 100 ? 'youngster' : 'centenarian';
return `${str0}${personExp}${str1}${ageStr}${str2}`;
}

const person = 'Tom';
const age = 28;

const output = myTag`That ${person} is a ${age}.`;

console.log(output); // That Tom is a youngster.

解构赋值

1. 数组的解构赋值

可以从数组中提取值,按照对应位置,对变量赋值。这种写法属模式匹配,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

let [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3

注意细节:

1、左右结构不同

let [a, b, c, d] = [1, 2, 3];
console.log(a, b, c, d); // 1 2 3 undefined

2、跳过部分

let [a, , c] = [1, 2, 3];
console.log(a, c); // 1 3

3、默认值

let [a, b, c, d = 666] = [1, 2, 3];
console.log(a, b, c, d); // 1 2 3 666

let [a = 11, b = 22, c, d = 666] = [];
console.log(a, b, c, d); // 11 22 undefined 666

4、嵌套

let [a, b, c] = [1, 2, [3]];
console.log(a, b, c); // 1 2 [3]

let [a, b, [c]] = [1, 2, [3]];
console.log(a, b, c); // 1 2 3

5、数组的对象解构

const str = '23,zgh,boy';
// { 数组下标: 变量名 }
const { 1: name, 2: sex, 0: age } = str.split(',');
console.log(name, sex, age); // zgh boy 23

示例:交换数组元素

let arr = [1, 2, 3];

// 解构赋值
[arr[0], arr[1]] = [arr[1], arr[0]];

// 使用临时变量
// let between = arr[0];
// arr[0] = arr[1];
// arr[1] = between;

2. 对象的解构赋值

let { name, age } = { name: 'zgh', age: 22 };
console.log(name, age); // zgh 22
提示

对象与数组解构的不同点:

  • 数组的元素是按次序排列的,变量的取值由它的位置决定
  • 对象的属性没有次序,变量必须与属性同名,才能取到正确的值

3. 函数参数的解构赋值

let f = ([a, b]) => a + b;
f([1, 2]); // 3

上述代码可将数组[1, 2]看作一个参数param,即param = [1, 2]

函数扩展

为函数的参数设置默认值

可以给函数的参数设置默认值,如果不指定该函数的参数值,就会使用默认参数值

function Person(name = 'zgh', num = 22) {
const name = name || 'zgh';
const num = num || 22;
}
Person();
Person('Jack', 20);

如果没有设置默认值,调用时 num 传入 0,0 为 false,那么例子中的 num 结果就为 22 而不是 0

箭头函数

ES6 允许使用箭头=>定义函数

// 1.不带参数
let sum = () => 1 + 2;
// 等同于
let sum = function () {
return 1 + 2;
};

// 2.带一个参数
let sum = a => a;
// 等同于
let sum = function (a) {
return a;
};

// 3.带多个参数,需要使用小括号将参数括起来
let sum = (a, b) => a + b;
// 等同于
let sum = function (a, b) {
return a + b;
};

// 4.代码块部分多于一条语句需要用大括号将其括起来,并且使用return语句返回。
let sum = (a, b) => {
let c = a + b;
return c;
};

// 5.返回对象,就必须用小括号把该对象括起来
let person = name => ({ name: 'zgh', age: 22 });
// 等同于
let person = function (name) {
return { name: 'zgh', age: 22 };
};

箭头函数的 this 指向

箭头函数本身是没有thisarguments的,在箭头函数中引用 this 实际上是调用的是定义时的父执行上下文的 this。

  • 使用call,apply,bind都不能改变 this 指向
  • 箭头函数没有原型属性prototype
  • 不能用作构造函数,即 new 指令
let obj = {
say() {
let f1 = () => console.log(this);
f1();
}
};
let res = obj.say;
res(); // f1执行时,say函数指向window,所以f1中的this指向window
obj.say(); // f1执行时,say函数指向obj,所以f1中的this指向obj

对象扩展

对象简写

  • 属性的简写

条件:属性的值是一个变量,且变量名称和键名是一致的

let name = 'zgh';
let age = 22;

// ES5写法
let obj = { name: name, age: age };

// ES6写法
let obj = { name, age };
  • 方法的简写
// ES5写法
let obj = {
hello: function () {
console.log('hello');
}
};

// ES6写法
let obj = {
hello() {
console.log('hello');
}
};

Map

Map 是一种用来存储键值对的数据结构。类似于对象

// 创建一个Map实例
let myMap = new Map();

// 添加键值对
myMap.set('a', 'hello');
myMap.set([1, 2, 3], { name: 'zgh' });

// 也可以在声明时就添加键值对,二维数组
const user = new Map([
['foo', 'zgh'],
['baz', 23]
]);

// 查看集合中元素的数量
myMap.size;

// 获取相应的键值
myMap.get('a');

// 删除一个键值对
myMap.delete('a');

// 判断该键值对是否存在
myMap.has('a');

// 删除集合中所有的键值对
myMap.clear();

// 可以遍历
myMap.forEach((value, key) => {
console.log(key + ': ' + value);
});

Map 和 Object 有什么不同?

  • 二者都属于键值对结构
  • 对象的键名只能是String或者Symbol类型,而 Map 的键可以是任意类型的值
  • 对象可以从原型链继承属性和方法,而 Map 不具备继承性

Map 的使用场景

1、缓存

Map 可以用来缓存一些计算结果,避免重复计算。比如缓存斐波那契数列的结果。

const fibCache = new Map();
function fibonacci(n) {
if (n < 2) {
return n;
}
if (fibCache.has(n)) {
return fibCache.get(n);
}
const result = fibonacci(n - 1) + fibonacci(n - 2);
fibCache.set(n, result);
return result;
}

console.log(fibonacci(6)); // 8

2、数据结构

Map 可以用作一些数据结构的基础,比如字典、哈希表等。例如实现哈希表时,可以使用 Map 来存储键值对。

class HashTable {
constructor() {
this.table = new Map();
}

put(key, value) {
this.table.set(key, value);
}

get(key) {
return this.table.get(key);
}

remove(key) {
this.table.delete(key);
}
}

const hashObj = new HashTable();
hashObj.put('a', 1);
console.log(hashObj.get('a'));
console.log(hashObj);

3、状态管理

Map 可以用于管理应用程序的状态。例如在 React 中,可以使用 Map 来存储组件的状态(这里只是例子,在 React 中实际上不要这么做!)

class Counter extends React.Component {
constructor(props) {
super(props);
this.state = new Map([['count', 0]]);
}

increment() {
this.setState(state => {
let count = state.get('count') + 1;
return new Map([['count', count]]);
});
}

render() {
return (
<div>
Count: {this.state.get('count')}
<button onClick={() => this.increment()}>Increment</button>
</div>
);
}
}

关于 Map 的编程题

1、编写一个函数,接受一个数组作为参数,返回一个 Map,其中键为数组中的元素,值为元素在数组中出现的次数。例如:

countOccurrences([1, 2, 3, 2, 3, 3]); // Map { 1 => 1, 2 => 2, 3 => 3 }

2、编写一个函数,接受一个 Map 作为参数,返回一个由 Map 的键值对颠倒后的新 Map。例如:

invertMap(
new Map([
['a', 1],
['b', 2],
['c', 3]
])
); // Map { 1 => 'a', 2 => 'b', 3 => 'c' }

3、编写一个函数,接受两个 Map 作为参数,返回一个新 Map,其中包含两个 Map 的所有键值对。例如:

mergeMaps(
new Map([
['a', 1],
['b', 2]
]),
new Map([
['c', 3],
['d', 4]
])
); // Map { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 }

4、编写一个函数,接受一个 Map 作为参数,返回一个新 Map,其中包含原始 Map 中所有值大于 10 的键值对。例如:

filterMap(
new Map([
['a', 5],
['b', 10],
['c', 15]
])
); // Map { 'c' => 15 }

5、编写一个函数,接受一个 Map 作为参数,返回一个新 Map,其中包含原始 Map 中所有键值对的值的平方。例如:

mapValues(
new Map([
['a', 2],
['b', 3],
['c', 4]
])
); // Map { 'a' => 4, 'b' => 9, 'c' => 16 }

6、编写一个函数,接受一个 Map 作为参数,返回一个新 Map,其中包含原始 Map 中所有键值对的值的和。例如:

sumValues(
new Map([
['a', 2],
['b', 3],
['c', 4]
])
); // 9

7、编写一个函数,接受一个 Map 作为参数,返回一个新 Map,其中包含原始 Map 中所有键值对的键和值的乘积。例如:

multiplyKeysAndValues(
new Map([
['a', 2],
['b', 3],
['c', 4]
])
); // Map { 'a' => 2, 'b' => 6, 'c' => 12 }

8、编写一个函数,接受两个 Map 作为参数,返回一个新 Map,其中包含原始 Map1 中所有键值对的键和 Map2 中对应键的值的乘积。例如:

multiplyMaps(
new Map([
['a', 2],
['b', 3],
['c', 4]
]),
new Map([
['a', 10],
['c', 20]
])
); // Map { 'a' => 20, 'c' => 80 }

9、编写一个函数,接受一个 Map 和一个回调函数作为参数,对于 Map 中的每个键值对,使用回调函数将键和值进行操作,并返回一个新 Map。例如:

mapMapValues(
new Map([
['a', 2],
['b', 3],
['c', 4]
]),
(key, value) => [key.toUpperCase(), value * 2]
); // Map { 'A' => 4, 'B' => 6, 'C' => 8 }

10、编写一个函数,接受一个 Map 和一个数组作为参数,将数组中的元素作为键,Map 中对应键的值作为值,返回一个新的 Map。例如:

mapFromArray(
new Map([
['a', 2],
['b', 3],
['c', 4]
]),
['a', 'c']
); // Map { 'a' => 2, 'c' => 4 }

WeakMap

WeakMap 是一种键必须是对象的 Map。WeakMap 的键是弱引用的,如果键所引用的对象被垃圾回收,则键值对会被自动删除。

特点:

  • 键必须是对象,值可以是任意类型
  • 键是弱引用,键所引用的对象可以被垃圾回收
  • 没有 clear 方法,不能遍历其中的键值对
let weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, 'value');

console.log(weakMap);
console.log(weakMap.get(obj)); // 'value'

obj = null; // obj 被垃圾回收,weakMap 会自动清除该键值对

Set

Set 是一种存储唯一值的数据结构。类似于数组

Set()接受具有iterable可迭代接口的数据结构作为参数,如数组、类数组、字符串等。不能接受对象结构,否则报错。

// 声明一个Set实例
let mySet = new Set();
let mySet2 = new Set([1, 2, 3]);

// 添加元素
mySet.add(1);
mySet.add('hi');
mySet.add([2, 'hello']);

// 判断集合中是否存在一个元素1
mySet.has(1); // true

// 删除集合中的字符串
mySet.delete('hi');

// 获取集合中元素的数量
mySet.size; // 3

// 删除集合中所有的元素
mySet.clear();

// 两个对象是不相等的
const set2 = new Set();
set2.add({});
set2.size; // 1
set2.add({});
set2.size; // 2

遍历操作

let mySet = new Set(['a', 'b', 'c']);

// 遍历
mySet.forEach(item => console.log(item));

// entries()返回的遍历器同时包括键名和键值,二者一样
for (let i of mySet.entries()) {
console.log(i);
}
// ["a", "a"]
// ["b", "b"]
// ["c", "c"]

// keys()返回键名
for (let i of mySet.keys()) {
console.log(i);
}
// 'a'
// 'b'
// 'c'

// values()返回键值,结果同keys()
for (let i of mySet.values()) {
console.log(i);
}

数据去重

Set 只存储唯一值,可给数组去重:

let arr = [1, 1, 2, 2, 3, 3];
let res1 = [...new Set(arr)]; // [1, 2, 3]

// 或者使用 Array.from()
let res2 = Array.from(new Set(arr)); // [1, 2, 3]

也可以给字符串去重:

const str = [...new Set('ababbc')].join('');
console.log(str); // 'abc'

交集、并集、差集

intersection() 求交集,返回一个新集合

const mySet1 = new Set([1, 3, 5, 7, 9]);
const mySet2 = new Set([1, 4, 9]);
const resSet = mySet1.intersection(mySet2);
console.log(resSet); // Set(2) { 1, 9 }

nunion() 求并集

const evens = new Set([2, 4, 6, 8]);
const squares = new Set([1, 4, 9]);
console.log(evens.union(squares)); // Set(6) { 2, 4, 6, 8, 1, 9 }

difference() 求差集

const odds = new Set([1, 3, 5, 7, 9]);
const squares = new Set([1, 4, 9]);
console.log(odds.difference(squares)); // Set(3) { 3, 5, 7 }

symmetricDifference() 返回一个包含此集合或给定集合中的元素的新集合,但不包含同时存在于这两个集合中的元素。

const evens = new Set([2, 4, 6, 8]);
const squares = new Set([1, 4, 9]);
console.log(evens.symmetricDifference(squares)); // Set(5) { 2, 6, 8, 1, 9 }

判断

isSubsetOf() 返回一个布尔值,指示此集合中的所有元素是否都在给定的集合中。

const fours = new Set([4, 8, 12, 16]);
const evens = new Set([2, 4, 6, 8, 10, 12, 14, 16, 18]);
console.log(fours.isSubsetOf(evens)); // true

isSupersetOf() 返回一个布尔值,指示给定集合中的所有元素是否都在此集合中。

const evens = new Set([2, 4, 6, 8, 10, 12, 14, 16, 18]);
const fours = new Set([4, 8, 12, 16]);
console.log(evens.isSupersetOf(fours)); // true

isDisjointFrom() 返回一个布尔值,指示此集合是否与给定集合没有公共元素。

const primes = new Set([2, 3, 5, 7, 11, 13, 17, 19]);
const squares = new Set([1, 4, 9, 16]);
console.log(primes.isDisjointFrom(squares)); // true

WeakSet

WeakSet 是一种类似于 Set 的数据结构,但是其成员必须是对象,并且这些对象都是弱引用的。

特点:

  • 成员必须是对象
  • 成员是弱引用,成员对象可以被垃圾回收
  • 没有 clear 方法,不能遍历其中的成员
let weakSet = new WeakSet();
let obj = {};
weakSet.add(obj);

console.log(weakSet.has(obj)); // true
obj = null; // obj 被垃圾回收,weakSet 会自动清除该成员

扩展操作符

...可以叫做 spread(扩展)或者 rest(剩余)操作符

剩余运算符一般会用在函数的参数里面。比如想让一个函数支持更多的参数,参数的数量不受限制,这个时候就可以使用剩余操作符

function Name(x, y, ...z) {
console.log(x); // a
console.log(y); // b
console.log(z); // ["c", "d", "e"]
}
Name('a', 'b', 'c', 'd', 'e');

剩余操作符后面的变量会变成一个数组,多余的参数会被放入这个数组中

扩展运算符用在数组的前面,作用就是将这个数组展开

const arr1 = ['a', 'b', 'c', 'd', 'e'];
const arr2 = ['f', 'g'];
const arr3 = [...arr1, ...arr2]; // ["a", "b", "c", "d", "e", "f", "g"]

// 等同于concat
const arr4 = arr1.concat(arr2);

展开对象:

const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // {a: 1, b: 2, c: 3}

const obj3 = { a: 1, b: 2, c: 3 };
const { a, ...x } = obj3;
console.log(a); // 1
console.log(x); // {b: 2, c: 3}

使用扩展运算符展开一个新的对象,第二个对象的属性值会覆盖第一个对象的同名属性值

const obj1 = { a: 1, b: 2, c: 3 };
const obj2 = { b: 30, c: 40, d: 50 };
const merged = { ...obj1, ...obj2 }; // {a: 1, b: 30, c: 40, d: 50}

Proxy

Proxy 可以创建一个对象的代理,拦截并自定义对象的基本操作(如属性访问、赋值、函数调用等)。

const proxy = new Proxy(target, handler);
  • target:被代理的目标对象
  • handler:配置对象

1. 属性保护

在 ES6 之前,可以使用Object.defineProperty去保护对象的私有属性。例如:

let sign = { _appid: '12345678', _appkey: '666', desc: 'zgh的密钥' };

Object.defineProperties(sign, {
_appid: {
writable: false
},
_appkey: {
writable: false
}
});

但是如果想对多个属性进行保护,就得对多个属性进行声明writable: false,显然很麻烦,这时就可以用 Proxy 来解决这个问题。

Proxy 意味着我们代理了这个对象,该对象所有的属性操作都会经过 Proxy

let sign = { _appid: '123456', _appkey: '666', desc: 'zgh的密钥' };
let signProxy = new Proxy(sign, {
get(target, property, receiver) {
return target[property];
},
set(target, propName, value, receiver) {
if (propName !== 'desc') {
console.log('该属性是私有属性,不允许修改!');
} else {
target[propName] = value;
}
}
});
console.log(signProxy._appid); // "123456"
signProxy._appkey = 'dd'; // 该属性是私有属性,不允许修改!
console.log(signProxy._appkey); // "666"

这时依然可以直接修改 sign 对象,如果希望对象完全不可修改,可以直接将 sign 写到 Proxy 的 target

2. Proxy 的 this 问题

如果 target 对象存在 this,那么不做任何拦截的情况下,target 的 this 所指向的是 target,而不是代理对象 proxy。

const target = {
foo() {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);

target.foo(); // false
proxy.foo(); // true

可以结合 Reflect 使用。

3. 响应式系统

模拟 Vue3 使用 Proxy 实现响应式系统:

function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key); // 追踪依赖
return Reflect.get(target, key);
},
set(target, key, value) {
Reflect.set(target, key, value);
trigger(target, key); // 触发更新
return true;
}
});
}

const data = reactive({ count: 0 });
data.count++; // 自动触发视图更新

4. 表单验证

示例:有一个记账的对象,记录着用户的存款金额,为了方便以后计算,要保证存入的数据类型必须为Number

let account = { num: 8888 };

const validator = {
get(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
if (key === 'num' && typeof value != 'number') {
throw new TypeError('The num is not an number');
}
return Reflect.set(target, key, value, receiver);
}
};

let proxyAccount = new Proxy(account, validator);

proxyAccount.num = 233; // 正常
proxyAccount.num = '666'; // 抛出错误

5. Proxy 和 Object.defineProperty 的区别

核心区别:

特性ProxyObject.defineProperty
拦截操作范围拦截对象的所有基本操作(如 getsetdeleteinapply 等)仅拦截单个属性的 getset
目标对象代理整个对象直接修改原对象
新增属性支持自动拦截动态新增的属性需手动对新属性调用 defineProperty
数组响应性直接支持数组方法(如 pushpop需要重写数组方法或特殊处理
兼容性ES6+(不支持 IE11 及更早版本)ES5+(广泛支持)
性能较慢(因多层代理和复杂拦截逻辑)较快(但初始化时遍历属性可能较慢)

说明:

1、动态属性处理

  • Proxy 自动处理动态属性
  • Object.defineProperty 需手动处理动态属性
const proxy = new Proxy(
{},
{
get(target, key) {
return Reflect.get(target, key);
},
set(target, key, value) {
return Reflect.set(target, key, value);
}
}
);

proxy.age = 25; // 自动触发 set
console.log(proxy.age); // 自动触发 get
const obj = { name: 'zgh' };

// 初始化时拦截 name
Object.defineProperty(obj, 'name', {});

// 新增属性 age 时,需手动拦截
Object.defineProperty(obj, 'age', {
get() {},
set(value) {}
});

2、对数组的支持

  • Proxy 直接支持数组
  • Object.defineProperty 需要特殊处理数组
const arrProxy = new Proxy([], {
set(target, key, value) {
console.log(`设置数组索引 ${key}${value}`);
return Reflect.set(target, key, value);
}
});

arrProxy.push(1);
// 输出: 设置数组索引 0 为 1
// 输出: 设置数组索引 length 为 1
const arr = [];
['push', 'pop', 'shift'].forEach(method => {
const original = Array.prototype[method];
arr[method] = function (...args) {
console.log(`调用 ${method}`);
return original.apply(this, args);
};
});

arr.push(1); // 输出 "调用 push"

Reflect 反射

Reflect 是 ES6 引入的一个内置对象。

用途:

  • 统一对象操作 API(替代 Object、Function 等方法)
  • 与 Proxy 配合实现代理逻辑
  • 通过 receiver 显式控制 this

1. 替代旧的对象操作

旧方式:

const obj = { a: 1, b: 2, c: 3 };

// 1. 读写属性
obj.a;
obj[b] = 3;

// 2. 检查属性是否存在
if ('a' in obj) {
}

// 3. 删除属性
delete obj.a;

Reflect 方式:

  • 读取属性:Reflect.get(target, key, receiver)
  • 设置属性:Reflect.set(target, key, value, receiver)
const obj = { a: 1, b: 2, c: 3 };

// 读写属性
Reflect.get(obj, 'a'); // 1
Reflect.set(obj, 'b', 3); // 将 obj.b 的值改为3

// 验证属性存在
Reflect.has(obj, 'a'); // true

// 删除属性
Reflect.deleteProperty(obj, 'a');

2. 函数式操作

1、调用函数并绑定 this

function greet(msg) {
return `${this.name}-${msg}`;
}

const context = { name: 'zgh' };

// 旧方式
// greet.call(context, 'hello')

Reflect.apply(greet, context, ['hello']);

2、调用构造函数

class Person {
constructor(name) {
this.name = name;
}
}

// 旧方式
const p1 = new Person('zgh');

// Reflect 方式
const p2 = Reflect.construct(Person, ['hello']);

3. 与 Proxy 配合实现代理逻辑

const target = { name: 'zgh' };
const proxy = new Proxy(target, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
}
});

4. receiver

receiver 可以改变 this 指向。

const obj = {
a: 1,
get b() {
return this.a * 2;
},
set c(num) {
this.list.push(num);
}
};

const receiverObj = { a: 3, list: [] };

Reflect.get(obj, 'b'); // 2 ,this 指向 obj
Reflect.get(obj, 'b', receiverObj); // 6 ,this 指向 receiverObj

Reflect.set(obj, 'c', 6, receiverObj);
console.log(receiverObj.list); // [6](而非修改 obj)

5. Reflect 的其他方法

// 获取对象的属性名称列表
const obj = { a: 1, b: 2, c: 3 };
console.log(Reflect.ownKeys(obj)); // [ 'a', 'b', 'c' ]

// 获取对象的原型
const obj2 = { a: 1 };
console.log(Reflect.getPrototypeOf(obj2));

// 修改对象的原型
const obj3 = { a: 1 };
const proto = { b: 2 };
Reflect.setPrototypeOf(obj3, proto);
console.log(obj3.b); // 2

// 代替call和apply方法
function fn(a, b, c) {
console.log(a, b, c);
}
Reflect.apply(fn, null, [1, 2, 3]); // 1 2 3

// 获取对象的属性描述符
const obj4 = { a: 1 };
// { value: 1, writable: true, enumerable: true, configurable: true }
console.log(Reflect.getOwnPropertyDescriptor(obj4, 'a'));

空值合并运算符

  • 写法:a ?? b
  • 如果左侧参数是 nullundefined,则 ?? 返回其右侧参数,否则返回其左侧参数
  • 效果等同于 (a !== null && a !== undefined) ? a : b

假设有表达式为:left ?? right

  • 当 left 是:0''false,会返回 left 的值
  • 当 left 是 null、undefined,会返回 right 的值
const a = null ?? 'hi'; // hi

const b = 0 ?? 42; // 0

注意:

  • ??运算符的优先级非常低,仅略高于 ?=,使用时要考虑是否添加括号
  • 如果没有明确添加括号,不能将其与||&&一起使用

??||的区别

  • || 返回第一个真值,?? 返回第一个已定义的值
  • || 无法区分 false0、空字符串""NaNnullundefined
let a = 0;
a || 1; // 1
a ?? 1; // 0

当 left 是 0、''、false 时,||会返回 right 的值

双感叹号!!

单个感叹号!表示取反,双感叹号确保结果类型是布尔类型。

!0; // true

!undefined; // true

!null; // true

!''; // true

!!0; // false

!!undefined; // false

!!null; // false

!!''; // false

可选链

当位于 ?. 前面的值为 undefinednull 时,会立即阻止代码的执行,并返回 undefined

const obj = { name: 'zgh' };
obj?.a;

可选链的三种形式:

  • obj?.pron
  • obj?.[pron]
  • obj.method?.()

逻辑运算符和赋值运算符

&&=

x &&= y等价于:x && (x = y),当 x 为真时,x = y

||=

x ||= y等价于:x || (x = y),仅在 x 为 false 的时候,x = y

??=

x ??= y 等价于 x ?? (x = y),仅在 x 为 null 或 undefined 的时候,x = y