Proxy objects are used to create a Proxy for an object to intercept and customize basic operations (such as property lookup, assignment, enumeration, function calls, and so on). Proxy is used in many libraries and browser frameworks, such as VUe3, which uses Proxy for data responsiveness. This article takes you through the use and limitations of proxies.

Proxy parameters and Description

const target = {}
const handler = {
  get(target, key, recevier) {
    return target[key]
  },
  set(target, key, val, recevier) {
    target[key] = val
    return true}}const proxy = new Proxy(target, handler)
Copy the code

parameter

  • Target Is the object that needs to be wrapped. It can be any variable
  • The Handle proxy configuration is typically an object that uses functions as property valuesCatcher functionTo call these properties

When you operate on proxy, you run the corresponding catcher function if it exists in the handler object, and if it does not, you process target directly. Most operations on objects in JavaScript have internal methods, which are the lowest level of work. For example, [[Get]] is called when data is read and [[Set]] is called when data is written. We cannot call it directly from the method name, and the catcher function in the Proxy configuration intercepts calls to these internal methods.

Internal methods and catcher functions

The following table describes the mapping between internal methods and catcher functions:

Internal methods Catcher function Function parameters Function return value hijacked
[[Get]] get target.property.recevier any Reads the properties
[[Set]] set target.property.value.recevier booleanIndicates whether the operation Write attributes
[[HasProperty]] has target.property boolean The in operator
[[Delete]] deleteProperty target.property booleanIndicates whether the operation The delete operator
[[Call]] apply target.thisArg.argumentsList any A function call
[[Construct]] construct target.argumentsList.newTarget object New operator
[[GetPrototypeOf]] getPrototypeOf target objectornull Object.getPrototypeOf
[[SetPrototypeOf]] setPrototypeOf target.prototype booleanIndicates whether the operation Object.setPrototypeOf
[[IsExtensible]] isExtensible target boolean Object.isExtensible
[[PreventExtensions]] preventExtensions target booleanIndicates whether the operation Object.preventExtensions
[[DefineOwnProperty]] defineProperty target.property.descriptor booleanIndicates whether the operation Object.defineProperty

ObjectdefineProperties
[[GetOwnProperty]] getOwnPropertyDescriptor target.property objectundefined Object.getOwnPropertyDescriptor

for… in

Object.keys/values/entries
[[OwnPropertyKeys]] ownKeys target An enumerableobject. Object.getOwnPropertyNames

Object.getOwnPropertySymbols

for… in

Object.keys/values/entries

Description of trap function parameters

  • targetIs the target object that is passed as the first argumentnew Proxy
  • propertyThe name of the property to be set or retrieved orSymbol
  • valueThe new property value to set
  • recevierThe object that was originally called. Is usuallyproxyItself, but may be invoked indirectly in other ways (so not necessarilyproxyAs such, I’ll explain later)
  • thisArgThe context object when called
  • argumentsListThe array of arguments when called
  • newTargetThe constructor originally called
  • descriptorThe descriptor of the property to be defined or modified

Here we focus on recevier and newTarget, the other parameters of the catcher function.

Transform the console log

Using console.log in a Proxy’s catcher function can easily create an endless loop because if console.log(POxy) reads the Proxy’s properties, it may go through the catcher function, which then console.log(poxy) again. To facilitate debugging, I’ve modified the following console.log.

// Check whether it is printed by checking whether it is in log mode
let isLog = false
{
  const logs = []
  const platformLog = console.log
  const resolvedPromise = Promise.resolve()
  // Whether logs are currently being executed
  let isFlush = false

  console.log = (. args) = > {
    logs.push(args)
    isFlush || logFlush()
  }

  
  const logFlush = () = > {
    isFlush = true
    resolvedPromise.then(() = > {
      isLog = true
      logs.forEach(args= > {
        platformLog.apply(platformLog, args)
      })
      logs.length = 0
      isLog = false
      isFlush = false}}})Copy the code

Recevier and this on the proxied method

Recevier is the object that was originally called, what does that mean? It’s whoever called the Proxy that went through the catcher function. See below for an example

const animal = {
  _name: 'animals'.getName() {
    isLog || console.log(this)
    return this._name
  }
}

const animalProxy = new Proxy(animal, {
  get(target, key, recevier) {
    isLog || console.log(recevier)
    return target[key]
  }
})

// The object originally called is animalProxy,
// The recevier parameter of the get catcher function is the animalProxy
// This is the animalProxy
animalProxy.getName()

const pig = {
  // Inherit animalProxy from the prototype
  __proto__: animalProxy,
  test: animalProxy
}

// There is no name in pig
// The object originally called is PIG
// The recevier argument to the get catcher function is pig
// This is the proxied pig
pig.getName()

// The initial object called is Pig. test, which is the animalProxy
// The recevier parameter of the get catcher function is the animalProxy
// This is the animalProxy
pig.test.getName()
Copy the code

The above example clearly shows that recevier is the caller when the proxy object is called, which is the same mechanism as this in function.

NewTarget parameters

Class object support was added in ES6 to the original constructor called newTarget, which is primarily an object that recognizes inheritance relationships in a class, as shown in the example below

const factoryClassProxy = type= > 
  new Proxy(type, {
    construct(target, args, newTarget) {
      console.log(newTarget)

      const instance = newtarget(... args)if(target.prototype ! == newTarget.prototype) {Object.setPrototypeOf(instance, newTarget.prototype)
      }
      return instance
    }
  })

const AnimalProxy = factoryClassProxy(
  class {
    name = 'animals'
    getName() {
      return this.name
    }
  }
)

const PigProxy = factoryClassProxy(
  class Animal extends AnimalProxy {
    name = 'pig'})const PetsPigProxy = factoryClassProxy(
  class Pig extends PigProxy {
    name = 'Pet Pig'})// The construct catcher function fires three times,
// The NewTarget is PetsPigProxy for the first time
// The second time PigProxy triggers NewTarget as PetsPigProxy
// The third time AnimalProxy triggers NewTarget to PetsPigProxy
const pig = new PetsPigProxy()
Copy the code

When a new Type() is called externally, both the parent class and the current construct catcher function’s newTarget parameter point to that Type. You’ll notice that the inner implementation of the Construct catcher function at the top has added a set prototype, which involves the new keyword, so let’s talk about the inner workings of new and super when the user uses the new keyword

  • Create a prototype pointing to the current momentclassObject of the prototype
  • The currentclassfunction-buildingthisPoint to the object created in the previous step and execute
  • When faced withsuper()The function call will be currentthisPoint to the parent constructor and execute
  • If the parent class also existssuper()Function call, perform the previous step again
  • super()If no object is returned, it is returned by defaultthis
  • willsuper()The result of execution is set to that of the current constructorthis
  • The currentclassThe constructor completes and returns by default if no object is returnedthis

So when we don’t specify a prototype, the code above will lose all of the subclass’s prototypes and always point to the top parent class, because super also calls the Construct catcher function, where new creates a prototype that points to the object of the current class’s prototype, and on return changes the subclass’s this to the object it just created Object, so a subclass’s this prototype has only its parent’s. The new. Target object is the parent constructor. If you use console.log, you will find that this instance is an Animal object.

const factoryClassProxy = (() = > {
  const instanceStack = []
  const getInstance = () = > instanceStack[instanceStack.length - 1]
  const removeInstance = () = > instanceStack.pop()
  const setInstance = instance= > {
    instanceStack.push(instance)
    return instance
  }

  return type= > 
    new Proxy(type, {
      construct(target, args, newTarget) {
        const isCurrent = target.prototype === newTarget.prototype
        const currentInsetance = isCurrent
          ? setInstance(Object.create(target.prototype))
          : getInstance()

        if (currentInsetance) {
          target.apply(currentInsetance, args)
          removeInstance()
          return currentInsetance
        } else {
          return newtarget(... args) } } }) })();Copy the code

But unfortunately, the class constructor is limited. During class construction, new.target checks whether the current call is made through the new keyword. Class only allows the new keyword to be called, directly through the function call error, so this method is also invalid, I have not found other methods, if you have a method to trouble the comment section post thank you. One of the more recent objects to address this problem is Reflect and we’ll talk about it in general later on.

Proxy objects with private attributes

Class attributes are public by default and can be detected or modified by external classes. In the ES2020 lab draft, the ability to define private class fields was added, written with a # prefix. We will change the above example to class form, first change the Animal object as follows:

class Animal {
  #name = 'animals'
  getName() {
    isLog || console.log(this)
    return this.#name
  }
}

const animal = new Animal()
const animalProxy = new Proxy(animal, {
  get(target, key, recevier) {
    return target[key]
  },
  set(target, key, value, recevier) {
    target[key] = value
  }
})
// TypeError: Cannot read private member #name from an object whose class did not declare it
console.log(animalProxy.getName())
Copy the code

Animalproxy.getname () : animalProxy.getName() : animalProxy () : animalProxy () : animalProxy () Error received, we need to change this to the correct point, as follows:

const animalProxy = new Proxy(animal, {
  get(target, key, recevier) {
    const value = target[key]
    return typeof value === 'function'
      ? value.bind(target)
      : value
  },
  ...
})
/ / animals
console.log(animalProxy.getName())
Copy the code

A proxy has built-in objects with internal slots

There are many built-in objects such as Map, Set, Date, and Promise that use internal slots. Internal slots are similar to the private properties of the above objects and are not allowed to be accessed externally, so when the proxy does not handle them directly, they can cause errors such as:


const factoryInstanceProxy = instance= > 
  new Proxy(instance, {
    get(target, prop) {
      return target[prop]
    },
    set(target, prop, val) {
      target[prop] = val
      return true}})// TypeError: Method Map.prototype.set called on incompatible receiver #<Map>
const map = factoryInstanceProxy(new Map())
map.set(0.1)

// TypeError: this is not a Date object.
const date = factoryInstanceProxy(new Date())
date.getTime()

// Method Promise.prototype.then called on incompatible receiver #<Promise>
const resolvePromise = factoryInstanceProxy(Promise.resolve())
resolvePromise.then()

// TypeError: Method Set.prototype.add called on incompatible receiver #<Set>
const set = factoryInstanceProxy(new Set())
set.add(1)
Copy the code

Internal slots only allow internal access. Proxy does not have this internal slot property, so it can only fail. You can handle this problem the same way you would with an object with a private property. Bind this to function so that internal slots are found correctly when accessing it.

const factoryInstanceProxy = instance= > 
  new Proxy(instance, {
    get(target, prop) {
      const value = target[key]
      return typeof value === 'function'
        ? value.bind(target)
        : value
    }
    ...
  })
Copy the code

OwnKeys catcher function

Some of you may be wondering why the ownKeys catcher should be singled out. It’s easy to see, isn’t it? Don’t worry, we look down, there is still a point of knowledge need to pay attention to. Let’s look at an example:

const user = {
  name: 'bill'.age: 29.sex: 'male'.// _ Prefix is recognized as private attribute and cannot be accessed
  _code: '44xxxxxxxxxxxx17'
}

const isPrivateProp = prop= > prop.startsWith('_')

const userProxy = new Proxy(user, {
  get(target, prop) {
    return! isPrivateProp(prop) ? target[prop] :null
  },
  set(target, prop, val) {
    if(! isPrivateProp(prop)) { target[prop] = valreturn true
    } else {
      return false}},ownKeys(target) {
    return Object.keys(target)
      .filter(prop= >! prop.startsWith('_'))}})console.log(Object.keys(userProxy))

Copy the code

Good everything is expected to run, this time the product came to add a demand, according to the first two automatic identification of the current user’s province, head melon son a turn, directly in the agency to identify the addition is not good, we will change the code

// Attach attribute list
const provinceProp = 'province'
// Attach attribute list
const attach = [ provinceProp ]

// Get the province method from code
const getProvinceByCode = (() = > {
  const codeMapProvince = {
    '44': 'Guangdong'. }return code= > codeMapProvince[code.substr(0.2)]
})()


const userProxy = new Proxy(user, {
  get(target, prop) {
    let value = null

    switch(prop) {
      case provinceProp: 
        value = getProvinceByCode(target._code)
        break;
      default:
        value = isPrivateProp(prop) ? null : target[prop]
    }
    
    return value
  },
  set(target, prop, val) {
    if (isPrivateProp(prop) || attach.includes(prop)) {
      return false
    } else {
      target[prop] = val
      return true}},ownKeys(target) {
    return Object.keys(target)
      .filter(prop= >! prop.startsWith('_'))
      .concat(attach)
  }
})


console.log(userProxy.province)       / / in guangdong province
console.log(Object.keys(userProxy))   // ["name", "age", "sex"]
Copy the code

You can see that direct access to the additional properties of the proxy is normal, but using object. keys to get the property list can only list the properties of the user Object. This is because Object.keys calls the inner method [[GetOwnProperty]] for each property to retrieve its property descriptor, returning a non-symbol key that itself has an Enumerable key. Enumerable is obtained from the property descriptor of the object. In the example above, province has no property descriptor and therefore no Enumerable. Therefore, province is ignored. [[GetOwnProperty]] is retrieved by the getOwnPropertyDescriptor descriptor, so we can add the descriptor descriptor to it.


const userProxy = new Proxy(user, {
  ...
  getOwnPropertyDescriptor(target, prop) {
    return attach.includes(prop)
      ? { configurable: true.enumerable: true }
      : Object.getOwnPropertyDescriptor(target, prop)
  }
})

// ["name", "age", "sex", "province"]
console.log(Object.keys(userProxy))
Copy the code

Note that the control system must be true, as the Proxy prevents you from Proxy the descriptor of this property if it is not configurable.

Reflect

We used the imperfect Construct catcher handler in the newTarget argument above, creating subclasses multiple times to new the superclass object, and eventually passing out the top superclass object as well, as seen in console.log. In fact, Proxy has a perfect partner, and that’s Reflect. Reflect is a built-in object that provides methods to intercept JavaScript operations. These methods are the same as those of the Proxy catcher. All Proxy captures have corresponding Reflect methods, and Reflect is not a function object, so it is not constructible. We can use them like Math, such as reflect.get (…). Reflect and Object methods also have a lot of overlap, in addition to their one-to-one correspondence with Proxy traps.

The following table describes the mappings between Reflect and capture functions. For most of the corresponding Reflect parameters and capture functions, refer to internal methods and capture functions

Catcher function Reflect corresponding method The method parameters Method return value
get Reflect.get() target.property.recevier The value of the attribute
set Reflect.set() target.property.value.recevier BooleanValue indicates whether the property was set successfully.
has Reflect.has() target.property BooleanType indicates whether the property exists.
deleteProperty Reflect.deleteProperty() target.property BooleanValue indicates whether the property was successfully deleted
apply Reflect.apply() target.thisArg.argumentsList The call is completed with the specified parameters andthisValue is the result returned after a given function.
construct Reflect.construct() target.argumentsList.newTarget target(ifnewTargetIf yes, isnewTarget) for the prototype, calltargetFunction is the constructor,argumentListThe object instance for which the parameters are initialized.
getPrototypeOf Reflect.getPrototypeOf() target The prototype of a given object. Returns if the given object has no inherited propertiesnull.
setPrototypeOf Reflect.setPrototypeOf() target.prototype BooleanValue indicates whether the stereotype has been set up successfully.
isExtensible Reflect.isExtensible() target BooleanValue indicates whether the object is extensible
preventExtensions Reflect.preventExtensions() target BooleanValue indicates whether the target object was successfully set to unextensible
getOwnPropertyDescriptor Reflect.getOwnPropertyDescriptor() target.property Returns the property descriptor if the property exists in the given target object; Otherwise, returnundefined.
ownKeys Reflect.ownKeys() target Consists of the target object’s own property keyArray.

Reflect’s recevier parameter

When the reflect. get or reflect. set methods are used, the optional recevier argument is passed in, and the getter or setter can be used to change this to use, We can’t change the this of the getter or setter without reflecting because they’re not a method. See the example below:

const user = {
  _name: 'The Little Master of Dining'.get name() {
    return this._name
  },
  set name(newName) {
    this._name = newName
    return true}}const target = {
  _name: 'bill'
}
const name = Reflect.get(user, 'name', target)
// bill
console.log(name)

Reflect.set(user, 'name'.'lzb', target)
// { _name: 'lzb' }
console.log(target)
// {_name: 'dinnermaster'}
console.log(user)
Copy the code

Reflect’s newTarget parameter

An optional newTarget argument is passed when reflect. construct is an implementation of a new Class method, For example, new User(‘bill’) is the same as reflect.construct (User, [‘ Bill ‘]), while newTarget can change the prototype of the created Object. In ES5 this can be done with Object.create, but with a slight difference. In the constructor new.target you can see the current constructor. If implemented using ES5, this object is undefined because it is not created by new

function OneClass() {
  console.log(new.target)
  this.name = 'one';
}

function OtherClass() {
  console.log(new.target)
  this.name = 'other';
}

// Create an object:
var obj1 = Reflect.construct(OneClass, args, OtherClass);
// Prints function OtherClass

// It is equivalent to the above method:
var obj2 = Object.create(OtherClass.prototype);
OneClass.apply(obj2, args);
/ / print undefined

console.log(obj1.name); // 'one'
console.log(obj2.name); // 'one'

console.log(obj1 instanceof OneClass); // false
console.log(obj2 instanceof OneClass); // false

console.log(obj1 instanceof OtherClass); // true
console.log(obj2 instanceof OtherClass); // true
Copy the code

The construct trap

In the newTarget parameter we implement the imperfect Construct catcher, and by reading Reflect we see a solution that perfectly fits the implementation we want. Reflects. construct not only recognizes new.target, Can also handle the creation of more object problems, let’s modify the implementation, as shown in the following example


const factoryClassProxy = type= > 
  new Proxy(type, {
    construct(target, args, newTarget) {
      return Reflect.construct(... arguments) } })const AnimalProxy = factoryClassProxy(
  class {
    name = 'animals'
    getName() {
      return this.name
    }
  }
)

const PigProxy = factoryClassProxy(
  class Animal extends AnimalProxy {
    name = 'pig'})const PetsPigProxy = factoryClassProxy(
  class Pig extends PigProxy {
    name = 'Pet Pig'})Copy the code

Proxy setter and getter functions

We know what recevier refers to by reading this on recevier and proxied methods. Consider this code

const animal = {
  _name: 'animals'.getName() {
    return this._name
  },
  get name() {
    return this._name
  }
}

const animalProxy = new Proxy(animal, {
  get(target, key, recevier) {
    return target[key]
  }
})


const pig = {
  __proto__: animalProxy,
  _name: 'pig'
}

console.log(pig.name)
console.log(animalProxy.name)
console.log(pig.getName())
console.log(animalProxy.getName())
Copy the code

If you run the code above you’ll see that the print order is animal, animal, pig, animal, and using getName through the method is fine, because the proxy gets the implementation of getName and then accesses it through the current object, so this is whoever is currently calling it, but through the getter call, The method implementation is called when the target[key] is passed, so this is always the target that points to the current proxy. To fix this, you need to fix this through the proxy’s catchers, and recevier is the object that points to the current caller. But unlike member methods, getters can fix this directly with bind, call, and apply, so we’ll use reflect.get instead. And the setter works exactly the same way and I’m not going to talk too much about it, but I’ll refer to the bottom

const animal = {
  _name: 'animals'.getName() {
    return this._name
  },
  get name() {
    return this._name
  }
}

const animalProxy = new Proxy(animal, {
  get(target, key, recevier) {
    return Reflect.get(... arguments) } })const pig = {
  __proto__: animalProxy,
  _name: 'pig'
}

console.log(pig.name)
console.log(animalProxy.name)
console.log(pig.getName())
console.log(animalProxy.getName())
Copy the code

Proxy and Reflect

Because Reflect and Proxy traps have corresponding methods, in most cases we can use Reflect’s API directly to combine Proxy operations. We can focus on what the Proxy is supposed to do, such as the code below

new Proxy(animal, {
  get(target, key, recevier) {
    // Specific business.return Reflect.get(... arguments) },set(target, property, value, recevier) {
    // Specific business.return Reflect.set(... arguments) },has(target, property) {
    // Specific business.return Reflect.has(... arguments) } ... })Copy the code

Proxy. Revocable Revokes the Proxy

If there is such a business, we are making a shopping mall system. The product requires tracking the specific traces of the operation of the user’s goods, such as expanding the details of the goods, clicking on the video of the goods, etc. In order to decouplex the specific business, it is a good choice to use Proxy, so we wrote the following code

// track-commodity.js
// Specific trace code
const track = {
  // Play the video
  introduceVideo(){... },// Get the product details
  details(){... }}export const processingCommodity = (commodity) = > 
  new Proxy(commodity, {
    get(target, key) {
      if (track[key]) {
        track[key]()
      }
      return Reflect.get(... arguments) } })// main.js
// Specific business use
commodity = processingCommodity(commodity)
Copy the code

We have written above, good is very perfect, but a pile of late don’t want her whereabouts was tracking, customer response products require us to change again, the user can asked not tracking in the set, can’t restart the refresh the page directly, also can’t let the cache object reloading of goods this time, if allowed new commodity agent is very simple just add a judgment, The new API proxy. revocable(target, handler) method can be used to create a revocable Proxy object. The method takes the same arguments as new Proxy(target, handler), passing the first argument to the object to be propped up and the second argument to the catcher. Revoke returns an object whose proxy returns target’s proxy object. Revoke returns a method to revoke the proxy, as follows

const { proxy, revoke } = Proxy.revocable(target, handler)
Copy the code

Next, let’s improve our trace code as follows

// track-commodity.js.// Why use WeakMap instead of Map, because it does not prevent garbage collection.
// If the commodity broker has no reference other than WeakMap, it will be cleared from memory
const revokes = new WeakMap(a)export const processingCommodity = (commodity) = > {
  const { proxy, revoke } = Proxy.revocable(commodity, {
    get(target, key) {
      if (track[key]) {
        track[key]()
      }
      return Reflect.get(... arguments) } }) revokes.set(proxy, revoke)return proxy
}
export const unProcessingCommodity = (commodity) = > {
  const revoke = revokes.get(commodity)
  if (revoke) {
    revoke()
  } else {
    return commodity
  }
}

// main.js
// Check whether traceable is enabled
const changeCommodity = () = > 
  commodity = setting.isTrack
    ? processingCommodity(commodity)
    : unProcessingCommodity(commodity)

/ / initialization
changeCommodity()
// Listen Settings changed
bus.on('changeTrackSetting', changeCommodity)
Copy the code

When REVOKE () revokes a proxy, we do not return the commodity object before the proxy. What should we do?

Get the proxied object by proxy

Yes, I think there are two better schemes. Let me introduce them respectively. 1: Proxy. Revocable revocable example: since we have added Proxy and REVOKE WeakMap objects, why not add a Proxy and target object

.const commoditys = new WeakMap(a)const revokes = new WeakMap(a)const processingCommodity = (commodity) = > {
  const { proxy, revoke } = Proxy.revocable(commodity, {
    get(target, key) {
      if (track[key]) {
        track[key]()
      }
      return Reflect.get(... arguments) } }) commoditys.set(proxy, commodity) revokes.set(proxy, revoke)return proxy
}
const unProcessingCommodity = (commodity) = > {
  const revoke = revokes.get(commodity)
  if (revoke) {
    revoke()
    return commoditys.get(commodity)
  } else {
    return commodity
  }
}
Copy the code

2: Different from the first solution, the second solution is to directly add logic processing to the agent’s GET catcher. Since we can intercept GET, we can add some built-in logic of our track-commodity.js, which is to return the original object of the agent when we get a key. Of course, this key should not conflict with the key of commodity used in business, and only internal use should be ensured, so we need to use Symbol. As long as we do not export, users will not be able to get this key. Please refer to the code below

.const toRaw = Symbol('getCommodity')
const revokes = new WeakMap(a)const processingCommodity = (commodity) = > {
  const { proxy, revoke } = Proxy.revocable(commodity, {
    get(target, key) {
      if (key === toRaw) {
        return target
      }
      if (track[key]) {
        track[key]()
      }
      return Reflect.get(... arguments) } }) revokes.set(proxy, revoke)return proxy
}
const unProcessingCommodity = (commodity) = > {
  const revoke = revokes.get(commodity)
  if (revoke) {
    // Be careful to use it before revoking the proxy
    const commodity = commodity[toRaw]
    revoke()
    return commodity
  } else {
    return commodity
  }
}
Copy the code

Limitations of Proxy

Proxies provide a unique way to adjust the behavior of existing objects, but they are not perfect and have limitations.

Proxy private property

We in the agent with the object of the private property is introduced how to avoid this is the problem of the current agent cannot access private properties, but it also has certain problem, because an object must not only access to the private property in the method, if you have access to their private property, it has a certain problem handling, such as the code below


class Animal {
  #name = 'animals'
  feature = 'They generally feed on organic matter, they can feel, they can move, they can move on their own. Something that moves or is capable of moving. '
  getName() {
    return this.#name
  }
  getFeature() {
    return this.feature
  }
}

const animal = new Animal()

const animalProxy = new Proxy(animal, {
  get(target, key, recevier) {
    const value = Reflect.get(... arguments)return typeof value === 'function'
      ? value.bind(target)
      : value
  }
})

const pig = {
  __proto__: animalProxy,
  feature: 'Pigs are vertebrates, mammals, domestic animals, and ancient omnivorous mammals, mainly divided into domestic pigs and wild pigs.'
}

/ / animals
console.log(pig.getName())
// They generally feed on organic matter and can feel, move and move autonomously. Something that moves or is capable of moving
console.log(pig.getFeature())
Copy the code

Since any function will bind to the animal that is currently propired to, this will be the animal that pig inherited from the prototype animalProxy. Also, this means that we need to be familiar with the API of the propired object. By identifying if this is bound to private property access, you need to know the proxied object’s API. Pig.getname () will cause TypeError if you bind the pig.getName() attribute without the proxy. Bind the pig.getName() attribute without the proxy. Once you know to use the private properties API, you can simply identify whether the currently accessed object is a proxy for the original object. The specific processing code is shown below

const targets = new WeakMap(a)const privateMethods = ['getName']
const animalProxy = new Proxy(animal, {
  get(target, key, recevier) {
    const isPrivate = privateMethods.includes(key) 
    if(isPrivate && targets.get(recevier) ! == target) {throw `${key}Methods can only be called by themselves
    }
    
    const value = Reflect.get(... arguments)if (isPrivate && typeof value === 'function') {
      return value.bind(target)
    } else {
      return value
    }
  }
})
targets.set(animalProxy, animal)

const pig = {
  __proto__: animalProxy,
  feature: 'Pigs are vertebrates, mammals, domestic animals, and ancient omnivorous mammals, mainly divided into domestic pigs and wild pigs.'
}

/ / animals
console.log(animalProxy.getName())
// TypeError
// console.log(pig.getName())
// Pigs are vertebrates, mammals, domestic animals and ancient omnivorous mammals, mainly divided into domestic pigs and wild pigs
console.log(pig.getFeature())
Copy the code

target ! == Proxy

The agent must be a different object from the original object, so when we use the original object for management, the agent cannot manage properly. For example, the following agent does a centralized management of all user instances:

const users = new Set(a)class User {
  constructor() {
    users.add(this)}}const user = new User()
// true
console.log(users.has(user))
const userProxy = new Proxy(user, {})
// false
users.has(userProxy)
Copy the code

So in the development of this kind of problem need special attention, in the development of an object to do the proxy, all the management of the proxy also need another layer of proxy, the original object to the original object, proxy to proxy, such as the above example can be improved through the following code


const users = new Set(a)class User {
  constructor() {
    users.add(this)}}// Get the original object
const getRaw = (target) = > target[toRaw] ? target[toRaw] : target
const toRaw = Symbol('toRaw')
const usersProxy = new Proxy(users, {
  get(target, prop) {
    // Note that Set size is an attribute, not a method.
    // Reflect. Get (... The arguments)
    let value = prop === 'size' 
      ? target[prop]
      : Reflect.get(... arguments) value =typeof value === 'function'
      ? value.bind(target)
      : value

    // This is just two API examples. When adding or judging must be added by the original object,
    // Because the management of the original object can only put the original object
    if (prop === 'has' || prop === 'add') {
      return (target, ... args) = >value(getRaw(target), ... args) }else {
      return value
    }
  }
})

const factoryUserProxy = (user) = > {
  const userProxy = new Proxy(user, {
    get(target, prop, recevier) {
      if (prop === toRaw) {
        return target
      } else {
        return Reflect.get(... arguments) } } })return userProxy
}


const user = new User()
const userProxy = factoryUserProxy(user)
// true
console.log(users.has(user))
// true
console.log(usersProxy.has(user))
// true
console.log(usersProxy.has(userProxy))
// true
console.log(users.size)
// true
console.log(usersProxy.size)
// It will be converted to add the original object, and the original object already exists, so it cannot be added
usersProxy.add(userProxy)
/ / 1
console.log(users.size)
/ / 1
console.log(usersProxy.size)
Copy the code

That’s all for Proxy. This article introduces most of the problems and usage of Proxy.