一、First-class objects
在理解函数作为一等对象前,先列举下 JavaScript 中对象支持的操作:
- 可以通过 {}字面量创建
- 可以被赋值给变量、数组项,可以作为其他对象的属性 - 1 
 2
 3- var ninja = {} // 赋值给变量 
 ninjaArray.push({}) // 作为数组项
 ninja.data = {} // 作为其他对象的属性
- 可以作为函数的参数或返回值 
- 可以拥有支持动态创建与赋值的属性1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11function hide(ninja) { 
 ninja.visibility = false
 }
 hide({}) // 对象作为函数参数
 function returnNewNinja() {
 return {} // 对象作为函数返回值
 }
 var ninja = {}
 ninja.name = "Hanzo" // 动态创建的属性
函数作为一等对象
JavaScript 中的函数拥有作为对象的所有特性,因此可以像对待任何其他对象一样对其进行使用。
- 通过字面量创建: function ninjaFunction() {}
- 将函数赋值给变量:var ninjaFunction = function() {}
- 数组中加入函数作为数据项:ninjaArray.push(function() {})
- 函数作为对象的属性:ninja.data = function() {}
- 函数作为其他函数的参数或返回值 - 1 
 2
 3
 4
 5
 6
 7
 8- function call(ninjaFunction) { 
 ninjaFunction()
 }
 call(function() {}) // 函数作为参数
 function returnNewNinjaFunction() {
 return function() {} // 函数作为返回值
 }
- 函数可以拥有支持动态创建和赋值的属性 - 1 
 2- var ninjaFunction = function () {} 
 ninjaFunction.name = "Hanzo"
函数即对象,只不过拥有一项额外的特性(能够被调用)以完成一些特定的动作。任何可以对对象做出的操作,同样可以应用在函数身上。
Callback functions
回调函数即在后续的某个指定的时间节点被其他代码调用(call back)的函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var text = "Domo arigato"
function useless(ninjaCallback) {
  console.log("In useless function")
  return ninjaCallback()
}
function getText() {
  console.log("In getText function")
  return text
}
console.log(useless(getText))
// In useless function
// In getText function
// Domo arigato
或者1
2
3
4
5
6
7
8var text = 'Domo arigato'
function useless(ninjaCallback) {
  return ninjaCallback()
}
console.log(useless(function() { return text }))
// Domo arigato
回调函数在数组排序中的使用:1
2
3
4var values = [0, 3, 2, 5, 7, 4, 8, 1]
values.sort(function(value1, value2){
    return value1 - value2
})
Self-memoizing functions
memoization 是指构建一个特殊的函数,该函数可以将之前计算过的值缓存在自己内部,之后再做同样的计算时则可以直接读取缓存的值而不必重新计算。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22function isPrime(value) {
  if (!isPrime.answers) {
    isPrime.answers = {}
  }
  if (isPrime.answers[value] !== undefined) {
    return isPrime.answers[value]
  }
  var prime = value !== 1
  for (var i = 2; i < value; i++) {
    if (value % i === 0) {
      prime = false
      break
    }
  }
  return isPrime.answers[value] = prime
}
console.log(isPrime(5))
console.log(isPrime.answers)
// true
// { '5': true }
二、函数定义
JavaScript 提供以下几种定义函数的方式:
- 函数声明(表达式):function myFun() { return 1 }
- Arrow function:myArg => myArg * 2
- 函数构造器:new Function('a', 'b', 'return a + b')
- 生成器函数:function* myGen() { yield 1 }
函数声明是最基础的定义函数的方式,其基本格式如下:1
2
3
4function myFunctionName(myFirstArg, mySecondArg) {
    myStatement1
    myStatement2
}
函数声明代码可以出现在另一个函数内部:1
2
3
4
5
6function ninja() {
  function hiddenNinja() {
    return "ninja here"
  }
  return hiddenNinja()
}
函数表达式
作为 JavaScript 中的一等对象,函数可以通过字面量创建,可以赋值给变量和对象属性,可以作为另一个函数的参数或返回值。
也因此可以将其作为表达式使用,即成为其他代码语句的一部分(比如放在赋值语句的等号右边、充当参数或返回值等)1
2
3
4
5var myFunc = function() {}
myFunc(function() {  // 作为参数
    return function() {}  // 作为返回值
})
函数表达式甚至可以放置在通常应该使用函数标识符的地方,在声明的同时立即完成调用,称为 immediate function:1
2
3
4
5
6
7myFunctionName(3)  // 普通调用
(function() {})(3)  // immediate call
+function () {} ()
-function () {} ()
!function () {} ()
~function () {} ()
Arrow function
在很多情况下,arrow function 可以看作对普通函数表达式的简化。如之前的排序示例:1
2
3
4var values = [0, 3, 2, 5, 7, 4, 8, 1]
values.sort(function(value1, value2){
    return value1 - value2
})
使用 arrow function 则可以改为如下形式:1
2var values = [0, 3, 2, 5, 7, 4, 8, 1]
values.sort((value1, value2) => value1 - value2)
arrow function 最简单的语法形式为 param => expression,一个基本示例如下:1
2var greet = name => "Greetings, " + name
console.log(greet('Oishi'))  // Greetings, Oishi
更复杂一点的形式如:1
2
3
4
5
6var greet = name => {
  var helloString = 'Greetings, '
  return helloString + name
}
console.log(greet('Oishi'))  // Greetings, Oishi
参数
Rest prarmeters1
2
3
4
5
6
7
8function multiMax(first, ...remainingNumbers){
  var sorted = remainingNumbers.sort(function(a, b){
    return b - a
  })
  return first * sorted[0]
}
console.log(multiMax(3, 1, 2, 3))  // 9
Default parameters1
2
3
4
5
6function performAction(ninja, action = "skulking") {
  return ninja + " " + action
}
console.log(performAction("Fuma"))  // Fuma skulking
console.log(performAction("Yagyu", "sneaking"))  // Yagyu sneaking
甚至可以这样写:1
2
3
4
5function performAction(ninja, action = "skulking",
                       message = ninja + " " + action) {
  return message
}
console.log(performAction("Yoshi"))  // Yoshi skulking
三、函数调用
作为“函数”调用1
2
3
4
5
6
7function ninja(name) { console.log(name) }
ninja('Hattori')  // Hattori
var samurai = function(name) { console.log(name) }
samurai('Hattori');  // Hattori
(function(name) { console.log(name) })('Hattori')  // Hattori
作为方法调用:1
2
3var ninja = {}
ninja.skulk = function() {}
ninja.skulk()
作为方法调用与作为函数调用的区别:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function whatsMyContext() {
  return this
}
console.log(whatsMyContext() === global)  // true
var getMyThis = whatsMyContext
console.log(getMyThis() === global)  // true
var ninja1 = {
  getMyThis: whatsMyContext
}
console.log(ninja1.getMyThis() === ninja1)  // true
var ninja2 = {
  getMyThis: whatsMyContext
}
console.log(ninja2.getMyThis() === ninja2)  // true
作为构造器函数:1
2function whatsMyContext(){ return this }
new whatsMyContext()
注意与函数构造器(如 new Function('a', 'b', 'return a + b') )的区别:函数构造器用来从字符串中动态地创建函数,而构造器函数则用来创建对象实例。
构造器函数在调用时一般会执行以下操作:
- 创建一个空的对象
- 新创建的对象绑定给 this参数传递给构造器,成为构造器函数的上下文
- 新创建的对象作为 new操作的返回值被返回
| 1 | function Ninja() { | 
若构造器函数的定义中本身具有返回值,则分为两种情况:
- 若构造器的定义中返回了一个非对象值(字符串、数字等),则该返回值被忽略,由构造器创建的 this对象被返回作为new表达式的值
- 若构造器的定义中返回了一个对象,则该对象作为 new 表达式的值,由构造器创建的 this对象则被忽略1 
 2
 3
 4
 5
 6
 7
 8
 9
 10function Ninja() { 
 this.skulk = function() {
 return true
 }
 return 1
 }
 var ninja = new Ninja()
 console.log(ninja) // Ninja { skulk: [Function] }
 console.log(ninja.skulk()) // true
| 1 | var puppet = { | 
apply 和 call 方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function juggle() {
  var result = 0
  for (var n = 0; n < arguments.length; n++) {
    result += arguments[n]
  }
  this.result = result
}
var ninja1 = {}
var ninja2 = {}
juggle.apply(ninja1, [1,2,3,4])
juggle.call(ninja2, 5,6,7,8)
console.log(ninja1.result)  // 10
console.log(ninja2.result)  // 26