上一次看了第6章,面向对象。这里接着看第7章。
第7章:函数表达式
定义函数有两种方式:函数声明、函数表达式
//函数声明 function functionName(arg0,arg1,arg2){ //code... } //函数表达式 var functionName = function(arg0,arg1,arg2){ //code... };
函数声明有个重要的特征是函数申明提升。就是在执行代码前会先读取函数声明,意味着可以把函数声明放在调用它的语句后面。
//函数声明提升 sayHi(); //Hello function sayHi(){ alert("Hello"); }
函数表达式看起来好像是常规的变量赋值语句。即创建一个函数并将它复制给变量 functionName。这种情况下创建的函数叫做匿名函数。创建的匿名函数能赋值给变量,也能作为其他函数的值返回。
函数表达式在调用前必须先赋值。
//函数表达式没有函数提升 sayHi(); //Error var sayHi = function(){ alert("Hello"); };
1. 递归
递归函数是在一个函数通过名字调用自身的情况下构成的。
//递归函数: 名称 function factorial(num){ if (num <= 1){ return 1; } else { return num * factorial(num-1); } } var anotherFactorial = factorial; factorial = null; alert(anotherFactorial(4)); //error! //递归函数: arguments.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
使用 arguments.callee 替代函数名,确保无论怎样调用函数都不会出问题。
在严格模式下("use strict"),不能访问 arguments.callee。 可以使用函数表达式解决问题。
"use strict" //递归函数: 表达式 var factorial = function f(num){ if (num <= 1){ return 1; } else { return num * f(num-1); } }
2. 闭包
闭包是指有权访问另一个函数作用域中的变量的函数。 创建闭包的常用方式,就是在一个函数内部创建另一个函数。(创建的内部函数可以访问外部函数中声明的所有局部变量、参数和其他内部函数,当该内部函数在外部函数外被调用,就生成了闭包)。
当一个函数调用时会创建一个执行环境及相应的作用域链,并把作用域链赋值给一个特殊的内部属性( [[scope]] )。然后使用 this、arguments和其他明明参数的值来初始化函数的活动对象。在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,......一直到作用域链的终点全局执行环境。
在函数执行过程中,为读写变量的值,需要在作用域链中查找变量。
function compare(value1,value2){ if(value1 < value2){ return -1; }else if(value1 > value2){ return 1; }else{ return 0; } } var result = compare(5,10);
先定义了compare() 函数, 然后再全局作用域中调用了它。调用compare()创建了包含 this、arguments、value1、value2的活动对象。全局执行环境的变量对象(this、result、compare)在compare()执行环境的作用域链中处于第二位。
在作用域链中,闭包只能取得包含函数中任何变量的最后一个值。闭包保存的是整个变量对象,不是某个特殊的变量。
function createFunctions(){ var result = new Array(); for (var i = 0; i < 5; i++){ result[i] = function(){ return i; }; } return result; } var arr = createFunctions(); console.log(arr[0]()) //5 console.log(arr[1]()) //5 console.log(arr[2]()) //5 console.log(arr[3]()) //5 console.log(arr[4]()) //5 function createFunctions2(){ var result = new Array(); for (var i = 0; i < 5; i++){ result[i] = function(num){ return function(){ return num; } }(i); } return result; } var arr2 = createFunctions2(); console.log(arr2[0]()) //0 console.log(arr2[1]()) //1 console.log(arr2[2]()) //2 console.log(arr2[3]()) //3 console.log(arr2[4]()) //4
内存泄漏
补充:
执行环境:每调用一个函数时(执行函数时),会为该函数创建一个封闭的局部的运行环境,即该函数的执行环境。函数总是在自己的执行环境中执行,如读写局部变量、函数参数、运行内部逻辑。创建执行环境的过程包含了创建函数的作用域,函数也是在自己的作用域下执行的。从另一个角度说,每个函数执行环境都有一个作用域链,子函数的作用域链包括它的父函数的作用域链。
作用域、作用域链、调用对象:函数作用域分为词法作用域和动态作用域。词法作用域是函数定义时的作用域,即静态作用域。当一个函数定义时,他的词法作用域就确定了,词法作用域说明的是在函数结构的嵌套关系下,函数作用的范围。这个时候也就形成了该函数的作用域链。作用域链就是把这些具有嵌套层级关系的作用域串联起来。函数的内部[[scope]]属性指向了该作用域链。动态作用域是函数调用执行时的作用域。当一个函数被调用时,首先将函数内部[[scope]]属性指向了函数的作用域链,然后会创建一个调用对象,并用该调用对象记录函数参数和函数的局部变量,将其置于作用域链顶部。动态作用域就是通过把该调用对象加到作用域链的顶部来创建的,此时的[[scope]]除了具有定义时的作用域链,还具有了调用时创建的调用对象。换句话说,执行环境下的作用域等于该函数定义时就确定的作用域链加上该函数刚刚创建的调用对象,从而也形成了新的作用域链。所以说是动态的作用域,并且作用域链也随之发生了变化。再看这里的作用域,其实是一个对象链,这些对象就是函数调用时创建的调用对象,以及他上面一层层的调用对象直到最上层的全局对象。
3. 块级作用域
在JavaScript中,没有块级作用域概念。可以用匿名函数模仿块级作用域。
var functionName = function(){ //块级作用域 } (function(){ //块级作用域 })(); (function(){ //块级作用域 }());
4. 私有变量
在JavaScript中,没有私有成员的概念。任何在函数中定义的变量,都可以作为私有变量(函数的参数、局部变量、内部函数)。
// add函数中有3个私有变量:num1 num2 num。 function add(num1,num2){ var num = num1 + num2; return num; }
这章介绍了递归函数、闭包、作用域等。要好好的理解闭包、作用域链、执行环境这些概念。