函数基础
在 JavaScript 中函数是第一型对象,也就是说,函数可以共处,可以将其视为其他任意类型的 JavaScript 对象, 就好像普通的数据类型一样,函数可以被变量引用,或声明成对象字面量, 甚至作为参数进行传递。 如下所示:
- 它们可以通过字面量进行创建。
- 它们可以赋值给变量, 数组或其他对象的属性。
- 可以作为参数传递。
- 可以作为函数的返回值进行返回。
- 可以拥有动态创建并赋值的属性,还有方法。
由于我们的大部分代码运行,都是函数调用的结果,所以我们将会发现通用和强大的构造器将给我们提供很大的灵活性和力量。
除了可以像其他对象类型使用以上的功能以外,函数还有一个特殊的功能:调用,并且这些调用通常都是以异步方式进行的,比如浏览器中的事件处理。
浏览器编程和 GUI 桌面应用程序的唯一不同就是, 代码不负责事件轮询和派发, 而是浏览器帮我们处理,我们的职责是为浏览器中发生的各种事件建立处理程序。事件处理程序有一个更通用的名称:回调函数,但是事件处理程序只是回调的一个例子,我们可以在自己的代码中使用回调。需要特别注意的是: 浏览器中的事件轮询是单线程的,每个事件都是按照在队列中所放置的顺序来处理的。所有其他事件必须等到这个事件处理结束以后才能继续。浏览器把事件放到队列上的机制是在事件轮询模型之外,确定事件何时发生并把它们放到事件队列上的过程所处的线程,并不参与事件本身的处理。
JavaScript 语言最重要的功能之一是可以在代码的任何地方创建函数,只要能用表达式,就能创建函数。在一个函数不用被多个地方引用的时候,这一特性可以消除使用不必要的名称所带来的全局命名空间污染。在浏览器中,如果一个命名函数声明在顶层, window
对象上的同名属性则会引用到该函数。
简单验证函数声明
当我们创建一个函数时,我们不仅要关注该函数可用的作用域,还要关注函数自身创建的作用域,以及函数内部的声明是如何影响这些作用域的。在 JavaScript 中作用域是由 function
进行声明的,而不是代码块。声明的作用域是创建于代码块,但不是终结于代码块的。
函数作用域验证
- 变量声明的作用域开始于声明的地方,结束于所在函数的结尾,与代码嵌套无关。
- 命名函数的作用域是指声明该函数的整个函数范围,与代码嵌套无关。
- 对于作用域声明,全局上下文就像一个包含页面所有代码的超大型函数。
函数调用
事实证明,函数调用的方式对其函数内部的代码是如何执行的,有着巨大的影响,尤其是 this
参数。以下是函数调用的4种方式:
- 作为一个函数调用
- 作为一个方法调用,在对象上调用
- 作为构造器调用,创建一个新对象
- 通过
apply()
或call()
方法进行调用
函数参数
从实参到形参
如果传入的参数个数和声明的形参数量不一致,不会抛错。处理策略如下:
- 实参数量 > 形参数量,超出部分不予分配。即使这些多余的参数没有赋值给形参,我们依然可以获取。
- 实参数量 < 形参数量,超出部分赋值 undefined。
所有的函数调用都会附加传递两个隐式参数:this
和 arguments
,这两个参数不出现在参数列表中,但是会传递给函数,并最终存在于函数的作用域中
arguments 参数
这个参数是传递给函数的所有参数的一个集合。注意: arguments
参数不能作为数组调用,只是一个类数组。
this 参数
this
参数引用了与该函数调用进行隐式关联的一个对象,被称之为调用上下文,因为它依赖于函数的调用方式。
作为函数进行调用
这是最一般的情况,这样说是为了区别于其他的3种调用方式。
|
|
这时候,函数的上下文就是全局上下文,一般为 window
对象。事实上,这是函数作为方法调用的一种特殊情况,因为在这里函数的所有者是 window
。
作为方法进行调用
|
|
将函数作为对象的一个方法进行调用时,该(拥有该方法的)对象就变成了函数上下文。在浏览器中的普通函数调用是方法调用的一个特殊例子,特殊之处在于不用显示的表明 window
对象。
作为构造器进行调用
将函数作为构造器调用是 JavaScript 的一个特性,因为当进行构造器调用时,会发生如下事件:
- 创建一个新的空对象。
- 传递给构造器的对象 (即在上一步中创建的空对象) 是
this
参数,从而成为构造器的函数上下文。这时候,其上下文是一个新分配的对象。 - 如果没有显示的返回值,新创建的对象则作为构造器的返回值进行返回。
任何干扰函数创建一个新对象并对其进行设置后,并将其作为返回值来返回的函数都不适合作为构造器。
作为构造器进行调用的函数与其他普通函数的编码一般是不同的,如下:
- 以大写字母开头进行命名。
- 一般情况下,以普通函数的方式调用构造器函数是没有什么意义的。
使用 apply() 和 call() 方法进行调用
作为第一型对象,函数可以有自己的属性和方法,顺便说一下,函数是由 Function()
构造器创建的。
一个极为有用的应用场景:在 调用回调函数的时候
鉴于 apply()
和 call()
的功能基本相同,怎么选择取决于代码的清晰度提高,更精确的回答是:用最能匹配参数的那个方法。如果在变量里有很多无关的值或者是指定为字面量,使用 call()
方法则可以直接将其作为参数列表传进去,但是如果这些参数已经在数组里了,或者很容易放到数组中,那么使用 `apply(**** 会更好。
总结
- 以函数式语言学习 JavaScript,用来编写复杂代码。
- 函数是第一型对象,和 JavaScript 中的其他对象一样,和其它任意对象类型一样,都具有如下特点。
- 通过字面量创建。
- 赋值给变量或属性。
- 作为参数传递。
- 作为函数结果返回。
- 拥有属性和方法。
- 每个对象都有一个小宇宙,即可以被调用。
- 函数是通过字面量进行创建的,其名称是可选的。
- 在页面的生命周期中,浏览器可以将函数作为各种类型的事件处理程序进行调用。
- 函数内部的作用域与其他语言不同。
- 变量的作用域开始于声明处,结束于函数尾部。
- 其可以跨域边界块。
- 内部函数在当前函数的任何位置均可用(提升)。
- 函数的形参列表和实际参数列表的长度可以不同。
- 未被赋值的参数设置为
undefined
。 - 多出的参数不会被绑定到参数名称。
- 未被赋值的参数设置为
- 每个函数调用都会传入两个隐式参数。
arguments
来表示实际传入的参数集合。this
来表示函数上下文的对象引用。
- 可以用不同的方法进行函数调用,不同的调用机制决定了函数上下文的不同。
提示:有一种新的思维层面是函数是程序的构件块而不是命令式语句。