深入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 相关文章推荐
js setTimeout opener的用法示例详解
Oct 23 Javascript
javascript中直接引用Microsoft的COM生成Word
Jan 20 Javascript
JS替换文本域内的回车示例
Feb 18 Javascript
javascript创建对象、对象继承的实用方式详解
Mar 08 Javascript
Bootstrap所支持的表单控件实例详解
May 16 Javascript
js实现按钮控制带有停顿效果的图片滚动
Aug 30 Javascript
JavaScript学习笔记整理_关于表达式和语句
Sep 19 Javascript
JS中append字符串包含onclick无效传递参数失败的解决方案
Dec 26 Javascript
js学习总结_基于数据类型检测的四种方式(必看)
Jul 04 Javascript
jQuery模拟html下拉多选框的原生实现方法示例
May 30 jQuery
在react-antd中弹出层form内容传递给父组件的操作
Oct 24 Javascript
js判断两个数组相等的5种方法
May 06 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
PHP $_FILES函数详解
2011/03/09 PHP
win7计划任务定时执行PHP脚本设置图解
2014/05/09 PHP
CI框架自动加载session出现报错的解决办法
2014/06/17 PHP
php去除字符串中空字符的常用方法小结
2015/03/17 PHP
PHP图像处理类库及演示分享
2015/05/17 PHP
jQuery实现点击标题输入详细信息
2013/04/16 Javascript
js 控制页面跳转的5种方法
2013/09/09 Javascript
jquery通过a标签删除table中的一行的代码
2013/12/02 Javascript
前端轻量级MVC框架CanJS详解
2014/09/26 Javascript
jQuery实现动态添加、删除按钮及input输入框的方法
2017/04/27 jQuery
JS伪继承prototype实现方法示例
2018/06/20 Javascript
vue调用语音播放的方法
2019/09/27 Javascript
Python中实现字符串类型与字典类型相互转换的方法
2014/08/18 Python
Python格式化压缩后的JS文件的方法
2015/03/05 Python
利用python写个下载teahour音频的小脚本
2017/05/08 Python
了解不常见但是实用的Python技巧
2019/05/23 Python
Python pandas用法最全整理
2019/08/04 Python
使用Python进行中文繁简转换的实现代码
2019/10/18 Python
Python中xml和dict格式转换的示例代码
2019/11/07 Python
Python模块的定义,模块的导入,__name__用法实例分析
2020/01/07 Python
jupyter notebook 增加kernel教程
2020/04/10 Python
Python Http请求json解析库用法解析
2020/11/28 Python
巴西体育用品商店:Lojão dos Esportes
2018/07/21 全球购物
饿了么订餐官网:外卖、网上订餐
2019/06/28 全球购物
Skyscanner新西兰:全球领先的旅游搜索网站
2019/08/26 全球购物
英国领先的独立酒精饮料零售商:DrinkSupermarket
2021/01/13 全球购物
英语师范专业毕业生自荐信
2013/09/21 职场文书
毕业生自荐书
2014/02/02 职场文书
优秀学生获奖感言
2014/02/15 职场文书
我的中国梦演讲稿高中篇
2014/08/19 职场文书
2014广电局实施党的群众路线教育实践活动方案思想汇报
2014/09/22 职场文书
不同意离婚代理词
2015/05/23 职场文书
超强台风观后感
2015/06/09 职场文书
商业计划书如何写?关键问题有哪些?
2019/07/11 职场文书
python 爬取哔哩哔哩up主信息和投稿视频
2021/06/07 Python
SQLyog的下载、安装、破解、配置教程(MySQL可视化工具安装)
2022/09/23 MySQL