在我们写代码写到一定阶段的时候,就会想深究一下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. Java 在指定目录建立指定文件名的文件 并输入内容

    package runoob; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream ...

  2. MySQL, 创建一个只读用户和一个所有权限用户

    安装pasa需要配置mysql.基本知识学习一下 http://www.cnblogs.com/mr-wid/archive/2013/05/09/3068229.html MySQL 为关系型数据库 ...

  3. python练手项目

    文本操作 逆转字符串--输入一个字符串,将其逆转并输出. 拉丁猪文字游戏--这是一个英语语言游戏.基本规则是将一个英语单词的第一个辅音音素的字母移动到词尾并且加上后缀-ay(譬如"banan ...

  4. Angularjs scope

    $scope: var myapp = angular.module('myapp', []); myapp .controller('parent', function ($scope,$timeo ...

  5. windows 7 右下角登陆信息去除

    ---恢复内容开始--- 开始--运行中输入regedit 找到注册表键值HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\NlaSvc\Par ...

  6. SPI

    SPI的通信原理以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,有4根线,单向传输时只要3根线. (1)MOSI(SDO) – 主设备数据输出,从设备数据输入(Master Out Sla ...

  7. C++转义字符

    R"()"括号中间的字符串可以去掉转义字符

  8. CSV导入

    import java.io.FileReader; import java.io.IOException; import java.util.List; import au.com.bytecode ...

  9. 博客开篇:随笔《从windows到linux的转变》。

    在QQ群里讨论到了WINDOWS和LINUX.MAC,用手机码了如下回复,索性转过来当做博客的开篇.:) unix 和linux 在外很火的主要原因是开源,国外崇尚自由的精神是从出生就在细胞里的,而w ...

  10. LEETCODE —— Surrounded Regions

    Total Accepted: 43584 Total Submissions: 284350 Difficulty: Medium Given a 2D board containing 'X' a ...