在我们写代码写到一定阶段的时候,就会想深究一下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函数作用域与闭包的更多相关文章

  1. JavaScript 函数作用域和闭包

    函数作用域和闭包  词法作用域   它们在定义它们的作用域里运行,而不是在执行的作用域运行,但是只有在运行时,作用域链中的属性才被 定义(调用对象),此时,可访问任何当前的绑定.   调用对象     ...

  2. JavaScript从作用域到闭包

    目录 作用域 全局作用域和局部作用域 块作用域与函数作用域 作用域中的声明提前 作用域链 函数声明与赋值 声明式函数.赋值式函数与匿名函数 代码块 自执行函数 闭包  作用域(scope) 全局作用域 ...

  3. 我认知的javascript之作用域和闭包

    说到javascript,就不得不说javascript的作用域和闭包:当然,还是那句老话,javascript在网上都说得很透彻了,我也就不过多的强调了: 作用域:javascript并没有像其他的 ...

  4. 浅谈JavaScript 函数作用域当中的“提升”现象

    在JavaScript当中,定义变量通过var操作符+变量名.但是不加 var 操作符,直接赋值也是可以的. 例如 : message = "hello JavaScript ! " ...

  5. JavaScript 函数作用域的“提升”现象

    在JavaScript当中,定义变量通过var操作符+变量名.但是不加 var 操作符,直接赋值也是可以的.例如 : message = "hello JavaScript ! " ...

  6. JavaScript函数表达式、闭包、模仿块级作用域、私有变量

    函数表达式是一种非常有用的技术,使用函数表达式可以无需对函数命名,从而实现动态编程.匿名函数,是一种强大的方式,一下总结了函数表达式的特点: 1.函数表达式不同于函数声明,函数声明要求有名字,但函数表 ...

  7. javascript的作用域和闭包(三)闭包与模块

    一些很重要的说明:前面三篇博客详细的介绍了,引擎与编译器和作用域的关系,重点需要理解的是编译器中的分词与词法分析,JavaScript的特有的“赋值操作的左右侧”引用操作:编译阶段的词法作用域的工作原 ...

  8. 深入理解javascript函数参数与闭包(一)

    在看此文章,希望先阅读关于函数基础内容 函数定义与函数作用域 的章节,因为这篇文章或多或少会涉及函数基础的内容,而基础内容,我放在函数定义函数作用域 章节. 本文直接赘述函数参数与闭包,若涉及相关知识 ...

  9. JavaScript之作用域与闭包详解

    前言: JavaScript是一种应用非常广泛的语言,其也有一些自身特点和优势,本文重在讲述其作用域机制以及闭包,会从一些实例来探讨其机理. 作用域在JavaScript程序员日常使用中有不同的含义, ...

随机推荐

  1. 可能是Mac环境变量恢复的参考

    因为要做物联网实验的缘故,于是在Mac上用Android Studio想导入SensorSimulator的demo项目. 根据SensorSimulator的相关说明,需要先将Sensor Simu ...

  2. study java language

    2016.11.30 1).About the Java Technology 2).The Java Language Environment: Contents

  3. AFNetworking 3.0

    AFN 一.什么是AFN 全称是AFNetworking,是对NSURLConnection的一层封装 虽然运行效率没有ASI高,但是使用比ASI简单 在iOS开发中,使用比较广泛 AFN的githu ...

  4. 【Cocos2d-x 3.x】内存管理机制与源码分析

    侯捷先生说过这么一句话 :  源码之前,了无秘密. 要了解Cocos2d-x的内存管理机制,就得阅读源码. 接触Cocos2d-x时, Cocos2d-x的最新版本已经到了3.2的时代,在学习Coco ...

  5. some simple recursive lisp programs

    1. Write a procedure count-list to count the number of elements in a list (defun count-list (numbers ...

  6. Android 的 DatePicker、TimePicker或NumberPicker

    布局文件加上这个就可以,去除日期选择器.时间选择器或数值选择器的可编辑状态. android:descendantFocusability="blocksDescendants" ...

  7. float和double精度问题

    System.out.println(new BigDecimal(253.90).doubleValue() * 100);25390.0精度正确 System.out.println(new Bi ...

  8. hibernate查询语句hql中的占位符?参数与命名参数:name设值方式搞混

    先贴出异常 Struts has detected an unhandled exception: Messages: Position beyond number of declared ordin ...

  9. it市场

    SAP收购Sybase意欲何为 http://tech.diannaodian.com/dw/data/sybase/2012/0925/199681.html 德国行业管理软件解决方案提供商SAP宣 ...

  10. 总结.NET 中什么时候用 Static

    静态类和类成员用于创建无需创建类的实例就能够访问的数据和函数.当类中没有依赖对象标识的数据或行为时,就可以使用静态类.静态类成员是可独立于任何对象标识的数据和行为,即无论对象发生什么更改,这些数据和函 ...