Js中的闭包理解
title: JS中的闭包
date: 2019-07-21 17:01:11
tags: [javascript]
categories: [web]
JS中的闭包
闭包就是跨作用域访问变量
闭包是指有权访问另外一个函数作用域中的变量的函数.可以理解为(能够读取其他函数内部变量的函数)
测试1
var name = 'wangxi' function user () { // var name = 'wangxi' function getName () { console.log(name) } getName() } user() // wangxi
在 getName 函数中获取 name,首先在 getName 函数的作用域中查找 name,未找到,进而在 user 函数的作用域中查找,
同样未找到,继续向上回溯,发现在全局作用域中存在 name,因此获取 name 值并打印。
这里很好理解,即变量都存在在指定的作用域中,如果在当前作用中找不到想要的变量,则通过作用域链向在父作用域中继续查找,
直到找到第一个同名的变量为止(或找不到,抛出 ReferenceError 错误)。这是 js 中作用域链的概念,
即子作用域可以根据作用域链访问父作用域中的变量,那如果相反呢***,
在父作用域想访问子作用域中的变量呢?——这就需要通过闭包来实现。***
function user () { var name = 'wangxi' return function getName () { return name } } var userName = user()() console.log(userName) // wangxi
分析代码我们知道,name 是存在于 user 函数作用域内的局部变量,正常情况下,在外部作用域(这里是全局)中是无法访问到 name 变量的,但是通过闭包(返回一个包含变量的函数,这里是 getName 函数),可以实现跨作用域访问变量了(外部访问内部)。因此上面的这种说法完整的应该理解为:
闭包就是跨作用域访问变量 —— 内部作用域可以保持对外部作用域中变量的引用从而使得外部作用域可以访问内部作用域中的变量。
测试2
<script> function user(){ var a = 1; return function(){ console.log(a++); }; } /**** 1)aaa 是将 user() 的运行结果赋值给它,即 return 返回的匿名函数, 此时有一个闭包,则每次调用 aaa 时都访问的同一个 a,aaa() 第一次运行结果为 1,第二次为2 2)bbb 将是将 user 这个函数名赋值给它,则调用 bbb() 后返回一个匿名函数表达式, 即function(){console.log(a++)}; ****/ var aaa = user(); console.log(aaa); //ƒ(){console.log(a++);} console.log(aaa()); //1 undefined aaa(); //2 aaa(); //3 aaa(); //4 var bbb = user; console.log(bbb); //ƒ user(){var a = 1;return function(){console.log(a++);};} console.log(bbb()); //ƒ(){console.log(a++);} console.log(bbb()()); //1 undefined console.log(bbb()()); //1 undefined </script>
闭包就是一个引用了父环境的对象,并且从父环境中返回到更高层的环境中的一个对象
function user () { var name = 'wangxi' return name } var userName = user() console.log(userName) // wangxi
问:这是闭包吗?
答:当然不是。首先要明白闭包是什么。虽然这里形式上看好像也是在全局作用域下访问了 user 函数内的局部变量 name,但是问题是,user 执行完,name 也随之被销毁了,即函数内的局部变量的生命周期仅存在于函数的声明周期内,函数被销毁,函数内的变量也自动被销毁。
但是使用闭包就相反,函数执行完,生命周期结束,但是通过闭包引用的外层作用域内的变量依然存在,并且将一直存在,直到执行闭包的的作用域被销毁,这里的局部变量才会被销毁(如果在全局环境下引用了闭包,则只有在全局环境被销毁,比如程序结束、浏览器关闭等行为时才会销毁闭包引用的作用域)。因此为了避免闭包造成的内存损耗,建议在使用闭包后手动销毁。
function user () { var name = 'wangxi' return function getName () { return name } } var userName = user()() // userName 变量中始终保持着对 name 的引用 console.log(userName) // wangxi userName = null // 销毁闭包,释放内存
为什么 user()() 是两个括号:执行 user() 返回的是 getName 函数,要想获得 name 变量,需要对返回的 getName 函数执行一次,所以是 user()()
分析一下代码:在全局作用域下创建了 userName 变量(爷爷),保存了对 user 函数最终返回结果的引用(即局部变量 name 的值),执行 user()()(爸爸),返回了 name(孙子),正常情况下,在执行了 user()() 之后,user 的环境(爸爸)应该被清除掉,但是因为返回的结果 name(孙子)引用了爸爸的环境(因为 name 本来就是存在于 user 的作用域内的),导致 user 的环境无法被释放(会造成内存损耗)。
那么【“闭包就是一个引用了父环境的对象,并且从父环境中返回到更高层的环境中的一个对象。”】如何理解?
我们换个说法:如果一个函数引用了父环境中的对象,并且在这个函数中把这个对象返回到了更高层的环境中,那么,这个函数就是闭包。
解析:getName 函数中引用了 user(父)环境中的对象(变量 name),并且在函数中把 name 变量返回到了全局环境(更高层的环境)中,因此,getName 就是闭包。
JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里
var name = 'Schopenhauer' function getName () { console.log(name) } function myName () { var name = 'wangxi' getName() } myName() // Schopenhauer
如果执行 myName() 输出的结果和你想象的不一样,你就要再回去看看上面说的这句话了。
JavaScript 中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里执行 myName,函数内部执行了 getName,而 getName 是在全局环境下定义的,因此尽管在 myName 中定义了变量 name,对getName 的执行并无影响,getName 中打印的依然是全局作用域下的 name。
测试:JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里
var name = 'Schopenhauer' function getName () { var name = 'Aristotle' var intro = function() { // 这是一个闭包 console.log('I am ' + name) } return intro } function showMyName () { var name = 'wangxi' var myName = getName() //得到intro函数 myName() //执行intro() } showMyName() // I am Aristotle
总结
什么是闭包?
简单来说,闭包是指可以访问另一个函数作用域变量的函数,一般是定义在外层函数中的内层函数。
为什么需要闭包?
局部变量无法共享和长久的保存,而全局变量可能造成变量污染,所以我们希望有一种机制既可以长久的保存变量又不会造成全局污染。
特点
- 占用更多内存
- 不容易被释放
何时使用?
变量既想反复使用,又想避免全局污染
如何使用?
- 定义外层函数,封装被保护的局部变量。
- 定义内层函数,执行对外部函数变量的操作。
- 外层函数返回内层函数的对象,并且外层函数被调用,结果保存在一个全局的变量中。