首頁  >  文章  >  web前端  >  Proxy使用案例詳解

Proxy使用案例詳解

php中世界最好的语言
php中世界最好的语言原創
2018-05-25 11:57:542302瀏覽

這次帶給大家Proxy使用案例詳解,Proxy使用的注意事項有哪些,下面就是實戰案例,一起來看一下。

Proxy是什麼

首先,我們要清楚,Proxy是什麼意思,這個單字翻譯過來,就是 代理。 
可以理解為,有一個很火的明星,開通了一個微博帳號,這個帳號非常活躍,回覆粉絲、到處點讚之類的,但可能並不是真的由本人在維護的。 
而是在背後有一個其他人 or 團隊來運營,我們就可以稱他們為代理人,因為他們發表的微博就代表了明星本人的意思。
P.S. 強行舉例子,因為自己不追星,只是猜測可能會有這樣的營運團隊

##這個代入到

JavaScript<a href="http://www.php.cn/wiki/48.html" target="_blank"></a>#當中來,就可以理解為對物件函數的代理操作。

JavaScript中的Proxy

Proxy是ES6中提供的新的API,可以用來定義物件各種基本操作的自訂行為 


(在文件中被稱為traps,我覺得可以理解為一個針對對象各種行為的鉤子)  拿它可以做很多有意思的事情,在我們需要對一些對象的行為進行控制時將變得非常有效。

Proxy的語法

建立一個

Proxy的實例需要傳入兩個參數

  1. target  要被代理的對象,可以是一個objectfunction

  2. handlers對該代理對象的各種操作行為處理

  3. let target = {}
    let handlers = {} // do nothing
    let proxy = new Proxy(target, handlers)
    proxy.a = 123
    console.log(target.a) // 123
在第二個參數為空物件的情況下,基本上可以理解為是對第一個參數所做的一次淺拷貝 


(Proxy必須是淺拷貝,如果是深拷貝則會失去了代理的意義)

#Traps(各種行為的代理)

就像上邊的範例程式碼一樣,如果沒有定義對應的

trap,則不會起任何作用,相當於直接操作了target。 當我們寫了某個
trap以後,在做對應的動作時,就會觸發我們的回呼函數,由我們來控制被代理物件的行為。

最常用的兩個

trap應該就是getset了。 早年
JavaScript有著在定義物件時針對某個屬性進行設定gettersetter

let obj = {
  _age: 18,
  get age ()  {
    return `I'm ${this._age} years old`
  },
  set age (val) {
    this._age = Number(val)
  }
}
console.log(obj.age) // I'm 18 years old
obj.age = 19
console.log(obj.age) // I'm 19 years old
就像這段程式碼描述的一樣,我們設定了一個屬性

_age,然後又設定了一個get ageset age。 接著我們可以直接呼叫
obj.age來取得一個回傳值,也可以將其賦值。 這麼做有幾個缺點:

  1. 針對每個要代理的屬性都要寫對應的

    gettersetter

  2. 必須還要存在一個儲存真實值的

    key(如果我們直接在getter裡邊呼叫this. age則會出現堆疊溢位的情況,因為無論何時呼叫this.age進行取值都會觸發getter

Proxy很好的解決了這兩個問題:

let target = { age: 18, name: 'Niko Bellic' }
let handlers = {
  get (target, property) {
    return `${property}: ${target[property]}`
  },
  set (target, property, value) {
    target[property] = value
  }
}
let proxy = new Proxy(target, handlers)
proxy.age = 19
console.log(target.age, proxy.age)   // 19,          age : 19
console.log(target.name, proxy.name) // Niko Bellic, name: Niko Bellic
我們透過建立

get set兩個trap來統一管理所有的操作,可以看到,在修改proxy的同時,target的內容也被修改,而且我們對proxy的行為進行了一些特殊的處理。 而且我們不需要額外的用一個
key來儲存真實的值,因為我們在trap內部操作的是target對象,而不是proxy物件。

拿Proxy來做些什麼

因為在使用了

Proxy後,物件的行為基本上都是可控的,所以我們可以拿來做一些之前實現起來比較複雜的事情。 在下邊列出了幾個簡單的適用場景。

解决对象属性为undefined的问题

在一些层级比较深的对象属性获取中,如何处理undefined一直是一个痛苦的过程,如果我们用Proxy可以很好的兼容这种情况。

(() => {
  let target = {}
  let handlers = {
    get: (target, property) => {
      target[property] = (property in target) ? target[property] : {}
      if (typeof target[property] === 'object') {
        return new Proxy(target[property], handlers)
      }
      return target[property]
    }
  }
  let proxy = new Proxy(target, handlers)
  console.log('z' in proxy.x.y) // false (其实这一步已经针对`target`创建了一个x.y的属性)
  proxy.x.y.z = 'hello'
  console.log('z' in proxy.x.y) // true
  console.log(target.x.y.z)     // hello
})()

我们代理了get,并在里边进行逻辑处理,如果我们要进行get的值来自一个不存在的key,则我们会在target中创建对应个这个key,然后返回一个针对这个key的代理对象。  
这样就能够保证我们的取值操作一定不会抛出can not get xxx from undefined  
但是这会有一个小缺点,就是如果你确实要判断这个key是否存在只能够通过in操作符来判断,而不能够直接通过get来判断。

普通函数与构造函数的兼容处理

如果我们提供了一个Class对象给其他人,或者说一个ES5版本的构造函数。  
如果没有使用new关键字来调用的话,Class对象会直接抛出异常,而ES5中的构造函数this指向则会变为调用函数时的作用域。  
我们可以使用apply这个trap来兼容这种情况:

class Test {
  constructor (a, b) {
    console.log('constructor', a, b)
  }
}
// Test(1, 2) // throw an error
let proxyClass = new Proxy(Test, {
  apply (target, thisArg, argumentsList) {
    // 如果想要禁止使用非new的方式来调用函数,直接抛出异常即可
    // throw new Error(`Function ${target.name} cannot be invoked without 'new'`)
    return new (target.bind(thisArg, ...argumentsList))()
  }
})
proxyClass(1, 2) // constructor 1 2

我们使用了apply来代理一些行为,在函数调用时会被触发,因为我们明确的知道,代理的是一个Class或构造函数,所以我们直接在apply中使用new关键字来调用被代理的函数。

以及如果我们想要对函数进行限制,禁止使用new关键字来调用,可以用另一个trap:construct

function add (a, b) {
  return a + b
}
let proxy = new Proxy(add, {
  construct (target, argumentsList, newTarget) {
    throw new Error(`Function ${target.name} cannot be invoked with 'new'`)
  }
})
proxy(1, 2)     // 3
new proxy(1, 2) // throw an error

用Proxy来包装fetch

在前端发送请求,我们现在经常用到的应该就是fetch了,一个原生提供的API。
我们可以用Proxy来包装它,使其变得更易用。

let handlers = {
  get (target, property) {
    if (!target.init) {
      // 初始化对象
      ['GET', 'POST'].forEach(method => {
        target[method] = (url, params = {}) => {
          return fetch(url, {
            headers: {
              'content-type': 'application/json'
            },
            mode: 'cors',
            credentials: 'same-origin',
            method,
            ...params
          }).then(response => response.json())
        }
      })
    }
    return target[property]
  }
}
let API = new Proxy({}, handlers)
await API.GET('XXX')
await API.POST('XXX', {
  body: JSON.stringify({name: 1})
})

GETPOST进行了一层封装,可以直接通过.GET这种方式来调用,并设置一些通用的参数。

实现一个简易的断言工具

写过测试的各位童鞋,应该都会知道断言这个东西  
console.assert就是一个断言工具,接受两个参数,如果第一个为false,则会将第二个参数作为Error message抛出。  
我们可以使用Proxy来做一个直接赋值就能实现断言的工具。

let assert = new Proxy({}, {
  set (target, message, value) {
    if (!value) console.error(message)
  }
})
assert['Isn\'t true'] = false      // Error: Isn't true
assert['Less than 18'] = 18 >= 19  // Error: Less than 18

统计函数调用次数

在做服务端时,我们可以用Proxy代理一些函数,来统计一段时间内调用的次数。  
在后期做性能分析时可能会能够用上:

function orginFunction () {}
let proxyFunction = new Proxy(orginFunction, {
  apply (target, thisArg. argumentsList) {
    log(XXX)
    return target.apply(thisArg, argumentsList)
  }
})

全部的traps

这里列出了handlers所有可以定义的行为 (traps)

具體的可以查看MDN-Proxy  
裡邊同樣有一些例子
traps description
#get 取得某個key
#set 設定某個key
has 使用in運算子判斷某個key是否存在
apply 函數調用,僅在代理物件為function時有效
ownKeys 取得目標物件所有的key
construct 函數透過實例化調用,僅在代理物件為function時有效
isExtensible 判斷物件是否可擴展,Object.isExtensible的代理程式
deleteProperty 刪除一個property
#defineProperty 定義一個新的property

##getPrototypeOf

取得原型物件


#setPrototypeOf

設定原型物件

####################### ####preventExtensions######設定物件為不可擴充############getOwnPropertyDescriptor######取得一個自有屬性###(不會去原型鏈查找)### 的屬性描述###############相信看了本文案例你已經掌握了方法,更多精彩請關注php中文網其它相關文章! ######推薦閱讀:#########在HTML中使用JS方法總結###############CSS選擇器使用方法總結#### #####

以上是Proxy使用案例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn