什么是“闭包”?

发布于 2024-07-04 18:23:33 字数 50 浏览 8 评论 0原文

我问了一个关于柯里化的问题,其中提到了闭包。 什么是闭包? 它与柯里化有什么关系?

I asked a question about Currying and closures were mentioned.
What is a closure? How does it relate to currying?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(22

几度春秋 2024-07-11 18:23:33

关闭非常容易。 我们可以这样考虑:
闭包 = 函数 + 其词法环境

考虑以下函数:

function init() {
    var name = “Mozilla”;
}

上述情况下的闭包是什么?
函数 init() 及其词法环境中的变量(即名称)。
闭包 = init() + name

考虑另一个函数:

function init() {
    var name = “Mozilla”;
    function displayName(){
        alert(name);
}
displayName();
}

这里的闭包是什么?
内部函数可以访问外部函数的变量。 displayName() 可以访问父函数 init() 中声明的变量名称。 但是,如果存在,将使用 displayName() 中相同的局部变量。

闭包1: init 函数 + (名称变量 + displayName() 函数) --> 词法范围

闭包 2: displayName 函数 + ( name 变量 ) --> 词汇范围

Closure is very easy. We can consider it as follows :
Closure = function + its lexical environment

Consider the following function:

function init() {
    var name = “Mozilla”;
}

What will be the closure in the above case ?
Function init() and variables in its lexical environment ie name.
Closure = init() + name

Consider another function :

function init() {
    var name = “Mozilla”;
    function displayName(){
        alert(name);
}
displayName();
}

What will be the closures here ?
Inner function can access variables of outer function. displayName() can access the variable name declared in the parent function, init(). However, the same local variables in displayName() will be used if they exists.

Closure 1 : init function + ( name variable + displayName() function) --> lexical scope

Closure 2 : displayName function + ( name variable ) --> lexical scope

娇柔作态 2024-07-11 18:23:33

柯里化(Currying):它允许您通过仅传入函数参数的子集来部分评估函数。 考虑一下:

function multiply (x, y) {
  return x * y;
}

const double = multiply.bind(null, 2);

const eight = double(4);

eight == 8;

闭包:闭包只不过是访问函数作用域之外的变量。 重要的是要记住,函数内的函数或嵌套函数不是闭包。 当需要访问函数作用域之外的变量时,总是使用闭包。

function apple(x){
   function google(y,z) {
    console.log(x*y);
   }
   google(7,2);
}

apple(3);

// the answer here will be 21

Currying : It allows you to partially evaluate a function by only passing in a subset of its arguments. Consider this:

function multiply (x, y) {
  return x * y;
}

const double = multiply.bind(null, 2);

const eight = double(4);

eight == 8;

Closure: A closure is nothing more than accessing a variable outside of a function's scope. It is important to remember that a function inside a function or a nested function isn't a closure. Closures are always used when need to access the variables outside the function scope.

function apple(x){
   function google(y,z) {
    console.log(x*y);
   }
   google(7,2);
}

apple(3);

// the answer here will be 21
陌上芳菲 2024-07-11 18:23:33

下面是一个说明Scheme 编程语言中的闭包的示例。

首先我们定义一个函数,定义一个局部变量,在函数外部不可见。

; Function using a local variable
(define (function)
  (define a 1)
  (display a) ; prints 1, when calling (function)
  )
(function) ; prints 1
(display a) ; fails: a undefined

这是相同的示例,但现在该函数使用在函数外部定义的全局变量。

; Function using a global variable
(define b 2)
(define (function)
  (display b) ; prints 2, when calling (function)
  )
(function) ; prints 2
(display 2) ; prints 2

最后,这是一个带有自己的闭包的函数的示例:

; Function with closure
(define (outer)
  (define c 3)
  (define (inner)
    (display c))
  inner ; outer function returns the inner function as result
  )
(define function (outer))
(function) ; prints 3

Here is an example illustrating a closure in the Scheme programming language.

First we define a function defining a local variable, not visible outside the function.

; Function using a local variable
(define (function)
  (define a 1)
  (display a) ; prints 1, when calling (function)
  )
(function) ; prints 1
(display a) ; fails: a undefined

Here is the same example, but now the function uses a global variable, defined outside the function.

; Function using a global variable
(define b 2)
(define (function)
  (display b) ; prints 2, when calling (function)
  )
(function) ; prints 2
(display 2) ; prints 2

And finally, here is an example of a function carrying its own closure:

; Function with closure
(define (outer)
  (define c 3)
  (define (inner)
    (display c))
  inner ; outer function returns the inner function as result
  )
(define function (outer))
(function) ; prints 3
屋檐 2024-07-11 18:23:33

Groovy 中的一个简单示例供您参考:

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1

A simple example in Groovy for your reference:

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1
深巷少女 2024-07-11 18:23:33

请看下面的代码以更深入地理解闭包:

        for(var i=0; i< 5; i++){            
            setTimeout(function(){
                console.log(i);
            }, 1000);                        
        }

这里会输出什么? 0,1,2,3,4 不会因为闭包而变成 5,5,5,5,5

那么它会如何解决呢? 答案如下:

       for(var i=0; i< 5; i++){
           (function(j){     //using IIFE           
                setTimeout(function(){
                               console.log(j);
                           },1000);
            })(i);          
        }

让我简单解释一下,当一个函数创建时什么也没有发生,直到它调用所以 for 循环在第一个代码中调用了 5 次但没有立即调用,所以当它在 1 秒后调用 ie 时,而且这是异步的,所以在这个 for 循环完成之前并将值 5 存储在 var i 中,最后执行 setTimeout 函数五次并打印 5,5,5,5,5

这里它如何使用 IIFE 即立即调用函数表达式来解决

       (function(j){  //i is passed here           
            setTimeout(function(){
                           console.log(j);
                       },1000);
        })(i);  //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4

有关更多信息,请了解执行上下文以了解闭包。

  • 还有一个解决方案可以使用let(ES6功能)来解决这个问题,但在幕后,上面的函数是有效的

     for(让 i=0; i< 5; i++){            
           设置超时(函数(){ 
                          控制台.log(i); 
                      },1000);                         
       } 
    
      输出:0,1,2,3,4 
      

=> 更多说明:

在内存中,当 for 循环执行图片时,如下所示:

Loop 1)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Loop 2)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Loop 3)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Loop 4)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Loop 5)

     setTimeout(function(){
                    console.log(i);
                },1000);  

这里 i 没有执行,然后在完成循环后, var i 在内存中存储了值 5 但它是作用域在其子函数中始终可见,因此当函数在 setTimeout 内部执行五次时,它会打印 5,5,5,5,5

以便解决此问题,请使用 IIFE 作为解释多于。

Please have a look below code to understand closure in more deep:

        for(var i=0; i< 5; i++){            
            setTimeout(function(){
                console.log(i);
            }, 1000);                        
        }

Here what will be output? 0,1,2,3,4 not that will be 5,5,5,5,5 because of closure

So how it will solve? Answer is below:

       for(var i=0; i< 5; i++){
           (function(j){     //using IIFE           
                setTimeout(function(){
                               console.log(j);
                           },1000);
            })(i);          
        }

Let me simple explain, when a function created nothing happen until it called so for loop in 1st code called 5 times but not called immediately so when it called i.e after 1 second and also this is asynchronous so before this for loop finished and store value 5 in var i and finally execute setTimeout function five time and print 5,5,5,5,5

Here how it solve using IIFE i.e Immediate Invoking Function Expression

       (function(j){  //i is passed here           
            setTimeout(function(){
                           console.log(j);
                       },1000);
        })(i);  //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4

For more, please understand execution context to understand closure.

  • There is one more solution to solve this using let (ES6 feature) but under the hood above function is worked

     for(let i=0; i< 5; i++){           
         setTimeout(function(){
                        console.log(i);
                    },1000);                        
     }
    
    Output: 0,1,2,3,4
    

=> More explanation:

In memory, when for loop execute picture make like below:

Loop 1)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Loop 2)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Loop 3)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Loop 4)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Loop 5)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Here i is not executed and then after complete loop, var i stored value 5 in memory but it's scope is always visible in it's children function so when function execute inside setTimeout out five time it prints 5,5,5,5,5

so to resolve this use IIFE as explain above.

以可爱出名 2024-07-11 18:23:33

如果您来自 Java 世界,您可以将闭包与类的成员函数进行比较。 看这个例子

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

函数g是一个闭包:g关闭了a。所以g可以与成员函数,a 可以与类字段进行比较,函数 f 可以与类进行比较。

If you are from the Java world, you can compare a closure with a member function of a class. Look at this example

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

The function g is a closure: g closes a in. So g can be compared with a member function, a can be compared with a class field, and the function f with a class.

初懵 2024-07-11 18:23:33

闭包
每当我们在另一个函数内部定义一个函数时,内部函数就可以访问声明的变量
在外部函数中。 闭包最好用例子来解释。
在清单 2-18 中,您可以看到内部函数可以从
外部范围。 外部函数中的变量已被内部函数关闭(或绑定在内部函数中)。 因此这个术语
关闭。 这个概念本身非常简单且相当直观。

Listing 2-18:
    function outerFunction(arg) {
     var variableInOuterFunction = arg;

     function bar() {
             console.log(variableInOuterFunction); // Access a variable from the outer scope
     }
     // Call the local function to demonstrate that it has access to arg
     bar(); 
    }
    outerFunction('hello closure!'); // logs hello closure!

来源: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf

Closures
Whenever we have a function defined inside another function, the inner function has access to the variables declared
in the outer function. Closures are best explained with examples.
In Listing 2-18, you can see that the inner function has access to a variable (variableInOuterFunction) from the
outer scope. The variables in the outer function have been closed by (or bound in) the inner function. Hence the term
closure. The concept in itself is simple enough and fairly intuitive.

Listing 2-18:
    function outerFunction(arg) {
     var variableInOuterFunction = arg;

     function bar() {
             console.log(variableInOuterFunction); // Access a variable from the outer scope
     }
     // Call the local function to demonstrate that it has access to arg
     bar(); 
    }
    outerFunction('hello closure!'); // logs hello closure!

source: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf

过期情话 2024-07-11 18:23:33

来自 Lua.org

当一个函数被编写为包含在另一个函数中时,它可以完全访问封闭函数中的局部变量; 此功能称为词法作用域。 尽管这听起来很明显,但事实并非如此。 词法作用域加上一等函数是编程语言中的一个强大概念,但很少有语言支持该概念。

From Lua.org:

When a function is written enclosed in another function, it has full access to local variables from the enclosing function; this feature is called lexical scoping. Although that may sound obvious, it is not. Lexical scoping, plus first-class functions, is a powerful concept in a programming language, but few languages support that concept.

蹲墙角沉默 2024-07-11 18:23:33

在正常情况下,变量受作用域规则约束:局部变量仅在定义的函数内工作。 关闭是为了方便而暂时打破此规则的一种方式。

def n_times(a_thing)
  return lambda{|n| a_thing * n}
end

在上面的代码中,lambda(|n| a_thing * n} 是闭包,因为 a_thing 是由 lambda(匿名函数创建者)引用的。

现在,如果您输入函数变量中生成的匿名函数

foo = n_times(4)

将打破正常的作用域规则并开始在内部使用 4

foo.call(3)

返回 12。

In a normal situation, variables are bound by scoping rule: Local variables work only within the defined function. Closure is a way of breaking this rule temporarily for convenience.

def n_times(a_thing)
  return lambda{|n| a_thing * n}
end

in the above code, lambda(|n| a_thing * n} is the closure because a_thing is referred by the lambda (an anonymous function creator).

Now, if you put the resulting anonymous function in a function variable.

foo = n_times(4)

foo will break the normal scoping rule and start using 4 internally.

foo.call(3)

returns 12.

她如夕阳 2024-07-11 18:23:33

简而言之,函数指针只是指向程序代码库中某个位置的指针(如程序计数器)。 而闭包=函数指针+堆栈帧

In short, function pointer is just a pointer to a location in the program code base (like program counter). Whereas Closure = Function pointer + Stack frame.

.

〃温暖了心ぐ 2024-07-11 18:23:33

闭包为 JavaScript 提供了状态。

编程中的状态仅仅意味着记住事情。

示例

var a = 0;

a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3

在上面的例子中,状态存储在变量“a”中。 接下来,我们将 1 添加到“a”几次。 我们只能这样做,因为我们能够“记住”该值。 状态持有者“a”将该值保存在内存中。

通常,在编程语言中,您希望跟踪事物、记住信息并在以后访问它。

在其他语言中,这通常是通过使用类来完成的。 类就像变量一样,跟踪其状态。 反过来,该类的实例也有状态。 状态只是意味着您可以稍后存储和检索的信息。

示例

class Bread {
  constructor (weight) {
    this.weight = weight;
  }

  render () {
    return `My weight is ${this.weight}!`;
  }
}

我们如何从“render”方法中访问“weight”? 好吧,感谢国家。 Bread 类的每个实例都可以通过从“状态”(内存中我们可以存储该信息的位置)读取它来呈现自己的权重。

现在,JavaScript 是一种非常独特的语言,它在历史上没有类(现在有,但在幕后只有函数和变量),因此闭包为 JavaScript 提供了一种记住事物并稍后访问它们的方法。

示例

var n = 0;
var count = function () {
  n = n + 1;
  return n;
};

count(); // # 1
count(); // # 2
count(); // # 3

上面的示例实现了使用变量“保持状态”的目标。 这很棒! 然而,这有一个缺点,即变量(“状态”持有者)现在暴露了。 我们可以做得更好。 我们可以使用闭包。

示例

var countGenerator = function () {
  var n = 0;
  var count = function () {
    n = n + 1;
    return n;
  };

  return count;
};

var count = countGenerator();
count(); // # 1
count(); // # 2
count(); // # 3

这太棒了。

现在我们的“count”函数可以计数了。 它之所以能够这样做,是因为它可以“保持”状态。 这种情况下的状态是变量“n”。 该变量现已关闭。 时间和空间上都是封闭的。 及时,因为您将永远无法恢复它、更改它、为其分配值或直接与其交互。 在太空中,因为它在地理上嵌套在“countGenerator”函数中。

为什么这太棒了? 因为无需涉及任何其他复杂的工具(例如类、方法、实例等),我们就能够
1. 隐藏
2.远程控制

我们隐藏状态,变量“n”,这使得它成为私有变量!
我们还创建了一个 API,可以以预定义的方式控制该变量。 特别是,我们可以像这样调用 API“count()”,并将“n”从“距离”加 1。 除了通过 API 之外,任何人都无法以任何方式访问“n”。

JavaScript 的简单性确实令人惊叹。

关闭是造成这种情况的一个重要原因。

Closures provide JavaScript with state.

State in programming simply means remembering things.

Example

var a = 0;

a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3

In the case above, state is stored in the variable "a". We follow by adding 1 to "a" several times. We can only do that because we are able to "remember" the value. The state holder, "a", holds that value in memory.

Often, in programming languages, you want to keep track of things, remember information and access it at a later time.

This, in other languages, is commonly accomplished through the use of classes. A class, just like variables, keeps track of its state. And instances of that class, in turns, also have state within them. State simply means information that you can store and retrieve later.

Example

class Bread {
  constructor (weight) {
    this.weight = weight;
  }

  render () {
    return `My weight is ${this.weight}!`;
  }
}

How can we access "weight" from within the "render" method? Well, thanks to state. Each instance of the class Bread can render its own weight by reading it from the "state", a place in memory where we could store that information.

Now, JavaScript is a very unique language which historically does not have classes (it now does, but under the hood there's only functions and variables) so Closures provide a way for JavaScript to remember things and access them later.

Example

var n = 0;
var count = function () {
  n = n + 1;
  return n;
};

count(); // # 1
count(); // # 2
count(); // # 3

The example above achieved the goal of "keeping state" with a variable. This is great! However, this has the disadvantage that the variable (the "state" holder) is now exposed. We can do better. We can use Closures.

Example

var countGenerator = function () {
  var n = 0;
  var count = function () {
    n = n + 1;
    return n;
  };

  return count;
};

var count = countGenerator();
count(); // # 1
count(); // # 2
count(); // # 3

This is fantastic.

Now our "count" function can count. It is only able to do so because it can "hold" state. The state in this case is the variable "n". This variable is now closed. Closed in time and space. In time because you won't ever be able to recover it, change it, assign it a value or interact directly with it. In space because it's geographically nested within the "countGenerator" function.

Why is this fantastic? Because without involving any other sophisticated and complicated tool (e.g. classes, methods, instances, etc) we are able to
1. conceal
2. control from a distance

We conceal the state, the variable "n", which makes it a private variable!
We also have created an API that can control this variable in a pre-defined way. In particular, we can call the API like so "count()" and that adds 1 to "n" from a "distance". In no way, shape or form anyone will ever be able to access "n" except through the API.

JavaScript is truly amazing in its simplicity.

Closures are a big part of why this is.

左耳近心 2024-07-11 18:23:33

这是另一个现实生活中的例子,使用游戏中流行的脚本语言——Lua。 我需要稍微改变库函数的工作方式,以避免标准输入不可用的问题。

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error( 'Can not use default of stdin.' )
  end

  old_dofile( filename )
end

当此代码块完成其作用域时,old_dofile 的值消失(因为它是本地的),但是该值已包含在闭包中,因此新的重新定义的 dofile 函数可以访问它,或者更确切地说,与该函数一起存储的副本作为“升值”。

Here is another real life example, and using a scripting language popular in games - Lua. I needed to slightly change the way a library function worked to avoid a problem with stdin not being available.

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error( 'Can not use default of stdin.' )
  end

  old_dofile( filename )
end

The value of old_dofile disappears when this block of code finishes it's scope (because it's local), however the value has been enclosed in a closure, so the new redefined dofile function CAN access it, or rather a copy stored along with the function as an 'upvalue'.

伴梦长久 2024-07-11 18:23:33

闭包是 JavaScript 中的一项功能,函数可以访问自己的作用域变量、外部函数变量以及全局变量。

即使外部函数返回后,闭包也可以访问其外部函数作用域。 这意味着即使在函数完成之后,闭包也可以记住并访问其外部函数的变量和参数。

内部函数可以访问其自身作用域、外部函数作用域和全局作用域中定义的变量。 并且外部函数可以访问自己作用域和全局作用域中定义的变量。

闭包示例

var globalValue = 5;

function functOuter() {
  var outerFunctionValue = 10;

  //Inner function has access to the outer function value
  //and the global variables
  function functInner() {
    var innerFunctionValue = 5;
    alert(globalValue + outerFunctionValue + innerFunctionValue);
  }
  functInner();
}
functOuter();  

输出为20,即内部函数自身变量、外部函数变量和全局变量值之和。

Closure is a feature in JavaScript where a function has access to its own scope variables, access to the outer function variables and access to the global variables.

Closure has access to its outer function scope even after the outer function has returned. This means a closure can remember and access variables and arguments of its outer function even after the function has finished.

The inner function can access the variables defined in its own scope, the outer function’s scope, and the global scope. And the outer function can access the variable defined in its own scope and the global scope.

Example of Closure:

var globalValue = 5;

function functOuter() {
  var outerFunctionValue = 10;

  //Inner function has access to the outer function value
  //and the global variables
  function functInner() {
    var innerFunctionValue = 5;
    alert(globalValue + outerFunctionValue + innerFunctionValue);
  }
  functInner();
}
functOuter();  

Output will be 20 which sum of its inner function own variable, outer function variable and global variable value.

绅士风度i 2024-07-11 18:23:33

这是一个真实世界的例子,说明了闭包为何如此出色……这直接来自我的 Javascript 代码。 让我举例说明。

Function.prototype.delay = function(ms /*[, arg...]*/) {
  var fn = this,
      args = Array.prototype.slice.call(arguments, 1);

  return window.setTimeout(function() {
      return fn.apply(fn, args);
  }, ms);
};

使用方法如下:

var startPlayback = function(track) {
  Player.play(track);  
};
startPlayback(someTrack);

现在假设您希望延迟开始播放,例如在此代码片段运行后 5 秒后开始。 嗯,使用 delay 很简单,它的闭包:

startPlayback.delay(5000, someTrack);
// Keep going, do other things

当您使用 5000ms 调用 delay 时,第一个代码段将运行,并将传入的参数存储在这是关闭。 然后 5 秒后,当 setTimeout 回调发生时,闭包仍然维护这些变量,因此它可以使用原始参数调用原始函数。
这是一种柯里化或函数装饰。

如果没有闭包,您将不得不以某种方式在函数外部维护这些变量的状态,从而将逻辑上属于函数内部的东西乱扔在函数外部的代码中。 使用闭包可以极大地提高代码的质量和可读性。

Here's a real world example of why Closures kick ass... This is straight out of my Javascript code. Let me illustrate.

Function.prototype.delay = function(ms /*[, arg...]*/) {
  var fn = this,
      args = Array.prototype.slice.call(arguments, 1);

  return window.setTimeout(function() {
      return fn.apply(fn, args);
  }, ms);
};

And here's how you would use it:

var startPlayback = function(track) {
  Player.play(track);  
};
startPlayback(someTrack);

Now imagine you want the playback to start delayed, like for example 5 seconds later after this code snippet runs. Well that's easy with delay and it's closure:

startPlayback.delay(5000, someTrack);
// Keep going, do other things

When you call delay with 5000ms, the first snippet runs, and stores the passed in arguments in it's closure. Then 5 seconds later, when the setTimeout callback happens, the closure still maintains those variables, so it can call the original function with the original parameters.
This is a type of currying, or function decoration.

Without closures, you would have to somehow maintain those variables state outside the function, thus littering code outside the function with something that logically belongs inside it. Using closures can greatly improve the quality and readability of your code.

飘然心甜 2024-07-11 18:23:33

首先,与这里大多数人告诉您的相反,闭包不是函数! 那么它是什么?
它是在函数的“周围上下文”(称为其环境)中定义的一组符号,这使其成为 CLOSED 表达式(即,其中每个符号已定义并具有值,因此可以对其进行求值)。

例如,当您有一个 JavaScript 函数时:

function closed(x) {
  return x + 3;
}

它是一个封闭表达式,因为其中出现的所有符号都在其中定义(它们的含义很明确),因此您可以对其求值。 换句话说,它是自包含的

但是如果你有一个像这样的函数:

function open(x) {
  return x*y + 3;
}

它是一个开放表达式,因为其中有尚未定义的符号。 即,y。 当查看这个函数时,我们无法分辨 y 是什么以及它的含义,我们不知道它的值,因此我们无法计算这个表达式。 也就是说,在我们知道 y 的含义之前,我们无法调用该函数。 这个y被称为自由变量

这个y需要一个定义,但是这个定义不是函数的一部分——它是在其他地方定义的,在它的“周围上下文”(也称为环境)中。 至少这就是我们所希望的 :P

例如,它可以全局定义:

var y = 7;

function open(x) {
  return x*y + 3;
}

或者可以在包装它的函数中定义:

var global = 2;

function wrapper(y) {
  var w = "unused";

  return function(x) {
    return x*y + 3;
  }
}

环境中为表达式中的自由变量赋予其含义的部分是 关闭。 之所以这样称呼它,是因为它通过为其所有自由变量提供这些缺失的定义,将开放表达式转换为封闭表达式,以便我们对其进行评估。

在上面的示例中,内部函数(我们没有给出名称,因为我们不需要它)是一个开放表达式,因为其中的变量 y 是free – 它的定义在函数之外,在包装它的函数中。 该匿名函数的环境是一组变量:

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

现在,闭包是该环境的一部分,它通过提供关闭内部函数所有自由变量的定义。 在我们的例子中,内部函数中唯一的自由变量是y,因此该函数的闭包是其环境的这个子集:

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

环境中定义的其他两个符号不是 该函数的闭包的一部分,因为它不需要它们运行。 他们不需要关闭它。

更多关于其背后的理论:
https://stackoverflow.com/a/36878651/434562

值得注意的是,在上面的示例中,包装函数将其内部函数作为值返回。 我们调用该函数的时刻可以与该函数被定义(或创建)的时刻相距很远。 特别是,它的包装函数不再运行,并且它在调用堆栈上的参数也不再存在 :P 这会产生问题,因为内部函数在调用时需要 y 存在。叫做! 换句话说,它需要闭包中的变量以某种方式超越包装函数并在需要时出现。 因此,内部函数必须为这些变量创建一个快照,使其闭包并将它们存储在安全的地方以供以后使用。 (调用堆栈之外的某个地方。)

这就是为什么人们经常将术语“闭包”混淆为特殊类型的函数,该函数可以对他们使用的外部变量或用于执行操作的数据结构进行快照。存储这些变量供以后使用。 但我希望你现在明白,它们不是闭包本身 - 它们只是在编程语言中实现闭包的方法,或者是允许来自闭包的变量的语言机制。函数的闭包在需要时出现。 关于闭包存在很多误解,这些误解(不必要地)使这个主题比实际情况更加混乱和复杂。

First of all, contrary to what most of the people here tell you, closure is not a function! So what is it?
It is a set of symbols defined in a function's "surrounding context" (known as its environment) which make it a CLOSED expression (that is, an expression in which every symbol is defined and has a value, so it can be evaluated).

For example, when you have a JavaScript function:

function closed(x) {
  return x + 3;
}

it is a closed expression because all the symbols occurring in it are defined in it (their meanings are clear), so you can evaluate it. In other words, it is self-contained.

But if you have a function like this:

function open(x) {
  return x*y + 3;
}

it is an open expression because there are symbols in it which have not been defined in it. Namely, y. When looking at this function, we can't tell what y is and what does it mean, we don't know its value, so we cannot evaluate this expression. I.e. we cannot call this function until we tell what y is supposed to mean in it. This y is called a free variable.

This y begs for a definition, but this definition is not part of the function – it is defined somewhere else, in its "surrounding context" (also known as the environment). At least that's what we hope for :P

For example, it could be defined globally:

var y = 7;

function open(x) {
  return x*y + 3;
}

Or it could be defined in a function which wraps it:

var global = 2;

function wrapper(y) {
  var w = "unused";

  return function(x) {
    return x*y + 3;
  }
}

The part of the environment which gives the free variables in an expression their meanings, is the closure. It is called this way, because it turns an open expression into a closed one, by supplying these missing definitions for all of its free variables, so that we could evaluate it.

In the example above, the inner function (which we didn't give a name because we didn't need it) is an open expression because the variable y in it is free – its definition is outside the function, in the function which wraps it. The environment for that anonymous function is the set of variables:

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Now, the closure is that part of this environment which closes the inner function by supplying the definitions for all its free variables. In our case, the only free variable in the inner function was y, so the closure of that function is this subset of its environment:

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

The other two symbols defined in the environment are not part of the closure of that function, because it doesn't require them to run. They are not needed to close it.

More on the theory behind that here:
https://stackoverflow.com/a/36878651/434562

It's worth to note that in the example above, the wrapper function returns its inner function as a value. The moment we call this function can be remote in time from the moment the function has been defined (or created). In particular, its wrapping function is no longer running, and its parameters which has been on the call stack are no longer there :P This makes a problem, because the inner function needs y to be there when it is called! In other words, it requires the variables from its closure to somehow outlive the wrapper function and be there when needed. Therefore, the inner function has to make a snapshot of these variables which make its closure and store them somewhere safe for later use. (Somewhere outside the call stack.)

And this is why people often confuse the term closure to be that special type of function which can do such snapshots of the external variables they use, or the data structure used to store these variables for later. But I hope you understand now that they are not the closure itself – they're just ways to implement closures in a programming language, or language mechanisms which allows the variables from the function's closure to be there when needed. There's a lot of misconceptions around closures which (unnecessarily) make this subject much more confusing and complicated than it actually is.

故事灯 2024-07-11 18:23:33

我将举一个例子(在 JavaScript 中):

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();
x(); returns 1
x(); returns 2
...etc...

这个函数 makeCounter 的作用是返回一个函数,我们将其称为 x,该函数将进行计数每次调用时都会有一个。 由于我们没有向 x 提供任何参数,因此它必须以某种方式记住计数。 它知道在哪里根据所谓的词法作用域找到它 - 它必须查找定义它的位置才能找到该值。 这个“隐藏”值就是所谓的闭包。

这是我的柯里化示例:

function add (a) {
  return function (b) {
    return a + b;
  }
}

var add3 = add(3);
    
add3(4); returns 7

您可以看到,当您使用参数 a(为 3)调用 add 时,该值包含在返回的函数我们定义为add3。 这样,当我们调用 add3 时,它就知道在哪里找到 a 值来执行加法。

I'll give an example (in JavaScript):

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();
x(); returns 1
x(); returns 2
...etc...

What this function, makeCounter, does is it returns a function, which we've called x, that will count up by one each time it's called. Since we're not providing any parameters to x, it must somehow remember the count. It knows where to find it based on what's called lexical scoping - it must look to the spot where it's defined to find the value. This "hidden" value is what is called a closure.

Here is my currying example again:

function add (a) {
  return function (b) {
    return a + b;
  }
}

var add3 = add(3);
    
add3(4); returns 7

What you can see is that when you call add with the parameter a (which is 3), that value is contained in the closure of the returned function that we're defining to be add3. That way, when we call add3, it knows where to find the a value to perform the addition.

憧憬巴黎街头的黎明 2024-07-11 18:23:33

凯尔的回答非常好。 我认为唯一需要说明的是,闭包基本上是创建 lambda 函数时堆栈的快照。 然后,当函数重新执行时,堆栈将恢复到执行函数之前的状态。 因此,正如 Kyle 提到的,当 lambda 函数执行时,隐藏值 (count) 可用。

Kyle's answer is pretty good. I think the only additional clarification is that the closure is basically a snapshot of the stack at the point that the lambda function is created. Then when the function is re-executed the stack is restored to that state before executing the function. Thus as Kyle mentions, that hidden value (count) is available when the lambda function executes.

倾`听者〃 2024-07-11 18:23:33

闭包是一个可以引用另一个函数中的状态的函数。 例如,在 Python 中,这使用闭包“inner”:

def outer (a):
    b = "variable in outer()"
    def inner (c):
        print a, b, c
    return inner

# Now the return value from outer() can be saved for later
func = outer ("test")
func (1) # prints "test variable in outer() 1

A closure is a function that can reference state in another function. For example, in Python, this uses the closure "inner":

def outer (a):
    b = "variable in outer()"
    def inner (c):
        print a, b, c
    return inner

# Now the return value from outer() can be saved for later
func = outer ("test")
func (1) # prints "test variable in outer() 1
吹梦到西洲 2024-07-11 18:23:33

为了帮助促进对闭包的理解,检查如何用过程语言实现闭包可能会很有用。 这个解释将遵循Scheme 中闭包的简单实现。

首先,我必须介绍命名空间的概念。 当您将命令输入到Scheme解释器中时,它必须计算表达式中的各种符号并获取它们的值。 示例:

(define x 3)

(define y 4)

(+ x y) returns 7

定义表达式将值 3 存储在 x 的位置中,将值 4 存储在 y 的位置中。 然后,当我们调用 (+ xy) 时,解释器会查找命名空间中的值,并能够执行操作并返回 7。

但是,在Scheme 中,有一些表达式允许您临时覆盖符号的值。 下面是一个示例:

(define x 3)

(define y 4)

(let ((x 5))
   (+ x y)) returns 9

x returns 3

let 关键字的作用是引入一个新的命名空间,其中 x 的值为 5。您会注意到它仍然可以看到 y 为 4,使得返回的总和为 9。您还可以看到,一旦表达式结束 x 又回到 3。从这个意义上说,x 已被局部值暂时屏蔽。

过程语言和面向对象语言具有类似的概念。 每当您在函数中声明一个与全局变量同名的变量时,您都会得到相同的效果。

我们将如何实现这一点? 一种简单的方法是使用链表 - 头部包含新值,尾部包含旧名称空间。 当您需要查找某个符号时,您可以从头部开始,然后顺着尾部查找。

现在让我们暂时跳到一流函数的实现。 或多或少,函数是调用函数时执行的一组指令,最终返回值。 当我们读入函数时,我们可以在幕后存储这些指令并在调用函数时运行它们。

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns ?

我们将 x 定义为 3,plus-x 为其参数 y,加上 x 的值。 最后,我们在 x 被新 x 屏蔽的环境中调用 plus-x,该 x 的值为 5。如果我们仅存储函数 plus-x 的操作 (+ xy),因为我们处于上下文中x 为 5 时,返回的结果将为 9。这就是所谓的动态作用域。

然而,Scheme、Common Lisp 和许多其他语言都有所谓的词法作用域 - 除了存储操作 (+ xy) 之外,我们还存储该特定点的名称空间。 这样,当我们查找值时,我们可以看到 x 在这种情况下实际上是 3。这是一个闭包。

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns 7

总之,我们可以使用链表来存储函数定义时命名空间的状态,允许我们从封闭范围访问变量,并为我们提供本地屏蔽变量而不影响其余部分的能力。程序。

To help facilitate understanding of closures it might be useful to examine how they might be implemented in a procedural language. This explanation will follow a simplistic implementation of closures in Scheme.

To start, I must introduce the concept of a namespace. When you enter a command into a Scheme interpreter, it must evaluate the various symbols in the expression and obtain their value. Example:

(define x 3)

(define y 4)

(+ x y) returns 7

The define expressions store the value 3 in the spot for x and the value 4 in the spot for y. Then when we call (+ x y), the interpreter looks up the values in the namespace and is able to perform the operation and return 7.

However, in Scheme there are expressions that allow you to temporarily override the value of a symbol. Here's an example:

(define x 3)

(define y 4)

(let ((x 5))
   (+ x y)) returns 9

x returns 3

What the let keyword does is introduces a new namespace with x as the value 5. You will notice that it's still able to see that y is 4, making the sum returned to be 9. You can also see that once the expression has ended x is back to being 3. In this sense, x has been temporarily masked by the local value.

Procedural and object-oriented languages have a similar concept. Whenever you declare a variable in a function that has the same name as a global variable you get the same effect.

How would we implement this? A simple way is with a linked list - the head contains the new value and the tail contains the old namespace. When you need to look up a symbol, you start at the head and work your way down the tail.

Now let's skip to the implementation of first-class functions for the moment. More or less, a function is a set of instructions to execute when the function is called culminating in the return value. When we read in a function, we can store these instructions behind the scenes and run them when the function is called.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns ?

We define x to be 3 and plus-x to be its parameter, y, plus the value of x. Finally we call plus-x in an environment where x has been masked by a new x, this one valued 5. If we merely store the operation, (+ x y), for the function plus-x, since we're in the context of x being 5 the result returned would be 9. This is what's called dynamic scoping.

However, Scheme, Common Lisp, and many other languages have what's called lexical scoping - in addition to storing the operation (+ x y) we also store the namespace at that particular point. That way, when we're looking up the values we can see that x, in this context, is really 3. This is a closure.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns 7

In summary, we can use a linked list to store the state of the namespace at the time of function definition, allowing us to access variables from enclosing scopes, as well as providing us the ability to locally mask a variable without affecting the rest of the program.

江南月 2024-07-11 18:23:33

不包含自由变量的函数称为纯函数。

包含一个或多个自由变量的函数称为闭包。

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo 
  // foo is a free variable from the outer environment
}

src:https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure

Functions containing no free variables are called pure functions.

Functions containing one or more free variables are called closures.

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo 
  // foo is a free variable from the outer environment
}

src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure

陌路黄昏 2024-07-11 18:23:33

tl;dr

闭包是一个函数,其作用域分配给(或用作)变量。 因此,名称闭包:范围和函数被封闭并像任何其他实体一样使用。

深入的维基百科风格解释

根据维基百科,闭包是:

在具有一流函数的语言中实现词法作用域名称绑定的技术。

这意味着什么? 让我们看一些定义。

我将通过这个例子解释闭包和其他相关定义:

function startAt(x) {
    return function (y) {
        return x + y;
    }
}

var closure1 = startAt(1);
var closure2 = startAt(5);

console.log(closure1(3)); // 4 (x == 1, y == 3)
console.log(closure2(3)); // 8 (x == 5, y == 3)

一流函数

基本上这意味着我们可以像任何其他实体一样使用函数。 我们可以修改它们,将它们作为参数传递,从函数返回它们或将它们分配给变量。 从技术上讲,它们是一等公民,因此得名:一等函数。

在上面的示例中,startAt 返回一个 (anonymous) 函数,该函数执行以下操作:被分配给closure1closure2。 正如您所看到的,JavaScript 像对待任何其他实体(一等公民)一样对待函数。

名称绑定

名称绑定是为了找出变量的数据(标识符) 参考文献。 范围在这里非常重要,因为它将决定如何解析绑定。

在上面的示例中:

  • 在内部匿名函数的作用域中,y 绑定到 3
  • startAt 的作用域中,x 绑定到 15(取决于闭包)。

在匿名函数的作用域内,x 未绑定到任何值,因此需要在上层(startAt)作用域中进行解析。

词法范围

正如 维基百科所述,范围:

是绑定有效的计算机程序区域:名称可用于引用实体

有两种技术:

  • 词法(静态)作用域:通过搜索其包含块或函数来解析变量的定义,如果搜索失败,则搜索外部包含块,依此类推。
  • 动态作用域:搜索调用函数,然后搜索调用该调用函数的函数,依此类推,在调用堆栈中向上进行。

如需更多说明,查看此问题看看维基百科

在上面的示例中,我们可以看到 JavaScript 是词法作用域的,因为当解析 x 时,会在上层(startAt's)作用域中搜索绑定,基于源代码(查找 x 的匿名函数在 startAt 内定义),而不是基于调用堆栈、函数调用的方式(范围)。

包装(关闭)

在我们的示例中,当我们调用 startAt 时,它将返回一个(一流)函数,该函数将被分配给 closure1closure2< /code> 因此创建了一个闭包,因为传递的变量 15 将保存在 startAt 的范围内,该范围将被封闭与返回的匿名函数。 当我们使用相同的参数 (3) 通过 closure1closure2 调用此匿名函数时,y 的值会立即找到(因为这是该函数的参数),但是 x 并未绑定在匿名函数的作用域中,因此解析在(词法上)上层函数作用域中继续进行(即保存在闭包中),其中 x 被发现绑定到 15。 现在我们知道求和的所有内容,因此可以返回结果,然后打印结果。

现在您应该了解闭包及其行为方式,这是 JavaScript 的基本部分。

柯里化

哦,你还了解了 柯里化 的含义:你使用函数(闭包)来传递每个操作的参数,而不是使用具有多个参数的一个函数。

tl;dr

A closure is a function and its scope assigned to (or used as) a variable. Thus, the name closure: the scope and the function is enclosed and used just like any other entity.

In depth Wikipedia style explanation

According to Wikipedia, a closure is:

Techniques for implementing lexically scoped name binding in languages with first-class functions.

What does that mean? Lets look into some definitions.

I will explain closures and other related definitions by using this example:

function startAt(x) {
    return function (y) {
        return x + y;
    }
}

var closure1 = startAt(1);
var closure2 = startAt(5);

console.log(closure1(3)); // 4 (x == 1, y == 3)
console.log(closure2(3)); // 8 (x == 5, y == 3)

First-class functions

Basically that means we can use functions just like any other entity. We can modify them, pass them as arguments, return them from functions or assign them for variables. Technically speaking, they are first-class citizens, hence the name: first-class functions.

In the example above, startAt returns an (anonymous) function which function get assigned to closure1 and closure2. So as you see JavaScript treats functions just like any other entities (first-class citizens).

Name binding

Name binding is about finding out what data a variable (identifier) references. The scope is really important here, as that is the thing that will determine how a binding is resolved.

In the example above:

  • In the inner anonymous function's scope, y is bound to 3.
  • In startAt's scope, x is bound to 1 or 5 (depending on the closure).

Inside the anonymous function's scope, x is not bound to any value, so it needs to be resolved in an upper (startAt's) scope.

Lexical scoping

As Wikipedia says, the scope:

Is the region of a computer program where the binding is valid: where the name can be used to refer to the entity.

There are two techniques:

  • Lexical (static) scoping: A variable's definition is resolved by searching its containing block or function, then if that fails searching the outer containing block, and so on.
  • Dynamic scoping: Calling function is searched, then the function which called that calling function, and so on, progressing up the call stack.

For more explanation, check out this question and take a look at Wikipedia.

In the example above, we can see that JavaScript is lexically scoped, because when x is resolved, the binding is searched in the upper (startAt's) scope, based on the source code (the anonymous function that looks for x is defined inside startAt) and not based on the call stack, the way (the scope where) the function was called.

Wrapping (closuring) up

In our example, when we call startAt, it will return a (first-class) function that will be assigned to closure1 and closure2 thus a closure is created, because the passed variables 1 and 5 will be saved within startAt's scope, that will be enclosed with the returned anonymous function. When we call this anonymous function via closure1 and closure2 with the same argument (3), the value of y will be found immediately (as that is the parameter of that function), but x is not bound in the scope of the anonymous function, so the resolution continues in the (lexically) upper function scope (that was saved in the closure) where x is found to be bound to either 1 or 5. Now we know everything for the summation so the result can be returned, then printed.

Now you should understand closures and how they behave, which is a fundamental part of JavaScript.

Currying

Oh, and you also learned what currying is about: you use functions (closures) to pass each argument of an operation instead of using one functions with multiple parameters.

甩你一脸翔 2024-07-11 18:23:33

变量作用域

当声明局部变量时,该变量就有一个作用域。 通常,局部变量仅存在于声明它们的块或函数中。

function() {
  var a = 1;
  console.log(a); // works
}    
console.log(a); // fails

如果我尝试访问局部变量,大多数语言都会在当前作用域中查找它,然后向上查找父作用域,直到到达根作用域。

var a = 1;
function() {
  console.log(a); // works
}    
console.log(a); // works

当块或函数完成后,不再需要其局部变量,并且通常会耗尽内存。

这就是我们通常期望事情发生的方式。

闭包是一个持久的局部变量作用域

闭包是一个持久的作用域,即使在代码执行移出该块之后,它也保留局部变量。 支持闭包的语言(例如 JavaScript、Swift 和 Ruby)将允许您保留对作用域(包括其父作用域)的引用,即使在声明这些变量的块已完成执行之后,前提是您保留引用到某个地方的那个块或函数。

作用域对象及其所有局部变量都与该函数相关联,并且只要该函数存在,它就会持续存在。

这为我们提供了函数的可移植性。 我们可以预期,当我们稍后调用该函数时,首次定义该函数时在作用域内的任何变量仍然在作用域内,即使我们在完全不同的上下文中调用该函数也是如此。

例如,

这是一个非常简单的 JavaScript 示例,它说明了这一点:

outer = function() {
  var a = 1;
  var inner = function() {
    console.log(a);
  }
  return inner; // this returns a function
}

var fnc = outer(); // execute outer to get inner 
fnc();

这里我在函数中定义了一个函数。 内部函数可以访问外部函数的所有局部变量,包括a。 变量a在内部函数的范围内。

通常,当函数退出时,其所有局部变量都会被清除。 但是,如果我们返回内部函数并将其分配给变量fnc,以便它在outer退出后仍然存在,inner 也被定义为持久化。 变量a已经被封闭——它在一个闭包内。

请注意,变量a 完全是fnc 私有的。 这是一种在函数式编程语言(例如 JavaScript)中创建私有变量的方法。

您可能会猜到,当我调用 fnc() 时,它会打印 a 的值,即“1”。

在没有闭包的语言中,当函数 outer 退出时,变量 a 将被垃圾收集并丢弃。 调用 fnc 会抛出错误,因为 a 不再存在。

在 JavaScript 中,变量 a 会持续存在,因为变量作用域是在函数首次声明时创建的,并且只要函数继续存在就会持续存在。

a属于outer范围。 inner 的作用域有一个指向 outer 作用域的父指针。 fnc 是一个指向inner 的变量。 只要 fnc 存在,a 就会存在。 a 在闭包内。

进一步阅读(观看)

我制作了一个 YouTube 视频,通过一些实际示例查看此代码用法。

Variable scope

When you declare a local variable, that variable has a scope. Generally, local variables exist only within the block or function in which you declare them.

function() {
  var a = 1;
  console.log(a); // works
}    
console.log(a); // fails

If I try to access a local variable, most languages will look for it in the current scope, then up through the parent scopes until they reach the root scope.

var a = 1;
function() {
  console.log(a); // works
}    
console.log(a); // works

When a block or function is done with, its local variables are no longer needed and are usually blown out of memory.

This is how we normally expect things to work.

A closure is a persistent local variable scope

A closure is a persistent scope which holds on to local variables even after the code execution has moved out of that block. Languages which support closure (such as JavaScript, Swift, and Ruby) will allow you to keep a reference to a scope (including its parent scopes), even after the block in which those variables were declared has finished executing, provided you keep a reference to that block or function somewhere.

The scope object and all its local variables are tied to the function and will persist as long as that function persists.

This gives us function portability. We can expect any variables that were in scope when the function was first defined to still be in scope when we later call the function, even if we call the function in a completely different context.

For example

Here's a really simple example in JavaScript that illustrates the point:

outer = function() {
  var a = 1;
  var inner = function() {
    console.log(a);
  }
  return inner; // this returns a function
}

var fnc = outer(); // execute outer to get inner 
fnc();

Here I have defined a function within a function. The inner function gains access to all the outer function's local variables, including a. The variable a is in scope for the inner function.

Normally when a function exits, all its local variables are blown away. However, if we return the inner function and assign it to a variable fnc so that it persists after outer has exited, all of the variables that were in scope when inner was defined also persist. The variable a has been closed over -- it is within a closure.

Note that the variable a is totally private to fnc. This is a way of creating private variables in a functional programming language such as JavaScript.

As you might be able to guess, when I call fnc() it prints the value of a, which is "1".

In a language without closure, the variable a would have been garbage collected and thrown away when the function outer exited. Calling fnc would have thrown an error because a no longer exists.

In JavaScript, the variable a persists because the variable scope is created when the function is first declared and persists for as long as the function continues to exist.

a belongs to the scope of outer. The scope of inner has a parent pointer to the scope of outer. fnc is a variable which points to inner. a persists as long as fnc persists. a is within the closure.

Further reading (watching)

I made a YouTube video looking at this code with some practical examples of usage.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文