zoukankan      html  css  js  c++  java
  • 【重温基础】14.元编程

    本文是 重温基础 系列文章的第十四篇。
    这是第一个基础系列的最后一篇,后面会开始复习一些中级的知识了,欢迎持续关注呀!
    接下来会统一整理到我的【Cute-JavaScript】JavaScript基础系列中。

    今日感受:独乐乐不如众乐乐。

    系列目录:

    本章节复习的是JS中的元编程,涉及的更多的是ES6的新特性。

    1. 概述

    元编程,其实我是这么理解的:让代码自动写代码,可以更改源码底层的功能
    元,是指程序本身。
    有理解不到位,还请指点,具体详细的介绍,可以查看维基百科 元编程
    从ES6开始,JavaScrip添加了对ProxyReflect对象的支持,允许我们连接并定义基本语言操作的自定义行为(如属性查找,赋值,枚举和函数调用等),从而实现JavaScrip的元级别编程。

    • Reflect: 用于替换直接调用Object的方法,并不是一个函数对象,也没有constructor方法,所以不能用new操作符。
    • Proxy: 用于自定义对象的行为,如修改setget方法,可以说是ES5中Object.defineProperty()方法的ES6升级版。
    • 两者联系: API完全一致,但Reflect一般在Proxy需要处理默认行为的时候使用。

    参考资料

    名称 地址
    Reflect MDN Reflect
    Proxy MDN Proxy
    元编程 MDN 元编程

    本文主要从Proxy介绍,还会有几个案例,实际看下怎么使用。

    2. Proxy介绍

    proxy 用于修改某些操作的默认行为,可以理解为一种拦截外界对目标对象访问的一种机制,从而对外界的访问进行过滤和修改,即代理某些操作,也称“代理器”。

    2.1 基础使用

    基本语法:

    let p = new Proxy(target, handler);
    

    proxy实例化需要传入两个参数,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

    let p = new Proxy({}, {
        get: function (target, handler){
            return 'leo';
        }
    })
    p.name; // leo
    p.age;  // leo
    p.abcd; // leo
    

    上述a实例中,在第二个参数中定义了get方法,来拦截外界访问,并且get方法接收两个参数,分别是目标对象所要访问的属性,所以不管外部访问对象中任何属性都会执行get方法返回leo

    注意

    • 只能使用Proxy实例的对象才能使用这些操作。
    • 如果handler没有设置拦截,则直接返回原对象。
    let target = {};
    let handler = {};
    let p = new Proxy(target, handler);
    p.a = 'leo'; 
    target.a;  // 'leo'
    

    同个拦截器函数,设置多个拦截操作

    let p = new Proxy(function(a, b){
        return a + b;
    },{
        get:function(){
            return 'get方法';
        },
        apply:function(){
            return 'apply方法';
        }
    })
    

    这里还有一个简单的案例:

    let handler = {
        get : function (target, name){
            return name in target ? target[name] : 16;
        }
    }
    
    let p = new Proxy ({}, handler);
    p.a = 1;
    console.log(p.a , p.b);
    // 1   16
    

    这里因为 p.a = 1 定义了p中的a属性,值为1,而没有定义b属性,所以p.a会得到1,而p.b会得到undefined从而使用name in target ? target[name] : 16;返回的默认值16

    Proxy支持的13种拦截操作
    13种拦截操作的详细介绍:打开阮一峰老师的链接

    • 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)、for…in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而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.2 取消Proxy实例

    使用Proxy.revocable方法取消Proxy实例。

    let a = {};
    let b = {
        get: function(target, name) {
            return "[[" + name + "]]";
        }
    };
    let revoke = Proxy.revocable(a, b);
    let proxy = revoke.proxy;
    
    proxy.age;           // "[[age]]"
    revoke.revoke();
    proxy.age;           // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
    
    proxy.age = 10;      // Uncaught TypeError: Cannot perform 'set' on a proxy that has been revoked
    delete proxy.age;    // Uncaught TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked
    typeof proxy;        // "object"
    

    2.3 实现 Web服务的客户端

    const service = createWebService('http://le.com/data');
    service.employees().than(json =>{
        const employees = JSON.parse(json);
    })
    
    function createWebService(url){
        return new Proxy({}, {
            get(target, propKey, receiver{
                return () => httpGet(url+'/'+propKey);
            })
        })
    }
    

    3. Proxy实践

    3.1 数据拦截验证

    通过Proxy代理对象的setget方法来进行拦截数据,像Vue就是用数据拦截来实现数据绑定。

    let handler = {
        // 拦截并处理get方法
        // 可以理解为设置get方法返回的默认值
        get : function (target, key){
            return key in target ? target[key] : 30;
        },
        
        // 拦截并处理set方法
        // 可以理解为设置set方法的默认行为
        set : function (target, key, value){
            if(key === "age"){
                if (!Number.isInteger(value)){
                    throw new TypeError("age不是一个整数!");
                }
                if (value > 200){
                    throw new TypeError("age不能大于200!");
                }
            }
            // 保存默认行为
            target[key] = value;
        }
    }
    
    let p = new Proxy({}, handler);
    p.a = 10;         // p.a   => 10
    p.b = undefined;  // p.b   => undefined
    p.c;              // 默认值 30
    p.age = 100;      // p.age => 100
    p.age = 300;      // Uncaught TypeError: age不能大于200!
    p.age = "leo";    // Uncaught TypeError: age不是一个整数!
    

    3.2 函数节流

    通过拦截handler.apply()方法的调用,实现函数只能在1秒之后才能再次被调用,经常可以用在防止重复事件的触发。

    let p = (fun, time) => {
        // 获取最后点击时间
        let last = Date.now() - time;
        return new Proxy (fun, {
            apply(target, context, args){
                if(Date.now() - last >= time){
                    fun.bind(target)(args);
                    // 重复设置当前时间
                    last = Date.now();
                }
            }
        })
    }
    
    let p1 = () => {
        console.log("点击触发");
    }
    let time = 1000; // 设置时间
    let proxyObj = p(p1, time);
    // 监听滚动事件
    document.addEventListener('scroll', proxyObj);
    

    3.3 实现单例模式

    通过拦截construct方法,让不同实例指向相同的constructer,实现单例模式。

    let p = function(fun){
        let instance;
        let handler = {
            // 拦截construct方法
            construct: function(targer, args){
                if(!instance){
                    instance = new fun();
                }
                return instance;
            }
        }
        return new Proxy(fun, handler);
    }
    
    // 创建一个construct案例
    function Cons (){
        this.value = 0;
    }
    
    // 创建实例
    let p1 = new Cons();
    let p2 = new Cons();
    
    // 操作
    p1.value = 100; 
    // p1.value => 100 , p2.value => 0
    // 因为不是相同实例
    
    // 通过Proxy实现单例
    let singleton = p(Cons);
    let p3 = new singleton();
    let p4 = new singleton();
    p3.value = 130; 
    // p1.value => 130 , p2.value => 130
    // 现在是相同实例
    

    参考资料

    1. MDN 元编程
    2. ES6中的元编程-Proxy & Reflect
    本部分内容到这结束

    Author 王平安
    E-mail pingan8787@qq.com
    博 客 www.pingan8787.com
    微 信 pingan8787
    每日文章推荐 https://github.com/pingan8787/Leo_Reading/issues
    JS小册 js.pingan8787.com

    bg

    个人博客:http://www.pingan8787.com 微信公众号【前端自习课】和千万网友一起,每日清晨,享受一篇前端优秀文章。 目前已连续推送文章 600+ 天,愿每个人的初心都能一直坚持下去!
  • 相关阅读:
    redis在java项目中的使用
    Nginx+Tomcat搭建高性能负载均衡集群
    Redis 数据类型
    MySQL 索引概述
    Spring boot 中的WebMvcConfigurerAdapter、WebMvcConfigurationSupport与WebMvcConfigurer区别
    DAO与DTO名词解释
    FindBugs-IDEA插件的使用
    Map 中有 HashMap、TreeMap、HashTable、LinkedHashMap,首先简单说一下他们之间的区别:
    javax.el.PropertyNotFoundException:
    内省(introspector)------>JavaBean
  • 原文地址:https://www.cnblogs.com/pingan8787/p/11838206.html
Copyright © 2011-2022 走看看