创建函数的方式有两种:函数声明,函数表达式
- 函数声明
函数声明的重要特征:函数声明提升,执行代码之前会先读取函数声明。可以将函数声明放在调用它的语句后面。
- 函数表达式
创建了一个匿名函数并将它赋值给变量functionName
函数表达式和其他表达式一样使用前必须先赋值。
1. 递归
- arguments.callee表示一个正在执行的函数的指针,通过使用arguments.callee代替函数名,可以确保无论怎样调用函数都不会出现问题。
-
但是在严格模式下,不能访问arguments.callee属性,访问会出现错误。使用命名函数表达式来实现
2.闭包
2.1 回顾作用域
作用域:变量和函数的可访问范围。
js中作用域包括:全局作用域,局部作用域
-
全局作用域:在代码中任何地方都能访问。
1)最外层函数和最外层函数之外定义的变量拥有全局作用域
var num=0;//直接定义的变量
//最外层的函数
function exp(){
alert(1);
}
2)所有未使用var进行定义,而直接赋值的变量拥有全局作用域。
function exp(){
//没有使用关键字定义,直接赋值的变量,相当于创建全局变量
num=0;
}
alert(num);
3)所有window对象的属性拥有全局作用域
一般情况下,window对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等等
-
局部作用域:在固定的代码片段内可访问,比如函数内部。
function exp(){
//定义一个局部变量,只能在exp()函数内部访问
var num=1;
alert(num);
//定义了一个函数,只能在exp()函数内调用
function print(){
alert(1);
}
//调用该函数
print();
}
//无法访问局部变量num
alert(num); ERROR
//无法访问print()函数
print();ERROR
-
注意:js中没有块级作用域
<script type="text/javascript">
var m = 5;
if(m == 5){
var n = 10;
}
alert(n); //10
</script>
对于js,没有块级作用域,if{ }里面定义的变量n就是全局变量,当然可以访问。
2.2 回顾作用域链
- javascript的一个重要概念是执行环境(execution context),每一个执行环境都有相应的变量对象,执行环境中定义的所有变量和函数都存储在该变量对象中。
- 在web中全局执行环境的变量对象是window对象。每一个函数都有执行环境,函数的活动对象就是执行环境的变量对象。
- 当某个函数被调用时,会创建一个执行环境(execution context)及作用域链,作用域链在我理解就是存储变量对象的有序数组,并把作用域链赋值给execution context的内部属性[[scope]]。然后使用this,arguments和其他命名参数的值来初始化活动对象。
- 在作用域链中,外部函数的活动对象处于第二位,外部函数的外部函数的活动对象处于第三位...,一直到全局对象。
<script type="text/javascript">
var temp=0;
function add(num1,num2){
alert(temp);
return num1+num2;
}
add(3,5);
</script>
当调用add(3,5)函数时演示上述过程:
- 在函数中访问一个变量时,就会从作用域链中从第0位开始依次搜索具有相应名字的变量。
- 内部环境能够通过作用域链访问所有的外部环境,而外部环境不能访问内部环境。
2.3 闭包
我们可以通过作用域链,在内部环境直接访问到外部环境的变量。
但有时我们需要在外部环境中访问到内部环境中的变量,这是就需要用到闭包。
闭包就是能够读取其他函数内部变量的函数,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
function f1(){
//定义了一个局部变量
var n=3;
//我们想要在全局环境中alert(n)这个局部变量,可以定义一个函数
function f2(){
alert(n);
}
//将函数作为返回值返回
return f2;
}
var result=f1();
result();//就会执行alert(3),3。
闭包的用途
1)能够读取函数的内部变量
2)让这些变量的值始终保持在内存中
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
2.4 this对象
this对象是在运行时基于函数的执行环境绑定的:
全局函数中,this等于window
当函数被作为某个对象的方法调用时,this等于那个对象。
匿名函数的执行环境具有全局性,this等于window。
例1:
分析:
定义了一个全局变量name="The Window"
定义了一个对象object,包含了属性name="My Object",函数getNameFunc()
方法getNameFunc()中返回的是一个匿名函数,上面提到匿名函数的执行环境具有全局性,匿名函数中的this表示window,所以这里返回的是全局变量name:the Window
例2:
这里我们在object对象中,将this对象赋值给了that。如上所述,当函数被作为某个对象的方法调用时,this指向该对象,所以这里的this就是object对象。
然后在匿名函数中返回了that.name,就是object对象的name属性。
例3:
2.5 内存泄漏
在IE9之前的版本中,如果闭包的作用域链中保存着一个HTML元素,那么该元素将无法被销毁。
3.模仿块级作用域
我们可以使用匿名函数来实现块级作用域
上述代码进行简化,直接用函数代替变量名
4.私有变量
任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。
私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
我们将有权访问私有变量和私有函数的方法称为特权方法。
1)在构造函数中定义特权方法
缺点:构造函数的缺点是针对每个实例都会创建同样一组新方法。
2)使用私有作用域(块级作用域)实现特权方法