Let’s take a dive at two constructs that were introduced in the JavaScript ES6 specification:
让我们深入研究一下JavaScript ES6规范中引入的两个构造:
A solid understanding of these constructs will prove handy when working with JavaScript at a relatively low level. In this article, we will go over certain use cases where these constructs fit right in and will definitely save you several keystrokes.
当在相对较低的水平上使用JavaScript时,对这些构造有扎实的了解将非常方便。 在本文中,我们将介绍一些适合这些构造的用例,它们肯定会为您节省几次击键。
A Proxy, in simple terms, is an object that controls access to another object. According to the MDN docs:
简单来说,代理服务器是控制对另一个对象的访问的对象。 根据MDN文档 :
The Proxy object is used to define custom behaviour for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
Proxy对象用于定义基本操作的自定义行为(例如,属性查找,赋值,枚举,函数调用等)。
This description roughly translates into the idea that we can intercept fundamental operations (e.g function invocation, property lookup, assignment, enumeration, etc) on objects and functions and perform our own custom operations during run-time! Awesome, yeah?
此描述大致转化为一种思想,我们可以拦截对对象和函数的基本操作(例如,函数调用,属性查找,赋值,枚举等),并在运行时执行我们自己的自定义操作! 太好了,是吗?
Before we proceed, there are three key terms associated with the Proxy object:
在继续之前,有三个与代理对象相关的关键术语:
When used correctly, the proxy object will powerfully complement its target object.
如果使用正确,代理对象将强大地补充其目标对象。
Creating a Proxy
创建代理
We can create a JavaScript Proxy using the Proxy constructor — new Proxy().
The Proxy constructor takes in two objects as parameters. The first is the target object the proxy virtualizes, and the second is the handler object which contains a set of methods called “traps.” These traps will govern property access to the target object:
我们可以使用Proxy构造函数new Proxy().
创建一个JavaScript Proxy new Proxy().
Proxy构造函数接受两个对象作为参数。 第一个是代理虚拟化的目标对象,第二个是处理程序对象,其中包含一组称为“陷阱”的方法。 这些陷阱将控制对目标对象的属性访问:
const proxy = new Proxy(targetObject, handler);
We can create a simple Proxy object by passing a target object and an empty handler to the Proxy constructor:
我们可以通过将目标对象和空处理程序传递给Proxy构造函数来创建一个简单的Proxy对象:
const targetObj = {name: 'Target'};
const proxy = new Proxy(targetObj, {});
proxy.name; // returns 'Target'
The Proxy object we defined above currently does nothing to the target object. It just passes the request for the “name” property to the target object. In order to define one or more custom behaviours on the proxy for its target, we will need to declare one or more handlers.
我们上面定义的Proxy对象目前对目标对象没有任何作用。 它只是将对“ name”属性的请求传递给目标对象。 为了在代理服务器上为其目标定义一个或多个自定义行为,我们将需要声明一个或多个处理程序。
Learn more about traps and handlers.
了解有关陷阱和处理程序的更多信息。
One of such handlers is the get
trap handler. This example below intercepts “getter” calls to properties on the target object:
此类处理程序之一是get
trap处理程序。 下面的示例截取对目标对象的属性的“ getter”调用:
const data = {
firtName: 'Bryan',
lastName: 'John'
};
const handler = {
get(target, prop) {
return prop in target ? target[prop] : 'This property doesn’t exist, sorry';
}
};
const proxy = new Proxy(data, handler);
console.log(proxy.firstName); // Returns 'Bryan'
console.log(proxy.age); // Returns 'No such property'
In the code above, we have the handler
object which contains a get
trap. The get
trap intercepts the requests to access the target object, made by the proxy object, and returns the requested property if it is available or “This property doesn’t exist, sorry” if it's not.
在上面的代码中,我们具有包含get
陷阱的handler
对象。 get
陷阱拦截由代理对象发出的访问目标对象的请求,并返回所请求的属性(如果可用),或者返回“此属性不存在,对不起”(如果没有)。
If we want to intercept calls to set a property on an object, we will need to use the set
trap.
如果我们要拦截调用以设置对象的属性,则需要使用set
陷阱。
Let’s look at a more useful example. We will use the set
trap to check if the actualPay
property on an object is set. If this property exists, we will deduct 3% from the amount paid as the transaction fee and assign the new value to the actualPay
property:
让我们看一个更有用的例子。 我们将使用set
陷阱检查对象上的actualPay
属性是否已设置。 如果存在此属性,我们将从已支付的金额中扣除3%作为交易费,并将新值分配给actualPay
属性:
const transaction = {};
const handler = {
set(target, prop, value) {
if(prop === 'actualPay' && typeof value === "number" && value > 0) {
value = value * 0.97;
}
target[prop] = value;
}
};
const proxy = new Proxy(transaction, handler);
proxy.actualPay = 1000;
console.log(proxy.actualPay); //Returns '970'
A full list of proxy traps and their sample usages can be found here in the MDN docs.
可以在MDN文档中找到代理陷阱的完整列表及其示例用法。
Example Use Cases
示例用例
A benefit with proxies is that you don’t need to know or define the properties beforehand. This is in contrast to the ES5 getters/setters which requires the availability of the properties beforehand:
代理的好处是您不需要事先知道或定义属性。 这与ES5获取器/设置器相反,后者要求事先提供属性:
const data = {
_firstName: 'John',
_lastName: 'Doe',
get firstName() {
console.log('getting the firstname: ', this._firstName);
},
get lastName() {
console.log('getting the lastname: ', this._lastName);
},
};
data.firstName; //logs -> getting the firstname: John
data.lastName; //logs -> getting the firstname: Doe
In the example above, we defined getters
for the firstname and lastname properties. However, if we add a new property — age
— we will need to define a new getter — get age()
— on the data
object to access that property:
在上面的示例中,我们为firstname和lastname属性定义了getters
。 但是,如果我们添加新属性age
我们将需要在data
对象上定义一个新的getter- get age()
以访问该属性:
data.age = 23; // adds a new property -- age
console.log(data.age); // logs 23 but doesn't automatically have a getter
With Proxies, we can simply register a get
trap for all requests to access the properties of the object, including those that are weren’t declared at author time:
使用代理,我们可以简单地为所有访问对象属性的请求注册一个get
陷阱,包括那些在作者创建时未声明的请求:
const proxyObj = new Proxy({
firstName: 'John',
lastName: 'Doe',
}, {
get(targetObj, property) {
console.log(`getting the ${property} property: ${targetObj[property]}`);
}
});
proxyObj.firstName; //Returns -> getting the firstName: John
proxyObj.lastName;// Returns -> getting the lastName property: Doe
proxyObj.age = 23;
console.log(proxyObj.age);// Returns -> getting the age property: 23
In the example above, we are able to log the values of all the properties on the object using a Proxy.
在上面的示例中,我们能够使用Proxy记录对象上所有属性的值。
There are many more use cases where Proxies will suffice, for instance, we could create a custom object validator that checks an object’s properties to make sure that only intended types can be set as values.
有很多用例可以满足代理要求,例如,我们可以创建一个自定义对象验证器,以检查对象的属性,以确保只能将预期类型设置为值。
We could also create a custom authentication system that ensures that the client is authorized to perform operations on the target. The possibilities are endless!
我们还可以创建一个自定义的身份验证系统,以确保授权客户端对目标执行操作。 可能性是无止境!
A few more possible uses cases for Proxies are:
代理的其他一些可能的用例是:
When a function is invoked, the JavaScript engine starts to execute the code from the top of the function to the bottom. This model of execution is called run to completion and it’s nice when you want your function to run just as it is defined.
调用函数时,JavaScript引擎开始从函数顶部到底部执行代码。 这种执行模型称为“ 运行到完成” ,当您希望函数按定义运行时,它非常有用。
However, there are times where you’d wish to pause the function’s execution, run some other snippet of code, then continue right where you left off. Generators are the answer to this wish!
但是,有时候您希望暂停函数的执行,运行其他代码段,然后从上次中断的地方继续执行。 发电机就是这个愿望的答案!
What is a Generator?
什么是发电机?
In simple terms, a generator is a function that can stop midway and then continue from where it stopped.
简而言之,生成器是一种可以在中途停止然后从停止位置继续运行的功能。
Let’s consider this analogy - Imagine that you are working on your Todo list for the day and your boss politely asks you to immediately work on something else. I bet your next actions, summarized in five steps, would be:
让我们考虑一个类比-想象一下,您正在一天的待办事项列表中工作,而老板礼貌地要求您立即进行其他工作。 我敢打赌,归纳为以下五个步骤,您的下一个操作将是:
You just behaved like a generator function! I mean, except for bullet point 2 and 5. A generator function can pause its execution, run something else, and remember where it paused on its execution then continue from there.
您的行为就像一个生成器函数! 我的意思是,除了第2点和第5点。生成器函数可以暂停其执行,运行其他操作,并记住执行时在何处暂停,然后从那里继续。
Generator functions are ES6 constructs that can simplify the asynchronous control flow of JavaScript applications while implicitly maintaining their internal state. When a generator function is called, it first creates an iterator object called a generator before executing the function’s code. This generator object, according to the ECMAScript specification:
生成器函数是ES6构造,可以简化JavaScript应用程序的异步控制流,同时隐式维护其内部状态。 调用生成器函数时,在执行该函数的代码之前,它首先创建一个称为生成器的迭代器对象。 根据ECMAScript规范 ,此生成器对象:
Is an instance of a generator function and conforms to both the Iterator and Iterable interfaces.
是生成器函数的实例,并同时符合Iterator和Iterable接口。
The Iterable protocol provides a lot of flexibility in specifying how to iterate over values in an object using the for..of
construct. The Iterator protocol defines a standard way for values in an object to be iterated over. The iteration can be achieved using the .next()
method as we will see later in this article.
Iterable协议在指定如何使用for..of
构造遍历对象中的值时提供了很大的灵活性。 迭代器协议为对象中的值定义了一种标准方法。 可以使用.next()
方法来实现迭代,这将在本文的后面看到。
Info: The async/await construct is based on generators. You can learn more here.
信息:异步/等待构造基于生成器。 您可以在此处了解更多信息 。
Working with Generators
使用发电机
Generator functions are created using the function*
syntax. Its values are generated by calling the next()
method and execution can be paused using the yield
keyword. Each time the function is called, it returns a new Generator object which can be iterated over once:
生成器函数是使用function*
语法创建的。 它的值是通过调用next()
方法生成的,可以使用yield
关键字暂停执行。 每次调用该函数时,它都会返回一个新的Generator对象,该对象可以迭代一次:
function* getCurrency() {
console.log('the generator function has started');
const currencies = ['NGN', 'USD', 'EUR', 'GBP', 'CAD'];
for (const currency of currencies) {
yield currency;
}
console.log('the generator function has ended');
}
const iterator = getCurrency();
console.log(iterator.next());
// logs -> 'the generator function has started'
// {value: 'NGN', done: false}
console.log(iterator.next());
// {value: 'USD', done: false}
console.log(iterator.next());
//{value: 'EUR', done: false}
console.log(iterator.next());
//{value: 'GBP', done: false}
console.log(iterator.next());
//{value: 'CAD', done: false}
console.log(iterator.next());
// the generator function has ended
// {value: undefined, done: true}
In the example above, the generator function getCurrency()
sends out data using the yield
keyword. Calling the next()
method on the generator object returns it’s yield
value and a Boolean — done
— which becomes true
after all the values of the generator function have been iterated over, as seen in the last iteration in the example above.
在上面的示例中,生成器函数getCurrency()
使用yield
关键字发送数据。 在生成器对象上调用next()
方法将返回它的yield
值和一个布尔值— done
—在迭代了生成器函数的所有值之后变为true
,如上例中的上一次迭代所示。
We can also use the next()
method to pass down data to the generator function:
我们还可以使用next()
方法将数据传递给生成器函数:
function* displayCurrrency() {
console.log(`currency info to be sent into the generator function: ${yield}`);
}
const iterator = displaycurrency();
iterator.next(); //this starts the generator function
iterator.next('US dollars');
// logs -> currency info to be sent into the generator function: US dollars
In the example above, next('US dollars')
sends data into the generator function and “replaces” the yield
keyword with 'US dollars.'
在上面的示例中, next('US dollars')
将数据发送到生成器函数中,并将yield
关键字“替换”为“ USD”。
There’s a lot more to Generators and we have only had a scrape at the surface here. You can learn more about Generators here.
生成器还有更多功能,这里我们只刮了一点。 您可以在此处了解有关生成器的更多信息。
We’ve had a look at JavaScript Proxies and how they can control and customize the behaviour of JavaScript objects. We also saw that a generator object can send data in and out of its generator function.
我们已经了解了JavaScript代理以及它们如何控制和自定义JavaScript对象的行为。 我们还看到,生成器对象可以将数据发送进出其生成器函数。
When used correctly, these ES6 constructs can greatly improve code architecture and design.
如果使用正确,这些ES6构造可以大大改善代码体系结构和设计。
翻译自: https://scotch.io/tutorials/proxies-and-generators-in-javascript