深入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学习历程和心得小结
Aug 16 Javascript
javascript中将Object转换为String函数代码 (json str)
Apr 29 Javascript
下载文件个别浏览器文件名乱码解决办法
Mar 19 Javascript
推荐 21 款优秀的高性能 Node.js 开发框架
Aug 18 Javascript
d3.js实现立体柱图的方法详解
Apr 28 Javascript
jquery中有哪些api jQuery主要API
Nov 20 jQuery
vue devtools的安装与使用教程
Aug 08 Javascript
详解ES6 export default 和 import语句中的解构赋值
May 28 Javascript
使用xampp将angular项目运行在web服务器的教程
Sep 16 Javascript
vue-element-admin 菜单标签失效的解决方式
Nov 12 Javascript
vue el-tree 默认展开第一个节点的实现代码
May 15 Javascript
JS的时间格式化和时间戳转换函数示例详解
Jul 27 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
CodeIgniter针对lighttpd服务器URL重写的方法
2015/06/10 PHP
一个完整的php文件上传类实例讲解
2015/10/27 PHP
thinkPHP模板中for循环与switch语句用法示例
2016/11/30 PHP
var与Javascript变量隐式声明
2009/09/17 Javascript
防止动态加载JavaScript引起的内存泄漏问题
2009/10/08 Javascript
两种WEB下的模态对话框 (asp.net或js的分别实现)
2009/12/02 Javascript
JS控件的生命周期介绍
2012/10/22 Javascript
网站如何做到完全不需要jQuery也可以满足简单需求
2013/06/27 Javascript
利用javascript判断文件是否存在
2013/12/31 Javascript
js实现多选项切换导航菜单的方法
2015/02/06 Javascript
全面解析多种Bootstrap图片轮播效果
2016/05/27 Javascript
Node4-5静态资源服务器实战以及优化压缩文件实例内容
2019/08/29 Javascript
微信小程序实现日历签到
2020/09/21 Javascript
vuex中遇到的坑,vuex数据改变,组件中页面不渲染操作
2020/11/16 Javascript
Vue中避免滥用this去读取data中数据
2021/03/02 Vue.js
Python函数的周期性执行实现方法
2016/08/13 Python
Python3实现抓取javascript动态生成的html网页功能示例
2017/08/22 Python
JSONLINT:python的json数据验证库实例解析
2017/11/28 Python
python自动截取需要区域,进行图像识别的方法
2018/05/17 Python
Python图片转换成矩阵,矩阵数据转换成图片的实例
2018/07/02 Python
Python闭包思想与用法浅析
2018/12/27 Python
python如何实现视频转代码视频
2019/06/17 Python
pytorch 使用单个GPU与多个GPU进行训练与测试的方法
2019/08/19 Python
html svg生成环形进度条的实现方法
2019/09/23 HTML / CSS
香港唯港荟酒店预订:Hotel ICON
2018/03/27 全球购物
西班牙在线药店:DosFarma
2020/03/28 全球购物
军训的自我鉴定
2013/12/10 职场文书
机关作风建设剖析材料
2014/10/11 职场文书
拾金不昧感谢信
2015/01/21 职场文书
市场部经理岗位职责
2015/02/02 职场文书
辞职信的写法
2015/02/27 职场文书
2015财务年度工作总结范文
2015/05/04 职场文书
乡镇法制宣传日活动总结
2015/05/05 职场文书
电影地道战观后感
2015/06/04 职场文书
导游词之清晏园
2019/11/22 职场文书
Go语言-为什么返回值为接口类型,却返回结构体
2021/04/24 Golang