JavaScript之函数定义与闭包
来源:互联网 发布:淘宝运动品牌店铺排名 编辑:程序博客网 时间:2024/05/17 14:25
函数表达式
函数表达式是JavaScript中既强大又容易让人困惑的特性。
函数的定义
函数的定义有两种方式:函数声明 和 函数表达式。
函数声明
函数声明的语法:
function functionName () { //函数体}
function是关键字,后面跟着的是函数的名字,这就是指定函数名的方法。谷歌、火狐、苹果、欧朋等浏览器给函数定义了一个非标准的name属性,它的值 等于 跟在function关键字后面的 函数名:
function sum (num1, num2) { //函数体 return num1 + num2;}console.log(sum.name); //sum
函数声明有个重要的特性,就是函数声明提升,即:在执行代码之前会先读取函数声明。这就意味着可以将函数声明放在调用它的语句之后。
console.log(sum(2, 3)); //5function sum (num1, num2) { //函数体 return num1 + num2;}
这段代码不会出错,因为在执行之前会先读取函数的声明,这段代码在引擎中可以认为是这样的:
//先读取了函数声明function sum (num1, num2) { //函数体 return num1 + num2;}console.log(sum(2, 3)); //5 接着再调用函数
函数表达式
定义函数的第二种方法就是使用 函数表达式,创建函数表达式最常见的格式是:
var functionName = function (num1, num2, num3) { //函数体};
这种形式就像是变量赋值语句,即创建一个函数并将其赋值给一个变量,这个情况下创建的函数是匿名函数(也拉姆达函数),关键字function后面没有标识符,name属性为空字符。
函数表达式与其它表达式一样,调用前先赋值,或者会出错,如:
console.log(sum(10,3)); //提示:sum is not a functionvar sum = function (num1, num2) { return num1 + num2;};
没有赋值就调用函数表达式是会报错的。如果先赋值,再调用就不会报错了。
var sum = function (num1, num2) { return num1 + num2;}; console.log(sum(10,3)); //13
理解函数提升,关键是理解 函数声明 与 函数表达式 的区别。
能够创建函数再将其赋值给变量,也能够把函数作为其它函数的值返回。就如前面所学的内部属性中一个升序比较函数:
function bj (proName) { return function (object1, object2) { var value1 = object1[proName]; var value2 = object2[proName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } };}
函数bj()就返回了一个匿名函数,返回的函数可能会被赋值给一个变量,或者被其它方式调用。但 在bj()内部,它就是一个匿名的函数,在把函数当作返回值来使用的情况下,都可以使用匿名函数。
递归函数
递归函数就是 通过自身函数名调用自己的函数。一个经典的阶乘函数就是一个递归函数:
function jshs (num) { if (num <= 1) { return 1; } else if (num > 1) { return num * jshs(num - 1); }}
如果此时我们将上述代码修改如下:
var jshs1 = jshs;jshs = null;console.log(jshs1(5)); //出错
将函数jshs赋值给变量jshs1,再将jshs赋值null,这样指向原始函数的引用只有一个。但在执行代码时,由于必须执行jshs()函数,但jshs()已经不是函数了,就会报错。
解决这样的问题就引用arguments.callee属性。callee是一个指针,它指向拥有arguements属性的函数,也就是指向函数自己本身。
我们将上述代码用arguments.callee修改一下:
function jshs (num) { if (num <= 1) { return 1; } else if (num > 1) { return num * arguments.callee(num - 1); }}var jshs1 = jshs;jshs = null;console.log(jshs1(5)); //120
这样就不会报错了,通过arguments.callee代替函数名,不管函数名怎样修改,其都指向函数本身。在写递归的情况下,arguments.callee比函数名更保险。
在严格模式下,用arguments.callee也会报错,这时我们可以用例命名函数来完成这个任务:
var jshs = (function f (num) { if (num <= 1) { return 1; } else if () { return num * f(num -1); }});
创建一个名为f的命名函数,将其赋值给jshs,即便赋值给其它变量,函数内部的f函数名也不会变,照样能执行。在严格与非严格模式下都是可行的。
闭包
变量的作用域
要理解闭包,首先必须理解JavaScript特殊的变量作用域。
变量的作用域分为两种:全局作用域 和 局部作用域。
JavaScript语言的特殊之处:就在于 函数内部可以直接读取全局变量。
var n = 999;function f1 () { alert(n);}f1(); //999
函数外部无法读取函数内的局部变量。
function f1 () { var n = 999;}alert(n); //error
注意:函数内部声明变量时,一定要用关键字var,如果不用的话,实际上是声明了一个全局变量。
function f1 () { n = 999;}f1();console.log(n); //999
如何从外部读取函数内部的局部变量
出于某种原因,我们需要得到函数内部的局部变量,但正常情况下,是无法办到的,只有通过变通方法才能实现。那就是 在函数内部再定义一个函数。
function f1 () { n = 999; function f2 () { alert(n); //999 }}
上面的代码中,函数f2被包括在函数f1内部,这时f1的所有局部变量对于 f2来说都是可见的。但反过来就不行,f2内部的局部变量对于f1来说是不可见的,这就是"链式作用域"。
子对象会一级一级地向上逐级寻找所有父对象的变量。所以,父对象的所有变量,对于子对象来说都是可见的,反之不可见的。
既然f2可以读取f1的局部变量,那么只要把f2作为f1的返回值,我们不就可以在f1外部读取它内部变量了么。
function f1 () { var n = 999; function f2 () { alert(n); //999 } return f2();}var a = f1();console.log(a); //999
其中,函数f2就是闭包。
何为闭包
所谓闭包,就是 有权访问其它函数内部变量(作用域中的变量)的 函数。创建闭包的方式:就是在函数内部再创建一个函数。
function bj (proName) { return function (object1, object2) { //内部函数访问外部函数中的变量 var value1 = object1[proName]; var value2 = object2[proName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } };}
即使内部函数被返回了,但依然能访问外部函数的变量,之所以能访问外部函数的变量,是因为 内部函数的作用域链 包含了外部函数bj()的作用域。
要理解其中的细节的,就必须清楚第一次调用函数会发生什么。
当某个函数被第一次调用时,会创建一个执行环境及相应的作用域链,并将这作用域链赋值给一个特殊的内部属性[[Sope]],然后this、argument和其它命名参数的值来初始化函数的活动对象。在作用域链中,外部函数的活动对象始终处于第二位置,外部的外部函数的活动对象处于第三位置,依次类推,直至作为作用域的终点全局作用域为止。
在函数执行的过程中,为 读取和写入 变量的值,就需要在作用域链中查找变量。如下例子:
先定义了compare()函数,然后又在全局作用域中调用了它。当第一次调用compare()时,会创建一个包含this、argument、value1和valu2的活动对象。全局执行环境的变量对象(包含this、result和compare)在compare()执行环境的作用域链中则处于第二位。
后台的每个执行环境都有一个变量对象。全局环境的变量对象始终存在,而compare()函数这种局部环境的变量对象,只存在于函数执行的过程中。在创建compare()函数时,会创建一个预先包含全局变量对象的作用域链,并将其保存在内部属性[[Scope]]中。当调用compare()函数时,会为函数创建一个执行环境,并复制[[Scope]]中的对象创建执行环境的作用域链。此后,又有活动对象被创建并推入执行环境的作用域链前端。
function compare (value1, value2) { if (value1 < value2) { return -1; } else if (value < value2) { return 1; } else { return 0; }}var result = compare(5, 10);
先定义了compare()函数,然后又在全局作用域中调用了它。当第一次调用compare()时,会创建一个包含this、argument、value1和valu2的活动对象。全局执行环境的变量对象(包含this、result和compare)在compare()执行环境的作用域链中则处于第二位。
后台的每个执行环境都有一个变量对象。全局环境的变量对象始终存在,而compare()函数这种局部环境的变量对象,只存在于函数执行的过程中。在创建compare()函数时,会创建一个预先包含全局变量对象的作用域链,并将其保存在内部属性[[Scope]]中。当调用compare()函数时,会为函数创建一个执行环境,并复制[[Scope]]中的对象创建执行环境的作用域链。此后,又有活动对象被创建并推入执行环境的作用域链前端。
作用域链是指向变量对象的指针列表,只引用不实际包含变量对象。
无论什么时候在函数中访问一个变量,都会在作用域链中查找相应名字的变量。当函数执行完毕后,就会从内存中被销毁,内存中只留下全局作用域(全局执行环境中的变量对象)。
无论什么时候在函数中访问一个变量,都会在作用域链中查找相应名字的变量。当函数执行完毕后,就会从内存中被销毁,内存中只留下全局作用域(全局执行环境中的变量对象)。
function bj (proName) { return function (object1, object2) { //内部函数访问外部函数中的变量 var value1 = object1[proName]; var value2 = object2[proName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } };}
在另一个函数A 内部创建的函数B 会把包含函数(包含此函数的外部函数A)的活动对象添加到它的作用域中,因此,函数bj()的活动对象已经在内部的匿名函数中作用域中了。
匿名函数从bj()函数中被返回后,它的作用域链被初始化为包含bj()函数的活动对象和全局变量对象。这样匿名函数就可以访问bi()函数中的所有变量,更重要的是,bj()函数执行完成后,其活动对象不会被销毁,因为匿名函数的作用域依然在引用这个活动对象。也就是说,当bj()函数返回后,它的作用域链被销毁,但它的活动对象不会被销毁,仍然在内存中,直到匿名函数被销毁后,bj()函数的活动对象才会被销毁。
注:由于闭包会携带包含它的函数的作用域,因此会比其它函数占用更多的内存。所以不能过度地会用闭包,这样会占用大多内存的。
闭包与变量
作用域链的这种机制有一个问题,那就是闭包只能取得包含函数中(外部函数)任何变量的最后一个值。闭包保存的是整个变量对象,而不是某个特殊的变量。如下:
function createFunction () { var arr = new Array(); //创建一个局部变量数组。 for (var i = 0; i < 5; i ++) { arr[i] = function () { //此函数的作用域链上保存着同一个变量i return i; }; } return arr; //返回数组引用。这个数组的元素是一个函数}var arr = createFunction(); //返回长度为5的数组console.log(arr[0]()); //5console.log(arr[1]()); //5console.log(arr[4]()); //5
上例并没有如想像的返回0,1,5等数值。而是每盒函数都返回5。因为每个函数的作用域链上都保存着函数createFunction()的活动对象(活动对象有argument,this,变量arr,变量i),所以每个函数引用的都是同一个变量i。当creatFunction()函数返回后,变量i已经自增到量大值5了,变量i的值为5,此时每个函数都引用着 保存变量i的同一个变量对象(这个变量对象就是createFunction()函数的执行环境中的变量对象,包含了artument,this,变量arr,变量i),所以在每个函数中的变量都是5。这样就没有达到我们预期的效果。
解决这个问题的方法就是:通过创建另一个匿名函数强制让闭包行为符合我们的预期。如:
function createFunction () { var arr = new Array(); //创建一个局部变量数组。 for (var i = 0; i < 5; i ++) { arr[i] = function (num) { //将执行匿名函数的结果保存在数组中。 return function () { //这个闭包函数用于保存不同num的值 return num; //这样,返回的不同num的值可以保存在数组中。 }; }(i);//定义匿名函数时并调用,变量i的当前值会复制给参数num。 } return arr; //返回数组引用。这个数组的元素是一个函数}var arr = createFunction(); //返回长度为5的数组console.log(arr[0]()); //0console.log(arr[1]()); //1console.log(arr[4]()); //4
通过创建一个匿名函数,达到了我们预期的值。我们没有把闭包直接赋值给数组,而是创建了一个匿名函数,并把立即执行这个匿名函数的结果赋值给数组,这个匿名函数有一个参数num,也就是最终要返回的值。在每次调用这个匿名函数时,都会传入一个参数i,由于函数参数是按值传递的,会把每次传入的变量i的当前值复制给参数num。在这个匿名函数的内部又创建了一个闭包函数,用于保存不同的num值,并把这个不同的num值返回给数组中的每个函数。这样,arr数组中的每个函数都有自己的num变量,而不像之前一样,都调用的同一个变量i。
创建并调用匿名函数的方式:var func = function () {//函数体}(i); 创建一个匿名函数并调用它,可以传入参数。那么变量func中保存着的是匿名函数执行的结果。
1 0
- JavaScript之函数定义与闭包
- javascript之闭包与匿名函数
- JavaScript作用域、上下文环境、函数对象的定义与调用、匿名函数的定义与调用、闭包
- JavaScript基础之函数定义与调用
- JavaScript函数与闭包
- JavaScript学习笔记之函数表达式与闭包
- Javascript之函数定义
- JavaScript之函数定义
- javascript函数作用域与闭包
- Javascript 匿名函数与闭包
- javascript的匿名函数与闭包
- [JavaScript]-----匿名函数与闭包
- JavaScript匿名函数与闭包
- 详解JavaScript中的函数与闭包
- javascript 匿名函数与闭包
- Javascript中的返回函数与闭包
- javascript函数闭包之初体验~~~
- JavaScript加强之匿名函数(闭包)
- 一个自定义的秒表计时器
- JavaScript面向对象入门学习笔记——变量的作用域
- ORACLE XE 报无监听
- SpringBOOT入门
- pro文件详解
- JavaScript之函数定义与闭包
- 很实用的android开源项目
- UGUI 过渡动画插件,模仿NGUI的Tween (转载)
- js 计算rem值
- php读取mysql分页查询
- poj 2299 Ultra-QuickSort
- 背景
- 设置JVM内存设置
- 杭州辉为科技HW9X35-GKA主控板