CPPAlien / JS-QA

前端知识问答
0 stars 0 forks source link

stale closure problem #40

Open CPPAlien opened 4 years ago

CPPAlien commented 4 years ago
// Example 0, revisited - this is BUGGY!
function useState(initialValue) {
  var _val = initialValue
  // no state() function
  function setState(newVal) {
    _val = newVal
  }
  return [_val, setState] // directly exposing _val
}
var [foo, setFoo] = useState(0)
console.log(foo) // logs 0 without needing function call
setFoo(1) // sets _val inside useState's scope
console.log(foo) // logs 0 - oops!!

_val 在 useState 函数中首次赋值后,再也没被修改过,而内部函数无法去修改,因为闭包持有了 _val 的引用,更新 _val 并没有更新外部的foo,这其实不是一个闭包问题,而是 javascript 的引用复制问题;类似

let a = {x: 1}
b = a;
b = {x: 2}
console.log(a) // {x: 1},因为 b 指向了一个新的内存区;就和上述情况一样
CPPAlien commented 4 years ago

内存模型

普通函数

function add(num){
     var sum = 5;
     return sum + num;
 }

js函数也是对象,对象拥有自己的属相。scope属性是每个函数都有的属相,这个属性只供js引擎使用。scope属性指向了一个链表(scopeChain),scopeChain保存了函数被创建的时的全局变量。 447940-20151120102024827-1046025470

var sum = add(4)

447940-20151120104314186-2054647680

函数被创建之后才能被执行。函数执行时,会创建一个运行时上下文(execution context),这个运行时上下文做两件事情:

1.运行时上下文会创建自己的scopeChain,然后将创建时的scopeChain中的globalObject拷贝过来

2.创建activation object(活动对象),活动对象的作用是记录函运行时的形参实参与定义的局部变量,然后将活动对象插入到运行时上下文的scopeChain中index为0的位置。每当函数执行过程中遇到变量时,先去搜索活动对象,也就是局部变量,然后再去搜索全局变量。

闭包的模型

父函数执行的时,js引擎发现在函数的内部定义了函数,闭包函数也是函数,也需要为闭包函数记录创建时的上下文,比较特殊的是,此时的父函数处于执行期,父函数的执行期上下文还没有被销毁,刚好,闭包函数就直接引用了父函数的执行器上下文。

447940-20151120113720890-1382021415

根据这个过程,我们可以解释为什么闭包函数可以访问自身的参数与局部变量,父函数的环境以及全局变量。闭包函数遇到一个变量的时候,会从上到下由内而外的搜索变量,因此,尽量将变量定义成局部变量,定义成局部变量可以提高访问速度,局部变量会被gc及时的释放,不会长期的占用内存。闭包能够访问反函数是因为闭包保存了父函数的环境变量的引用。没有内存,就没有闭包。