函数
如果函数挂载在一个对象上,作为对象的一个属性,就称为它为对象的方法。当通过这个对象来调用函数是,该对象就是此调用的上下文,也就是该函数的 this 的值。用于初始化一个新创建的对象的函数称为构造函数。
在 javascript 里,函数即对象,可以把函数赋值给变量,或者作为参数传递给其他函数。因为函数就是对象,所以可以给它们设置属性,甚至调用它们的方法。
javascript 的函数可以嵌套在其他函数中定义,这样它们就可以访问它们被定义时所处的作用域中的任何变量,构成了一个闭包。
函数定义
如果一个函数定义表达式包含名称,函数的局部作用域将会包含一个绑定到函数对象的名称。实际上,函数的名称将成为函数内部的一个局部变量。
函数声明语句“被提前”到外部脚本或外部函数作用域的顶部,所以以这种方式声明的函数,可以被在它定义之前的所调用。不过,以表达式定义的函数就另当别论了,为了调用一个函数,必须能引用它,而要使用一个以表达式方式定义的函数之前,必须把它赋值给一个变量。变量的声明提前了,但给变量赋值是不会提前的,所以,以表达式方式定义的函数在定义之前无法调用。
没有返回值的函数有时候称为过程。
嵌套函数
函数声明语句并非真正的语句,ES 规范只允许它们作为顶级语句。它们可以出现在全局代码里,或者内嵌在其他函数中,但它们不能出现在循环、条件判断,或者 try/catch/finally 以及 with 语句中(具体浏览器实现可能有所不同)。函数表达式可以出现在 javascript 代码的任何地方。
函数调用
方法调用
对方法调用的参数和返回值的处理,和普通函数调用完全一致,例如o.m()。但是,方法调用和函数调用有一个重要的区别--上下文。属性访问表达式由两部分组成:一个对象(o)和属性名(m)。像这样的方法调用表达式里,对象 o 成为调用上下文,函数体可以使用关键字 this 引用该对象。
方法链
当方法不需要返回值时,最好直接返回 this。如果在设计的 API 中一直采用这种方式,使用 API 就可以进行“链式调用”风格的编程。
不要将方法的链式调用和构造函数的链式调用混为一谈。
构造函数调用
如果函数或者方法调用之前带有关键字 new,它就构成构造函数调用。
当构造函数调用创建一个新的空对象,这个对象继承自构造函数的 prototype 属性。构造函数试图初始化这个新创建的对象。在表达式 new o.m()中,调用上下文并不是 o。
实参和形参
可选形参
在函数function f(a) { return a;}中,a 是作为形参传入的,相当于var a。
可变长的实参列表:实参对象
arguments 并不是真正的数组,它是一个实参对象。每个实参对象都包含以数字为索引的一组元素以及 length 属性,但它毕竟不是真正的数组。可以理解为它是一个对象,只是碰巧具有以数字为索引的属性。
在非严格模式下,当一个函数包含若干形参,实参对象的数组元素是函数形参所对应实参的别名,实参对象中以数字索引,并且形参名称可以认为是相同变量的不同命名。通过实参名字来修改实参值的话,通过 arguments[]数组也可以获取到更改后的值,例如:
在非严格模式中,函数里的 arguments 仅仅是一个标识符,在严格模式中,它变成了一个保留字。严格模式中函数无法使用 arguments 作为形参名或局部变量名,也不能给 arguments 赋值。
callee 和 caller 属性
除了数组元素,实参对象还定义了 callee 和 caller 属性。在 ES5 严格模式中,对这两个属性的读写操作都会产生一个类型错误。而在非严格模式下,callee 属性指代当前正在执行的函数。caller 是非标准的,但大多数浏览器都实现了这个属性,它指代调用当前正在执行的函数的函数。通过 caller 属性可以访问调用栈。callee 属性在某些时候会非常有用,比如在匿名函数中通过 callee 来递归地调用自身:
实参类型
javascript 是一种非常灵活的弱类型语言,有时适合编写实参类型和实参个数的不确定性的函数。接下来的 flexisum()方法就是这样。比如,它可以接收任意数量的实参并可以递归地处理数组的情况,这样的话,它就可以用做不定实参或者实参是数组的函数。例如:
作为值的函数
自定义函数属性
javascript 中的函数并不是原始值,而是一种特殊对象,也就是说,函数可以拥有属性。当函数需要一个“静态”变量来调用时保持某个值不变,最为方便的方法就是给函数定义属性,而不是定义全局变量。例如:
包含
作用域链
如果将一个局部变量看做是自定义实现的对象的属性话,那么可以换个角度来解读变量作用域。每一段 javascript 代码(全局代码或函数)都有一个与之关联的作用域链。这个作用域链是一个对象列表或者链表,这组对象定义了这段代码“作用域中”的变量。当 javascript 需要查找变量 x 的值的时候(这个过程称作“变量解析”),他会从链中的第一个对象开始查找,如果这个对象有一个名为 x 的属性,则会直接使用这个属性的值,如果第一个对象中不存在名为 x 的属性,则会继续查找下一个对象,以此类推。如果作用域链上没有任何一个对象含有属性 x,那么就认为这段代码的作用域链上不存在 x,并抛出一个引用错误。
在 javascript 的最顶层代码中(也就是不包含在任何函数定义内的代码),作用域链由一个全局对象组成。在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象。当定义一个函数时,它实际上保存一个作用域链。当调用这个函数时,它创建一个新的更长的表示函数调用作用域的“链”。对于嵌套函数来说,每次调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数时,内部嵌套函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的。内部函数在每次定义的时候都有微妙的差别 --- 在每次调用外部函数时,内部函数的代码都是相同的,而且关联这段代码的作用域链也不相同。
实现闭包
我们将作用域链描述为一个对象列表,不是绑定的栈。每次调用 javascript 函数的时候,都会为之创建一个新的对象用来保存局部变量,把这个对象添加至作用域中。当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。如果不存在嵌套的函数,也没有其他的引用指向这个绑定对象,它就会被当做垃圾回收掉。如果定义看嵌套的函数,每个嵌套的函数都各自对应一个作用域链(毕竟嵌套函数也是函数),并且这个作用域链指向一个变量绑定对象。但如果这些嵌套的函数对象在外部函数中保存下来(与之相对的是将嵌套函数作为返回值等),那么它们也会和所指向的变量绑定对象一样当做垃圾回收。但是如果这个函数定义了嵌套的函数,并将它作为返回值返回或者存储在某处的属性里,这时就会有有一个外部引用指向这个嵌套的函数(一般是 var a = 一个返回的闭包函数)。它就不会被当做垃圾回收,并且它所指向的变量绑定对象也不会被当做垃圾回收。
一个闭包经典的对比例子:
上面这段代码创建了 10 个闭包,并将它们存到了一个数组中。这些闭包都是在同一个函数(同一个匿名函数,同一个作用域内)调用中定义的,因此它们可以共享变量 i。当 constfuncs()返回时,变量 i 的值是 10,所有的闭包都共享这一个值,因此数组中函数的返回值都是同一个值。关联到闭包的作用域链都是“活动的”,记住这一点非常重要。嵌套的函数不会将作用域内的私有成员私有成员复制一份,也不会对所绑定的变量生成静态快照。
属性方法构造
我们看到在 javascript 程序中,函数是值。对函数执行 typeof 运算会返回字符串'function',但函数是 javascript 中特殊的对象因为函数也是对象,它们也可以有属性和方法。
length 属性
函数的 length 属性是只读属性,它代表函数形参的数量。
prototype 属性
每一个函数都包含一个 prototype 属性,这个属性是指向一个对象的引用,这个对象称为“原型对象”。每一个函数都包含不同的原型对象。当将函数用做构造函数的时候,新创建的对象会从原型对象上继承属性。
call()方法和 apply()方法
我们可以将 call()和 apply()看做是某个对象的方法,通过啊用方法的形式来间接调用函数。call()和 apply()的第一个实参是要调用函数的母对象,它是调用上下文,在函数体内通过 this 来获得对它的引用。要想以对 o 的方法来调用函数 f(),可以这样使用:
每行代码和下面代码的功能类似:
在 ES5 的严格模式中,call()和 apply()的第一个实验都会变为 this 的值,哪怕传入的实验是原始值甚至是 null 或 undefined。在 ES3 和非严格模式中,传入的 null 和 undefined 都会被全局对象代替,而其他原始值则会被相应包装对象所代替。
对于 call()来说,第一个调用上下文实参之后的所有实参就是要传入待调用函数的值。例如:
apply()方法和 call()类似,但传入实参的形式和 call()有所不同,它的实参都放入数组中:
需要注意的是,传入 apply 的参数数组是可以是类数组对象也可以是真实数组。
bind()方法
ES5 中的 bind()方法不仅仅是将函数绑定至第一对象,它还附带一些其他应用:除了第一个实参之外,传入 bind()的实参也将绑定至 this,这种附的应用是常见的函数式编程技术,有时候称为“柯里化”例如:
bind()方法返回的是一个函数对象,这个函数对象的 length 属性是绑定函数的形参个数减去绑定实参的个数(length 的值不能小于 0)。再者,ES5 的 bind()方法可以顺带用做构造函数。如果 bind()返回的函数用做构造函数,将忽略传入 bind()的 this,原始函数就会以构造函数的形式调用,在运行时将 bind()所返回的函数用做构造函数时,所传入的实参会原封不动的传入原始函数。由 bind()方法所返回的函数并不包含 prototype 属性,并且将这些绑定的函数用做构造函数时所创建的对象从原始的未绑定的构造函数继承 prototype。
Function()构造函数
函数可以通过 Function()构造函数定义:
这个函数与下面的代码定义函数几乎等价:
关于 Function()构造函数有几点需要特别注意:
Function()构造函数允许 javascript 在运行时动态地创建爱并编译函数。每次调用
Function()构造函数都会解析函数体,并创建新的函数对象。如果是在一个循环或者多次调用的函数中执行这个构造函数,执行效率会受影响。关于
Function()构造函数非常重要的一点,就是它所创建的函数并不是使用词法作用域,相反,函数代码体的编译总会在顶层函数(全局作用域)执行。
可调用的对象
“可调用的对象”是一个对象,可以在函数调用表达式中调用这个对象。所有的函数都是可调用的,但并非所有的可调用对象都是函数。比如 RegExp对象。对其执行 typeof 运算的结果并不统一,在有浏览器中返回function,在有些中返回object。
Last updated