Skip to content

Latest commit

 

History

History
338 lines (243 loc) · 8.71 KB

每周学习分享.md

File metadata and controls

338 lines (243 loc) · 8.71 KB

Event Loop

V8 有一个 Call Stack 跟 Memory Heap。

Memory heap 用于存放跟读取数据的地方,毕竟程序只是进行了读写操作。

翻译成 heap 的语言就是分配以及释放内存。

call stack 的作用是帮助我们记录程序执行到了哪一步,然后我们可以按照顺序执行代码。

由于 JS 只有一个线程,意味着它只有一个 call stack 然后在同一时间只能干一件事情。

const name = "george";

上面的代码在说请给变量name分配内存,然后让内存指向堆中的"george"

Stack Overflow

function foo() {
  return foo();
}

foo();
function computeMaxCallStackSize() {
  try {
    return 1 + computeMaxCallStackSize();
  } catch (e) {
    // Call stack overflow
    return 1;
  }
}

上面的函数执行结果在不同的环境中不同:

能够循环调用的次数取决于两个因素:

  1. 栈的大小
  2. 栈帧的大小

Call Stack 例子

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);

什么是 blocking?

可以理解为 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 QueueEvent Loop重新放到 call stack 中。

Event Loop 只有一项简单的任务要做:

它查看 Call Stack 跟 Callback Queue,只要 call stack 是空的,它将推送 callback queue 中的第一个 callback 函数到 call stack

Closure

什么是 Closure

闭包就是函数以及这个函数的引用环境。闭包是一个包含了自由变量的代码块,自由变量在内存中被定义在代码块所处的 context。

再看看一个比较通俗易懂的闭包定义:

Consider:

something closes over something else
|_______| |_________| |____________|
    |          |             |
 subject      verb        object

Here:

  1. The subject is the closure. A closure is a function.
  2. The closure “closes over” (as in encloses) the set of its free variables.
  3. 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:

  1. The function named add has only one variable, named x, which is not a free variable because it is defined within the scope of add itself.
  2. The function named closure has two variables, named x and y, out of which x is a free variable because it is defined in the scope of add (not closure) and y is not a free variable because it is defined in the scope of closure itself.

Hence, in the second case the function named closure is said to “close over” the variable named x.

Therefore:

  1. The function named closure is said to be a closure of the variable named x.
  2. The variable named x is said to be an upvalue of the function named closure.

That's all there is to it.

C 没有闭包

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.

Javascript 需要闭包

对于 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);

现在size12size14size16分别能够调整网页字体为 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相当于闭包的私有方法,而incrementdecrementvalue相当于公开方法 。这三种公开方法是共享了 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.

逃逸分析可以分析变量的作用域,这对于垃圾回收非常有用。