JavaScript 解密 —— 函数初步

2020-06-11  本文已影响0人  rollingstarky

一、First-class objects

在理解函数作为一等对象前,先列举下 JavaScript 中对象支持的操作:

var ninja = {}  // 赋值给变量
ninjaArray.push({})  // 作为数组项
ninja.data = {}  // 作为其他对象的属性
function hide(ninja) {
    ninja.visibility = false
}
hide({})  // 对象作为函数参数

function returnNewNinja() {
    return {}  // 对象作为函数返回值
}

var ninja = {}
ninja.name = "Hanzo"  // 动态创建的属性
函数作为一等对象

JavaScript 中的函数拥有作为对象的所有特性,因此可以像对待任何其他对象一样对其进行使用。

function call(ninjaFunction) {
    ninjaFunction()
}
call(function() {})  // 函数作为参数

function returnNewNinjaFunction() {
    return function() {}  // 函数作为返回值
}
var ninjaFunction = function () {}
ninjaFunction.name = "Hanzo"

函数即对象,只不过拥有一项额外的特性(能够被调用)以完成一些特定的动作。任何可以对对象做出的操作,同样可以应用在函数身上。

Callback functions

回调函数即在后续的某个指定的时间节点被其他代码调用(call back)的函数。

var 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

或者

var text = 'Domo arigato'

function useless(ninjaCallback) {
  return ninjaCallback()
}

console.log(useless(function() { return text }))
// Domo arigato

回调函数在数组排序中的使用:

var values = [0, 3, 2, 5, 7, 4, 8, 1]
values.sort(function(value1, value2){
    return value1 - value2
})
Self-memoizing functions

memoization 是指构建一个特殊的函数,该函数可以将之前计算过的值缓存在自己内部,之后再做同样的计算时则可以直接读取缓存的值而不必重新计算。

function 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 myFunctionName(myFirstArg, mySecondArg) {
    myStatement1
    myStatement2
}

函数声明代码可以出现在另一个函数内部:

function ninja() {
  function hiddenNinja() {
    return "ninja here"
  }
  return hiddenNinja()
}
函数表达式

作为 JavaScript 中的一等对象,函数可以通过字面量创建,可以赋值给变量和对象属性,可以作为另一个函数的参数或返回值。
也因此可以将其作为表达式使用,即成为其他代码语句的一部分(比如放在赋值语句的等号右边、充当参数或返回值等)

var myFunc = function() {}

myFunc(function() {  // 作为参数
    return function() {}  // 作为返回值
})

函数表达式甚至可以放置在通常应该使用函数标识符的地方,在声明的同时立即完成调用,称为 immediate function

myFunctionName(3)  // 普通调用
(function() {})(3)  // immediate call

+function () {} ()
-function () {} ()
!function () {} ()
~function () {} ()
Arrow function

在很多情况下,arrow function 可以看作对普通函数表达式的简化。如之前的排序示例:

var values = [0, 3, 2, 5, 7, 4, 8, 1]
values.sort(function(value1, value2){
    return value1 - value2
})

使用 arrow function 则可以改为如下形式:

var values = [0, 3, 2, 5, 7, 4, 8, 1]
values.sort((value1, value2) => value1 - value2)

arrow function 最简单的语法形式为 param => expression,一个基本示例如下:

var greet = name => "Greetings, " + name
console.log(greet('Oishi'))  // Greetings, Oishi

更复杂一点的形式如:

var greet = name => {
  var helloString = 'Greetings, '
  return helloString + name
}

console.log(greet('Oishi'))  // Greetings, Oishi
参数

Rest prarmeters

function 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 parameters

function performAction(ninja, action = "skulking") {
  return ninja + " " + action
}

console.log(performAction("Fuma"))  // Fuma skulking
console.log(performAction("Yagyu", "sneaking"))  // Yagyu sneaking

甚至可以这样写:

function performAction(ninja, action = "skulking",
                       message = ninja + " " + action) {
  return message
}
console.log(performAction("Yoshi"))  // Yoshi skulking

三、函数调用

作为“函数”调用

function 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

作为方法调用:

var ninja = {}
ninja.skulk = function() {}
ninja.skulk()

作为方法调用与作为函数调用的区别:

function 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

作为构造器函数:

function whatsMyContext(){ return this }
new whatsMyContext()

注意与函数构造器(如 new Function('a', 'b', 'return a + b') )的区别:函数构造器用来从字符串中动态地创建函数,而构造器函数则用来创建对象实例。

构造器函数在调用时一般会执行以下操作:

function Ninja() {
  this.skulk = function() {
    return this
  }
}

var ninja1 = new Ninja()
console.log(ninja1.skulk() === ninja1)  // true

var ninja2 = new Ninja()
console.log(ninja2.skulk() === ninja2)  // true

若构造器函数的定义中本身具有返回值,则分为两种情况:

function Ninja() {
  this.skulk = function() {
    return true
  }
  return 1
}

var ninja = new Ninja()
console.log(ninja)  // Ninja { skulk: [Function]  }
console.log(ninja.skulk())  // true
var puppet = {
  rules: false
}

function Emperor() {
  this.rules = true
  return puppet
}

var emperor = new Emperor()
console.log(emperor)  // { rules: false  }
console.log(emperor.rules)  // false

applycall 方法:

function 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

参考资料

Secrets of the JavaScript Ninja, Second Edition

上一篇 下一篇

猜你喜欢

热点阅读