JS红宝书笔记 10.11-10.16 函数
函数表达式
函数声明的关键特点是函数声明提升,即函数声明会在代码执行之前获得定义,这意味着函数声明可以出现在调用它的代码之后
函数表达式看起来像一个普通的变量定义和赋值,即创建一个函数再把它赋值给一个变量,这样创建的函数叫做匿名函数或兰姆达函数,因为function关键字后面没有标识符,未赋值给其他变量的匿名函数的name属性是空字符串
函数表达式跟JS中的其他表达式一样,需要先赋值后使用
任何时候,只要函数被当做值来使用,它就是一个函数表达式
递归
递归函数通常的形式是一个函数通过名称调用自己
arguments.callee是一个指向正在执行的函数的指针,因此可以在函数内部递归调用,但严格模式下不能访问。
可以使用命名函数表达式达到目的
尾调用优化
尾调用:外部函数的返回值是内部函数的返回值
如果函数的逻辑允许基于尾调用将其销毁,则引擎就会那么做
尾调用优化的条件:
- 代码在严格模式下执行
- 外部函数的返回值是对尾调用函数的调用
- 尾调用函数返回后不需要执行额外的逻辑
- 尾调用函数不是引用外部函数作用域中自由变量的闭包
无论是递归尾调用还是非递归尾调用,都可以应用优化,引擎并不区分尾调用中调用的是函数自身还是其他函数
闭包
闭包指的是那些引用了另一个作用域中变量的函数,通常是在嵌套函数中实现的,原理是包含外部函数作用域的作用域链
在调用一个函数时,会为这个函数调用创建一个执行上下文,并创建一个作用域链。然后用arguments和其他命名参数来初始化这个函数的活动对象。外部函数的活动对象是内部函数作用域链上的第二个对象。这个作用域链一直向外串起了所有包含函数的活动对象,直到全局执行上下文才终止
函数执行时,每个执行上下文中都会有一个包含其中变量的对象,全局上下文中的叫变量对象,它会在代码执行期间始终存在。而函数局部上下文中的叫活动对象,只在函数执行期间存在。作用域链其实是一个包含指针的列表,每个指针指向一个变量对象,但物理上并不会包含相应的对象
函数内部的代码在访问变量时,就会使用给定的名称从作用域链中查找变量。函数执行完毕后,局部活动对象会被销毁,内存中就只剩下全局作用域
在一个函数内部定义的函数会把其包含函数的活动对象添加到自己的作用域链中,活动对象并不能在它执行完毕后销毁,因为匿名函数的作用域链中仍然有对它的引用
因为闭包会保留它们包含函数的作用域,所以比其他函数更占用内存。过度使用闭包可能导致内存过度占用
在闭包中使用this,如果内部函数没有使用箭头函数定义,则this对象会在运行时绑定到执行函数的上下文。如果在全局函数中调用,则this在非严格模式下等于window,严格模式下等于undefined。如果作为某个对象的方法调用,则this等于这个对象。匿名函数在这种情况下不会绑定到某个对象,这就意味着this会指向window,除非在严格模式下this指向undefined
每个函数在被调用时都会自动创建两个特殊变量:this和arguments。内部函数永远不可能直接访问外部函数的这两个变量
赋值表达式的值是函数本身
立即调用函数表达式
立即调用的匿名函数又被称作立即调用的函数表达式(IIFE),它类似于函数声明,但由于被包含在括号中,所以会被解释为函数表达式。紧跟在第一组括号后面的第二组括号会立即调用前面的函数表达式
使用IIFE可以模拟块级作用域,即在一个函数表达式内部声明变量,然后立即调用这个函数,这样位于函数体作用域的变量就像是在块级作用域中一样
IIFE不会导致闭包相关问题,因为不存在对这个匿名函数的引用,只要函数执行完毕,其作用域链就可以被销毁
IIFE可以用于锁定参数值
私有变量
任何定义在函数或块中的变量,都可以认为是私有的,因为在这个函数或块的外部无法访问其中的变量。私有变量包括函数参数、局部变量,以及函数内部定义的其他函数
如果函数中创建了一个闭包,则这个闭包能通过其作用域链访问其外部的变量。基于这一点,就可以创建出能够访问私有变量的公有方法
特权方法是能够访问函数私有变量的公有方法。在对象上有两种方法创建特权方法:构造函数实现,静态私有变量实现
function MyObject(){let privateVariable = 10function privateFunction(){return false}this.publicMethod = function(){privateVariable++return privateFunction}
}
构造函数实现是把所有私有变量和私有函数都定义在构造函数中,然后,再创建一个能够访问这些私有成员的特权方法。这样做之所以可行,是因为定义在构造函数中的特权方法其实是一个闭包,它具有访问构造函数中定义的所有变量和函数的能力。缺点是每个实例都会重新创建一遍新方法
(function(){let privateVariable = 10function privateFunction(){return false}MyObject = function(){}MyObject.prototype.publicMethod = function(){privateVariable++return privateFunction()}
})
静态私有变量通过使用私有作用域定义私有变量和函数来实现,在这个模式中,匿名函数表达式创建了一个包含构造函数及其方法的私有作用域。首先定义的是私有变量和私有函数,然后有定义了构造函数和公有方法。公有方法定义在构造函数的原型上,与典型的原型模式一样。
这个模式定义的构造函数没有使用函数声明,使用的是函数表达式。函数声明会创建内部函数,在这里不是必须的。
构造函数标识符未使用任何关键字,因为不使用关键字声明的变量会创建在全局作用域中,可以在私有作用域外部被访问
该模式,私有变量和私有函数是由实例共享的,因为特权方法定义在原型上,所以同样是由实例共享的。特权方法作为一个闭包,始终引用着包含它的作用域
let singleton = function(){let privateVariable = 10function privateFunction(){return false}return {publicProperty:truepublicMethod(){privateVariable++return privateFunction()}}
}
模块模式在一个单例对象上实现了相同的隔离和封装。模块模式使用了匿名函数返回一个对象,在匿名函数内部,首先定义私有变量和私有函数。之后,创建一个要通过匿名函数返回的对象字面量。这个对象字面量中只包含可以公开访问的属性和方法。因为这个对象定义在匿名函数内部,所以它的公有方法都可以访问同一个作用域的私有变量和私有函数。
本质上,对象字面量定义了单例对象的公共接口。如果单例对象需要进行某种初始化,并且需要访问私有变量时,那就可以采用这个模式