V8 有一个 Call Stack 跟 Memory Heap。
Memory heap 用于存放跟读取数据的地方,毕竟程序只是进行了读写操作。
翻译成 heap 的语言就是分配以及释放内存。
call stack 的作用是帮助我们记录程序执行到了哪一步,然后我们可以按照顺序执行代码。
由于 JS 只有一个线程,意味着它只有一个 call stack 然后在同一时间只能干一件事情。
const name = "george";
上面的代码在说请给变量name
分配内存,然后让内存指向堆中的"george"
function foo() {
return foo();
}
foo();
function computeMaxCallStackSize() {
try {
return 1 + computeMaxCallStackSize();
} catch (e) {
// Call stack overflow
return 1;
}
}
上面的函数执行结果在不同的环境中不同:
能够循环调用的次数取决于两个因素:
- 栈的大小
- 栈帧的大小
function multiply(a, b) {
return a * b;
}
function square(n) {
return multiply(n, n);
}
function printSquare(n) {
var squared = square(n);
console.log(squared);
}
printSqure(4);
可以理解为 stack 上的程序执行很慢,于是就想办法通过使用 callback 在后面运行它。
下面的 setTimeout 函数的第一个参数就是一个 callback 函数
console.log("hi");
setTimeout(function () {
console.log(" how are you");
}, 3000);
console.log("you");
首先 main 函数会被放到 stack 中,然后依次执行。但是 setTimeout
中的 callback 函数会触发 Web Api
,等待三秒,然后 callback 函数被发送到Callback Queue
被Event Loop
重新放到 call stack 中。
Event Loop
只有一项简单的任务要做:
它查看 Call Stack 跟 Callback Queue,只要 call stack 是空的,它将推送 callback queue 中的第一个 callback 函数到 call stack
闭包就是函数以及这个函数的引用环境。闭包是一个包含了自由变量的代码块,自由变量在内存中被定义在代码块所处的 context。
再看看一个比较通俗易懂的闭包定义:
Consider:
something closes over something else
|_______| |_________| |____________|
| | |
subject verb object
Here:
- The subject is the closure. A closure is a function.
- The closure “closes over” (as in encloses) the set of its free variables.
- The object is the set of the free variables of the closure.
Consider a simple function:
function add(x) {
return function closure(y) {
return x + y;
};
}
Here:
- The function named
add
has only one variable, namedx
, which is not a free variable because it is defined within the scope ofadd
itself. - The function named
closure
has two variables, namedx
andy
, out of whichx
is a free variable because it is defined in the scope ofadd
(notclosure
) andy
is not a free variable because it is defined in the scope ofclosure
itself.
Hence, in the second case the function named closure
is said to “close over” the variable named x
.
Therefore:
- The function named
closure
is said to be a closure of the variable namedx
. - The variable named
x
is said to be an upvalue of the function namedclosure
.
That's all there is to it.
C 语言没有闭包,因为 C 语言中函数的变量都分配到了栈上,当函数返回,sum 的内存空间将失效。然而闭包在 Go 中是有效的,因为 Go 会将 sum 分配到堆内存。
- C has function pointers, which allow passing functions as arguments. It has static local variables, which allow you to keep state between multiple calls of a function. But it doesn't have closures because there's only one copy of the function.
对于 JAVA 这样的面向对象语言,字段这样的变量定义在类里面,生存在类的实例-Object 里面。由于 JS 并不是面向对象语言,因此闭包实际上类似于提供了将字段跟方法关联的手段。
因此任何你需要一个有着单一方法对象的地方你都可以使用闭包。
这种需求在开发 Web 页面的时候很常用:
假设我们需要通过按钮调整字体大小,可以定义一个返回闭包的函数:
function makeSizer(size) {
return function () {
document.body.style.fontSize = size + "px";
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
现在size12
、size14
、size16
分别能够调整网页字体为 12,14 和 16 像素,接着我们可以将函数绑定到具体的按钮上:
document.getElementById("size-12").onclick = size12;
document.getElementById("size-14").onclick = size14;
document.getElementById("size-16").onclick = size16;
定义按钮:
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
JAVA 这样的语言允许你定义私有方法,意味着方法只能被类中的其他方法调用。JAVASCRIPT 并没有原生的方法支持私有方法,但我们可以通过闭包实现这一点。闭包同时也是管理全局命名空间的一种强大办法。
例子:
var counter = (function () {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function () {
changeBy(1);
},
decrement: function () {
changeBy(-1);
},
value: function () {
return privateCounter;
},
};
})();
console.log(counter.value()); // 0.
counter.increment();
counter.increment();
console.log(counter.value()); // 2.
counter.decrement();
console.log(counter.value()); // 1.
上面的例子中changeBy
相当于闭包的私有方法,而increment
、decrement
、value
相当于公开方法
。这三种公开方法是共享了 lexical 作用域的闭包。
当然也可以将生成闭包的函数赋值给一个变量而不是定义后直接调用:
var counter1 = makeCounter();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email" /></p>
<p>Name: <input type="text" id="name" name="name" /></p>
<p>Age: <input type="text" id="age" name="age" /></p>
</body>
</html>
<script>
function showHelp(help) {
document.getElementById("help").textContent = help;
}
function setupHelp() {
var helpText = [
{ id: "email", help: "Your e-mail address" },
{ id: "name", help: "Your full name" },
{ id: "age", help: "Your age (you must be over 16)" },
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
//for 循环返回了三个闭包,但是由于在同一个scope中产生因此三个作用域共用了item变量
document.getElementById(item.id).onfocus = function () {
showHelp(item.help);
};
}
}
setupHelp();
</script>
因此下面的结构体其实也是一种闭包:
type Closure struct {
F func() int
i *int
}
不过使用下面的这种形式来返回是 go 语言经常使用:
func f(i int) func() int {
return func() int {
i++
return i
}
}
或者把变量从参数列表放到函数体(其实是一回事):
package main
import "fmt"
func generateClosure() func(int) int {
var sum int = 0
return func(n int) int {
sum += n
return sum
}
}
func main() {
c := generateClosure()
fmt.Println(c(10))
fmt.Println(c(5))
}
对上面的代码进行 escape 分析:
go build --gcflags=-m closure.go
分析结果:
# command-line-arguments
./closure.go:8:9: can inline generateClosure.func1
./closure.go:16:13: inlining call to fmt.Println
./closure.go:17:13: inlining call to fmt.Println
./closure.go:6:6: moved to heap: sum
./closure.go:8:9: func literal escapes to heap
./closure.go:16:15: c(10) escapes to heap
./closure.go:16:13: []interface {} literal does not escape
./closure.go:17:15: c(5) escapes to heap
./closure.go:17:13: []interface {} literal does not escape
<autogenerated>:1: .this does not escape
- Escape Analysis: is the process that the compiler uses to determine the placement of values that are created by your program. Specifically, the compiler performs static code analysis to determine if a value can be placed on the stack frame for the function constructing it, or if the value must “escape” to the heap.
逃逸分析可以分析变量的作用域,这对于垃圾回收非常有用。