第二十八节: ES6 Iterator与Proxy

1. Iterator

1.1 迭代器的理解

迭代器是一种接口、是一种机制。

为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator 的作用有三个:

  1. 为各种数据结构,提供一个统一的、简便的访问接口;
  2. 使得数据结构的成员能够按某种次序排列;
  3. 主要供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)

  1. 参数

第一个参数: target 是你要代理的对象

第二个参数,handle是对代理对象做什么操作

{

set(){},

get(){},

deleteProperty(){},

has(){},

apply(),

......

}

  1. 返回值

返回一个新的对象

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 拦截程序接受三个参数

  1. target 代理的目标对象
  2. prop 操作的属性
  3. 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个参数

  1. target 代理的目标对象
  2. prop 操作的属性
  3. value 被写入的属性值
  4. 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个参数

  1. target 代理的目标对象
  2. 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个参数

  1. target 代理的目标对象
  2. 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拦截接受三个参数

  1. target 代理的目标函数
  2. thisArg 函数被调用时内部this的值
  3. 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 操作符调用函数

  1. target 代理目标函数
  2. 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.fooproxy['foo']
  • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = vproxy['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拿到语言内部的东西

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354