定义函数的方式
有两种,一种是函数声明,一种是函数表达式
函数声明
- 它的语法是
function fun(arg0,arg1,arg2){//函数体}
- name属性:可以访问到函数的名字。
alert(fun.name);//"fun"
- 函数声明有一个重要特征:函数声明提升
- 意思是执行代码之前会先读取函数声明,意味着可以把函数声明放在调用它的语句后面
sayhi();
function sayhi(){
alert("hi");
}
函数表达式
- 语法
var fun=function(arg0,arg1,arg2){//函数体};
- 这种情况下创建的函数叫做匿名函数(拉姆达函数),匿名函数的name属性是空字符串。
- 函数表达式在使用前必须先赋值。
递归
- 本质:自己调用自己
- 在编写递归函数时,如果显式的调用函数名(指针),会因为这个指针可能被重置了而出现错误。为了解除这种耦合性,可以使用arguments.callee代替函数名,这是一个指向正在执行的函数的指针。
- 在严格模式下,使用命名函数表达式来达成同上安全的效果。
var factorial=function fun(n){
if(n<=1)
return 1;
eles
return n*fun(n-1);
};
alert(factorial(4));//24
alert(fun(4));//error:fun is not defined
闭包
闭包指有权访问外部函数作用域中的变量的函数。
- 创建闭包的方式
在一个函数内部创建并返回另一个函数。 需要注意一点,在函数内部定义变量时,要用var,否则就是定义了一个全局变量。
先讲一下函数的作用域链
假设有个函数fun(value);在函数fun(value);被创建的时候,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。当调用fun(value)函数时,会为函数创建一个执行环境,然后复制[[Scope]]属性中的对象构建起执行环境的作用域链,之后,又有一个活动对象(arguments,value)被创建并被推入执行环境作用域链的前端。所以fun函数的执行环境包含两个变量对象:本地活动对象和全局变量对象,当在一个函数内部调用一个函数时,内部函数的执行环境包含内部活动对象,外函数对象,全局变量对象,以此可以得到作用域链。- 闭包
因为创建闭包就是在一个函数内部创建另一个函数,所以闭包的作用域链包含本地活动对象、包含它的函数的对象、全局变量对象,所以,当在f1内部定义且返回f2,而f2中可能会引用f1的变量。当返回f2时,f1已经执行完毕,其执行环境的作用域链已经被销毁,但它的活动对象还被包含在f2中,所以不会报错,直到f2执行完毕被销毁后,或者清空f2(f2=null),f1的活动对象才会被销毁。 - 常见的闭包的模式:
var x=function A(a){
return function B(b){...a...};//B是闭包
}
var y=a(b);//y实际上就是闭包B函数
- 闭包的缺点
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存,过度使用闭包可能会导致内存占用过多。
闭包与变量
- 小心:闭包会取得包含函数中任何变量的最后一个值。比如书中的这个例子:
function fun(){
var result=new Array();
for(var i=0;i<result.length;i++){
result[i]=funtion(){
return i;
};
}
return result;
}
var f=fun();
for(var i=0;i<result.length;i++){
console.log(f[i]());
}
- 这个例子中返回的result数组中的值都是10,为什么呢,因为result[i]=…;这一句代码中的function并没有执行,只是定义了,没有调用。所以当return result;时,i=10,所以之后的代码执行时,result的每个值都是10。要调用得需要有()。改正后的代码如下:
function fun(){
var result=new Array();
for(var i=0;i<result.length;i++){
result[i]=function(n){
return function(){
return n;
};
}(i);//定义了一个匿名函数,这样result[i]都有变量i的一个副本。
}
return result;
}
var f=fun();
for(var i=0;i<result.length;i++){
console.log(f[i]());
}
关于this对象
- this对象是在运行时基于函数的执行环境绑定的。
- 在全局函数中,this等于window;而当函数被作为某个对象的方法调用时,this等于那个对象。
- 调用分为:方法调用和函数调用,方法调用时,this指向对象,函数调用时,就用到了之前学到的作用域链。
- 会遇到的问题:匿名函数有时没有取得其包含作用域(或外部作用域)的this对象,解决办法就是,把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。arguments对象同样如此。因为内部函数在搜索这两个变量时,只会搜索到其活动对象为止。
内存泄漏
如果闭包的作用域链中保存着一个HTML元素,那么该元素将无法被销毁。为了防止在匿名函数中循环引用,可在匿名函数之后将元素赋为null。
模仿块级作用域
- 语法:
(function(){//这里是块级作用域})();
- 无论在什么地方,只要临时需要一些变量,就可以使用此方法。
- 这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。
(function(){
var now=new Date();
if(now.getMouth()==0&&now.getDate()==1){
alert("happy new year");
}
})();
- 优点:这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。
私有变量
- 任何在函数中定义的变量,都可以认为是私有变量。
- 利用闭包可以访问私有变量。
- 把有权访问私有变量和私有函数的公有方法称为特权方法。
两种创建特权方法的方式:
构造函数中定义特权方法:
模式:
function Object(){ //定义私有变量和私有函数 ... //特权方法 this.publicMethod=function(){ //访问私有变量和函数,可以进行修改等操作 ... }; }
在创建了Object实例后,除了使用publicMethod()这一个途径外,没有任何办法可以直接访问私有变量和函数。
- 利用私有和特权成员,可以隐藏那些不应该被直接修改的数据。
- 缺点:必须使用构造函数模式来达到这个目的,针对每个实例都会创建同样一组新方法。
在私有作用域中定义私有变量或函数。
模式:
(function (){ //私有变量和私有函数 ... //构造函数,创建全局变量 ... ///共有/特权方法 Object.prototype.publicMethod=function(){ //访问私有变量和函数,可以进行修改等操作 };//是在原型上定义的 })();
以这种方式创建静态私有变量会因为使用原型而增加代码复用,但每个实例都没有自己的私有变量。
模块模式
- 作用:为单例创建私有变量和特权方法。
- 语法形式:
var singleton=function(){
//私有变量和私有函数
...
//特权/共有方法和属性
return{
...
};
}();
- 如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,就可以使用模块模式。
增强的模块模式
- 增强的内容:在返回对象之前加入了对其增强的代码。
- 适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。
- 语法:
var singleton=function(){
//私有变量和私有函数
...
//创建对象
var object=new CustomType();
//添加特权/共有属性和方法
object.publicProperty=true;
...
//返回这个对象
return object;
}();