1. Iterator
1.1 迭代器的理解
迭代器是一种接口、是一种机制。
为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator 的作用有三个:
- 为各种数据结构,提供一个统一的、简便的访问接口;
- 使得数据结构的成员能够按某种次序排列;
- 主要供
for...of
消费。
1.2 Iterator的本质
Iterator本质上,就是一个指针对象。
过程是这样的:
(1)创建一个指针对象,指向当前数据结构的起始位置。
(2)第一次调用指针对象的next
方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next
方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next
方法,直到它指向数据结构的结束位置。
1.3. 普通函数实现Iterator
function myIter(obj){
let i = 0;
return {
next(){
let done = (i>=obj.length);
let value = !done ? obj[i++] : undefined;
return {
value,
done,
}
}
}
}
原生具备 Iterator 接口的数据结构如下。
- Array
- Map
- Set
- String
- 函数的 arguments 对象
- NodeList 对象
下面的例子是数组的Symbol.iterator
属性。
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
下面是另一个类似数组的对象调用数组的Symbol.iterator
方法的例子。
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
注意,普通对象部署数组的Symbol.iterator
方法,并无效果。
let iterable = {
a: 'a',
b: 'b',
c: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // undefined, undefined, undefined
}
字符串的包装类是一个类似数组的对象,也原生具有 Iterator 接口。
var someString = "hi";
typeof someString[Symbol.iterator]
// "function"
var iterator = someString[Symbol.iterator]();
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }
2. Proxy 代理
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。
讲通俗一点就是扩展(增强)了对象,方法(函数)的一些功能
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
Proxy其实是设计模式的一种,代理模式
2.1. 语法使用
new Proxy(target,handle)
- 参数
第一个参数: target 是你要代理的对象
第二个参数,handle是对代理对象做什么操作
{
set(){},
get(){},
deleteProperty(){},
has(){},
apply(),
......
}
- 返回值
返回一个新的对象
let obj = new Proxy(target,handle)
let proxy = new Proxy(target, handler);
Proxy 对象的所有用法,都是上面这种形式,不同的只是handler
参数的写法。其中,new Proxy()
表示生成一个Proxy
实例,target
参数表示所要拦截的目标对象,handler
参数也是一个对象,用来定制拦截行为。
2.2 如果没有做任何拦截设置
如果handler
没有设置任何拦截,那就等同于直接通向原对象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
上面代码中,handler
是一个空对象,没有任何拦截效果,访问proxy
就等同于访问target
。
let obj = {
name : 'wuwei'
}
console.log(obj.name);
// 我希望你在获取name 属性的时候做一些事情,那么我们就可以用代理模式
let newObj = new Proxy(obj,{
get(target,property){ // target就是代理对象obj,property就是用户访问的属性
// console.log(target,property); // {name: "wuwei"} "aaa"
console.log(`你访问了${property}属性`)
return target[property];
}
})
2.3 添加拦截处理程序
2.3.1 get 获取拦截
get 拦截程序接受三个参数
- target 代理的目标对象
- prop 操作的属性
- receiver 代理对象
例子: 访问代理对象上不具有的属性就报错
let obj = {
name:'aabb'
}
let proxy = new Proxy(obj, {
get(target,key,receiver){
if(!(key in receiver)){
throw new ReferenceError(`属性${key}不存在`)
}
return target[key]
}
})
console.log(newObj.name); // aabb
console.log(newObj.age); // 报错
例子:创建标签
var proxy = new Proxy({},{
get(target,property){
return function(attr={},...children){
const el = document.createElement(property);
// 添加属性
for(let key of Object.keys(attr)){
el[key] = attr[key]
}
// 添加子元素
for (let child of children){
if(typeof child == 'string'){
child = document.createTextNode(child)
}
el.appendChild(child)
}
return el;
}
}
})
2.3.2 set 设置拦截
set 拦截程序接受4个参数
- target 代理的目标对象
- prop 操作的属性
- value 被写入的属性值
- receiver 代理对象
var obj = new Proxy({},{
set(target,prop,value){
if(prop == 'age'){
if(!Number.isInteger(value)){
throw new TypeError('年龄必须为整数')
}
if(value > 200){
throw new RangeError('年龄超标了,必须小于200岁')
}
}
target[prop] = value;
}
})
obj.a = '12.5';
obj.name = 'wuwei';
console.log(obj); //Proxy {a: "12.5", name: "wuwei"}
// 以下是报错
obj.age = 12.6; // Uncaught TypeError: 年龄必须为整数
obj.age = 201; // Uncaught RangeError: 年龄超标了,必须小于200岁
2.3.3 has 包含拦截
使用in 操作符检查对象中是否包含某个属性. 触发has拦截
has 拦截程序接受2个参数
- target 代理的目标对象
- prop 需要判断的属性
let obj = {
a: 1,
b: 2
}
let proxy = new Proxy(obj,{
has(target,prop){
console.log(`判断属性${prop}是否存在于当前对象中`)
return prop in target
}
})
console.log('a' in newObj);
// 判断属性a是否存在于当前对象中
// true
2.3.4 deleteProperty 删除拦截
通过delete 操作符删除属性的时候,就会触发deleteProperty 拦截
deleteProperty 拦截处理程序接受2个参数
- target 代理的目标对象
- prop 删除操作的属性
var obj = {
a: 1,
b: 2
}
var newObj = new Proxy(obj,{
deleteProperty(target,prop){
console.log(`你要删除${prop}属性`);
// TODO
delete target[prop]
}
})
delete newObj.a
2.4 函数拦截
所有的代理拦截中, 只有apply 和 constructor 的代理目标是一个函数. apply 和construct 拦截方法覆写函数内部的[[ Call ]]和 [[ Constructor ]],这些内部方法.
2.4.1 apply 拦截
apply拦截接受三个参数
- target 代理的目标函数
- thisArg 函数被调用时内部this的值
- argumentsList 传递给函数的数组
function fn() {
console.log(11111)
return 42
}
let proxy = new Proxy(fn, {
apply(target, thisArg, argumentsList) {
return target.apply(this.Arg, argumentsList)
}
})
let obj = { name: 'bb' }
let res = proxy.apply(obj, [10, 20])
console.log(res)
2.4.2 construct 拦截
construct 拦截 new 操作符调用函数
- target 代理目标函数
- argumentsList 传递给函数的参数数组
function fn() {
return 42
}
let proxy = new Proxy(fn, {
apply(target, thisArg, argumentsList) {
return target.apply(this.Arg, argumentsList)
},
construct(target, argumentsList) {
console.log(arguments)
return new target(...argumentsList)
}
})
let res = new proxy(10, 20)
console.log(res)
2.5 取消代理 Proxy.revocable()
Proxy.revocable
方法返回一个可撤销代理实例, 参数与Proxy构造函数一致。
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
Proxy.revocable
方法返回一个对象,该对象的proxy
属性是Proxy
实例,revoke
属性是一个函数,撤销代理调用的函数。上面代码中.
当执行revoke
函数之后,任何与代理对象的交互都会触发错误。
2.6. Proxy支持的拦截操作
-
get(target, propKey, receiver):拦截对象属性的读取,比如
proxy.foo
和proxy['foo']
。 -
set(target, propKey, value, receiver):拦截对象属性的设置,比如
proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。 -
has(target, propKey):拦截
propKey in proxy
的操作,返回一个布尔值。 -
deleteProperty(target, propKey):拦截
delete proxy[propKey]
的操作,返回一个布尔值。 -
ownKeys(target):拦截
Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。 -
getOwnPropertyDescriptor(target, propKey):拦截
Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。 -
defineProperty(target, propKey, propDesc):拦截
Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值。 -
preventExtensions(target):拦截
Object.preventExtensions(proxy)
,返回一个布尔值。 -
getPrototypeOf(target):拦截
Object.getPrototypeOf(proxy)
,返回一个对象。 -
isExtensible(target):拦截
Object.isExtensible(proxy)
,返回一个布尔值。 -
setPrototypeOf(target, proto):拦截
Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。 -
apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如
proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。 -
construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如
new proxy(...args)
。
2.7.reflect 反射
function fn(a,b){
return a+b
}
var newFn = new Proxy(fn,{
apply(target,context,args){
// console.log(target,context,args);
// console.log(arguments);
// console.log(...arguments);
return Reflect.apply(...arguments);
}
})
console.log(newFn(3,2)); // 5
如果要增强方法需要跟Reflect配合
我也可以在反射时干些事情
var newFn = new Proxy(fn,{
apply(target,context,args){
// console.log(target,context,args);
// console.log(arguments);
// console.log(...arguments);
return Reflect.apply(...arguments)**3; //反射结果的3次方
}
})
console.log(newFn(3,2)); // 125
2.8 Reflect.apply()
和fn.apply()很相似
Reflect.apply(target,context,args) 有三个参数
target: 需要调用的函数
context: this指向
args : 参数数组
console.log(Math.ceil(4.4)); // 向上取整 5
// 反射调用Math.ceil 没有this指向,传入了null, 参数数组
let num = Reflect.apply(Math.ceil,null,[5.1]);
console.log(num); // 6
就是调用函数的不同的方式而已
function show(...args){
console.log(this);
console.log(args);
}
// 正常调用
show(1,2,3,4); // this是window, args是[1,2,3,4]
// call调用函数
show.call('aaa',1,2,3,4); // this是aaa, args是[1,2,3,4]
// apply调用函数
show.apply('aaa',[1,2,3,4]); // this是aaa, args是[1,2,3,4]
// reflect.apply调用函数
Reflect.apply(show,'aaa',[1,2,3,4]); // this是aaa, args是[1,2,3,4]
通过reflect拿到语言内部的东西
网友评论