js 闭包函数 构造函数

In this article, we will talk about closures and curried functions and we'll play around with these concepts to build cool abstractions. I want to show the idea behind each concept, but also make it very practical with examples and refactored code to make it more fun.

在本文中,我们将讨论闭包和咖喱函数,我们将围绕这些概念来构建酷抽象。 我想展示每个概念背后的想法,但也通过示例和重构代码使其变得非常实用,以使其更加有趣。

关闭 (Closures)

Closures are a common topic in JavaScript, and it's the one we'll start with. According to MDN:

在JavaScript中,闭包是一个常见的话题,这是我们要开始的话题。 根据MDN:

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).

闭包是捆绑在一起(封闭)的函数及其周围状态(词汇环境)的组合。

Basically, every time a function is created, a closure is also created and it gives access to the state (variables, constants, functions, and so on). The surrounding state is known as the lexical environment.

基本上,每次创建函数时,也会创建一个闭包,并且闭包可以访问状态(变量,常量,函数等)。 周围状态称为lexical environment

Let's show a simple example:

让我们展示一个简单的例子:

function makeFunction() {const name = 'TK';function displayName() {console.log(name);}return displayName;
};

What do we have here?

我们有什么在这里?

  • Our main function is called makeFunction

    我们的主要功能称为makeFunction

  • A constant named name is assigned with the string, 'TK'

    一个名为name的常量分配有字符串'TK'

  • The definition of the displayName function (which just logs the name constant)

    displayName函数的定义(仅记录name常量)

  • And finally, makeFunction returns the displayName function

    最后, makeFunction返回displayName函数

This is just a definition of a function. When we call the makeFunction, it will create everything within it: a constant and another function, in this case.

这只是一个函数的定义。 当我们调用makeFunction ,它将在其中创建所有内容:一个常量和另一个函数(在这种情况下)。

As we know, when the displayName function is created, the closure is also created and it makes the function aware of its environment, in this case, the name constant. This is why we can console.log the name constant without breaking anything. The function knows about the lexical environment.

众所周知,当创建displayName函数时,也会创建闭包,并且闭包使函数知道其环境(在这种情况下为name常量)。 这就是为什么我们可以在不破坏任何内容的情况下console.log name常量的原因。 该函数知道词法环境。

const myFunction = makeFunction();
myFunction(); // TK

Great! It works as expected. The return value of makeFunction is a function that we store in the myFunction constant. When we call myFunction, it displays TK.

大! 它按预期工作。 makeFunction的返回值是一个存储在myFunction常量中的函数。 当我们调用myFunction ,它将显示TK

We can also make it work as an arrow function:

我们还可以使其用作箭头功能:

const makeFunction = () => {const name = 'TK';return () => console.log(name);
};

But what if we want to pass the name and display it? Simple! Use a parameter:

但是,如果我们想传递名称并显示该名称怎么办? 简单! 使用参数:

const makeFunction = (name = 'TK') => {return () => console.log(name);
};// Or as a one-liner
const makeFunction = (name = 'TK') => () => console.log(name);

Now we can play with the name:

现在我们可以使用以下名称:

const myFunction = makeFunction();
myFunction(); // TKconst myFunction = makeFunction('Dan');
myFunction(); // Dan

myFunction is aware of the argument that's passed in, and whether it's a default or dynamic value.

myFunction知道传入的参数,以及它是默认值还是动态值。

The closure makes sure the created function is not only aware of the constants/variables, but also other functions within the function.

闭包确保所创建的函数不仅知道常量/变量,而且还知道函数内的其他函数。

So this also works:

所以这也适用:

const makeFunction = (name = 'TK') => {const display = () => console.log(name);return () => display();
};const myFunction = makeFunction();
myFunction(); // TK

The returned function knows about the display function and is able to call it.

返回的函数知道display函数并可以调用它。

One powerful technique is to use closures to build "private" functions and variables.

一种强大的技术是使用闭包来构建“私有”函数和变量。

Months ago I was learning data structures (again!) and wanted to implement each one. But I was always using the object oriented approach. As a functional programming enthusiast, I wanted to build all the data structures following FP principles (pure functions, immutability, referential transparency, etc.).

几个月前,我再次学习数据结构,并想要实现每个结构。 但是我一直在使用面向对象的方法。 作为一名编程程序员,我想遵循FP原则(纯函数,不变性,参照透明性等)构建所有数据结构。

The first data structure I was learning was the Stack. It is pretty simple. The main API is:

我正在学习的第一个数据结构是堆栈。 这很简单。 主要的API是:

  • push: add an item to the first place of the stack

    push :将项目添加到堆栈的第一位

  • pop: remove the first item from the stack

    pop :从堆栈中删除第一项

  • peek: get the first item from the stack

    peek :从堆栈中获取第一个项目

  • isEmpty: verify if the stack is empty

    isEmpty :验证堆栈是否为空

  • size: get the number of items the stack has

    size :获取堆栈中的项目数

We could clearly create a simple function to each "method" and pass the stack data to it. It could then use/transform the data and return it.

我们显然可以为每个“方法”创建一个简单的函数,并将堆栈数据传递给它。 然后,它可以使用/转换数据并返回它。

But we can also create a stack with private data and only expose the API methods. Let's do this!

但是我们也可以使用私有数据创建一个堆栈,并且只公开API方法。 我们开工吧!

const buildStack = () => {let items = [];const push = (item) => items = [item, ...items];const pop = () => items = items.slice(1);const peek = () => items[0];const isEmpty = () => !items.length;const size = () => items.length;return {push,pop,peek,isEmpty,size,};
};

Because we created the items stack inside our buildStack function, it is "private". It can be accessed only within the function. In this case, only push, pop, and so one could touch the data. This is exactly what we're looking for.

因为我们在buildStack函数中创建了items堆栈,所以它是“私有的”。 只能在功能内访问。 在这种情况下,只有pushpop等等,因此可以触摸数据。 这正是我们在寻找的东西。

And how do we use it? Like this:

以及我们如何使用它? 像这样:

const stack = buildStack();stack.isEmpty(); // truestack.push(1); // [1]
stack.push(2); // [2, 1]
stack.push(3); // [3, 2, 1]
stack.push(4); // [4, 3, 2, 1]
stack.push(5); // [5, 4, 3, 2, 1]stack.peek(); // 5
stack.size(); // 5
stack.isEmpty(); // falsestack.pop(); // [4, 3, 2, 1]
stack.pop(); // [3, 2, 1]
stack.pop(); // [2, 1]
stack.pop(); // [1]stack.isEmpty(); // false
stack.peek(); // 1
stack.pop(); // []
stack.isEmpty(); // true
stack.size(); // 0

So, when the stack is created, all the functions are aware of the items data. But outside the function, we can't access this data. It's private. We just modify the data by using the stack's builtin API.

因此,在创建堆栈时,所有功能都知道items数据。 但是在函数之外,我们无法访问此数据。 是私人的 我们只是通过使用堆栈的内置API来修改数据。

咖喱 (Curry)

"Currying is the process of taking a function with multiple arguments and turning it into a sequence of functions each with only a single argument."

“ Currying是将具有多个参数的函数转换为只有一个参数的函数序列的过程。”

- Frontend Interview

- 前端面试

So imagine you have a function with multiple arguments: f(a, b, c). Using currying, we achieve a function f(a) that returns a function g(b) that returns a function h(c).

因此,假设您有一个带有多个参数的函数: f(a, b, c) 。 使用currying,我们实现了一个函数f(a) ,该函数返回一个函数g(b) ,该函数返回一个函数h(c)

Basically: f(a, b, c) —> f(a) => g(b) => h(c)

基本上是: f(a, b, c) —> f(a) => g(b) => h(c)

Let's build a simple example that adds two numbers. But first, without currying:

让我们构建一个简单的示例,将两个数字相加。 但首先,无需进行粗略介绍:

const add = (x, y) => x + y;
add(1, 2); // 3

Great! Super simple! Here we have a function with two arguments. To transform it into a curried function we need a function that receives x and returns a function that receives y and returns the sum of both values.

大! 超级简单! 在这里,我们有一个带有两个参数的函数。 要将其转换为咖喱函数,我们需要一个接收x并返回一个接收y并返回两个值之和的函数。

const add = (x) => {function addY(y) {return x + y;}return addY;
};

We can refactor addY into a anonymous arrow function:

我们可以将addY重构为匿名箭头函数:

const add = (x) => {return (y) => {return x + y;}
};

Or simplify it by building one liner arrow functions:

或者通过构建一个划线箭头功能来简化它:

const add = (x) => (y) => x + y;

These three different curried functions have the same behavior: build a sequence of functions with only one argument.

这三个不同的curried函数具有相同的行为:仅使用一个参数构建一系列函数。

How can we use it?

我们如何使用它?

add(10)(20); // 30

At first, it can look a bit strange, but there's a logic behind it. add(10) returns a function. And we call this function with the 20 value.

乍一看,它可能看起来有些奇怪,但是背后有逻辑。 add(10)返回一个函数。 我们用20值调用此函数。

This is the same as:

这与以下内容相同:

const addTen = add(10);
addTen(20); // 30

And this is interesting. We can generate specialized functions by calling the first function. Imagine we want an increment function. We can generate it from our add function by passing 1 as the value.

这很有趣。 我们可以通过调用第一个函数来生成专门的函数。 想象我们想要一个increment函数。 我们可以通过将1作为值从add函数生成它。

const increment = add(1);
increment(9); // 10

When I was implementing Lazy Cypress, an npm library to record user behavior on a form page and generate Cypress testing code, I wanted to build a function to generate this string input[data-testid="123"]. So I had the element (input), the attribute (data-testid), and the value (123). Interpolating this string in JavaScript would look like this: ${element}[${attribute}="${value}"].

当我实现Lazy Cypress (一个npm库,用于在表单页面上记录用户行为并生成Cypress测试代码)时,我想构建一个函数来生成此字符串input[data-testid="123"] 。 因此,我有了元素( input ),属性( data-testid )和值( 123 )。 在JavaScript中插值该字符串应如下所示: ${element}[${attribute}="${value}"]

My first implementation was to receive these three values as parameters and return the interpolated string above:

我的第一个实现是接收这三个值作为参数,并返回上面的插值字符串:

const buildSelector = (element, attribute, value) =>`${element}[${attribute}="${value}"]`;buildSelector('input', 'data-testid', 123); // input[data-testid="123"]

And it was great. I achieved what I was looking for.

这很棒。 我达到了我想要的。

But at the same time, I wanted to build a more idiomatic function. Something where I could write "Get element X with attribute Y and value Z". So if we break this phrase into three steps:

但是同时,我想构建一个更加惯用的功能。 我可以在上面写“ G et element X with attribute Y and value Z ”的东西。 因此,如果我们将此短语分为三个步骤:

  • "get an element X": get(x)

    获取元素X ”: get(x)

  • "with attribute Y": withAttribute(y)

    具有属性Y ”: withAttribute(y)

  • "and value Z": andValue(z)

    和值Z ”: andValue(z)

We can transform buildSelector(x, y, z) into get(x)withAttribute(y)andValue(z) by using the currying concept.

我们可以使用currying概念将buildSelector(x, y, z)转换为get(x) withAttribute(y) andValue(z)

const get = (element) => {return {withAttribute: (attribute) => {return {andValue: (value) => `${element}[${attribute}="${value}"]`,}}};
};

Here we use a different idea: returning an object with function as key-value. Then we can achieve this syntax: get(x).withAttribute(y).andValue(z).

在这里,我们使用了一个不同的想法:返回一个具有函数作为键值的对象。 然后,我们可以实现以下语法: get(x).withAttribute(y).andValue(z)

And for each returned object, we have the next function and argument.

对于每个返回的对象,我们都有下一个函数和参数。

Refactoring time! Remove the return statements:

重构时间! 删除return语句:

const get = (element) => ({withAttribute: (attribute) => ({andValue: (value) => `${element}[${attribute}="${value}"]`,}),
});

I think it looks prettier. And here's how we use it:

我认为它看起来更漂亮。 这是我们的使用方式:

const selector = get('input').withAttribute('data-testid').andValue(123);selector; // input[data-testid="123"]

The andValue function knows about the element and attribute values because it is aware of the lexical environment like with closures that we talked about before.

andValue函数知道elementattribute值,因为它知道像我们前面讨论的闭包一样的词法环境。

We can also implement functions using "partial currying" by separating the first argument from the rest for example.

我们还可以通过将第一个参数与其余参数分开来使用“部分咖喱”实现函数。

After doing web development for a long time, I am really familiar with the event listener Web API. Here's how to use it:

经过很长时间的Web开发,我真的很熟悉事件监听器Web API 。 使用方法如下:

const log = () => console.log('clicked');
button.addEventListener('click', log);

I wanted to create an abstraction to build specialized event listeners and use them by passing the element and a callback handler.

我想创建一个抽象来构建专门的事件侦听器,并通过传递元素和回调处理程序来使用它们。

const buildEventListener = (event) => (element, handler) => element.addEventListener(event, handler);

This way I can create different specialized event listeners and use them as functions.

这样,我可以创建不同的专用事件侦听器并将其用作函数。

const onClick = buildEventListener('click');
onClick(button, log);const onHover = buildEventListener('hover');
onHover(link, log);

With all these concepts, I could create an SQL query using JavaScript syntax. I wanted to query JSON data like this:

通过所有这些概念,我可以使用JavaScript语法创建一个SQL查询。 我想这样查询JSON数据:

const json = {"users": [{"id": 1,"name": "TK","age": 25,"email": "tk@mail.com"},{"id": 2,"name": "Kaio","age": 11,"email": "kaio@mail.com"},{"id": 3,"name": "Daniel","age": 28,"email": "dani@mail.com"}]
}

So I built a simple engine to handle this implementation:

因此,我构建了一个简单的引擎来处理此实现:

const startEngine = (json) => (attributes) => ({ from: from(json, attributes) });const buildAttributes = (node) => (acc, attribute) => ({ ...acc, [attribute]: node[attribute] });const executeQuery = (attributes, attribute, value) => (resultList, node) =>node[attribute] === value? [...resultList, attributes.reduce(buildAttributes(node), {})]: resultList;const where = (json, attributes) => (attribute, value) =>json.reduce(executeQuery(attributes, attribute, value), []);const from = (json, attributes) => (node) => ({ where: where(json[node], attributes) });

With this implementation, we can start the engine with the JSON data:

通过此实现,我们可以使用JSON数据启动引擎:

const select = startEngine(json);

And use it like a SQL query:

并像SQL查询一样使用它:

select(['id', 'name']).from('users').where('id', 1);result; // [{ id: 1, name: 'TK' }]

That's it for today. I could go on and on showing you a lot of different examples of abstractions, but I'll let you play with these concepts.

今天就这样。 我可以继续向您展示许多不同的抽象示例,但是我将让您使用这些概念。

You can other articles like this on my blog.

您可以像这样的其他文章 在我的博客上 。

My Twitter and Github.

我的Twitter和Github 。

资源资源 (Resources)

  • Blog post source code

    博客文章源代码

  • Closures | MDN Web Docs

    封闭| MDN网络文档

  • Currying | Fun Fun Function

    咖喱 趣味功能

  • Learn React by building an App

    通过构建应用来学习React

翻译自: https://www.freecodecamp.org/news/playing-around-with-closures-currying-and-cool-abstractions/

js 闭包函数 构造函数

js 闭包函数 构造函数_JavaScript中的闭包,库里函数和酷抽象相关推荐

  1. pandas使用replace函数替换dataframe中的值:replace函数对dataframe中的多个值进行替换、即一次性同时对多个值进行替换操作

    pandas使用replace函数替换dataframe中的值:replace函数对dataframe中的多个值进行替换.即一次性同时对多个值进行替换操作 目录

  2. pandas使用replace函数替换dataframe中的值:replace函数对dataframe中指定数据列的值进行替换、替换具体数据列的相关值

    pandas使用replace函数替换dataframe中的值:replace函数对dataframe中指定数据列的值进行替换.替换具体数据列的相关值 目录

  3. c语言常用数学函数大全查询,C语言数学函数 C语言中全部可用的数学函数有哪些?...

    导航:网站首页 > C语言数学函数 C语言中全部可用的数学函数有哪些? C语言数学函数 C语言中全部可用的数学函数有哪些? 相关问题: 匿名网友: /*--------------------- ...

  4. Python中numpy.linalg库常用函数

    Python中numpy.linalg库常用函数 numpy.linalg Python中numpy.linalg库常用函数 简单记录所遇到的numpy库内置函数 矩阵与向量积 ①np.linalg. ...

  5. python闭包应用实例_Python中的闭包详细介绍和实例

    一.闭包 来自wiki: 闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数.这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外 ...

  6. python 闭包的作用_python中对闭包的理解

    运行环境声明:本人的代码在sublime text 3中写的,可以Ctrl+b运行.python版本是python3.6.如果您直接运行的,请自觉加入if __name__ == '__main__' ...

  7. js中trim函数_30天中的30个Excel函数:03 – TRIM

    js中trim函数 Yesterday, in the 30XL30D challenge, we took a poke at the lazy brother-in-law function -- ...

  8. alert 回调_JavaScript中到底什么时候回调函数Callback

    什么是回调函数Callback 简单的理解:回调函数是在另一个函数执行完毕后执行的函数 - 因此名称为'call back'. 复杂的理解:在JavaScript中,函数是对象.因此,函数可以将函数作 ...

  9. python dataframe函数_python pandas中DataFrame类型数据操作函数的方法

    这篇文章主要介绍了关于python pandas中DataFrame类型数据操作函数的方法,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下 python数据分析工具pandas中Data ...

最新文章

  1. 1 字节的 utf-8 序列的字节 1 无效_字节码文件结构详解
  2. 如何防止android app被误删除,如何避免手机清理缓存时误删了重要文件【注意事项】...
  3. C# WinForm中 获得当前鼠标所在控件 或 将窗体中鼠标所在控件名显示在窗体标题上...
  4. P2802 回家(dfs+三维数组标记+剪枝)
  5. erlang开发环境配置
  6. html5点击切换选项卡,简单纯js实现点击切换TAB标签实例
  7. HDU 2068 Choose the best route
  8. mac安装helm工具_适用于初学者的基本 kubectl 和 Helm 命令
  9. SpringMVC 参数绑定注解解析
  10. latex中页眉怎么去掉_latex 页眉页脚设置
  11. mpvue(3)主页面搭建
  12. 局域网内台式机使用笔记本作代理服务器上网
  13. 实践论:论认识和实践的关系
  14. 计算机软件录音注意事项,GOLDWAVE录音软件使用教程
  15. nginx 在海思平台移植编译
  16. [小黄书后台]文件上传到服务器
  17. 王姨劝我学HarmonyOS鸿蒙2.0系列教程之六自定义View涂鸦项目实战!
  18. 在深圳转户口这件小事
  19. 单枪匹马撸个聊天室, 支持Web/Android/iOS三端
  20. 中国剩余定理拓展中国剩余定理

热门文章

  1. 【Salient Object Detection】显著性物体检测资料汇总
  2. Qt QMYSQL driver not loaded 解决办法
  3. 2014创新工场校招笔试题及参考答案
  4. 【Linux】进程间通信-命名管道FIFO
  5. 2021.03.14.浩楠卷子
  6. 图片盒子控件 winform 114868210
  7. 熟悉HTML基本标签的分类测试分析 1218
  8. 草稿 断开式数据连接
  9. 办公自动化-表格的读写操作-xlrd-xlwt
  10. linux-gzip压缩