最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 一篇彻底理解Proxy | 建议收藏

    正文概述 掘金(LBJ)   2021-06-21   640

    这是我参与更文挑战的第20天,活动详情查看: 更文挑战

    没有前言,咱们直接单刀直入,直奔主题!

    首先从字面意思得知,Proxy是代理的意思

    那它是什么呢?通过typeof 来检测一下

    console.log(typeof Proxy)//function
    

    由上可以得知:

    1. Proxy是定义在window上的全局变量
    2. 它的类型是function

    并且首字母大写了,我们可以猜测,它并不是一个普通的function,它应该是一个构造函数或者说是一个类。

    那来试试看,万一可以直接执行呢!

    let proxy = Proxy()
    

    结果让你失望了,以上代码会报错:Uncaught TypeError: Constructor Proxy requires 'new'

    所以我们来试试正儿八经的试试new Proxy(),如下:

    let p = new Proxy()
    

    以上代码执行也会抛出错误:Uncaught TypeError: Cannot create proxy with a non-object as target or handler,意思是创建proxy对象时,不能使用不是对象的东西作为target或者handler传入,什么意思呢?答案是要按照它的要求来传递参数

    从以上报错信息,我们可以得出两个重要信息:

    1. Proxy在构造对象时接受两个参数:targethandler
    2. 两个参数的类型必须是object

    那问题来了,这两个参数targethandler分别表示什么呢?

    在最开始,我说过Proxy 的本意是代理意思,表示由它来“代理”某些操作;网上还有另外一种理解:

    所以我们可以很好的理解,target表示的就是要拦截(代理)的目标对象;而handler是用来定制拦截行为

    target很容易理解,关键就在handler里头到底可以填什么呢?分别用于拦截对象的什么操作呢?

    于是乎,我们猜测:handler中肯定存在与对象操作一一对应的方法?

    那我们先回顾我们是怎么操作对象?为了方便,我这里列举出操作对象的所有方式

    例如:js

    let obj = {
    	name: 'alice',
    	showName() {
    		console.log(`my name is ${this.name}`)
    	}
    }
    
    1. 获取对象属性
    console.log(obj.name)//alice
    
    1. 给对象添加属性
    obj.age = 12;
    
    1. 判断属性是否在对象中
    console.log('age' in obj)//true
    
    1. 删除对象属性
    delete obj.age
    
    1. 通过各种方法遍历对象的所有属性
    console.log(Object.getOwnPropertyNames(obj));//["name", "showName"]
    console.log(Object.getOwnPropertySymbols(obj));//[]
    console.log(Object.keys(obj))//["name", "showName"]
    for (let key in obj){
    	console.log(key)
    }//分别打印name showName
    
    1. 获取对象的某个属性的描述对象
    let d = Object.getOwnPropertyDescriptor(obj,'name')
    console.log(d)
    //{value: "alice", writable: true, enumerable: true, configurable: true}
    
    1. 使用Object身上的方法,为某个对象添加一个或多个属性
    Object.defineProperty(obj,'age',{			
    	value:12,
    	writable:true,
    	enumerable:true,
    	configurable:true
    })
    Object.defineProperties(obj,{
    	showAge:{
    		value:function(){console.log(`我今年${this.age}岁了`)},
    		writable:true,
    		enumerable:true,
    		configurable:true,
    	},
    	showInfo:{
    		value:function(){console.log(`我叫${this.name},我今年${this.age}岁了`)},
    		writable:true,
    		enumerable:true,
    		configurable:true,
    	}	
    })
    
    1. 获取一个对象的原型对象
    Object.getPrototypeOf(obj)		
    console.log(Object.getPrototypeOf(obj) === obj.__proto__)//true
    
    1. 设置某个对象的原型属性对象
    Object.setPrototypeOf(obj,null);
    //表示设置对象的原型为null,也可以传入其他对象作为其原型
    
    1. 让一个对象变得不可扩展,即不能添加新的属性
    Object.preventExtensions(obj)
    
    1. 查看一个对象是不是可扩展的
    console.log(Object.isExtensible(obj));//false,因为上面设置了该对象为不可扩展对象
    
    1. 如果对象为function类型,function类型的对象可以执行被执行符号()以及.call()和.apply()执行
    function fn(...args){
    	console.log(this,args) 
    }
    fn(1,2,3);
    fn.call(obj,1,2,3);
    fn.apply(obj,[1,2,3]);
    
    1. 一切皆是对象。如果对象作为构造函数时,则该对象可以用new生成出新的对象
    function Person(){}
    let p1 = new Person();
    

    以上都是对对象的一些操作!

    那回到我们的new Proxy(target,handler)中的handler,我们之前说了,handler是用于设置拦截行为的,其实拦截的内容就是上面这一系列的对象操作,当对象执行某个操作时,就会触发handler里面定义的东西,而这些东西本质是一个个函数。

    于是,我们对Proxy有了比较全面的认知,知道它其实是构造函数,它可以构造出代理对象,这个代理对象可以代理目标对象target做一些事,当执行某个操作时,它会执行该操作所对应的函数。那问题来了,handler中都有哪些函数呢?分别对应什么操作呢?

    为了看上去不乱,我这里先画个图

    补画图处...............................................

    下面我们一个个来讲

    1. get方法

    get方法可自动接受3个参数target, propKey, receiver,分别表示要代理的目标对象、对象上的属性以及代理对象,该方法用于拦截某个属性的读取操作,比如proxy.fooproxy['foo']

    var person = {
      name: "Alice"
    };
    var proxy = new Proxy(person, {
      get: function(target, propKey) {
        if (propKey in target) {
          return target[propKey];
        } else {
          throw new ReferenceError(`Prop name ${propKey} does not exist.`);
        }
      }
    });
    proxy.name // "Alice"
    proxy.age // 抛出错误:Uncaught ReferenceError: Prop name age does not exist.
    

    可以看到,上面我访问了不存在属性,正常情况下如果没有这个拦截函数,访问不存在的属性,只会返回undefined,这里由于被代理了,所以抛出错误了!

    而且console.log(proxy === receiver)返回true

    1. set方法

    set方法可自动接受4个参数:target, propKey, value, receiver,分别表示要代理的目标对象、对象上的属性、属性对应的值以及代理对象。

    该方法用于拦截对象属性操作,像proxy.foo = xxxproxy['foo'] = xxx,例如:

    var person = {
        name: "Alice"
    };
    var proxy = new Proxy(person, {
        set(target, propKey, value, receiver) {
            console.log(`设置 ${target} 的${propKey} 属性,值为${value}`);
            target[propKey] = value
        }
    });
    proxy.name = 'Tom'
    proxy.age = 18
    

    结果如下:

    一篇彻底理解Proxy | 建议收藏

    1. has方法

    has方法接受target, propKey,用于拦截propKey in proxy的操作,返回一个布尔值,表示属性是否存在。如下:

    var person = {
        name: "Alice"
    };
    var proxy = new Proxy(person, {
        has(target, propKey) {
            return propKey in target
        }
    });
    if('name' in proxy){
        console.log(proxy.name)
    }
    

    以上结果返回Alice

    1. deleteProperty方法

    可接收target, propKey,用于拦截delete操作,返回一个布尔值,表示是否删除成功。例如:

    var person = {
        name: "Alice"
    };
    var proxy = new Proxy(person, {
        deleteProperty(target, propKey) {
            return delete target[propKey]
        }
    });
    console.log(delete proxy.name)//ture
    console.log(proxy.name)//undefined
    
    1. ownKeys方法

    可接收target,用于拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环等类似操作,返回一个数组,表示对象所拥有的keys,如下:

    var person = {
        name: "Alice"
    };
    var proxy = new Proxy(person, {
        ownKeys(target) {
            return Object.getOwnPropertyNames(target)//为了省事
        }
    });
    console.log(Object.getOwnPropertyNames(proxy))
    

    返回["name"]

    1. getOwnPropertyDescriptor方法

    接收target和propKey,用于拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。如下:

    var person = {
        name: "Alice"
    };
    var proxy = new Proxy(person, {
        getOwnPropertyDescriptor(target,propKey){
            return Object.getOwnPropertyDescriptor(target, propKey)
        }
    });
    console.log(Object.getOwnPropertyDescriptor(proxy, 'name'))
    

    结果如下:

    一篇彻底理解Proxy | 建议收藏

    1. defineProperty方法

    接收target, propKey, propDesc,分别表示目标对象、目标对象的属性,以及属性描述配置,用于拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs)的操作,例如:

    var person = {};
    var proxy = new Proxy(person, {
        defineProperty(target,propKey,propKeypropDesc){        
            return Object.defineProperty(target, propKey, propKeypropDesc)        
        }
    });
    console.log(Object.defineProperty(proxy, 'name', {value:'Tom'}))
    console.log(person.name)
    
    1. preventExtensions方法

    可接收target,用于拦截Object.preventExtensions(proxy)操作,补充说明一下preventExtensions的作用是将一个对象变成不可扩展,也就是永远不能再添加新的属性。例如:

    var person = {
        name: "Alice"
    };
    var proxy = new Proxy(person, {
        preventExtensions(target){
            return Object.preventExtensions(target)
        }
    });
    Object.preventExtensions(proxy)
    proxy.age = 11;
    console.log(person)
    

    后面添加的age,并没有成功添加

    一篇彻底理解Proxy | 建议收藏

    1. getPrototypeOf(target)

    在使用Object.getPrototypeOf(proxy)会触发调用,返回一个对象

    var person = {
        name: "Alice"
    };
    var proxy = new Proxy(person, {
        getPrototypeOf(target){
            return Object.getPrototypeOf(target)
        }
    });
    console.log(Object.getPrototypeOf(proxy))
    
    1. isExtensible(target)

    当使用Object.isExtensible(proxy)时会触发调用,返回一个布尔值,表示是否可扩展,如下:

    var person = {
        name: "Alice"
    };
    var proxy = new Proxy(person, {
        isExtensible(target){
            return Object.isExtensible(target)
        }
    });
    console.log(Object.isExtensible(proxy))//true
    
    1. setPrototypeOf(target, proto)

    当调用Object.setPrototypeOf(proxy, proto)会触发该函数调用,例如:

    var person = {
        name: "Alice"
    };
    let proto = {}
    let proxy = new Proxy(person,{
        setPrototypeOf(target,proto){
            console.log(`设置${target}的原型为${proto}`);
            return Object.setPrototypeOf(target,proto)
        }
    });
    Object.setPrototypeOf(proxy,proto)
    console.log(Object.getPrototypeOf(person) === proto)
    

    结果如下

    一篇彻底理解Proxy | 建议收藏

    1. apply(target, object, args)

    接收三个参数target, object, args,分别表示目标对象、调用函数是的this指向以及参数列表,当Proxy实例作为函数调用时触发,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...),如下:

    function f (x,y){ return x + y}
    let proxy = new Proxy(f,{
        apply(target, object, args){
            console.log(`调用了f`);
            return f.call(object,...args)
        }
    })
    console.log(proxy(1,2));
    

    结果如下:

    一篇彻底理解Proxy | 建议收藏

    1. construct(target, args)

    接收target和args,表示目标函数即参数列表,当 Proxy 实例作为构造函数时触发该函数调用,比如new proxy(...args),例如:

    function F(){ }
    let proxy = new Proxy(F,{
        construct(target,  args){
            console.log(`调用了construct`);
            return new target(...args)
        }
    })
    console.log(new proxy())
    

    结果如下:

    一篇彻底理解Proxy | 建议收藏

    以上这些方法都被称为捕获器,这些捕获器分别捕获对象不同的操作行为。

    下面我们通过一个简单例子,来进一步搞清楚目标对象和代理对象之间的关系。

    如下

    var person = {
        name: "Alice"
    };
    var proxy = new Proxy(person, {
        set(target, propKey, value, receiver) {
            console.log(`设置 ${target} 的${propKey} 属性,值为${value}`);
            target[propKey] = value
        }
    });        
    proxy.age = 18
    person.sex = 'female'
    console.log(person,proxy)
    

    结果如图

    一篇彻底理解Proxy | 建议收藏

    通过上述例子,我们可以得出以下4点结论:

    1. 代理对象不等于目标对象,他是目标对象的包装品
    2. 目标对象既可以直接操作,也可以被代理对象操作,且两者相互关联
    3. 如果直接操作目标对象,则会绕过代理定义的各种拦截行为
    4. 如果用了代理,那肯定是希望给对象的操作嵌入我们定义的特殊行为,所以一般就操作代理对象就好

    如果你还模糊,那我来给你看看Proxy真面目。....也没啥好说的,其实就是一个构造函数,并且接受两个参数target和handler,返回代理对象

    function Proxy(target,handler){
        //...
    }
    

    这下彻底理解了Proxy,那我们来看看它的应用吧。由于代理模式是非常典型的编程模式,会在很多地方被应用,我们以Vue3数据响应式系统为例来简单讲讲!

    Vue3定义了一系列的响应式API,比如reactive、ref等等,它们的特点是:当时数据发生变化时,页面会对应更新UI,而底层用的就是Proxy!我以reactive为例

    function reactive(obj) {
        return new Proxy(obj, {
            get(target, key) {
                return target[key]
            },
            set(target, key, val) {
                target[key] = val
                // 这里当数据变化时,更新界面,于是我们考虑到这里需要update方法用户更新
                // 执行updata操作...
            }
        })
    }
    

    可以看到,数据对象obj通过reactive包装成了代理对象,当数据发生变化时,会调用set方法,在更新数据的同时,同时执行一些update的操作

    这就是典型的代理模式应用~

    到这里应该没有什么问题了吧,有问题欢迎下方留言告知,谢谢!

    END~


    起源地下载网 » 一篇彻底理解Proxy | 建议收藏

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元