Javascript函数闭包详解(通俗易懂
许多书上闭包过于复杂讲解难懂,自己理解了一下并总结啦~
讲闭包之前,需要先明白以下几个概念。
总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
1、执行上下文(execution context)
每创建一个函数同时就会创建一个执行环境,也就是执行上下文。全局执行上下文就是global环境,一个函数内部的当前执行环境就是当前执行上下文。
执行上下文定义了变量或函数有权访问其他数据,决定了他们各自的行为 。
2、执行上下文堆栈
活动的执行上下文在逻辑上组成一个堆栈。堆栈底部永远都是全局上下文(globalContext),而顶部就是当前(活动的)执行上下文。
当一个函数被创建且被调用时,在函数内部,当前的函数执行上下文被压入栈,若内部还有函数,则继续压入栈顶。栈底部永远是全局执行上下文。
3、变量对象
每个执行上下文中有会有与之关联的变量对象,在上下文中定义的所有变量和函数都会放在这里面。如果在函数中,我们称之为活动对象。
可以说变量对象是与执行上下文相关的数据作用域(scope of data) 。它是与执行上下文关联的特殊对象,用于存储被定义在执行上下文中的变量(variables)、函数声明(function declarations) 。
4、作用域链
作用域链是用来指向变量对象的,作用域的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域的最前端,始终都是当前执行的代码所在环境的变量对象。
子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
var f1 = function() {
function f2() {
function f3() {}
}
}
//f3中 作用域链就是 f3活动对象 -> f2活动对象 -> f1活动对象 -> global活动对象
可以开始讲闭包啦
定义:闭包是指有权访问另一个函数作用域中的变量的函数。
是不是有点过于抽象了?举个例子
var outter = function() {
var a = 1;
function inner() {
return a;
}
return inner; //返回里面这个函数
}
var result = outter(); //外部得到了返回的里面那个函数
console.log(result()); //1
由JavaScript的作用域链特性可知,在函数里面可以访问到外部的变量,但反过来是不行的。但上面这个例子做到了,这就是闭包。
既然inner可以读取outter中的局部变量,那么只要把inner作为返回值,我们不就可以在outter外部读取它的内部变量了吗!
上一节代码中的inner函数,就是闭包。
各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
使用闭包时常出现的一些问题
1、闭包只能取得包含函数中任何变量的最后一个值
好像有点抽象。不要忘记,闭包所保存的是整个变量对象,而不是某个特殊的变量。其实这里可以把它想象成类似于一个引用。
function createArray() {
var result = new Array();
for(var i = 0;i<10;i++) {
result[i] = function() {
return i;
};
}
return result;
}
var r = createArray();
console.log(r);
for(var i=0;i<10;++i)
console.log(r[i]()); //10个10
原因在于闭包保存的是整个变量对象,因此每个函数中都保存一样的变量对象。它们引用的都是同一个变量i。所以在执行闭包时,i已经执行到10,返回的i自然便都是10。
2、关于this对象
我们知道,this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数作为某个对象的方法调用时,this等于那个对象。
不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window
var name = "The Window";
var object = {
name: "My object",
getNameFunc: function() {
return function() {
return this.name;
};
}
}
console.log(object.getNameFunc()()); //The Window
为什么最后的结果是"The Window"而不是object里面的name"My object"呢?
首先,要理解函数作为函数调用和函数作为方法调用。
我们把最后的一句拆成两个步骤执行:
var first = object.getNameFunc();
var second = first
其中
- 第一步,获得的first为返回的匿名函数,此时的getNameFunc()作为object的方法调用,如果在getNameFunc()中使用this,此时的this指向的是object对象。
- 第二步,调用first函数,可以很清楚的发现,此时调用first函数,first函数没有在对象中调用,因此是作为函数调用的,是在全局作用域下,因此first函数中的this指向的是window。
为什么匿名函数没有取得其包含作用域(外部作用域)的this对象呢?
每个函数被调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。
那么,怎么获得外部作用变量中的this呢?
把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。
var name = "The Window";
var object = {
name: "My object",
getNameFunc: function() {
var name = 19;
var that = this;
return function() {
return that.name;
};
}
}
console.log(object.getNameFunc()()); //My object
此时调用,getNameFunc执行时的活动变量有哪些?name that function。在执行匿名函数时,同时引用了getNameFunc()中的活动对象,因此可以获取that和age的值。但是由于是在全局环境中调用的匿名函数,因此匿名函数内部的this还是指向window。
闭包用途总结
1、可以读取函数内部的变量,建立函数内部与外部的桥梁;
2、让一些变量的值始终保持在内存中。
使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
《JavaScript高等程序设计第3版》
Javascript函数闭包详解(通俗易懂的更多相关文章
- JavaScript的闭包详解
(1)定义: 函数内部返回一个函数,返回出来的这个函数叫做被我们称之为闭包(个人理解的最简单的表现形式,) (2)为什么要使用闭包呢? 局部变量在函数执行完之后就会被GC回收,有时候我们想在外部访问内 ...
- javascript:function 函数声明和函数表达式 详解
函数声明(缩写为FD)是这样一种函数: 有一个特定的名称 在源码中的位置:要么处于程序级(Program level),要么处于其它函数的主体(FunctionBody)中 在进入上下文阶段创建 影响 ...
- 从mixin到new和prototype:Javascript原型机制详解
从mixin到new和prototype:Javascript原型机制详解 这是一篇markdown格式的文章,更好的阅读体验请访问我的github,移动端请访问我的博客 继承是为了实现方法的复用 ...
- JavaScript严格模式详解
转载自阮一峰的博客 Javascript 严格模式详解 作者: 阮一峰 一.概述 除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict m ...
- eval()函数用法详解
eval()函数用法详解:此函数可能使用的频率并不是太高,但是在某些情况下具有很大的作用,下面就介绍一下eval()函数的用法.语法结构: eval(str) 此函数可以接受一个字符串str作为参数, ...
- Python闭包详解
Python闭包详解 1 快速预览 以下是一段简单的闭包代码示例: def foo(): m=3 n=5 def bar(): a=4 return m+n+a return bar >> ...
- JavaScript运行机制详解
JavaScript运行机制详解 var test = function(){ alert("test"); } var test2 = function(){ alert(& ...
- [转]JavaScript异步机制详解
原文: https://www.jianshu.com/p/4ea4ee713ead --------------------------------------------------------- ...
- js课程 1-3 Javascript变量类型详解
js课程 1-3 Javascript变量类型详解 一.总结 一句话总结:js对象点(属性方法),json对象冒号(属性方法).属性和方法区别只有一个括号. 1.json对象中的函数的使用? 函数名 ...
随机推荐
- undef用法
#undef的语法 定义:#undef 标识符,用来将前面定义的宏标识符取消定义. 整理了如下几种#undef的常见用法. 1. 防止宏定义冲突在一个程序块中用完宏定义后,为防止后面标识符冲突需要取消 ...
- nginx判定国家ip访问网站
我们可以通过GeoIP模块和MaxMind免费数据库来实现.MaxMind具有新版本的数据库GeoLite2,它仅支持CSV和mmdb格式.可以支持mngx_http_geoip2_modulemdb ...
- 性能测试之数据库监控分析工具PMM
PMM(Percona Monitoring and Management) 是一个用于管理和监控数据库性能的开源平台. . 它能提供全面的基于时间和各类监控指标.计数器的分析. 性能测试中,是做数据 ...
- CF#637 D. Nastya and Scoreboard DP
D. Nastya and Scoreboard 题意 一块电子屏幕上有n个数字. 每个数字是通过这样7个线段显示的,现在你不小心打坏了k个线段,给出打坏之后的n个数字的显示方式,问之前的屏幕表示的最 ...
- DNS注入以获取WebShell -asp_POST_DNS_SQLServer_GetWebShell
豹子安全-注入工具-asp_POST_DNS_SQLServer_GetWebShell 注意,是 DNS 注入.这种方式的注入会隐藏很多细节, 对于用户来说是透明的. 请看如下视频(该视频会停留15 ...
- python--遇到SyntaxError: Non-UTF-8 code starting with '\xb8' in file
在运行python中因为添加了中文注释,遇到SyntaxError: Non-UTF-8 code starting with '\xb8' in file 经过百度,说是Python的默认编码格式是 ...
- jmeter学习之安装篇(一)
一.Jmeter简介 Jmeter是Apache组织开发的基于Java开源项目,设计之初是用于做性能测试的,同时它在实现对各种接口的调用方面做的比较成熟,因此,常被用做接口功能测试和性能测试. Jme ...
- Nginx 、MySQL、Django 在 Docker-compose 中的部署
概述 本文主要记录了在 Linux 3.10.0-1062.el7.x86_64 下使用 docker-compose 搭建服务的过程,由于公司服务器在内网中,搭建镜像及下载依赖时需要外部代理,如果本 ...
- Java 中的过滤器Filter 和拦截器 Interceptor
1.先说拦截器 Interceptor 本项目以springboot为例: 新建 InterceptorConfig package com.opendev.mystudy.MyInterceptor ...
- jQuery的面试题
1.$的原理 答案: 1)$("选择器")是先查找DOM元素,再将DOM元素放入jQuery对象中 其中自带优化: 如果选择器是#id,则自动调用getElementById 如果 ...