资讯专栏INFORMATION COLUMN

Javascript闭包:从理论到实现,[[Scopes]]的每一根毛都看得清清楚楚

Chiclaim / 503人阅读

摘要:昨天我写到所有函数都是闭包,有些同学表示还是接受不能。换句话说,它正是函数和声明该函数的词法环境的组合。原因详见我的上一篇文章从过程式到函数式。我的相关文章闭包从过程式到函数式以上所有代码按授权。

昨天我写到“所有Javascript函数都是闭包”,有些同学表示还是接受不能。我好好的一个函数,怎么就成闭包了?那么,让我们来探究一下,Chrome(V8)到底是怎样实现闭包的。

从闭包到[[Scopes]]

现在按下F12,打开console,让我们随便找一个实验对象:

function simpleFunc() { }
// <- undefined

超简单超正常的函数吧,我们来验证一下:

simpleFunc
// <- ƒ simpleFunc() { }

说了超正常的,哪里闭包了?现在试试这个:

console.dir(simpleFunc)
// ƒ simpleFunc()
//   arguments: null
//   caller: null
//   length: 0
//   name: "simpleFunc"
//   prototype: {constructor: ƒ}
//   __proto__: ƒ ()
//   [[FunctionLocation]]: VM000:1
//   [[Scopes]]: Scopes[1]

咦,[[Scopes]]是什么?打开一看:

//   [[Scopes]]: Scopes[1]
//     0: Global {type: "global", name: "", object: Window}

这就是闭包的实现。东西都存在这里了。看起来simpleFunc只不过是纯洁的函数,但它实际上是(空的)自身代码+全局变量环境。换句话说,它正是“函数和声明该函数的词法环境的组合”。

再来个稍微复杂点的例子:

{
  let localVar = 1;
  function dirtyFunc() { return localVar++ }
}
// <- ƒ dirtyFunc() { return localVar++ }
console.dir(dirtyFunc)
// ƒ dirtyFunc()
//   [[Scopes]]: Scopes[2]
//     0: Block
//       localVar: 1
//     1: Global {type: "global", name: "", object: Window}

看,localVar存在这里了吧!大家老说什么“保持运行的数据状态”云云,其实都在[[Scopes]]里。dirtyFunc看起来是个普通的函数,但[[Scopes]]里却混了些东西。

所以,如果我们说人话,闭包实际上就是——

函数的代码+[[Scopes]]

超级好理解了吧。

宁愿用this也不用闭包

接下来让我们对闭包做些更深入的解析,然后就知道为什么大家宁愿用this也不用闭包了。

[[Scopes]]能用代码访问/复制/修改吗?

不能。想不靠console,找到副作用在哪儿?不行。想深拷贝目前状态?不行。想历史回放?不行。debug?自己慢慢琢磨去吧!

闭包会把所有东西都存下来吗?
{
  let localVar = 1;
  let unusedVar = 2;
  function dirtyFunc2() { return localVar++ }
}
console.dir(dirtyFunc2)
// ƒ dirtyFunc()
//   [[Scopes]]: Scopes[2]
//     0: Block
//       localVar: 1
//     1: Global {type: "global", name: "", object: Window}

至少Chrome是不会把所有东西都塞到闭包里的。

那闭包对垃圾回收没害处?
{
  let localVar = new Uint8Array(1000000000)
  function dirtyFunc3() { return localVar }
  function cleanFunc() { }
}
var dirtyFunc3 = null
console.dir(cleanFunc)
// ƒ cleanFunc()
//   [[Scopes]]: Scopes[2]
//     0: Block
//       localVar: Uint8Array(1000000000) [0, 0, …]
//     1: Global {type: "global", name: "", object: Window}

dirtyFunc3cleanFunc共享同一个[[Scopes]]项,但这个[[Scopes]]项并不会因为dirtyFunc3被回收而动态更新!所以无辜的cleanFunc就只好一直带着这1GB的垃圾,内存泄漏妥妥的。作为强迫症,这是我讨厌闭包最重要的原因。

真的所有Javascript函数都是闭包吗?
console.dir(alert)
// ƒ dirtyFunc()
//   [[Scopes]]: Scopes[0]
//     No properties

抱歉,我可能是不太严谨。很明显,浏览器自带的原生API函数都是在【里世界】声明的,所以没有词法环境,自然[[Scopes]]是空的。它们不是闭包。

最佳实践

宁愿用this也不用闭包。原因详见我的上一篇文章(从过程式到函数式)。

我的相关文章

Javascript闭包:从过程式到函数式

以上所有代码按Mozilla Public License, v. 2.0授权。
以上所有文字内容按CC BY-NC-ND 4.0授权。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95497.html

相关文章

  • Javascript闭包过程式函数式

    摘要:想方设法糅合过程式与函数式两种风格,忽略了闭包的基本假设,于是造出天坑。分散在各个闭包中的状态会成为的温床。当然,严格来说箭头函数也是一种闭包,因为它是函数和词法的组合。 编程语言的究极问题:过程式还是函数式? 闭包是函数式编程最先引进的,基本假设就是所有量都是常量。Javascript想方设法糅合过程式与函数式两种风格,忽略了闭包的基本假设,于是造出天坑。 是什么 闭包的定义是函数和...

    leone 评论0 收藏0
  • JavaScript基础系列---闭包及其应用

    摘要:所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。所以本文中将以维基百科中的定义为准即在计算机科学中,闭包,又称词法闭包或函数闭包,是引用了自由变量的函数。 闭包(closure)是JavaScript中一个神秘的概念,许多人都对它难以理解,我也一直处于似懂非懂的状态,前几天深入了解了一下执行环境以及作用域链,可戳查看详情,而闭包与作用域及作用域链的关系密不可分,所...

    leoperfect 评论0 收藏0
  • 前端学习笔记之闭包——看了一张图终于明白啥是闭包

    摘要:在一个闭包环境内修改变量值,不会影响另一个闭包中的变量。直到看到函数闭包闭包这篇文章的代码一部分,终于明白其中的逻辑了。 闭包 闭包定义:指拥有多个变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。函数内部可以直接读取全局变量。函数内部变量无法在函数外部访问。函数内部声明要用var或者let声明,不然会变成全局变量链式作用域:子对象会一级级向上寻找...

    andycall 评论0 收藏0
  • PyTips 0x04 - Python 闭包与作用域

    摘要:项目地址闭包在计算机科学中,闭包英语,又称词法闭包或函数闭包,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。 项目地址:https://git.io/pytips 闭包(Closure) 在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是...

    leejan97 评论0 收藏0
  • Lexical environments: ECMAScript implementation

    摘要:全局环境是作用域链的终点。环境记录类型定义了两种环境记录类型声明式环境记录和对象环境记录声明式环境记录声明式环境记录是用来处理函数作用域中出现的变量,函数,形参等。变量环境变量环境就是存储上下文中的变量和函数的。解析的过程如下 原文 ECMA-262-5 in detail. Chapter 3.2. Lexical environments: ECMAScript implement...

    roadtogeek 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<