匿名函数就是没有名字的函数,又称为拉姆达函数
1 函数声明和函数表达式之间的区别:
JS解释器对变量的处理:JS解释器在预编译阶段对使用var语句声明的变量进行索引,但是变量的初始化被忽略掉,直到执行期才为变量读取初始值
JS解释器对使用function语句声明的函数的处理:JS解释器不仅对函数名按照变量标识符进行索引,而且对函数体也进行了处理,因此,如果出现同名变量则在预编译阶段,前者就会被后者覆盖,因此在函数声明之前调用该函数,不会出错
JS解释器对匿名函数的处理:对匿名函数在预编译阶段视而不见,在执行期才按表达式逐行进行解释执行
函数声明会在函数执行前加载到作用域中,所以可以在函数的声明之前调用该函数,而函数表达式在代码执行到这一行才会有定义,另一个重要的区别是函数声明会给函数一个名字,也函数表达式是创建一个匿名函数
fun();
function fun(){
//函数体
}//函数声明
var fun=function(){
//函数体
};//函数表达式
2 闭包:有权访问另一个函数作用域中的变量的函数,创建闭包最常用的方法是在一个函数内部创建另一个函数
3 回顾原型链:
当JS解释器执行函数时,先创建一个执行环境,在这个执行环境中创建一个调用对象,在这个对象内存储当前域中所有的局部变量、参数、嵌套函数、外部引用和父级引用列表等
通过声明语句定义的变量和函数在预编译阶段就已经存储到符号表中了,然后把它们与调用对象中的同名属性进行映射即可。调用对象的声明周期和函数的声明周期一致
JS解释器通过作用域链将多个嵌套的作用域连在一起,并利用该链条来检索变量的值
当某个函数被第一次调用的时候,会创建一个执行环境,以及相应的作用域链,并把作用域链赋值给一个特殊的内部属性([[Scope]]),然后,使用this、arguments和其他命名参数的值来初始化一个活动对象(也称为调用对象),该活动对象内存储了当前域中所有的局部变量、参数、嵌套函数、外部引用和父级列表等,通过声明语句定义的变量和函数在预编译阶段就已经存储到符号表中了,在此阶段则把它们与活动对象中的同名属性进行映射即可。活动对象的生命周期和函数的生命周期一致
在作用域链中,外部函数的活动对象始终位于第二位,外部函数的外部函数的活动对象位于第三位。。。。直至作为作用域链终点的全局执行环境
function fun(v1,v2){
}//函数声明
var r=fun(12,5);//调用fun函数
以上代码先是定义了fun()函数,然后又在全局作用域调用了该函数,当第一次调用该函数时,会创建一个fun的执行环境以及作用域链,fun作用域链的第一个链接是链接到fun的活动对象,该活动对象包含this、arguments、v1和v2,第二位是全局执行环境的活动对象,该活动对象包含this,fun和r。
4 后台的每个执行环境都有一个表示变量的对象----变量对象,全局环境的变量对象始终存在。fun函数这样的局部环境的变量对象,只在函数执行的过程中存在
5 当创建fun函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中,当调用fun函数时,会为fun创建一个执行环境,然后通过复制fun函数的[[Scope]]属性中的对象构建起执行环境的作用域链,然后会为fun函数创建一个活动对象,并被推入执行环境作用域链的前端
6 作用域链的本质:一个指向变量对象的指针列表,它只是引用,并不实际包括变量对象
7 一般地,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域,但是闭包的情况有所不同
8 在另一个函数内部定义的函数会将外部函数的活动对象添加到它的作用域链中
function createFunction(p){
return function(o1,o2){
var v1=o1[p];
var v2=o2[p];
if(v1<v2){
return -1;
} else if(v1>v2){
return 1;
} else if(v1==v2){
return 0;
}
};
}
var fun=createFunction("name");//创建函数
alert(fun);
var result=fun({"name":"Jim"},{"name":"Jhon"});//调用函数
alert(result);
fun=null;//解除对匿名函数的引用,以便释放内存
在匿名函数从createFunction()中被返回后,它的作用域链被初始化为包含createFunction()函数的活动对象和全局变量对象,这样匿名函数就可以访问到createFunction()函数中所定义的所有变量,当createFunction()函数执行完毕后,由于其活动对象仍然被匿名函数所引用,所以不会被销毁,即createFunction()函数执行完毕后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中,直到匿名函数被销毁
9 关于匿名函数的this对象:
this对象在运行时,基于函数的执行环境进行绑定的,在全局函数中,this==window,而当函数被作为某个对象的方法被调用时,this就指向该对象了,但是由于匿名函数的执行环境具有全局性,因此其this对象通常指向window,当然可以通过call或者apply来改变函数的执行环境,让this对象指向其他对象
var name="window's name"; var obj={ name:"my obj's name", get:function(){ return function(){ return this.name; }; } }; alert(obj.get()());//window's name
var name="window's name"; var obj={ name:"my obj's name", get:function(){ var that=this; return function(){ return that.name; }; } }; alert(obj.get()());//my obj's name
在这个例子中,我们在定义匿名函数之前,将this指向的对象赋值给that变量,因此,即使函数返回之后,that也仍然指向obj
10 模仿块级作用域
小插曲:
var i=10; var i; alert(i);//10 //JS不会告诉你是否多次声明同一个变量,只会对后续声明视而不见,但是如果有初始化操作,后者会覆盖前者 var i=12; alert(i);//12
JS没有块级作用域的概念, 匿名函数可以用来模仿块级作用域(私有作用域),语法如下:
(function(){ //这里是块级作用域 })();
使用这种方法,可以减少闭包占用内存,导致内存泄漏的问题,因为没有指向匿名函数的引用,因此,只要函数执行完毕,就可以立即销毁其作用域链,等待垃圾回收机制回收了
11 私有变量
JS没有私有变量的概念,但是任何在函数中定义的变量,都可以认为是私有变量,因为函数的外部访问不到这些变量,私有变量包括函数的参数、局部变量以及在函数内部定义的其他函数
我们在函数的内部定义一个闭包,那么闭包通过自己的作用域链也可以访问到这些私有变量,我们把有权访问私有变量和私有函数的方法称为特权方法,利用私有变量和特权方法可以隐藏那些不应该被直接修改的数据
function Person(name){ this.getName=function(){ return name; }; this.setName=function(v){ name=v; }; } var p=new Person("Jim"); alert(p.getName());//Jim p.setName("Jhon"); alert(p.getName());//Jhon alert(p.name);//undefined
12 静态私有变量