深入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 相关文章推荐
Jquery+WebService 校验账号是否已被注册的代码
Jul 12 Javascript
JS清除IE浏览器缓存的方法
Jul 26 Javascript
JavaScript数据结构与算法之栈与队列
Jan 29 Javascript
js解决movebox移动问题
Mar 29 Javascript
详解Javascript中prototype属性(推荐)
Sep 03 Javascript
AngularJS全局scope与Isolate scope通信用法示例
Nov 22 Javascript
jQuery之动画ajax事件(实例讲解)
Jul 18 jQuery
原生js实现的移动端可拖动进度条插件功能详解
Aug 15 Javascript
JS页面获取 session 值,作用域和闭包学习笔记
Oct 16 Javascript
js实现漂亮的星空背景
Nov 01 Javascript
VUE 实现动态给对象增加属性,并触发视图更新操作示例
Nov 29 Javascript
JavaScript实现烟花绽放动画效果
Aug 04 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+jQuery 注册模块开发详解
2014/10/14 PHP
php中使用Ajax时出现Error(c00ce56e)的详细解决方案
2014/11/03 PHP
Zend Framework入门知识点小结
2016/03/19 PHP
PHP双向链表定义与用法示例
2018/01/31 PHP
php+ajax 文件上传代码实例
2019/03/18 PHP
基于jquery的图片的切换(以数字的形式)
2011/02/14 Javascript
dojo学习第一天 Tab选项卡 实现
2011/08/28 Javascript
JQuery中两个ul标签的li互相移动实现方法
2015/05/18 Javascript
Bootstrap的图片轮播示例代码
2015/08/31 Javascript
微信企业号开发之微信考勤Cookies的使用
2015/09/11 Javascript
JavaScript的设计模式经典之代理模式
2016/02/24 Javascript
jquery滚动条插件(可以自定义)
2016/12/11 Javascript
Javascript计算二维数组重复值示例代码
2016/12/18 Javascript
JavaScript函数中的this四种绑定形式
2017/08/15 Javascript
浅谈Webpack 持久化缓存实践
2018/03/22 Javascript
Angular父子组件通过服务传参的示例方法
2018/10/31 Javascript
Bootstrap简单实用的表单验证插件BootstrapValidator用法实例详解
2020/03/29 Javascript
Quasar Input:type=&quot;number&quot; 去掉上下小箭头 实现加减按钮样式功能
2020/04/09 Javascript
vue实现点击出现操作弹出框的示例
2020/11/05 Javascript
python uuid模块使用实例
2015/04/08 Python
Python中的高级函数map/reduce使用实例
2015/04/13 Python
分享8点超级有用的Python编程建议(推荐)
2019/10/13 Python
django列表筛选功能的实现代码
2020/03/27 Python
python中导入 train_test_split提示错误的解决
2020/06/19 Python
Grow Gorgeous美国官网:只要八天,体验唤醒毛囊后新生的茂密秀发
2018/06/04 全球购物
全球性的在线婚纱礼服工厂:27dress.com
2019/03/21 全球购物
法国春天百货官网:Printemps.com
2020/06/29 全球购物
团支书的期末学习总结自我评价
2013/11/01 职场文书
《诺贝尔》教学反思
2014/02/17 职场文书
给学校的建议书
2014/03/12 职场文书
个人考核材料
2014/05/15 职场文书
授权委托书样本
2014/09/25 职场文书
医院领导班子查摆问题对照检查材料思想汇报
2014/10/08 职场文书
店长岗位职责
2015/02/11 职场文书
党支部对转正的意见
2015/06/02 职场文书
python爬虫请求库httpx和parsel解析库的使用测评
2021/05/10 Python