深入Javascript函数、递归与闭包(执行环境、变量对象与作用域链)使用详解


Posted in Javascript onMay 08, 2013

函数表达式

1、JavaScript中定义函数有2钟方法:

1-1.函数声明:

function funcName(arg1,arg2,arg3){
  //函数体
}

①name属性:可读取函数名。非标准,浏览器支持:FF、Chrome、safari、Opera。

②函数声明提升:指执行代码之前会先读取函数声明。即函数调用可置于函数声明之前。

1-2.函数表达式:

var funcName = function(arg1,arg2,arg3){
  //函数体
};

①匿名函数(anonymous function,或拉姆达函数):function关键字后无标识符,name属性值为空字符串。在把函数当成值使用时,都可用匿名函数。

②类似其他表达式,函数表达式使用前需先赋值,故不存在"函数声明提升"那样的作用。

③ECMAScript中的无效函数语法:

if判断中的重复函数声明
if(condition){
    function sayHi(){
        alert("Hi!");
    }
} else {
    function sayHi(){
        alert("Yo!");
    }
}

浏览器JavaScript引擎修正错误差异:大多浏览器会返回第二个声明,忽略condition;FF则会在condition为true时返回第一个声明。

使用函数表达式可解决并实现:

if判断 函数表达式
var sayHi;
if(condition){
    sayHi = function(){
        alert("Hi!");
    }
} else {
    sayHi = function(){
        alert("Yo!");
    }
}

2、递归

递归函数,是在一个函数中通过名字调用自身的情况下构成的。

function factorial(num){   //一个经典的递归阶乘函数
    if (num <= 1){
        return 1;
    } else {
        return num * factorial(num-1);
    }
}

 

①若使用下列代码调用该函数,会出错:

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));

将factorial()函数保存到变量anotherFactorial中后,将factorial变量设为null后不再引用函数,而anotherFactorial(4)中要执行factorial()函数,故出错。

使用argument.callee(指向正在执行的函数的指针)可解决:

解决方案
function factorial(num){
    if (num <= 1){
        return 1;
    } else {
        return num * arguments.callee(num-1);
    }
}
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));  //24

在非严格模式,使用递归函数时,用argument.callee代替函数名更保险

在严格模式下,使用argument.callee会出错,可用函数表达式 代替 函数声明:

函数表达式代替函数声明
var factorial = function f(num){
    if (num <= 1){
        return 1;
    } else {
        return num * f(num-1);
    }
}

4、闭包

指有权访问另一个函数作用域中的变量的函数。(常见形式为函数嵌套)

function wai(pro){
    return function(obj1,obj2){
        var val1 = obj1[pro];
        var val2 = obj2[pro];
        if(val1<val2){
            return -1;
        }else if(val1>val2){
            return 1;
        }else{
            return 0;
        };
    }
}

return匿名函数时,匿名函数的作用域链初始化为包含函数的活动对象和全局变量对象。即匿名函数包含wai()函数的作用域。

每个函数被调用时,会创建一个执行环境、一个变量对象 及 相应的作用域链。

4-1.执行环境 及 作用域

执行环境execution context简称环境,定义了变量和函数有权访问的其他数据,并决定他们的各自行为。

①每个执行环境都有一个变量对象variable object,保存环境定义的所有变量和函数。该对象无法编码访问,但解析器在处理数据时会在后台使用它。

  全局变量对象是最外围的一个执行环境。在Web浏览器中被认为是window对象,故所有全局对象和函数都是window对象的属性和方法创建的。

  执行环境中的代码执行完后,该环境就被销毁,保存其中的变量和函数定义也随之销毁。

②代码在环境中执行时,会创建变量对象的一个作用域链scope chain,用于保证对执行环境有权访问的所有变量和函数的有序访问。

  作用域链前端,始终是当前执行的代码所在环境的变量对象。当该环境为函数时,会将活动对象作为变量对象。

  活动对象最开始只包含一个变量,即argumnt对象。

  作用域链中的下一个变量对象来自包含环境,而下一个变量对象来自下一个包含环境,直至延续到全局执行环境。

③标识符解析:从前段开始,沿着作用域链一级一级地搜索标识符的过程。【找不到通常会导致错误发生】

4-2.函数创建、执行时:

function compare(val1,val2){
     if(val1<val2){
        return -1;
    }else if(val1>val2){
        return 1;
    }else{
        return 0;
    };
}
var result = compare(5 , 10);

①创建函数compare()时,会创建一个预先包含全局变量对象的作用域链,并保存在内部[[scope]]属性中。

②局部函数compare()的变量对象,只在函数执行的过程中存在。

 当调函数时,会创建一个执行环境,再通过复制函数的[[scope]]属性中的对象 构建起执行环境的作用域链。

③第一次调用函数时,如compare(),会创建一个包含this、argument、val1 和 val2的活动对象。

④全局执行环境的变量对象(包括this、result、compare)在compare()执行环境的作用域链中处于第二位。

⑤作用域链 本质是一个指向变量对象的指针列表,只引用但不实际包含变量对象。

⑥无论什么时候在函数中访问一个变量,都会行作用域链中搜索具有相应名字的变量。

4-3.闭包的作用域链

在另外一个函数内部定义的函数会将包含函数的活动对象添加到它的作用域链中。

①将函数对象赋值null,等于通知垃圾回收例程将其清除,随着函数作用域链被销毁,其作用域链(不除了全局作用域)也会被安全销毁。

②由于闭包会携带包含函数的作用域,所以会比其他函数占用更多内存。

4-4.闭包与变量

作用域链的一个副作用:闭包只能取得包含函数中任何变量的最后一个值。

function createFunctions(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(){
            return i;
        };
    }
    return result;
}

①createFunctions()函数,将10个闭包赋值给数组result,再返回result数组。每个闭包都返回自己的索引,但实际上都返回10。

 因为每个函数(闭包)的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的是同一个变量i,当createFunctions函数执行完后i的值10,故闭包中的i也都为10。

②解决办法,不使用闭包,创建一个匿名函数,将i值赋值给其参数:

function createFunctions(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(num){
            return function(){
                return num;
            };
        }(i);
    }
    return result;
}

创建一个每次循环都会执行一次的匿名函数:将每次循环时包围函数的i值作为参数,存入匿名函数中。因为函数参数是按值传递的,而非引用,所以每个匿名函数中的num值 都为每此循环时i值的一个副本。

4-5.this对象

this对象是在运行时基于函数的执行环境绑定的。

在全局函数中,this等于window;当函数被某对象调用时,this为该对象。

匿名函数的执行环境有全局性,其this对象通常指window。通过call()或spply()改变函数执行环境时,this指向其对象。

①每个函数在被调用时,都会自动取得两个特殊变量:this和argument。内部函数在搜索这两个变量时,只会搜索到期活动对象为止,永远不可能访问外部函数的这两个变量。

不过将外部作用域的this对象保存在一个闭包能访问的变量里,就可让闭包访问该对象。

闭包 访问外部函数的this对象
var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        var that = this;
        return function(){
            return that.name;
        };
    }
};
alert(object.getNameFunc()());  //"MyObject"

 包围函数的argument对象 也可通过此方法被闭包访问。

5、函数声明 转换为 函数表达式

JavaScript将function关键字昨晚函数声明的开始,但函数声明后面不能跟圆括号,所以function(){......}();会出错。

要将函数声明转换为函数表达式,需为函数声明加一对圆括号:

(function(){
   //块级作用域
})();
Javascript 相关文章推荐
javascript中巧用“闭包”实现程序的暂停执行功能
Apr 04 Javascript
解决jQuery插件tipswindown与hintbox冲突
Nov 05 Javascript
潜说js对象和数组
May 25 Javascript
文本框只能选择数据到文本框禁止手动输入
Nov 22 Javascript
JS中的数组方法笔记整理
Jul 26 Javascript
jquery checkbox的相关操作总结
Oct 17 Javascript
Vuex入门到上手教程
Jun 20 Javascript
浅析vue 函数配置项watch及函数 $watch 源码分享
Nov 22 Javascript
jQuery+ajax实现批量删除功能完整示例
Jun 06 jQuery
vue 自定义组件的写法与用法详解
Mar 04 Javascript
react的hooks的用法详解
Oct 12 Javascript
JS实现九宫格拼图游戏
Jun 28 Javascript
基于jquery实现拆分姓名的方法(纯JS版)
May 08 #Javascript
jQuery cdn使用介绍
May 08 #Javascript
不用锚点也可以平滑滚动到页面的指定位置实现代码
May 08 #Javascript
jquery实现图片左右间隔滚动特效(可自动播放)
May 08 #Javascript
jQuery中fadeIn、fadeOut、fadeTo的使用方法(图片显示与隐藏)
May 08 #Javascript
jquery插件validate验证的小例子
May 08 #Javascript
jQuery ui插件的使用方法代码实例
May 08 #Javascript
You might like
星际争霸教主Flash的ID由来:你永远不会知道他之前的ID是www!
2019/01/18 星际争霸
PHP 和 MySQL 开发的 8 个技巧
2006/10/09 PHP
php array_map array_multisort 高效处理多维数组排序
2009/06/11 PHP
php文件系统处理方法小结
2016/05/23 PHP
PHP defined()函数的使用图文详解
2019/07/20 PHP
php-fpm超时时间设置request_terminate_timeout资源问题分析
2019/09/27 PHP
JavaScript 克隆数组最简单的方法
2009/02/12 Javascript
jquery中eq和get的区别与使用方法
2011/04/14 Javascript
将list转换为json失败的原因
2013/12/17 Javascript
在线一元二次方程计算器实例(方程计算器在线计算)
2013/12/22 Javascript
Javascript基础教程之关键字和保留字汇总
2015/01/18 Javascript
Javascript中关于Array.filter()的妙用详解
2016/12/04 Javascript
jQuery实现的form转json经典示例
2017/10/10 jQuery
使用Vue制作图片轮播组件思路详解
2018/03/21 Javascript
Node.js进阶之核心模块https入门
2018/05/23 Javascript
electron实现qq快捷登录的方法示例
2018/10/22 Javascript
详解element-ui表格中勾选checkbox,高亮当前行
2019/09/02 Javascript
vue 设置 input 为不可以编辑的实现方法
2019/09/19 Javascript
package.json各个属性说明详解
2020/03/11 Javascript
小程序富文本提取图片可放大缩小
2020/05/26 Javascript
[00:43]FTP典藏礼包 DOTA2三大英雄霸气新套装
2014/03/21 DOTA
深入理解 Python 中的多线程 新手必看
2016/11/20 Python
解决python删除文件的权限错误问题
2018/04/24 Python
pytorch神经网络之卷积层与全连接层参数的设置方法
2019/08/18 Python
python实现复制文件到指定目录
2019/10/16 Python
matplotlib.pyplot.plot()参数使用详解
2020/07/28 Python
比利时买床:Beter Bed
2017/12/06 全球购物
北京华建集团SQL面试题
2014/06/03 面试题
linux面试题参考答案(6)
2014/08/29 面试题
学习十八大报告感言
2014/02/04 职场文书
公证委托书大全
2014/04/04 职场文书
2014小学数学教研组工作总结
2014/12/06 职场文书
小学班主任个人总结
2015/03/03 职场文书
教学质量月活动总结
2015/05/11 职场文书
CSS使用伪类控制边框长度的方法
2022/01/18 HTML / CSS
Navicat Premium自定义 sql 标签的创建方式
2022/09/23 数据库