一、First-class objects
在理解函数作为一等对象前,先列举下 JavaScript 中对象支持的操作:
- 可以通过
{}
字面量创建 可以被赋值给变量、数组项,可以作为其他对象的属性
1
2
3var 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
8function call(ninjaFunction) {
ninjaFunction()
}
call(function() {}) // 函数作为参数
function returnNewNinjaFunction() {
return function() {} // 函数作为返回值
}函数可以拥有支持动态创建和赋值的属性
1
2var 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