定义函数表达式有两种方式:函数声明和函数表达式.

函数声明如下:

function functionName(arg0,arg1,arg2){//函数体
}

首先是function关键字,然后是函数的名字.

FF,Safrai,Chrome和Opera都给函数定义了一个非标准的name属性,通过这个属性可以访问到函数指定的名字.这个函数的值永远等于跟在function关键字后面的标识符.

//只在FF,Safari,Chrome和Opera有效
alert(functionName.name)//functionName

函数声明的特征就是函数声明提升(function declaration hoisting),意思是在执行代码之前会先读取函数声明.这就意味着可以把函数声明放在调用它的语句后面.

sayHi();
function sayHi(){alert("Hi!");
}

这种例子不会抛出错误,因为在代码执行之前会先读取函数声明.

第二种是函数表达式.

var functionName=function(arg0,arg0,arg2){//函数体
}

这种形式看起来好像是常规的变量赋值语句,即创建一个函数并将它赋值给变量functionName.这种情况下创建的函数叫做匿名函数(anonymous function),因为function关键字后面没有标识符.(匿名函数有时候也叫拉姆达函数.)匿名函数的name属性是空字符串.

函数表达式与其他表达式一样,在使用前必须先赋值.

以下代码会导致错误:

syaHi();//Uncaught ReferenceError: syaHi is not defined
var sayHi=function(){alert("Hi!");
}

不要像下面这样写代码,这在ECMAScript中属于无效语法,JavaScript引擎会尝试修正错误,但不同浏览器修改不同.

//不要这样做
if(condition){function sayHi(){alert("Hi!");}
}else{function sayHi(){alert("Yo!");}
}

如果是使用函数表达式,就没什么问题了.

//可以这样做
var sayHi;
if(condition){sayHi=function(){alert("Hi!");}
}else{sayHi=function(){alert("Yo!");}
}

能够创建函数再赋值给变量,也就能够把函数作为其它函数的值返回.

function creatComparisonFunction(propertyName){return function(object1,object2){var value1=object1[propertyName];var value2=object2[propertyName];if(value1<value2){return -1;}else if(value1>value2){return 1;}else{return 0;}};
}

creatComparisonFunction()就返回了一个匿名函数.返回的函数可能会被赋值给一个变量,或者以其他方式被调用;不过,在creatComparisonFunction()函数内部,它是匿名的.在把函数当成值来使用的情况下,都可以使用匿名函数.

7.1 递归

递归函数是一个函数通过名字调用自己的情况下构造的.

function factorial(num){if(num<=1){return 1;}else{return num*factorial(num-1);}
}

上面是一个经典的递归阶乘函数.下面的代码却可能导致它出错.

var anotherFactorial=factorial;
factorial=null;
alert(anotherFactorial(4));//Uncaught TypeError: factorial is not a function

以上代码先把factorial()函数保存在变量anotherFactorial中,之后又将factorial变量设为null,结果指向原始引用只剩下一个.接下来调用anotherFactorial()时,由于必须执行factorial(),而factorial()已经不再是函数,所以会导致错误.

这种情况下,使用arguments.callee可以解决.

arguments.callee是一个指向正在执行的函数的指针,因此可以用它来实现对函数的递归调用.

function factorial(num){if(num<=1){return 1;}else{return num*arguments.callee(num-1);}
}

在编写递归函数时,使用arguments.callee总比使用函数名更保险,因为它可以确保无论怎么调用函数都不会出问题.

但在严格模式下,不能通过脚本访问arguments.callee.

不过可以使用函数表达式来达成相同的结果.

var factorial=(function f(num){if(num<=1){return 1;}else{return num*f(num-1);}
});

console.log(factorial(4));//24

7.2 闭包

闭包是指有权访问另一个函数作用域中的变量的函数.创建闭包的常见方式,就是在一个函数内部创建另一个函数.

function creatComparisonFunction(propertyName){return function(object1,object2){var value1=object1[propertyName];var value2=object2[propertyName];if(value1<value2){return -1;}else if(value1>value2){return 1;}else{return 0;}};
}

加粗的两行代码是内部函数(一个匿名函数)中的代码,这两行代码访问了外部函数中的变量propertyName.即使这个内部函数被返回了,而且是在其他地方被调用了,但它仍然可以访问变量propertyName.之所以还能够访问这个变量,是因为内部函数的作用域链中包含creatComparisonFunction()的作用域.

当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链.然后,使用arguments和其他命名参数的值来初始化函数的活动对象(activation object).但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,....直至作为作用域链终点的全局执行环境.

在函数执行过程中,为读取和写入变量的值,就需要姑作用域链中查找变量.

function compare(value1,value2){if(value1<value2){return -1;}else if(value1>value2){return 1;}else{return 0;}}var result=compare(5,10)console.log(result)//-1

以上代码先定义了compare()函数,然后又在全局作用域中调用了它.当调用compare()时,会创建一个包含arguments,value1,value2的活动对象.全局执行环境的变量对象(包含result和compare)在compare()执行环境的作用域链中则处于第二位

后台的每个执行环境都有一个表示变量的对象--变量对象.全局环境的变量对象始终存在,而像compare()函数这样的局部环境的变量对象,则只在函数执行的过程中存在.在创建compare()函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中.当调用compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链.此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域链的前端.

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

无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量.一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象).但是,闭包的情况又有所不同.

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

var compare=creatComparisonFunction("name");
var result=comapre({name:"Nicholas"},{name:"Greg"});

下图展示了上面代码代码执行时,包含函数与内部匿名函数的作用域.

当createComparisonFunction()函数返回后,其执行环境的作用域会被销毁,但它的活动对象仍然会留在内存中;直到匿名函数被销毁后,createComparisonFunction()的活动对象都会被销毁.

//创建函数var compare=creatComparisonFunction("name");//调用函数var result=comapre({name:"Nicholas"},{name:"Greg"});//解除对匿名函数的引用(以便释放内存)compareNames=null;

通过将compareNames设置为等于null解除该函数的引用,就等于通知垃圾回收例程将其清除.随着匿名函数函数的作用域链被销毁,其他作用域(除了全局作用域)也都可以安全地销毁了.

由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存.过度使用闭包可能会导致内存占用过多,慎重使用闭包.

7.2.1 闭包和变量

作用域链的这种配置的机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值.

闭包里所保存的是整个变量对象,而不是某个特殊的变量.

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

上面代码里这个函数会返回一个函数数组.表面上看,似乎每个函数都应该返回自己的索引值,但实际上,每个函数都返回10.因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的都是同一个变量i.当createFunction()函数返回后,变量i的值是10,此时每个函数都引用着保存变量i的同一个变量对象,所以在每个函数内部i的值都是10.

但是,我们可以通过创建另一个匿名函数强制让闭包的行为符合预期.

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

重写之后,每个函数就会返回各自不同的索引值了.在这个版本中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行匿名函数的结果赋给数组.这里的匿名函数有一个参数num,也就是最终的函数要返回的值.在调用每个匿名函数时,我们传入了变量i.由于函数参数是按值传递的,所以就会将变量i的当前值复制给参数num.而在这个匿名函数内部,又创建并返回了一个访问num的闭包.这个一来,result数组中的每个函数都有自己num变量的一个副本,因此就可以返回各自不同的数值了.

7.2.2 关于this对象

this对象是在运行时基本函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象.不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window.

var name="the window";var object={name:"my object",getNameFunc:function(){return function(){return this.name;};}
};alert(object.getNameFunc()());//the window(在非严格模式下)

每个函数在被调用时都会自动取得两个特殊变量:this和arguments.内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量.

不过,把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了.

var name="the window";var object={name:"my object",getNameFunc:function(){ var that=this;return function(){return that.name;};}
};alert(object.getNameFunc()());//my object

在定义匿名函数之前,我们把this对象赋值给了一个名叫that的变量.而在定义了闭包之后,闭包也可以访问这个变量,因为它是我们在包含函数中特意声明的一个变量.即使在函数返回之后,that也仍然引用着object,所以调用object.getNameFunc()()就返回了my object.

注意:this和arguments也存在同样的问题.如果想访问作用域中的arguments对象,必须将对该对象的引用保存到另一个闭包能够访问的变量中.

var name="the window";var object={name:"my object",getName:function(){return this.name;}
};
console.log(object.getName());//my object
console.log((object.getName)());//my object
console.log((object.getName=object.getName)());//the window

最后一行代码先执行了一条赋值语句,然后再调用赋值后的结果.因为这个赋值表达式的值是函数本身,所以this的值不能得到维持,结果就返回了"this window".

7.2.3 内存泄露

由于IE9之前的版本对JScript对象和COM对象使用不同的垃圾收集例程,因此闭包在IE的这些版本中会导致一些特殊的问题.具体来说,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁.

function assignHandlet(){var element=document.getElementById("someElement");element.onclick=function(){alert(element.id);};
}

以上代码创建了一个作为element元素事件处理程序的闭包,而这个闭包则又创建了一个循环引用.由于匿名函数保存了一个对assignHandler()的活动对象的引用,因此应付导致无法减少element的引用数.只要匿名函数存在,element的引用数至少也是1,因此它所占用的内存就永远不会被回收.不过这个问题可以通过稍微改写一下代码来解决.

function assignHandlet(){var element=document.getElementById("someElement");var id=element.id;element.onclick=function(){alert(id);};element=null;
}

上面代码中,通过把element.id的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用.

记住:闭包会引用包含函数的整个活动对象,而其中包含着element.即使闭包不直接引用element,包含函数的活动对象也仍然会保存一个引用.因此,有必要把element变量设置为null.这样就能够解除对DOM对象的引用,顺利地减少其引用数,确保正常回收其占用的内存.

转载于:https://www.cnblogs.com/sunshinegirl-7/p/5003868.html

高程 第7章函数表达式 7.1递归 7.2闭包相关推荐

  1. MICK-SQL基础教程(第二版) 第六章 函数、谓词、CASE表达式

    第六章 函数.谓词.CASE表达式 函数 函数大致可以分为以下几种: 算术函数(用来进行数值计算的函数) 字符串函数(用来进行字符串操作的函数) 日期函数(用来进行日期操作的函数) 转换函数(用来转换 ...

  2. JS_高程7.函数表达式(1)

    定义函数的两种常见的方法: 1 . 函数声明 2. 函数表达式 # 差异 (1)函数声明 ,具有函数声明提升的特征. (2)函数声明的函数的name属性为函数的名称:使用函数表达式定义的函数在ES5中 ...

  3. 一篇文章带你了解JavaScript中的函数表达式,递归,闭包,变量,this对象,模块作用域

    作者 | Jeskson 来源 | 达达前端小酒馆 定义函数的方式: 第一种为 函数声明: 第二种为 函数表达式. 语法: function functionName(arg0, arg1, arg2 ...

  4. 函数表达式-JS高程

    函数表达式 定义函数的方式有两种: 函数声明 sayHi(); function sayHi() { alert('hi'); } 函数表达式 var functionName = function( ...

  5. C++ Primer 学习笔记(第四章:表达式)

    2019独角兽企业重金招聘Python工程师标准>>> ##C++ Primer 学习笔记(第四章:表达式) [TOC] ###4.1 基础 左值和右值: 当一个对象被用作右值的时候 ...

  6. c语言函数与编译预处理教学视频,C语言课程第6章 函数及编译预处理.ppt

    C语言课程第6章 函数及编译预处理 6.1模块化程序设计与函数 在设计较复杂的程序时,我们一般采用的方法是:把问题分成几个部分,每部分又可分成更细的若干小部分,逐步细化,直至分解成很容易求解的小问题. ...

  7. 一文探索 JavaScript 最强大特性—函数表达式

    函数是 ECMAScript 中最有意思的部分之一,这主要是因为函数实际上是对象.每个函数都是 Function 类型的实例,而 Function 也有属性和方法,跟其他引用类型一样.因为函数是对象, ...

  8. Python 精要参考(第二版) 第六章 函数与函数编程

    1. 第六章 函数与函数编程 为便于代码维护,绝大多数子程序都被分解并重新组织为函数以使代码模块化. 在 Python中定义一个函数很简单,Python从其它函数编程语言中借鉴了很多有用的思路用来简化 ...

  9. python编程设计大学ppt_Python程序设计-清华大学出版社-董付国第5章-函数的设计和使用PPT...

    PPT内容 这是Python程序设计-清华大学出版社-董付国第5章-函数的设计和使用PPT下载,主要介绍了函数定义:斐波那契数列:形参与实参:参数类型:默认值参数:关键参数:可变长度参数:参数传递的序 ...

最新文章

  1. kubernetes1.9管中窥豹-CRD概念、使用场景及实例
  2. java中子线程与主线程通信_Android笔记(三十二) Android中线程之间的通信(四)主线程给子线程发送消息...
  3. MySQL(4)---MYSQL 数据类型
  4. 【Elasticsearch】Elasticsearch analyzer 中文 分词器
  5. 案例:按照JSP Model2思想实现用户注册功能
  6. IT技术中的言情小说
  7. MessageBox 用法合集
  8. 数据分析法、数据分析方法论总结
  9. 计算机开关电源 原理与应用,开关电源原理与应用设计
  10. python数据分析:会员数据化运营(上)——知识点
  11. 如何给视频加背景音乐?简单快速上手,制作抖音等小视频必备!
  12. 数字经济是党和国家定下的重要发展战略
  13. html5课程总结500字,体育课心得体会500字(精选6篇)
  14. openlayers3中,在地图上添加静态边界线
  15. HTML5支持rpx,css小程序的元素尺寸单位px,rpx及rem
  16. 如何判断一个对象是否属于某个类
  17. 【疯壳·无人机开发教程1】开源编队无人机-开机测试
  18. 均衡器 Equalizer
  19. 【Linux操作系统应用T1】
  20. 排序---小白鼠排队

热门文章

  1. 【经典重温】所有数据无需共享同一个卷积核!谷歌提出条件参数化卷积CondConv(附Pytorch复现代码)...
  2. android 购票代码,android完美电影购票源码
  3. “那不勒斯”服务器芯片,32核/64超线程:AMD 公开 Naples “那不勒斯” 部分规格和性能...
  4. 收藏 | 清华团队将Transformer用到3D点云分割
  5. CNN与Transformer结合,东南大学提出新架构 ConvTransformer
  6. CVPR2020 | 跨模态行人重识别:共享与特异特征变换算法cm-SSFT
  7. 小白视角来看传说中的卷积神经网络
  8. 黑苹果睡眠重启后死机_iPhone卡屏、死机、黑屏?| 学会这招,秒解!
  9. 安全狗导致php错误,服务器安全狗导致ASP.NET网站运行出错的一个案例
  10. SOA ESB 微服务 浅析