一、定义函数的两种方式:函数声明和函数表达式
1、函数声明:
// 语法:
function funName(arg0,arg1,arg2){
// 函数体
}
函数声明有个重要特征是:函数声明提升,意思是在执行代码之前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。
sayHi(); // 函数调用
function sayHi(){ // 函数声明
alert("Hi!");
}
2、函数表达式:
var funName = function(arg0,arg1,arg2){
// 函数体
}
函数表达是很像常规的变量赋值,创建一个函数将它赋值给funName。这种情况下创建的函数叫做 匿名函数。匿名函数的name值是空字符串。
函数表达是和函数声明不一样,使用函数表达式时候必须先赋值在使用。
二、递归
概念:递归函数是在一个函数通过名字调用自身的情况下构成的,
递归算法的特点:
- 在函数过程中调用自身。
- 在递归过程中,必须有一个明确的条件判断递归的结束,既递归出口。
- 递归算法简洁但效率低,通常不作为推荐算法。
上面这些是百度百科的解释,讲的也是十分明确,大家配合实例来细细琢磨。
例如:
function funNum(num){ // 阶乘递归
if(num <= 1){
return 1
} else {
return num * funNum(num - 1)
}
}
上面的递归代码看似没有什么问题,但是当你将函数赋值给另一个变量就会出错,如下:
var a= funNum; // 将函数赋值给a变量
funNum = null; // 将函数设置成null,现在funNum已经不是一个函数了
alert(a(4)); //出错! 所以会出错
上面代码出错的问题可以使用arguments.callee()来解决,arguments.callee()是一个指向正在执行的函数指针,因此可以用它来实现递归,代码 如下:
function funNum(num){
if(num <= 0){
return 1
} else {
return num * arguments.callee(num - 1); // 指向正在执行的函数指针,确保无论怎样调用函数都不会出问题。因此,在编写递归函数时,使用 arguments.callee 总比使用函数名更保险。
}
}
console.log(funNum(4)); // 24
var a = funNum;
funNum = null;
console.log(a(4)); // 24
上面的代码在个严格模式下会报错,不过可以使用命名函数表达是来达成相同的效果:
"use strict" // 严格模式
var funNum = (function f(num){
if(num <= 0){
return 1;
} else {
return num * f(num - 1)
}
})
console.log(funNum(4));
var a = funNum;
funNum = null;
console.log(a(4))
理解闭包首先要理解js的变量作用域:全局变量和局部变量
1、作用域
js中函数内部可以直接读取全局变量,函数外部无法读取函数内部的局部变量:
var a = 100; // 全局变量
function fn(){
console.log(a); // 读取全局变量
}
fn(); // 100
*************************************************
function fn(){
var a = 100; // 函数内部局部变量
}
console.log(a); //a is not defined 报错 访问函数内部的局部变量
在局部变量中声明的变量一定要用var 关键字,不使用var关键字就会变成全局变量:
fucntion fn(){
a = 100;
}fn()
console.log(a); // 100
闭包的概念:闭包是指有权访问另一个函数作用域中的变量的函数。(js高级程序设计3版的解释)
创建闭包的常见方式,就是在一个函数内部创建另一个函数;
2、外部读取局部变量
正常情况下外部是无法得到函数内部的局部变量,要想得到函数的局部变量,就需要在函数内部在定义一个函数:
function f1(){
var n = 100;
function f2(){
console.log(n); // 100
}
}
在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了!
function f1(){
var n = 100;
function f2(){
console.log(n); // 100
}
return f2;
}
var a = f1();
a(); // 100
2、闭包的概念
闭包:是指有权访问另一个函数作用域中的变量函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。
作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。
一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。
但是,闭包的情况又有所不同。
在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。
3、闭包的用途
它的最大用处有两个,一个是读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999 // result实际上就是闭包f2函数
nAdd();
result(); // 1000
// 在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是
1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
//为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,
而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
//这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用
var关键字,因此nAdd是一个全局变量,而不是局部变量。
其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,
可以在函数外部对函数内部的局部变量进行操作。
4、闭包的缺点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
四、闭包中的this对象
在闭包中使用 this 对象也可能会导致一些问题。我们知道, this 对象是在运行时基于函数的执行环境绑定的:在全局函数中, this 等于 window ,而当函数被作为某个对象的方法调用时, this 等于那个对象。不过,匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window 。
var name = "the windw";
var obj = {
name:"my obj",
getNameFun:function(){
return function(){ // 匿名函数,执行环境是全局,
return this.name; // the windw 因为在匿名函数里,执行环境是全局,所以this指向的是window
}
}
}
console.log(obj.getNameFun()()); // the windw
为什么匿名函数没有取得其包含作用域(或外部作用域)的 this 对象呢???
每个函数在被调用时都会自动取得两个特殊变量: this 和 arguments 。内部函
数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。不过,把外部作用域中的 this 对象保存在一个闭包能够访问
到的变量里,就可以让闭包访问该对象了,如下所示。
var name = "the windw";
var obj = {
name:"my obj",
getNameFun:function(){
var that = this; // 外部作用域中的 this 对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象
return function(){ // 匿名函数,执行环境是全局,
return that.name; // my obj 在定义匿名函数之前,我们把 this对象赋值给了一个名叫 that 的变量。而在定义了闭包之后,闭包也可以访问这个变量,因为它是我们在包含函数中特意声名的一个变量。即使在函数返回之后, that 也仍然引用着 object ,所以调用object.getNameFunc()() 就返回了 "My Object" 。
}
}
}
console.log(obj.getNameFun()()); // my obj