Javascript中函数的定义和参数

函数的定义和参数

结构

  • 函数式的不同点到底是什么?
  • 函数作为对象的乐趣
  • 函数定义
  • 函数的实参和形参
  • 小结
  • 练习

函数式的不同点到底是什么?

函数式概念之所以如此重要, 原因之一在于函数是执行过程中的主要模块单元.

既然大多数代码都以函数作为模块单元, 那么就应该尽可能少的限制函数的使用方式, 从而引出函数是一等公民的概念. 函数拥有对象的所有能力. 从一等公民的概念上看, 函数应该具有如下能力:

// 使用字面量创建一个函数
function f() {}

// 将函数赋值给变量
var v = f
// 将函数插入到数组中
var arr = []
arr.push(f)
// 将函数作为对象的属性
var obj = {}
obj.f = f

// 将函数作为参数传递给另外一个函数
function call(func) {
  func()
}
call(f)

// 将函数作为一个值从另一个函数返回
function creator() {
  return function() {}
}

// 奇特的是, 函数其实是对象, 给一个函数添加属性
// 函数和其他非函数的对象的主要区别在于函数是可调用的, 而其它对象不是
f.status = "done"

函数式编程是一种编程风格, 或称为范式. 函数式编程不是唯一的编程范式, 对于同一个问题, 可以使用函数式风格解决, 也可以通过其他编程风格解决. 其他编程风格还有命令式(C语言), 面向对象式(Java). 函数式编程本身是一个较大的问题, 一般来说, 使用函数式编程的主要优势在于其代码易于测试, 扩展和模块化.

回调函数

高效使用Javascript编程的关键在于使用回调函数. 基于Javascript中函数作为一等对象且函数可以定义在任何表达式出现的位置, 也就可以方便的将函数做为回调函数传递给另一个函数完成调用. 这样的调用通常是异步的.

let arr = []

for(let i = 0; i < 20; i++) {
  arr.push(Number.parseInt(Math.random()*100))
}

// 将比较函数作为回调函数传递给排序函数, 该函数定义在参数列表中
arr.sort(function(v1, v2) {
  return v1 - v2
})

函数作为对象的乐趣

在Javascript中一个让人感觉惊讶的地方是函数是对象. 和其他所有对象一样可以赋予属性, 属性可以是任意的值. 如此可以产生一些特别的应用, 如赋值给函数一个id来管理函数的集合, 或者将函数每次计算得到的值缓存在其自身的属性中, 如此可以在后续调用时首先根据入参查找是否已经缓存过结果, 从而提高运算性能.

管理函数集合

var store = {
  nextId: 1,
  cache: {},
  add: function(fn) {
    if(!fn.id) {
      fn.id = this.nextId++
      this.cache[fn.id] = fn
      return true
    }
  }
}

store对象的作用是存储一个函数的集合用于如某个事件触发之后调用其中存储的所有回调函数. 也可以简单的使用数组来存储这样一个集合, 这样就需要一个方式确认向集合中添加函数时不会添加了重复的函数. 使用数组来存储这个集合也就意味着每次添加函数时都需要遍历一遍集合以确保集合中不会保存重复的函数. 而使用store对象则是更简洁的方法, 添加函数进入集合时不需要每次都遍历一遍集合, 只需简单的在函数上设置一个标志位即可.

自记忆函数

function isPrime(value) {
  // 首先创建缓存区
  if(!isPrime.cache) {
    isPrime.cache = {}
  }
  // 在缓存区查找缓存值
  if(isPrime.cache[value] !== undefined) {
    return isPrime.cache[value]
  }
  // 没有缓存值的情况下才执行运算
  let prime = value !== 0 && value !== 1
  for(let i = 2; i < value; i++) {
    if(value % i === 0) {
      prime = false
      break
    }
  }
  // 将执行运算的结果存入缓存区
  return isPrime.cache[value] = prime
}

以上代码利用了函数是对象的特性创建了一个可以缓存计算结果的函数, 此函数将这个缓存操作封装在其内部, 外部调用者无需以任何特殊方式取使用它就可以获得性能的提升.

函数定义

函数通常使用函数字面量来创建函数值, 然而作为第一类对象, 函数可以使用程序中的值定义的, 如字符串或变量中的值. 一共有4类函数定义方式:

  • 函数定义和函数表达式
    function func() { return 1 }
    var func = function() { return 1 }
  • 箭头函数
    param => param * 2
  • 函数构造函数
    new Function('a', 'b', 'return a + b')
  • 生成器函数
    function* gen() { yield 1 }

函数创建的方式影响了函数可被调用的时间, 函数的行为及函数可以在哪个对象上被调用.

函数声明和函数表达式

首先是函数声明, 其定义语法是: function func(a, b) { return a + b }, 它和函数表达式看起来类似, 其特点是函数声明是独立的Javascript代码块, 作为一个单独的Javascript语句.

接着是函数表达式, 其定义语法是: function(a, b) { return a + b }, 可以看到它就像一个没有名字的函数声明. 实际上函数表达式几乎总是其他表达式的一部分, 例如可以出现在赋值表达式的右值, 或出现在函数调用的参数列表中. 简单来说它就是一个表达式.

最后是立即函数, 对于基本的函数调用而言首先求值得到函数的标识符作为左值, 接着使用函数调用运算符(即一对括号)调用这个函数. 函数表达式的定义本身就可以作为函数标识符的左值, 因此可以使用一对括号立即调用这个新定义的函数, 这就是立即调用函数表达式(IIFE).

// 从语法层面依然需要一对括号括起函数表达式的定义
// 如果使用函数表达式定义函数, 而该定义独立的出现在程序代码中
// 而非作为其他表达式的一部分, 解释器将会报错, 因为其认为这是一个
// 忘记写函数名的函数声明, 因此需要让函数定义表达式出现在另一个表达式中,
// 如使用运算符通知解释器这里是一个表达式
;(function(){
  console.log('immediately')
})()
;(function() {
  console.log('expression')
}())

// 使用4个一元运算符依然可以通知解释器接下来的函数表达式是一个表达式而非语句
;+function(){
  console.log('+++')
}()
;-function(){
  console.log('---')
}()
;!function(){
  console.log('!!!')
}()
;~function(){
  console.log('~~~')
}()

箭头函数

Javascript中会大量使用函数, 因此ES6标准中出于简化创建函数方式的目的新增了箭头函数, 一般来说箭头函数就是函数表达式的简化版. 箭头函数有两种可选方式:

// 函数体就是一个表达式时省略了return, 该表达式的求值结果就是函数的值
var greet = name => 'Greetings ' + name

// 和函数表达式一样, 使用大括号包含了整个函数体
var greet = name => {
  var helloString = 'Greetings '
  return helloString + name
}

函数的实参和形参

  • 形参是定义函数时所列举的变量
  • 实参是调用函数时所传递给函数的值

当调用一个函数时所列举的实参会按照顺序赋值给函数内的形参. 当实参多余形参或形参多余实参时都不会抛出错误. 多出的实参被简单的丢弃, 而多出的形参则被赋值为undefined.

剩余参数

在形参列表中的最后一个形参可以使用剩余参数, 使用...标识这个形参是剩余参数, 它将接收所有还未被之前形参所接收的实参的值, 并将这些值放到一个数组中. 如果没有任何可以接收的实参那么剩余参数将被赋值为空数组而非undefined.

默认参数

默认参数是ES6的新特性, 其目的依然是书写简洁的代码, 看下面的例子中的ES6之前的默认参数处理方式和ES6之后的默认参数处理方式就可以看出其区别:

function sum(a, b) {
  // ES6之前处理默认参数需要写出冗长的代码
  a = typeof a === 'undefined' ? 0 : a
  b = typeof b === 'undefined' ? 0 : b
  return a + b
}

// ES6之后简单的在参数列表中指定其默认值
function sum(a=0, b=0) {
  return a + b
}

// 默认参数可以引用参数列表中之前的参数的值
// 现在如果只向sum传递一个参数那么它的功能就变成了double
function sum(a=0, b=a) {
  return a + b
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容