函数基础。为什么说函数是 JavaScript 世界的第一公民

一些使用 JavaScript 函数的基本注意事项,主要是 Secrets of the JavaScript Ninja 第三章的小结。适合有其他语言编程经验的人阅读。

要想真正掌握 JavaScript 编程,必须把 JavaScript 作为一种函数式编程语言来理解。

函数是 JavaScript 世界的第一类对象(first-class object)。所有对象(object)有如下功能:

  • 可以通过字面量(literals)创建
  • 可被赋于变量,数组元素和其他对象的属性(property)
  • 可以作为参数传递给函数
  • 可以作为函数的返回值
  • 可以含有能被动态创建和赋值的属性 JavaScript 函数拥有所有以上能力,能像其他对象一样使用。因此,我们说函数是第一类对象。 除了上述对象的功能,函数区别对象之处是能被调用。

函数与对象的区别: 函数可以被调用


函数有三种

1. 普通函数

function myFunc( ){
    ...
}

2. 内联函数。普通函数被赋给一个变量后变为内联函数

var iFunc = function myFunc( ){
    ...
}

内联函数有函数名但是不能用来调用,只有使用变量来调用。函数名只在函数内部可见,所以可以用来做递归(recursion)调用。性能上类似匿名函数,会有性能的损失。

3. 匿名函数。

function( ){
    ...
}
//或者赋给一个变量
var nFunc = function( ){
    ...
}

JavaScript 里匿名函数使用非常普遍。

JavaScript 语言的一个重要特点是可以在任何表达式允许出现的地方创建函数。

JavaScript 使用函数字面量(function literal)申明函数和数值字面量(numeric literal)创建数值是一样的过程。记住,作为第一类对象,函数可以和其他字符串和数值一样被使用。

作用域 (scope)

JavaScript 的作用域是整个函数,而不是 { }。这一点和其他多数语言都不一样。很多有经验的程序员新入手 JavaScript 的时候被坑。例如

if (true){
    var account = 100;
}
var num = account;
console.log( num ); // 这里会输出 100,account仍然可用

account 变量在 if 之外还继续存在,可以继续的合法使用。WTF!

函数作用域提升(hoisting)

就是非匿名函数可以在声明之前引用 (forward-referenced),当然前提是在同一个作用域内。注意,如果函数被赋予给了一个变量,例如:

var fval1 = function func1(){ return 100; }

就不会被提升了,因为变量的作用域不能被提升。fval1 和 func1 都不会被提升。实际上,在被赋予一个变量后, func1 名字已经失效,不能再用来调用函数了,虽然 console.log( fval1.name ) 还是会输出 func1,但是把 func1赋值给 fval1的行为让 func1 变为了内联函数( inline function), 类似匿名函数,名字虽然存在但是不能用来调用。 函数在 JavaScript 里真的比较特殊。而下面的

var fval2 = function (){ return 101; }

是匿名函数,当然也不会被提升。

只有如下这样的才能被提前引用:

function namedFunc(){ return 102; }

一共有 4 种调用函数的方式

  1. 直接调用
  2. 作为对象的方法调用
  3. 作为对象的构造函数调用
  4. 通过 apply( ) 或 call( ) 调用

调用 JavaScript 函数时如果形参(parameter)和实参(augment)的数目对不上,不会报错。

  • 如果是实参多于形参,多出来的部分被忽略掉。
  • 如果是形参多于实参,没被赋值的会被设为 undefined。

所有的函数调用都会有两个隐含的形参:arguments 和 this

  • arguments: 神似数组但不是数组。它有 length 属性,得到arguments 的长度,也可以用 index,例如 arguments[0] 访问第一个元素,但就只有这些了,没有数组具有的其他方法。
  • this:函数上下文(function context),具体是啥得看怎么被调用的

函数的第一种和第二种调用的方式 (直接调用和作为对象方法调用)其实是一样的。因为在浏览器里,第一种其实就是 window 对象的方法,如果函数是全局函数的话。

第三种,通过 new 关键字来调用构造函数。调用后,以下会发生:

  • 一个新的对象被创建
  • 这个对象作为 this 参数被传递给构造函数,变成这个构造函数的函数上下文
  • 没有显式返回值,这个新对象就作为这个构造函数的值被返回

构造函数的目的是创建一个新对象,初始化,然后作为构造值返回。

最后一种,apply( ) 和 call( ) 。当我们调用一个函数时,使用他们来显示指定任何对象作为函数的上下文。 apply( ) 和 call ( ) 的区别只是 apply( ) 的第二参数是数组,而 call( ) 是一串单独的元素。

最后,思考问题的时候要把函数作为基本的组件而不是祈使语句,这样你的 JavaScript 水平才能上一个台阶。