剖析JavaScript函数作用域与闭包
在我们写代码写到一定阶段的时候,就会想深究一下js,javascript是一种弱类型的编程语言,而js中一个最为重要的概念就是执行环境,或者说作用域。作用域重要性体现在哪呢?首先,函数在执行时会创建作用域链,我们可以通过作用域链,向上一层一层的找到我们想要的变量。其次,搞清楚作用域,有利于深层次的理解闭包。再次,可以写出更加优化的代码,提高执行速度,防止内存溢出。可以说,作用域是一个核心。
如果大家知道c语言,可以知道块级作用域的特点,每一块都是局部的作用域,在if(){}外边是访问不了if里面的变量的。但是js没有块级作用域概念,相对的是函数的作用域(执行环境)。如果我们把js跟c或c++结合起来理解的话,函数的执行就容易理解。
相信大家都知道,作用域(执行环境)分为全局作用域和局部作用域。在函数外部定义的变量或函数,都是具有全局作用域,而函数里面定义的变量,则具有局部作用域。
任何函数的执行依赖于函数中的变量作用域,变量作用域是在函数定义时决定的,而不是在函数执行时决定的。
先说说普通函数的执行。
function fn1(){
var test = 1;
fn2();
alert(test);
}
function fn2(){
test++;
}
fn1();
那结果是几呢?对不起,会直接报错。这就是上面所说的,虽然fn2执行时,会创建执行环境,但是fn2的作用域链中并没有fn1,因为fn2中的test的作用域是在fn2函数定义时确定的,画图表示就是:
从这个作用域链上,可以看出来,fn2在执行时,本身找不到test变量,则会顺着作用域链向上找,上层作用链就是全局变量,而不是fn1的作用域里的变量。在全局变量对象中找不到test变量,那么就报错了。
那么如果把代码变化一下:
function fn1(){
var test = 1;
(function fn2(){
test++;
}())
alert(test);
} fn1(); //
fn1(); //
这时结果就不会报错了,稍稍改动,变化如此之大,这是为什么?相信大家很清楚了。再画一下作用域链图:
在上面的图中出现了活动对象,什么是活动对象?
当fn1执行时,fn1会被压入栈中执行,这时会创建fn1的执行环境(作用域)。然后会使用arguments和参数、变量初始化活动对象。这个活动对象也就是变量对象。
,当执行到fn2时,又会把fn2函数压入栈顶,执行fn2,创建执行环境、作用域链、活动对象,当fn2执行完成后,fn2的执行环境就会销毁,作用域链和活动对象也会被销毁,然后把执行权还给fn1执行。执行到alert(test),把2弹出。
当是闭包时,情况就有很大的不同。(从技术上,每个函数都是一个闭包,这里的闭包讨论的是一个函数返回另外一个函数的情况)
function outer(){
var a= 0;
return function inner(){
return a++;
}
}
var result = outer();
console.log(result()); //
console.log(result()); //
console.log(result()); //
这个结果不是0,而是a进行了累加。这的原理又是什么呢?
原因还是函数的作用域链是在定义时确定的,而且在函数执行时仍然存在。
分析一下执行的步骤:
当执行outer()时,会把outer函数推入栈,当return时,返回的是一个函数而不是一个数值。这个inner()函数在定义时就有作用域链,作用域链中有外层outer函数中的变量,当outer返回后,inner()函数的作用域链还存在着,也就是我们平时说的inner函数占用了outer函数中的a变量。那为什么inner函数的作用域链中会有outer函数的变量呢?原因是在一个函数内部定义的函数会将包含函数(即外层函数)的活动对象添加到它的作用域链中。
再在上面的基础上继续变化一下,看看结果如何变化:
function outer(){
var a= 0;
return function inner(){
return a++;
}
}
var result = outer();
console.log(result()); //
console.log(result()); //
console.log(result()); // var ox = outer()();
console.log(ox);
这时,console.log(ox)的值是多少呢? 答案是0,而不是3
原因也很简单,函数每次运行,都会产生独立的执行环境(作用域),也就是说两次outer()的执行没有关系。上一次outer()执行完以后,它的执行环境早就已经释放了。
使用闭包有明显的好处,可以分别保存变量的值。特别在处理一些点击事件时,需要分别保存不同的变量值。但是闭包还是要慎用,因为闭包会使一些变量无法释放,占用内存,不利于优化。
闭包优化方法:
在我们不再使用用返回的值是,将其置为null
如把result和ox置为null,就可以解除对函数的引用,释放内存。
function outer(){
var a= 0;
return function inner(){
return a++;
}
}
var result = outer();
console.log(result()); //
console.log(result()); //
console.log(result()); // var ox = outer()();
console.log(ox);
result = null;
ox = null;
以上是我对作用域和闭包的一些认识,有错误或不足的地方欢迎大家指正!
剖析JavaScript函数作用域与闭包的更多相关文章
- JavaScript 函数作用域和闭包
函数作用域和闭包 词法作用域 它们在定义它们的作用域里运行,而不是在执行的作用域运行,但是只有在运行时,作用域链中的属性才被 定义(调用对象),此时,可访问任何当前的绑定. 调用对象 ...
- JavaScript从作用域到闭包
目录 作用域 全局作用域和局部作用域 块作用域与函数作用域 作用域中的声明提前 作用域链 函数声明与赋值 声明式函数.赋值式函数与匿名函数 代码块 自执行函数 闭包 作用域(scope) 全局作用域 ...
- 我认知的javascript之作用域和闭包
说到javascript,就不得不说javascript的作用域和闭包:当然,还是那句老话,javascript在网上都说得很透彻了,我也就不过多的强调了: 作用域:javascript并没有像其他的 ...
- 浅谈JavaScript 函数作用域当中的“提升”现象
在JavaScript当中,定义变量通过var操作符+变量名.但是不加 var 操作符,直接赋值也是可以的. 例如 : message = "hello JavaScript ! " ...
- JavaScript 函数作用域的“提升”现象
在JavaScript当中,定义变量通过var操作符+变量名.但是不加 var 操作符,直接赋值也是可以的.例如 : message = "hello JavaScript ! " ...
- JavaScript函数表达式、闭包、模仿块级作用域、私有变量
函数表达式是一种非常有用的技术,使用函数表达式可以无需对函数命名,从而实现动态编程.匿名函数,是一种强大的方式,一下总结了函数表达式的特点: 1.函数表达式不同于函数声明,函数声明要求有名字,但函数表 ...
- javascript的作用域和闭包(三)闭包与模块
一些很重要的说明:前面三篇博客详细的介绍了,引擎与编译器和作用域的关系,重点需要理解的是编译器中的分词与词法分析,JavaScript的特有的“赋值操作的左右侧”引用操作:编译阶段的词法作用域的工作原 ...
- 深入理解javascript函数参数与闭包(一)
在看此文章,希望先阅读关于函数基础内容 函数定义与函数作用域 的章节,因为这篇文章或多或少会涉及函数基础的内容,而基础内容,我放在函数定义函数作用域 章节. 本文直接赘述函数参数与闭包,若涉及相关知识 ...
- JavaScript之作用域与闭包详解
前言: JavaScript是一种应用非常广泛的语言,其也有一些自身特点和优势,本文重在讲述其作用域机制以及闭包,会从一些实例来探讨其机理. 作用域在JavaScript程序员日常使用中有不同的含义, ...
随机推荐
- 记录一些PHP7RCC1编译问题
1,php7rc1源码编译undefined symboles的问题 自己计划将php7环境部署到cubieboard上,懒得去找别人预编译的版本,所以动手从源码编译,中间遇到了一个小问题,此处记录一 ...
- ABAP 生产订单的创建与修改函数
ABAP 生产订单的创建与修改函数转自http://www.cnblogs.com/aBaoRong/archive/2012/04/11/2441946.html 如果生产订单过多,可以批量创建 ...
- 新冲刺Sprint3(第四天)
一.Sprint介绍 实现了SQLite数据库记录自动登录.注销功能 真机测试效果图: 二.Sprint周期 看板: 燃尽图:
- css 设置背景图片模糊,内容不模糊
需求:一个div设置了background: url,现在需要使图片背景模糊,div内的文字清晰显示. 原始代码: <!DOCTYPE html> <html lang=" ...
- SpringMVC中使用Cron表达式的定时器
SpringMVC中使用Cron表达式的定时器 cron(定时策略)简要说明 顺序: 秒 分 时 日 月 星期 年份 (7个参数,空格隔开各个参数,年份非必须参数) 通配符: , 如果分钟位置为* 1 ...
- 较老版本的AFNetworking使用心得
较老版本的 AFNetworking 下载链接 ( http://pan.baidu.com/s/14Cxga ) 将压缩包中的文件夹拖入xcode工程项目中并引入如下的框架 简单的 JOSN 解析例 ...
- cloudera manager安装spark后使用spark shell编写基于scala的world count
val file = sc.textFile("hdfs://zhcloudil-lcnode04:8020/user/cloudil/wc_spark.txt") val cou ...
- Django后台post请求中的csrf token
使用Requests库操作自己的Django站点,post登陆admin页面返回403,serverlog显示csrf token not set. csrf token是get登陆页面时服务器放在c ...
- UVA 1395 (kruskal)
/* 最大路与最小路的问题: 这道题看似简单,但是若不知道思路将无法写出. 思路:最小生成树很容易求出,但是最大值与最小值只差很难保证是最小的, 比如:1 5 5 6 100 101 很明显101 - ...
- 简易c语言文法
<程序>→<外部声明>|<程序><外部声明> <外部声明>→<函数定义>|<声明> <函数定义>→< ...