美文网首页
第二十八节: ES6 Iterator与Proxy

第二十八节: ES6 Iterator与Proxy

作者: 时光如剑 | 来源:发表于2020-10-31 19:29 被阅读0次

    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拿到语言内部的东西

    相关文章

      网友评论

          本文标题:第二十八节: ES6 Iterator与Proxy

          本文链接:https://www.haomeiwen.com/subject/opmgmktx.html