第七章 函数表达式
函数有一个非标准的name属性,通过这个属性可以访问到给定函数指定的名字。
(一)创建函数的几种方式:
1、函数声明
function functionName(arg0,arg1,arg2){}
2、函数表达式:如果想要创建匿名函数、给 prototype(原型)添加函数或是将函数用作其它对象的 property(属性),都可以用 Function Expression。
var functionName = function(arg0,arg1,arg2){}//匿名函数(拉姆达函数),function后面没有标识符,name属性为空字符串
函数声明与函数表达式的异同
- 函数表达式使用之前必须先赋值
- 注意:
if(condition){}else{}
两个大括号中可以是函数表达式,但不能是函数声明,函数声明只会识别else后面的函数。 - 在把函数当成值来使用的情况下,都可以使用匿名函数。
- 函数声明和函数表达式的函数名的使用范围:
函数声明:函数名在自身作用域和父作用域内是可获取的
函数表达式:函数名(如果有的话)在作用域外是不可获取的 - 优劣:一般情况下用函数表达式比较好,函数声明只是在当前作用域定义了一个变量
函数声明提升
解释:
- 函数声明和函数变量通常会被 JavaScript 解释器移(‘hoisted’)到当前作用域顶部
- 执行 JavaScript 过程中,有 Context(ECMA 5 将之分解为 LexicalEnvironment词汇环境、VariableEnvironment 变量环境和 ThisBinding绑定)和 Process(一系列按序调用的语句)两个概念。当程序进入执行域时,Declaration 会造成 VariableEnvironment。它们不同于 Statement(比如 return),也不遵循 Statement 的运行规则。
Function Expression 会被提升吗?
如
var bar = function() {
return 3;
};
等号左边的(var bar)是 Variable Declaration。Variable Declaration 会被提升,但是 Assignment Expression(赋值表达式)不会。所以当 bar 提升时,解释器会这样初始化:var bar = undefined。而函数定义本身不会被提升。
官方是禁止在非功能模块(比如 if)用函数声明
(二)递归调用(arguments.callee)
argument。callee是一个指向正在执行的函数的指针。这样比直接调用函数名更保险(防止函数中途变化)
1、a中途被重新复制容易造成错误
function a(){
if(num<= 1){
return 1;
}else{
return num*a(num-1);
}
}
2、
function a(num){
if(num<=1){
return 1;
}else{
return num*arguments.callee(num-1);
}
}
3、严格模式下,不能通过脚本访问argument.callee(所以下面这种方式,在严格模式非严格模式下都适用)
var a = (function f(num){
if(num<=1){
return 1;
}else{
return num*f(num-1);
}
})
(三)作用域链
作用域链本质上是一个指向变量对象的指针列表,只引用但不实际包含变量对象
当执行一个函数的时候发生了什么:
包含的东西:作用域链、执行环境、变量对象(活动对象)
全局环境的变量对象始终存在,而局部环境的变量对象,则只在函数执行的过程中存在。
- 在创建一个新函数时:会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部[[scope]]属性中
- 当调用这个函数时:会创建一个执行环境,然后通过复制函数的[[scope]]属性中的对象构建起执行环境的作用域链。作用域链第一位是当前活动对象,第二位是外层对象,最高位为全局变量对象。
- 后面创建活动对象:推入执行环境作用域链的前端。
- 当函数执行完毕后,局部活动对象就会被销毁。
(四)闭包
- 在函数内部定义的函数会将外部函数的活动对象添加到它的作用域链中。外部函数执行完毕后,其活动对象也不会被销毁,因为内部函数的作用域链仍然在引用这个活动对象。但是外部环境的作用域链会被销毁。(P180图,学会理解)
- 普通的在外部函数内部返回内部函数的副作用:
(1)有多个内部函数同时引用外部函数变量对象中的某个变量,会使这几个函数无法区分这个变量的值(一个for循环的例子)
例子:js中循环绑定处理程序(一个知识点)
(2)在闭包中使用this对象
- 改进的闭包:将立即执行的匿名函数的结果赋值。。。
function(num){
return function(){
与num相关的操作
}
}(i)
- 闭包的常用组合:this或者argument进行赋值传递
(五)this对象
1、this的几种情况
- 在全局函数中,this等于window
- 当函数被作为某个对象的方法调用时,this等于那个对象;
- 匿名函数的执行环境具有全局性,因此其this对象通常指向window。
2、每个函数在被调用时都会自动取得两个特殊变量:this和arguments.内部函数在搜索这两个变量时,只会搜索到其活动对象位置,因此永远不可能直接访问外部函数中的这两个变量。
(六)内存泄漏
1、闭包导致的内存泄漏:
function assignHandler(){
var element = document.getElementById('someElement');
element.onclick = function(){
alert(element.id);
}
}//此闭包,内部函数引用了外部函数中的变量导致其无法释放
- 原因:因为IE9之前版本的浏览器的垃圾回收机制——如果闭包的作用域链中保存着一个HTML匀速,那么就意味着该元素将无法被销毁
- 解决
function assignHandler(){
var element = document.getElementById('someElement');
var id = element.id //消除了循环引用,但不能完全解决内存泄漏问题,因为闭包会引用外部函数的整个活动对象。
element.onclick = function(){
alert(id)
}
element = null;
}
。。。
(七)自执行匿名函数模仿块级作用域
补充的基础知识:
- 在块语句中定义的变量,实际上是在外部函数中而非语句中创建的;
- javascript从来不会告诉你是否多次声明了同一个变量;遇到这种情况,它只会对后续的声明视而不见。(不过会执行后续声明中的变量初始化)
- 将匿名函数用括号括起来的原因:javascript将function关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号;然而,函数表达式后面可以跟圆括号。
为什么需要局部作用域:
限制全局作用域中添加过多的变量和函数。如果一个项目由多人开发,过得全局变量和函数很容易导致命名冲突。
(八)私有变量及特权方法
1、私有变量:
任何在函数中定义的变量,都可以认为是私有变量:函数的参数;局部变量;在函数内部定义的其他函数。
2、利用闭包创建用于访问私有变量的公有方法(特权方法)
(1)在构造函数上创建对象
的特权方法:针对每个实例都会创建同样一组新方法(浪费内存)
- 在构造函数中定义特权方法一;
function MyObject(){
var privateVariable =10;
function privateFunction(){
return false;
}
//特权方法
this.publicMethod = function(){
privateVariable++;
return privateFunction();
}
}
- 利用私有和特权成员,可以隐藏哪些不应该被直接修改的数据;
function Person(name){
this.getName = function(){
return name;
}
this.setName = function(){
name = value;
}
}
(2)在私有作用域中定义所有实例共享的特权方法(而其包含函数中的变量也就成了静态私有变量)
(function(){
var privateVariable = 10;
function privateFunction(){
return false;
}
myObject = function(){
}//没有使用函数声明而是函数表达式,因为函数声明只能创建局部函数。同时也没用var。因为我们想要全局。但是,这在严格模式下给未声明的变量赋值会导致错误。
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
}
})()
(3)为单例(只有一个实例的对象)创建私有变量和特权方法,使其得到增强
javascript是以对象字面量的方式来创建单例对象的。
var singleton = function(){
var privateVariable = 10;
function privateFunction(){
return false;
}
return{
publicProperty:true,
publicMethod:function(){
privateVariable++;
return privateFunction();
}
}
}()
- 用处:需要对单例进行某些初始化,同时又需要维护其私有变量时非常有用。如,用单例来管理应用程序级的信息。简言之,如果必须创建一个对象并以些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。
- 如果单例要求必须是某种类型的实例,同时还必须添加某些属性和方法,则使用增强的模块模式
var singleton = function(){
var privateVariable = 10;
function privateFunction(){
return false;
}
var object = new CustomType();
object.publicProperty = true;
object.publicMethod = function(){
privateVariable++;
return privateFunction();
}
return object;
}()