Design Mode|JavaScript Realization of Proxy Mode (Part 2)

Design Mode|JavaScript Realization of Proxy Mode (Part 2)

This is the 27th day that I participated in the Wenwen Challenge. For details of the event, please view: Wenwen Challenge

Article guide

The agency model has a lot of space, divided into three chapters, the first, the middle, and the next.

JavaScript implements proxy mode (part 1)

Content summary: Mainly introduce what is the proxy mode, the simple realization of the proxy mode

Link: juejin.cn/post/697850...

JavaScript Realization of Proxy Mode (Part 2)

Content summary: Analyze the various uses of the agency model

Link: juejin.cn/post/697903...

JavaScript implements proxy mode (part 2)

Content summary: Implement a more convenient proxy mode based on ES6 Proxy API

Link: juejin.cn/post/697924...

Proxy mode in ES6

Use ES6 Proxy API to implement virtual proxy

Picture preload

Virtual agents delay the creation of some expensive objects until they are really needed.

The previous code shows how ES5 implements image preloading. After ES6 introduces the Proxy API, it can also be used to achieve the same image preloading requirements.

/* * Proxy function for picture preloading * @param img node * @param loading image * @param The picture that really needs to be loaded */ const createImgProxy = ( img, loadingImg, realImg ) => { //Whether the loading is complete, the default is false let hasLoaded = false ; //Create a virtual img node const virtualImg = new Image(); //src value The path of the image that really needs to be loaded virtualImg.src = realImg; virtualImg.onload = () => { //When the real image is loaded, set the src attribute of the incoming img node instance to the image that really needs to be loaded Reflect .set(img, 'src' , realImg) ; //The status of the loading is changed to true, indicating that the loading is complete hasLoaded = true ; } return new Proxy (img, { /** * The get() catcher will be called in the operation of obtaining the attribute value. The corresponding reflection API method is Reflect.get(). * target: target object. * property: the string key property on the referenced target object. * receiver: proxy object or an object that inherits the proxy object. */ get ( obj, prop ) { //If src exists, and it has not been loaded if (prop === 'src' && !hasLoaded) { //Return the image in the loading state return loadingImg; } //Otherwise return normal parameters return Reflect .get(...arguments); } }); }; //Use the preloaded image proxy function const IMG = new new Image (); const imgProxy = createImgProxy (IMG, 'https://img.zcool.cn/community/01deed576019060000018c1bd2352d.gif' , 'HTTPS://img.zcool .cn/community/01b620577ccc8b0000012e7ede064f.jpg@1280w_1l_2o_100sh.jpg' ); document .body.appendChild(imgProxy); Copy code

Function throttling

The function throttling here is equivalent to the combined HTTP request in the ES5 example. The purpose of function throttling is to control the frequency of function calls. In a period of time, a certain function is executed only once.

Suppose there is such a simple function:

const Handler = () => Console .log ( 'the Do something ...' ); Document .addEventListener ( 'the Click' , Handler); duplicated code

Then use ES6 Proxy API to create a throttling factory function:

/* * Proxy function for function throttling * fn needs to be implemented by the agent to achieve function throttling function * rate delay time, how long can it be executed every delay, in milliseconds */ const createThrottleProxy = ( fn, rate ) => { //The time of the last click let lastClick = Date .now()-rate; return new Proxy (fn, { /**apply() The catcher will call the function It is called in time. The corresponding reflection API method is Reflect.apply(). *target: target object. *thisArg: the this parameter when calling the function. *argumentsList: the parameter list when calling the function */ apply ( target, thisArg, args ) { //If the time interval between the current time and the last click time exceeds the delay time required by the input parameter, the throttled function will be executed if ( Date .now()-lastClick >= rate) { fn(args); //execute the throttled function lastClick = Date .now(); //update the last click time } } }); }; Copy code

At this time, you can use the proxy function of function throttling to throttling the specified function.

const Handler = () => Console .log ( 'the Do something ...' ); const handlerProxy = createThrottleProxy (Handler, 1000 ); Document .addEventListener ( 'the Click' , handlerProxy); duplicated code

Use ES6 Proxy API to implement caching proxy-Fibonacci sequence caching optimization

The cache proxy can cache the calculation results of some expensive methods. When the function is called again, if the parameters are the same, the results in the cache can be directly returned without the need to perform the calculation again.

Suppose there is such an unoptimized Fibonacci calculation function.

//Fibonacci number calculation function const getFib = ( number ) => { if (number <= 2 ) { return 1 ; } else { return getFib(number- 1 ) + getFib(number- 2 ); } } Copy code

Using the proxy mode and ES6's Proxy API, you can create such a cache proxy factory function:

//The factory function of the cache proxy const getCacheProxy = ( fn, cache = new Map () ) => { return new Proxy (fn, { /**apply() The catcher will be called when the function is called. Corresponding reflection The API method is Reflect.apply(). *target: target object. *thisArg: the this parameter when calling the function. *argumentsList: the parameter list when calling the function */ apply ( target, thisArg, args ) { //Convert the parameters passed in during execution to a string const argsString = args.join( '' ); //If the cache Map exists, it means there is a cache if ( cache.has (argsString)) { //if the cache, the cache data returned directly Console .log ( `output $ {args} cached results: $ {cache.get (argsString)} ` ); return cache.get(argsString); } //If there is no cache, execute the function calculation result const result = fn(...args); //Then cache the result in the cache cache.set(argsString, result); //return result return result; } }) } Copy code

At this time, the calculation function of the Fibonacci sequence with the cache proxy can be used like this:

const getFibProxy = getCacheProxy(getFib); getFibProxy ( 40 ); //102,334,155 getFibProxy ( 40 ); //output buffer 40 Results: 102,334,155 copy the code

Implement a simple form validator using ES6 Proxy API

This is a very simple requirement. Assuming that we have such a form object and corresponding validation rules, in addition to using the strategy mode we have learned before, we can also use the proxy mode to achieve the requirement of form validation:

//form object const userForm = { account : '' , password : '' , } //verification method const validators = { account ( value ) { //account is only allowed in Chinese const re = /^[\u4e00-\u9fa5]+$/ ; return { valid : re.test(value), error : ' "account" is only allowed to be Chinese' } }, password ( value ) { //The length of password should be greater than 6 characters return { valid : value.length >= 6 , error : '"password "should more than 6 character' } } } Copy code

Using the proxy mode and ES6's Proxy API, you can create such a form authentication proxy function:

/** * Proxy function for form validation * @param target form parameters that need to be verified * @param validators form validation rules */ const getValidateProxy = ( target, validators ) => { return new Proxy (target, { //Cache validation rules let _validators: validators, /** * The set() catcher will be called in the operation of setting the attribute value. The corresponding reflection API method is Reflect.set(). * target: target object. * property: the string key property on the referenced target object. * value: The value to be assigned to the attribute. * receiver: Receive the object initially assigned. */ set ( target, prop, value ) { //If the value of the corresponding key is empty, the prompt key value must not be empty if (value === '' ) { console .error( `" ${prop} "is not allowed to be empty` ); return target[prop] = false ; } //If it is not empty, verify the corresponding form rule const validResult = this ._validators[prop](value); //If the verification passes, use the reflection API to return the default value if (validResult.valid) { return Reflect . set(target, prop, value); } else { //Otherwise, the corresponding error message will be prompted console .error( ` ${validResult.error} ` ); //Set the result of the corresponding value to false return target[prop] = false ; } } }) } Copy code

At this point, it can be used like this to complete the basic form verification requirements:

const userFormProxy = getValidateProxy(userForm, validators); = userFormProxy.account '123' ; //"Account" IS BE only allowed to Chinese userFormProxy.password = 'of He' ; //"password" Character. 6 Should More Within last duplicated code

Use ES6 Proxy API to implement private properties

In the past, it was very difficult to implement private properties in JavaScript, but

Public and Private Instance Fields Proposal
'S proposal has entered
Stage 3
Stage, which means we can use it in the future
#
Syntax to represent private properties and methods.

class User { ///Declare and assign #id = 'xyz' ;//This is a private property constructor ( name ) { this .name = name; } getUserId () { return this .#id; } } 1 copy the code

However, it is still very meaningful to study the implementation of private properties before the private property syntax. One way is to use ES6 Proxy API to hijack related properties and prevent them from returning private properties.

Create such a private property hijacking function, in this example, with

_
The attributes at the beginning will be considered private attributes:

/** * Private attribute hijacking function * @param obj The object that needs to be hijacked * @param needs to filter the function of private attributes **/ function getPrivateProps ( obj, filterFunc ) { return new Proxy (obj, { /** * The get() catcher will be called in the operation of obtaining the attribute value. The corresponding reflection API method is Reflect.get(). * target: target object. * property: the string key property on the referenced target object. * receiver: proxy object or an object that inherits the proxy object. */ get ( obj, prop ) { if (!filterFunc(prop)) { let value = Reflect .get(obj, prop); //If it is a method, point this to modify the original object if ( typeof value === ' function' ) { value = value.bind(obj); } return value; } }, /** * The set() catcher will be called in the operation of setting the attribute value. The corresponding reflection API method is Reflect.set(). * target: target object. * property: the string key property on the referenced target object. * value: The value to be assigned to the attribute. * receiver: Receive the object initially assigned. */ set ( obj, prop, value ) { if (filterFunc(prop)) { throw new TypeError ( `Can't set property " ${prop} "` ); } return Reflect .set(obj, prop, value); }, /** * The has() catcher will be called in the in operator. The corresponding reflection API method is Reflect.has(). * target: target object. * property: the string key property on the referenced target object. */ has ( obj, prop ) { return filterFunc(prop)? false : Reflect .has(obj, prop); }, /** * ownKeys() catcher will be called in Object.keys() and similar methods. The corresponding reflection API method is Reflect.ownKeys(). * target: target object. */ ownKeys ( obj ) { return Reflect .ownKeys(obj).filter( prop => !filterFunc(prop)); }, /** * The getOwnPropertyDescriptor() catcher will be called in Object.getOwnPropertyDescriptor(). The corresponding reflection API method is Reflect.getOwnPropertyDescriptor(). * target: target object. * property: the string key property on the referenced target object. */ getOwnPropertyDescriptor ( obj, prop ) { return filterFunc(prop)? undefined : Reflect .getOwnPropertyDescriptor(obj, prop); } }); } //The function of filtering private properties, the properties starting with _ will be considered as private properties function propFilter ( prop ) { return prop.indexOf( '_' ) === 0 ; } Copy code

It can be used at this time

getPrivateProps
, To achieve private properties:

const myObj = { public : 'hello' , _private : 'secret' , method : function () { console .log( this ._private); } }, myProxy = getPrivateProps(myObj, propFilter); console .log( JSON .stringify(myProxy)); //{"public":"hello"} console .log(myProxy._private); //undefined console .log( ' _private ' in myProxy); //false console .log( Object .keys(myProxy)); //["public", "method"] for ( let prop in myProxy) { console .log(prop);} //public method myProxy._private = 1 ; //Uncaught TypeError: Can not set property " _private" copy the code

Application of agent mode in actual projects

  1. Interceptor

    The way of using proxy mode to proxy object access is generally called interceptor .

    The idea of interceptor is very much applied in actual combat, for example, we often use it in projects

    Axios
    Instance to make HTTP requests, using interceptors
    interceptor
    You can check the data before the request in advance (
    request
    Request) and the data returned by the server (
    response
    ) Perform some preprocessing, such as:

    1. request
      The setting of the request header, and the setting of the cookie information;
    2. Preprocessing of authority information, such as authority verification operations or Token verification;
    3. Formatting of data format, such as binding to components
      Date
      Before requesting the data of the type, perform some serialization operations with a well-defined format;
    4. Format preprocessing of empty fields, and perform some filtering operations according to the backend;
    5. response
      Some general error handling, such as using the Message control to throw errors;

    In addition to HTTP-related interceptors, there are also route jump interceptors, which can perform some operations such as preprocessing of route jumps.

  2. Data responsiveness of the front-end framework

    Many front-end frameworks or state management frameworks now use the ones introduced above

    Object.defineProperty
    with
    Proxy
    To achieve data responsiveness, such as Vue, Vue 2.x uses the former, and Vue 3.x uses the latter.

    Passed in Vue 2.x

    Object.defineProperty
    To hijack each attribute
    setter/getter
    When the data changes, the message is published to the subscriber through the publish-subscribe mode, and the corresponding listener callback is triggered to realize the responsiveness of the data, that is, the two-way binding of the data to the view.

    Why does Vue 2.x to 3.x change from

    Object.defineProperty
    Switch to
    Proxy
    Well, because of some limitations of the former, the following defects are caused:

    1. It is not possible to monitor an item of the array directly set by index, for example:

      vm.items[indexOfItem] = newValue
      , So Vue2.x needs to use
      Vue.$set()
      Solve responsive problems.

    2. Cannot monitor the modification of the length of the array, for example:

      vm.items.length = newLength
      , Also need to use
      Vue.$set()
      Solve responsive problems.

    3. Cannot monitor ES6

      Set
      ,
      WeakSet
      ,
      Map
      ,
      WeakMap
      The change;

    4. Can't listen

      Class
      Type of data;

    5. Cannot monitor the new addition or deletion of object properties;

    In addition, there are performance differences. For these reasons, Vue 3.x is switched to

    Proxy
    To implement data monitoring. Of course, the disadvantage is that it is not friendly to IE users, and compatibility-sensitive scenarios require some trade-offs.

  3. Caching proxy

    In the previous content of Fibonacci sequence cache optimization , Fibonacci sequence cache optimization is to use the idea of caching proxy to cache the results of complex calculations, and directly return the previously cached calculation results when the parameters are consistent next time.

  4. Protection agent and virtual agent

    1. Protection agent : When an object may receive a large number of requests, a protection agent can be set to filter the requests through some conditional judgments;

      For example, in the previous example, Xiao Ming sent flowers to young lady through his girlfriend, and the girlfriend recognized that Xiao Ming did not recognize other people as protection agents.

    2. Virtual agent : There may be some expensive operations in the program. At this time, you can set up a virtual agent, and the virtual agent will perform the operation when it is suitable.

      For example, Xiao Ming hopes that the flowers sent by his girlfriends to the little sister will be delayed until the little sister is in a good mood, or the pre-loading of pictures, or even the current mainstream front-end skeleton screen placeholder technology, all belong to the category of virtual agents.

  1. Forward proxy and reverse proxy

    1. Forward proxy: The general access process is that the client directly sends a request to the target server and obtains the content. After using the forward proxy, the client sends a request to the proxy server instead, and specifies the target server (original server), and then the proxy server Communicate with the original server, forward the requested and obtained content, and then return it to the client. The forward proxy hides the real client, sends and receives requests for the client, and makes the real client invisible to the server;
    2. Reverse proxy: Compared with the general access process, after using a reverse proxy, the server that directly receives the request is the proxy server, and then forwards the request to the server that actually processes the internal network, and the result is returned to the client. The reverse proxy hides the real server, sends and receives requests for the server, and makes the real server invisible to the client.

    The biggest difference between them is that the object of the forward proxy is the client , the object of the reverse proxy is the server , the forward proxy hides the user, and the reverse proxy hides the server.

    Forward proxy

    First set up a proxy server of your own

    • The user sends a request to his proxy server
    • Own proxy server sends the request to the server
    • The server returns the data to its own proxy server
    • Your own proxy server then returns the data to the user

    Reverse proxy

    • The user sends a request to the server (the user is actually accessing the reverse proxy server, but the user does not know)
    • The reverse proxy server sends the request to the real server
    • The real server returns the data to the reverse proxy server
    • The reverse proxy server then returns the data to the user

    In the actual situation, sometimes accessing github will be slow or even unable to open. We need to use a server closer to the github server as a transit station to facilitate our access to GitHub, where the proxy object is the client, the github server The received ip address request is only the ip request of the transfer station server, the real client ip is hidden, so the forward proxy is used here.

    Reverse proxy is mostly used on the server side. For example, it is one of the common solutions to deal with browser cross-domain problems. CDN and network equipment load balancing can also be seen as reverse proxy. The object being proxied here is the server. For the user, he does not know the real server information behind the reverse proxy server, so the reverse dialing hides the server.

Advantages and disadvantages of reverse proxy

The main advantages of the agent model are:

  1. The proxy object can act as an intermediary and protect the target object between the visitor and the target object ;

  2. The proxy object can extend the function of the target object ;

  3. The proxy model can separate the visitor from the target object, which reduces the coupling of the system to a certain extent. If we want to moderately expand some functions of the target object, we can modify the proxy object to comply with the open-closed principle;

The disadvantage of the proxy mode is mainly to increase the complexity of the system. It is necessary to consider whether the current scenario really needs to introduce the proxy mode.

The difference between proxy mode and other modes

Many other modes, such as state mode, strategy mode, and visitor mode actually use proxy mode.

Proxy mode and adapter mode

Both the proxy mode and the adapter mode provide indirect access to another object. The difference between them:

  • Adapter mode: mainly used to solve the problem of mismatch between interfaces, usually to provide a different interface for the adapted object;
  • Proxy mode: provide indirect access to the target object, as well as the expansion of the target object's function, generally provide the same interface as the target object;

Agent mode and decorator mode

The decorator mode is similar to the proxy mode in its implementation, both execute some logic before or after accessing the target object, but the purpose and function are different:

  • Decorator mode: The purpose is to conveniently add functions to the target object, that is, to add functions dynamically;
  • Proxy mode: The main purpose is to control the access of other visitors to the target object;

Reference

[CUG-GZ] Advanced Front-end Knowledge-Agent Mode

www.yuque.com/cuggz/feplu...

Agent mode of front-end design mode

juejin.cn/post/684490...

Comic: What is the "agent mode"?

mp.weixin.qq.com/s/O8_A2Ms9M...

JavaScript design patterns and development practices

www.ituring.com.cn/book/1632

Re-understanding JavaScript design patterns from ES6 (5): proxy mode and proxy

segmentfault.com/a/119000001...

Use JavaScript native Proxy to optimize the application

juejin.cn/post/684490...