• 前端学习记录~2023.8.19~JavaScript重难点实例精讲~第7章 ES6(2)



    前言

    本章是第七章ES6相关的内容,也是最后一章。本篇为第二篇,后面还有一篇。

    现在ES6使用非常广泛,新增的箭头函数、类、Promise等新特性,可以方便地处理很多复杂的操作,极大地提高了开发效率。本章会记录ES6中最常用的新特性

    在学完后,希望掌握下面知识点:

    • let和const关键字
    • 解构赋值
    • 模板字符串
    • 箭头函数
    • Symbol类型
    • Set和Map数据结构
    • Proxy
    • Reflect
    • Promise
    • Iterator
    • Generator函数
    • Class及其用法
    • Module及其用法

    7.8 Set数据结构和Map数据结构

    7.8.1 Set数据结构

    ES6中新增了一种数据结构Set,表示的是一组数据的集合,类似于数组,但是Set的成员值都是唯一的没有重复

    要注意这个是不存在类型转换的,也就是必须严格相等才算重复。但是NaN是个例外,NaNNaN在进行严格相等的比较时是不相等的,但是在Set内部,NaNNaN是严格相等的,因此一个Set实例中只可以添加一个NaN

    Set本身是一个构造函数,可以接收一个数组或者类数组对象作为参数。

    (1)Set实例的属性

    • Set.prototype.constructor:构造函数,默认就是Set函数
    • Set.prototype.size:返回实例的成员总数

    (2)Set实例的函数

    • Set.prototype.add(value):添加一个值,返回Set结构本身
    • Set.prototype.delete(value):删除某个值,返回布尔值。删除成功返回true;如果删除失败,返回false
    • Set.prototype.has(value):判断是否是成员,返回布尔值
    • Set.prototype.clear():清除所有成员,无返回值

    (3)Set的常见用法

    1. 单一数组的去重

    由于Set成员值具有唯一性,因此可以使用Set来进行数组的去重

    let arr = [1, 3, 4, 2, 3, 2, 5];
    console.log(new Set(arr));	// Set {1,3,4,2,5}
    
    • 1
    • 2
    1. 多个数组的合并去重

    Set可以用于单个数组的去重,也可以用于多个数组的合并去重。实现方法是先使用扩展运算符将多个数组处理成一个数组,然后将合并后得到的数组传递给Set构造函数

    let arr1 = [1, 2, 3, 4]; 
    let arr2 = [2, 3, 4, 5, 6];
    let set1 = new Set([...arr1, ...arr2]);
    console.log(set1); //Set {1, 2, 3, 4, 5, 6}
    
    • 1
    • 2
    • 3
    • 4
    1. Set与数组的转换

    将数组转换为Set时,只需要通过Set的构造函数即可;将Set转换为数组时,通过Array.from()函数或者扩展运算符即可

    let arr1 = [1, 3, 5, 7]; 
    // 将数组转换为Set
    let set1 = new Set(arr1);
    console.log(set1); // Set { 1, 3, 5, 7 } 
    
    let set2 = new Set(); 
    set2.add('a'); 
    set2.add('b'); 
    // 将Set转换为数组,通过Array.from()函数 
    let arr2 = Array.from(set2); 
    console.log(arr2); // [ 'a', 'b' ] 
    // 将Set转换为数组,通过扩展运算符 
    let arr3 = [...set2];
    console.log(arr3); // [ 'a', 'b' ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    (4)Set的遍历

    1. forEach()函数

    针对Set数据结构,我们可以使用传统的forEach()函数进行遍历。forEach()函数的第一个参数表示的是Set中的每个元素,第二个参数表示的是元素的索引,从0开始。而在Set中没有索引的概念,它实际是键和值相同的集合,第二个参数表示的是键,实际与第一个参数相同,也返回数据值本身。

    let set = new Set([4, 5, 'hello']);
    set.forEach((item, index) => {
    	console.log(item, index);
    });
    // 4 4 
    // 5 5
    // hello hello
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. keys()函数:返回键名的遍历器
    2. values()函数:返回键值的遍历器
    3. entries()函数:返回键值对的遍历器

    通过后 3 个函数获得的对象都是遍历器对象Iterator,然后通过for...of循环可以获取每一项的值。

    因为Set实例的键和值是相等的,所以keys()函数和values()函数实际返回的是相同的值。

    let set = new Set(['red', 'green', 'blue']); 
    for (let item of set.keys()) { 
    	console.log(item);
    } 
    // red 
    // green 
    // blue
    
    for (let item of set.values()) { 
    	console.log(item);
    } 
    // red 
    // green 
    // blue
    
    for (let item of set.entries()) { 
    	console.log(item);
    } 
    // ["red", "red"] 
    // ["green", "green"]
    // ["blue", "blue"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    7.8.2 Map数据结构

    ES6中另一种新增的数据结构Map,与传统的对象字面量类似,它的本质是一种键值对的组合

    与对象字面量不同的是,对象字面量的键只能是字符串,对于非字符串类型的值会采用强制类型转换成字符串,而Map的键可以由各种类型的值组成

    // 传统的对象类型 
    const data = {}; 
    const element = document.getElementById('home'); 
    data[element] = 'first';
    console.log(data); // {[object HTMLDivElement]: "first"}
    
    // Map 
    const map = new Map(); 
    const element = document.getElementById('home'); 
    map.set(element, 'first');
    console.log(map); // {div#home => "first"}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Map本身是一个构造函数,可以接收一个数组作为参数,数组的每个元素同样是一个子数组,子数组元素表示的是键和值

    const map = new Map([ 
    	['name', 'kingx'], 
    	['age', 123]
    ]);
    console.log(map); // Map { 'name' => 'kingx', 'age' => 123 }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    类似于Set数据结构的元素值唯一性,在Map数据结构中,所有的都必须具有唯一性。如果对同一个键进行多次赋值,那么后面的值会覆盖前面的值

    对于Map键的唯一性也是用严格相等进行判断。此外同样虽然NaNNaN不严格相等,但是Map会将其视为一个相同的键

    如果Map实例的键是引用数据类型,则需要判断对象是否为同一个引用、是否占据同一个内存地址

    const map = new Map(); 
    map.set([0], '0'); 
    map.set([0], '1');
    console.log(map); // Map { [ 0 ] => '0', [ 0 ] => '1' }
    
    • 1
    • 2
    • 3
    • 4

    在上面的实例中,我们将数组[0]作为map的键,但是[0]作为引用类型数据,每次生成一个新的值都会占据新的内存地址,实际为不同的键,因此map在输出时会有两个元素值。

    如果希望元素[0]只占据同一个键,则可以将其赋给一个变量值,通过变量值添加到map中

    let arr = [0]; 
    const map = new Map(); 
    map.set(arr, '0'); 
    map.set(arr, '1');
    console.log(map); // Map { [ 0 ] => '1' }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (1)Map实例的属性

    • size属性:返回Map结构的成员总数

    (2)Map实例的函数

    • set(key, value):设置键名key对应的键值为value,set()函数返回的是当前Map对象,因此set()函数可以采用链式调用的写法
    • get(key):读取key对应的键值,如果找不到key,返回undefined
    • has(key):返回一个布尔值,表示某个键是否在当前Map对象中
    • delete(key):删除某个键,返回一个布尔值。删除成功返回true;如果删除失败,返回false
    • clear():清除所有成员,没有返回值。

    (3)Map的遍历

    与Set一样,Map的遍历同样可以采用4种函数,分别是forEach()函数、keys()函 数、values()函数、entries()函数

    (4)Map与其他数据结构的转换

    1. Map与数组的互相转换
    // Map转换为数组,可以通过扩展运算符实现
    const map = new Map(); 
    map.set('name', 'kingx'); 
    map.set('age', 12);
    const arr = [...map];
    console.log(arr); // [ [ 'name', 'kingx' ], [ 'age', 12 ] ]
    
    // 数组转换为Map,可以通过Map构造函数实现,使用new操作符生成Map的实例
    const arr = [[ 'name', 'kingx' ], [ 'age', 12 ]]; 
    const map = new Map(arr);
    console.log(map); // Map { 'name' => 'kingx', 'age' => 12 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. Map与对象的互相转换
    // Map转换为对象,如果Map的实例的键是字符串,则可以直接转换;如果键不是字符串,则会先转换成字符串然后再进行转换
    function mapToObj(map) { 
    	let obj = {}; 
    	for(let [key, value] of map) { 
    		obj[key] = value;
    	}
    	return obj; 
    }
    console.log(mapToObj(map)); // { name: 'kingx', age: 12 }
    
    // 对象转换为Map,只需要遍历对象的属性并通过set()函数添加到Map的实例中即可。
    function objToMap(obj) { 
    	let map = new Map(); 
    	for (let k of Object.keys(obj)) { 
    		map.set(k, obj[k]);
    	} 
    	return map;
    }
    console.log(objToMap({yes: true, no: false}));
    // Map {"yes" => true, "no" => false}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    1. Map与JSON的互相转换
    // Map转换为JSON字符串时,有两种情况,第一种是当Map的键名都是字符串时,可以先将Map转换为对象,然后调JSON.stringify()函数
    function mapToJson(strMap) { 
    	// 先将map转换为对象,然后转换为JSON 
    	return JSON.stringify(mapToObj(strMap));
    } 
    let myMap = new Map().set('yes', true).set('no', false);
    console.log(mapToJson(myMap)); // {"yes":true,"no":false}
    
    // Map转换为JSON字符串时的第二种情况是当Map的键名有非字符串时,我们可以先将Map转换为数组,然后调用JSON.stringify()函数
    function mapToArrayJson(map) { 
    	// 先通过扩展运算符转换为数组,再转换为JSON
    	return JSON.stringify([...map]); 
    } 
    let myMap2 = new Map().set(true, 7).set({foo: 3}, ['abc']);
    mapToArrayJson(myMap2); // [[true,7],[{"foo":3},["abc"]]]
    
    // JSON转换为Map。JSON字符串是由一系列键值对构成,键一般都为字符串。我们可以直接通过调用JSON.parse()函数先将JSON字符串转换为对象,然后再转换为Map
    function jsonToMap(jsonStr) { 
    	// 先转换为JSON对象,再转换为Map return 
    	objToMap(JSON.parse(jsonStr));
    }
    jsonToMap('{"yes": true, "no": false}'); // Map { 'yes' => true, 'no' => false }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    1. Map与Set的互相转换
    // Set转换为Map,Set中以数组形式存在的数据可以直接通过Map的构造函数转换为Map
    function setToMap(set) { 
    	return new Map(set);
    }
    const set = new Set([ ['foo', 1], ['bar', 2]
    ]);
    console.log(setToMap(set)); // Map { 'foo' => 1, 'bar' => 2 }
    
    // Map转换为Set,可以将遍历Map本身获取到的键和值构成一个数组,然后通过add()函数添加至set实例中
    function mapToSet(map) { 
    	let set = new Set();
    	for (let [k,v] of map) {
    		set.add([k, v]) 
    	} 
    	return set;
    }
    const map14 = new Map() .set('yes', true) .set('no', false);
    mapToSet(map14); // Set { [ 'yes', true ], [ 'no', false ] }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    7.9 Proxy

    7.9.1 Proxy概述

    ES6中新增了Proxy对象,从字面上看可以理解为代理器,主要用于改变对象的默认访问行为。实际表现是在访问对象之前增加一层拦截,任何对对象的访问行为都会通过这层拦截。在拦截中,我们可以增加自定义的行为。

    const proxy = new Proxy(target, handler);
    
    • 1

    Proxy实际上是一个构造函数,接收两个参数:

    • target:目标对象
    • handler:定义拦截的行为

    通过Proxy构造函数可以生成实例proxy,任何对proxy实例的属性的访问都会自动转发至target对象上,我们可以针对访问的行为配置自定义的handler对象,因此外界通过proxy访问target对象的属性时,都会执行handler对象自定义的拦截操作。

    // 定义目标对象 
    const person = { 
    	name: 'kingx', 
    	age: 23
    }; 
    // 定义配置对象 
    let handler = { 
    	get: function (target, prop, receiver) { 
    		console.log("你访问了person的属性"); 
    		return target[prop];
    	}
    }; 
    // 生成Proxy的实例 
    const p = new Proxy(person, handler);
    // 执行结果
    console.log(p.name); 
    // 你访问了person的属性
    // kingx
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    使用Proxy需要注意的点 :

    1. 必须通过代理实例访问
    2. 配置对象(也就是handler)不能为空对象

    7.9.2 Proxy实例函数

    Proxy实例函数共13种:

    1. get(target, propKey, receiver):拦截对象属性的读取操作。target表示的是目标对象,propKey表示的是读取的属性值,receiver表示的是配置对象
    2. set(target, propKey, value, receiver):拦截对象属性的写操作,即设置属性值。target表示目标对象,propKey表示的是将要设置的属性,value表示将要设置的属性的值,receiver表示的是配置对象
    3. has(target, propKey):拦截hasProperty的操作,返回一个布尔值。target表示目标对象,propKey表示判断的属性
    4. deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值,表示是否执行成功
    5. ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、 Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in循环等操作。其中target表示的是获取对象自身所有的属性名
    6. getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey)操作,返回属性的属性描述符构成的对象
    7. defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy,propDescs)操作,返回一个布尔值。target表示目标对象,propKey表示新增的属性,propDesc表示的是属性描述符对象
    8. preventExtensions(target):拦截Object.preventExtensions(proxy)操作,返回一个布尔值。表示的是让一个对象变得不可扩展,不能再增加新的属性
    9. getPrototypeOf(target):拦截Object.getPrototypeOf(proxy)操作,返回一个对象,表示的是拦截获取对象原型属性
    10. isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值,表示对象是否是可扩展的
    11. setPrototypeOf(target, proto) :拦截Object.setPrototypeOf(proxy, proto)操作,返回一个布尔值,表示的是拦截设置对象的原型属性的行为。proto表示新的原型对象
    12. apply(target, object, args):拦截Proxy实例作为函数调用的操作,例如proxy(…args)、proxy.call(object, …args)、proxy.apply(…),其中target表示目标对象,object表示函数的调用方,args表示函数调用传递的参数
    13. construct(target, args):拦截Proxy实例作为构造函数调用的操作。args表示函数调用传递的参数

    这些函数都有一个通用的特性,即如果在target中使用了this关键字,再通过Proxy处理后,this关键字指向的是Proxy的实例,而不是目标对象target

    7.9.3 Proxy多种函数的基本使用方法

    1. 读取不存在属性
    2. 读取负索引的值
    3. 禁止访问私有属性
    4. Proxy访问属性的限制
    5. 拦截属性赋值操作
    6. 隐藏内部私有属性
    7. 禁止删除某些属性
    8. 函数的拦截

    7.9.4 Proxy的使用场景

    (1)实现真正的私有

    JavaScript中虽然没有私有属性的语法,但存在一种约定俗成的下画线写法,我们可以通过Proxy处理下画线写法来实现真正的私有。

    需要实现下面几条:

    • 不能访问到私有属性,如果访问到私有属性则返回 undefined
    • 不能直接修改私有属性的值,即使设置了也无效
    • 不能遍历出私有属性,遍历出来的属性中不会包含私有属性
    const apis = { 
    	_apiKey: '12ab34cd56ef', 
    	getAllUsers: function () { 
    		console.log('这是查询全部用户的函数');
    	}, 
    	getUserById: function (userId) { 
    		console.log('这是根据用户id查询用户的函数');
    	}, 
    	saveUser: function (user) { 
    		console.log('这是保存用户的函数');
    	}
    }; 
    const proxy = new Proxy(apis, { 
    	get: function (target, prop) { 
    		if (prop[0] === '_') { 
    			return undefined;
    		} 
    		return target[prop];
    	},
    	set: function (target, prop, value) {
    		if (prop[0] !== '_') { 
    			target[prop] = value;
    		}
    	}, 
    	has: function (target, prop) { 
    		if (prop[0] === '_') { 
    			return false;
    		} 
    		return prop in target;
    	}
    }); 
    console.log(proxy._apiKey); // undefined 
    console.log(proxy.getAllUsers()); // 这是查询全部用户的函数 
    proxy._apiKey = '123456789'; // 设置无效 
    console.log('getUserById' in proxy); // true
    console.log('_apiKey' in proxy); // false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    (2)增加日志记录

    在日常的开发中,针对那些调用频繁、运行缓慢或者占用资源密集型的接口,我们期望能记录它们的使用情况,这个时候我们可以通过Proxy作为中间件增加日志记录。

    为了达到上面的目的,我们需要使用Proxy进行拦截,首先通过get()函数拦截到调用的函数名,然后通过apply()函数进行函数的调用。

    因此在实现上,get()函数会返回一个函数,在这个函数内通过apply()函数调用原始函数,然后调用记录操作日志的函数

    const apis = { 
    	_apiKey: '12ab34cd56ef', 
    	getAllUsers: function () { 
    		console.log('这是查询全部用户的函数');
    	}, 
    	getUserById: function (userId) { 
    		console.log('这是根据用户id查询用户的函数');
    	}, 
    	saveUser: function (user) { 
    		console.log('这是保存用户的函数');
    	}
    }; 
    
    // 记录日志的方法 
    function recordLog() { 
    	console.log('这是记录日志的函数');
    }
    const proxy = new Proxy(apis, { 
    	get: function (target, prop) { 
    		const value = target[prop]; 
    		return function (...args) { 
    			// 此处调用记录日志的函数 
    			recordLog(); 
    			// 调用真实的函数 
    			return value.apply(null, args);
    		} 
    	}
    });
    proxy.getAllUsers();
    //这是记录日志的函数
    //这是查询全部用户的函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    这样就可以在不影响原应用正常运行的情况下增加日志记录。

    如果我们只想要对特定的某些函数增加日志,那么可以在get()函数中进行特殊的处理,对函数名进行判断.

    (3)提供友好提示或者阻止特定操作

    通过Proxy,我们可以增加某些操作的友好提示或者阻止特定的操作,主要包括以下几类:

    • 某些被弃用的函数被调用时,给用户提供友好提示
    • 阻止删除属性的操作
    • 阻止修改某些特定的属性的操作

    7.10 Reflect

    7.10.1 Reflect概述

    总的来说,Reflect 是一个内置的对象,提供了一组有用的方法,用于操作对象和函数。它提供了一种与 Proxy 对象交互的方法,使开发人员可以使用相同的方法来处理对象和函数,同时提供更多的操作和控制选项。在使用 Reflect时候可以使代码更加简洁和易于理解,同时还提供了更多的操作和控制选项。

    可以这样理解:有一个名为Reflect的全局对象,上面挂载了对象的某些特殊函数,这些函数可以通过类似于Reflect.apply()这种形式来调用,所有在Reflect对象上的函数要么可以在Object原型链中找到,要么可以通过命令式操作符实现,例如delete和in操作符。

    Reflect对象的函数与Proxy对象的函数一一对应,只要是Proxy对象的函数,就能在Reflect对象上找到对应的函数。这就让Proxy对象可以方便地调用对应的Reflect对象上的函数,完成默认行为,并以此作为修改行为的基础。
    也就是说,不管Proxy对象怎么修改默认行为,总可以在Reflect对象上获取默认行为。
    而事实上Proxy对象也会经常随着Reflect对象一起进行调用。

    7.10.2 Reflect静态函数

    与Proxy对象不同的是,Reflect对象本身并不是一个构造函数,而是直接提供静态函数以供调用,Reflect对象的静态函数一共有13个:

    1. Reflect.apply(target, thisArg, args):通过指定的参数列表执行target函数,等同于执行Function.prototype.apply.call(target, thisArg, args)。target表示的是目标函数,thisArg表示的是执行target函数时的this对象,args 表示的是参数列表
    2. Reflect.construct(target, args [, newTarget]):执行构造函数,等同于执行new target(...args)。target表示的是构造函数,args表示的是参数列表。newTarget是选填的参数,如果增加了该参数,则表示将newTarget作为新的构造函数;如果没有增加该参数,则仍然使用第一个参数target作为构造函数
    3. Reflect.defineProperty(target, propKey, attributes):为对象定义属性。等同于执行Object.defineProperty()。propKey表示的是新增的属性名, attributes表示的是属性描述符对象集
    4. Reflect.deleteProperty(target, propKey):删除对象的属性,等同于执行delete obj[propKey]
    5. Reflect.get(target, propKey, receiver):获取对象的属性值,等同于执行target[propKey]。target表示的是获取属性的对象,propKey表示的是获取的属性,receiver表示函数中this绑定的对象
    6. Reflect.getOwnPropertyDescriptor(target, propKey):得到指定属性的描述对象,等同于执行Object.getOwnPropertyDescriptor()
    7. Reflect.getPrototypeOf(target):读取对象的__proto__属性,等同于执行Object.getPrototypeOf(obj)
    8. Reflect.has(target, propKey):判断属性是否在对象中
    9. Reflect.isExtensible(target):判断对象是否可扩展,等同于执行 Object.isExtensible()函数
    10. Reflect.ownKeys(target):获取对象的所有属性,包括Symbol属性,等同于Object.getOwnPropertyNamesObject.getOwnPropertySymbols之和
    11. Reflect.preventExtensions(target):让一个对象变得不可扩展,等同于执行Object.preventExtensions()
    12. Reflect.set(target, propKey, value, receiver):设置某个属性值,等同于执行target[propKey] = value。receiver为可选项,表示函数中this绑定的对象
    13. Reflect.setPrototypeOf(target, newProto):设置对象的原型prototype,等同于执行Object.setPrototypeOf(target, newProto)。target表示的是目标对象,newProto表示的是新的原型对象

    7.10.3 Reflect与Proxy

    ES6在设计的时候就将Reflect对象和Proxy对象绑定在一起了,Reflect对象的函数与Proxy对象的函数一一对应,因此很显然会经常在Proxy对象中调用Reflect对象对应的函数。

    下面的例子中使用Proxy对象拦截属性的读取、设置和删除操作,并配合Reflect对象实现:

    let target = { 
    	name: 'kingx'
    }; 
    const proxy = new Proxy(target, { 
    	get(target, prop) { 
    		console.log(`读取属性${prop}的值为${target[prop]}`);
    		return Reflect.get(target, prop);
    	}, 
    	set(target, prop, value) { 
    		console.log(`设置属性${prop}的值为${value}`); 
    		return Reflect.set(target, prop, value);
    	}, 
    	deleteProperty(target, prop) { 
    		console.log('删除属性: ' + prop); 
    		return Reflect.deleteProperty(target, prop);
    	} 
    });
    proxy.name; // 读取属性name的值为'kingx' 
    proxy.name = 'kingx2'; // 设置属性name的值为'kingx2'
    delete proxy.name; // 删除属性: name
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    Proxy对象和Reflect对象配合使用的一个最经典案例就是能够实现观察者模式:

    // 目标对象 
    const target = { 
    	name: 'kingx'
    }; 
    // 观察者队列,包含所有的观察者对象 
    const queueObservers = new Set(); 
    // 第一个观察者对象 
    function observer1(prop, value) {
    	console.log(`目标对象的${prop}属性值变为${value},观察者1开心地笑了`);
    }
    // 第二个观察者对象 
    function observer2(prop, value) { 
    	console.log(`目标对象的${prop}属性值变为${value},观察者2伤心地哭了`);
    } 
    // Proxy的set()函数,用于拦截目标对象属性修改的操作 
    function set(target, prop, value) { 
    	// 使用Reflect.set()函数修改属性 
    	const result = Reflect.set(target, prop, value); 
    	// 执行通知函数,通知所有的观察者 
    	result ? queueObservers.forEach(fn => fn(prop, value)) : ''; 
    	return result;
    } 
    // 为目标对象添加观察者 
    const observer = (fn) => queueObservers.add(fn);
    observer(observer1); 
    observer(observer2);
    // 通过Proxy生成目标对象的代理的函数 
    const observable = (target) => new Proxy(target, {set}); 
    // 获取代理 
    const proxy = observable(target);
    
    proxy.name = 'kingx2';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    最后我们执行proxy.name = 'kingx2’后,会进入Proxy的set()函数中,成功地修改了name属性值,并且通知观察者执行各自的操作

  • 相关阅读:
    最新:Selenium操作已经打开的Chrome(免登录)
    拧紧数据“安全阀”,筑牢个保“安全堤”
    Kotlin 协程 (5/6篇) - 响应式编程 Flow
    【HUAWEI】VLAN+OSPF+单臂路由
    HTML基础学习第三课(列表的创建、有序和无序)
    吃鸡游戏出现msvcp140.dll缺失的四个解决方法
    java基于springboot游乐场员工管理系统
    网络安全(黑客)自学
    冒泡排序
    2020年12月大学英语六级作文
  • 原文地址:https://blog.csdn.net/Viator_mp3/article/details/132513325