JS函数定义

函数是一段JS代码,它只定义一次,但可能被执行或调用任意次。你可能已经从诸如子例程(subroutine)或过程(procedure)这些名字里对函数的概念有所了解。

JS函数是参数化的,函数的定义会包括一个称为形参(parameter)的标识符列表,这些参数在函数体总像局部变量一样工作。JS函数调用会为形参提供实参(argument)的值,函数使用实参的值来计算返回值,成为函数调用表达式的值。

参数分为形参(parameter)和实参(argument)的区别,形参相当于函数中定义的变量,实参是在运行时的函数调用时传入的参数。

除了实参外,每次调用函数还会拥有另一个值,即本次调用的上下文,这就是 this 关键字的值。如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。当通过这个对象来调用函数时,该对象就是此次调用的上下文(context),也就是该函数的 this 的值。

在JS中,函数即对象,程序可随意操控他们。比如JS可把函数赋值给变量,或者作为参数传递给其他函数。因为函数就是对象,所以可以给它们设置属性,甚至调用他们的方法。

JS的函数可嵌套在它函数中定义,这样它们 就可以访问被定义时所处的作用域中的任何变量。这意味着JS函数构成了一个闭包(closure),它给JS带啦了非常强劲的编程能力。

用于初始化一个新创建的对象的函数称之为构造函数(constructor)

函数定义

函数对任何语言来说都是一个核心的概念,通过函数可以封装任意多条语句,可在任何地方任何时候调用。

函数使用 function 关键来定义,它可以用在函数定义表达式函数声明语句中。

函数声明语句并非真正的语句,ECMAScript规范只是允许它们作为顶级语句。它们可以出现在全局代码里,或内嵌在其他函数中,但它们不能出现在循环、条件判断、try/catch/finally、with语句中。

注意,此限制仅适用于语句声明形式定义的函数,函数定义表达式可以出现在JS代码的任何地方。

函数声明语句

函数提升 函数声明语句“被提前”到外部脚本或外部函数作用域的顶部,所以以这种方式声明的函数,可以被在它定义之前出现的代码所调用。不过,以表达式定义的函数就另当别论了,为了调用一个函数,必须要能引用它,而要使用一个以表达式方式定义的函数之前,必须把它赋值给一个变量。变量的声明提前了,但给变量赋值是不会提前的。所以,以表达式方式定义的函数在定义前无法调用。

// 函数通过函数名来调用
log('hello');// 函数提升

// ECMAScript中的函数使用`function`关键字来声明,后跟一组参数以及函数体。
function log(msg){
  console.log(msg);
}

大多数函数包含一条 return 语句, return 语句导致函数停止执行,并返回它的表达式的值给调用者。如果 return 语句没有一个与之相关的表达式,则它返回 undefined 值。如果一个函数不包含 return 语句,那它就只执行函数体中的每条语句,并返回 undefined 值给调用者。

// 计算两个笛卡尔坐标之间的距离
function distance(x1,y1, x2,y2){
  var dx = x2 - x1;
  var dy = y2 - y1;
  return Math.sqrt(dx*dx + dy*dy);
}
// 计算阶乘的递归函数
function factorial(n){
  if(n <= 1){
    return 1;
  }
  return n * factorial(n-1);
}

函数定义表达式

以表达式方式定义的函数,函数名称是可选的。一条函数声明语句实际上声明了一个变量,并把一个函数对象赋值给它。

// 函数表达式定义函数后赋值给变量
var square = function(n){
  return n * n;
}

相对而言,定义函数表达式时并没有声明一个变量。函数可以命名,如果一个函数定义表达式包含名称,函数的局部作用域将会包含一个绑定到函数对象的名称。实际上,函数的名称将成为函数内部的一个局部变量

// 函数表达式可包含函数名,这在递归时很有用。
var fn = function fact(n){
  if(n <= 1){
    return 1;
  }else{
    return n * fact(n-1);
  }
}

通常而言,以表达式方式定义函数时不需要名称,这会让定义它们的代码更为紧凑。函数定义表达式特别适合用来定义那些只会用到一次的函数

// 函数表达式作为参数传递给其他函数
arr.sort(function(a, b){
  return a-b;
});
// 函数表达式可定义后立即执行
var ten_squared = (function(n){
  return n  * n;
}(10));

函数返回值

  • 函数在定义时不必指定是否具有返回值
  • 函数可包含多个 return 语句
  • 函数的 return 语句也可以不带任何返回值
// ECMAScript中的函数在定义时不必指定是否具有返回值
function sum(num1, num2){
  // 实际上,任何函数在任何收都可以通过 return 语句后跟要返回的值来实现返回值。
  return sum1+sum2;// 函数在执行完 return 语句后停止并立即退出
  // 位于 return 语句之后的任何代码永远不会执行
}
var result = sum(1,2);// 3
// 一个函数可包含多个 return 语句
function diff(num1, num2){
  if(num1 < num2){
    return num2-num1;
  }else{
    return num1-num2;
  }
}
// 函数的 return 语句也可以不带任何返回值
// 这种情况下,函数在停止执行后将返回 undefined 值。
// 这种用法用在需要提前停止函数执行而又不需要返回值的情况。
function say(name, msg){
  return ;
  console.log(name,msg);// 永远不会执行
}

推荐的做法是要么让函数始终都返回一个值,要么永远都不要返回值。否则,如果函数有时候返回值,有时候不返回值,会给调试代码带来不便。

在JS的严格模式下(use strict)对函数有一些限制:

  • 不能将函数命名为 evalarguments
  • 不能将函数参数命名命名为 evalarguments
  • 不能出现两个命名参数同名的情况

如果发生以上情况,会导致语法错误,代码无法执行。

嵌套函数

JS中,函数可以嵌套在其他函数中,嵌套函数的有趣之处在于它的变量作用域规则:它们可以访问嵌套它们(或多重嵌套)的函数的参数和变量。

// 求三角形斜边
function hypotenuse(a,b){
  function square(n){
    return n  * n;
  }
  return Math.sqrt(square(a) + square(b));
}

理解函数参数

ECMAScript函数的参数与大多数其他语言中函数的参数有所不同。

ECMAScript函数不介意传递进来多少个参数,也不在乎传进来参数是什么数据类型。也就是说,即便你定义的函数只接收两个参数,在调用这个函数时也未必一定要传递两个参数。可传递一个、两个、三个、多个,甚至不传入参数,而JS解析器永远不会有什么怨言。

之所以会这样,原因是ECMAScript中的参数在内部是用一个数组来表示的。函数接收到的始终都是这个数组,而不关系数组中包含哪些参数。实际上,在函数体内可通过 arguments对象来访问这个参数数组,从而获取传递给函数的每个函数。

其实, arguments对象只是与数组类似,它并不是 Array 类的实例,也被称为 “伪数组”。arguments可以使用方括号语法访问它的每个元素,使用length属性来确定传递进来多少个参数。

function log(){
  // 不显式地使用命名参数
  console.log(arguments.length); // arguments对象的length属性可获知参数传递的个数
  console.log(arguments[0], arguments[1]);// 是通过访问伪数组arguments对象的下标来获取传递的参数
}

这个事实说明了ECMAScript函数的一个重要特点:“命名的参数只提供便利但不是必需的”。另外,在命名参数方面,其他语言可能需要实现创建一个函数签名,而将来的调用必须与该签名一致。但在ECMAScript中,没有这些条条框框,JS解析器不会验证命名参数

另一个与参数相关的重要点是,arguments对象可与命名参数一起使用。

function doAdd(num1, num2){
  if(arguments.length==1){
    console.log(num1+1);
  }else{
    console.log(arguments[0] + num2); 
  }
}

关于 arguments 的行为,还有一点比较有意思,那就是 arguments 的值永远与对应命名参数的值保持同步。

function doAdd(num1, num2){
  arguments[1] = 10; // arguments对象中的值会自动反映到对应的命名参数
  console.log(arguments[0] + num2);
}

arguments 对象中的值会自动反映到对应的命名参数。不过,这并不是说读取这两个值会访问相同的内存空间。它们的内存空间是独立的,但它们的值会同步。这种影响是单向的,即修改命名参数不会改变 arguments 中对应的值。

另外还要记住,如果只传入一个参数,那么为 arguments[1] 设置的值不会反应到命名参数中。这是因为 arguments 对象的长度是由传入的参数个数所决定的,不是由定义函数时的命名参数的个数决定给的

关于参数还要记住,没有传递值的命名参数将自动被赋予 undefined 值,这就跟定义变量但又没有初始化一样。

严格模式对如何使用 arguments 对象作出了一些限制:

  • 重写 arguments 的值会导致语法错误,代码将不会执行。
  • arguments 对象属性赋值将变得无效

ECMAScript中的所有参数传递的都是值,不可能通过引用传递参数。

没有重载

ECMAScript函数不能像传统意义上那样实现重载,在其他语言如Java中,可以为一个函数编写两个定义,只要这两个定义的签名(接收的参数的类型和数量)不同即可。

ECMAScript函数没有签名,因为其参数是由包含零或多个值的伪数组来表示的。所以没有函数签名,真正的重载是不可能做到的。

如果在ECMAScript中定义多个名字相同的函数,则该名字只属于后定义的函数。

如前所述,通过检查传入函数中参数的类型和数量并作出不同的反应,可以模仿方法的重载。

小结

  • ECMAScript中的函数与其他语言中的函数有诸多不同之处
  • ECMAScript函数无需指定返回值,因为任何ECMAScript函数都可以在任何时候返回任意值。
  • 未指定返回值的函数返回的是一个特殊的 undefined
  • ECMAScript中没有函数签名的概念,因为其函数参数是以一个包含零个或多个值的伪数组的形式传递的。
  • 可以向ECMAScript函数传递任意数量的参数,通过 arguments 对象来访问这些参数。
  • 由于不存在函数签名的特性,ECMAScript函数不能重载。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348

推荐阅读更多精彩内容