JavaScript中的函数概述

 2016年10月26日    95     声明


在JavaScript中,函数是一等对象。它即可以像普通对象一样有属性和方法,又可以被外部程序或自身调用。JavaScript中所有的函数都是一个Function对象。

  1. 函数介绍
  2. 函数定义
  3. arguments对象与ES6新增的参数表示方法
  4. 定义方法函数
  5. 块级函数、条件定义函数

1. 函数介绍

定义

通常来讲,函数是一个可以被外部代码调用或者函数本身递归调用的子程序。函数由函数体、传入参数、返回值三部分组成。一个函数的函数体由一系列的语句组成,函数可以接收传入参数,也可以返回一个值。

参数

参数分为形参实参。其中,调用函数时,传递给函数的值被称为函数的实参;而对应位置的函数参数名叫作形参。

如果实参是一个包含原始类型的变量(UndefinedNullBooleanNumberStringSymbol),那么在即使在函数内部修改对应形参的值,返回后,该实参变量的值也不会改变。如果实参是一个引用类型(Object),则在则在函数内部修改对应形参的值后,实参值也会发生变化,因为形参和该实参指向同一个对象。

如,使用引用类型实参时:

// 定义一个函数 myFunc
 function myFunc(theObject)
 {
   //实参 itbilu 和形参 theObject 指向同一个对象.
   theObject.name = "itbilu";
 }
 
 /*
  * 定义变量 itbilu
  * 并将其初始化为一个对象
  */
 var itbilu = {
   name: "IT笔录",
   domain: "http://itbilu.com",
   year: 2015
 };

 // 'IT笔录'
console.log(mycar.name);

 // 对象引用传给函数
 myFunc(itbilu);

 // 'itbilu',对象的属性已被修改.
 console.log(mycar.name);

返回值

函数总是会有一个返回值。函数一般使用return关键字返回一个值,如果函数中没有使用return语句,那会默认返回undefined。如果不要返回undefined,则在函数中必须使用return语句来指定所要返回的值。(使用new关键字调用一个构造函数除外)。


2. 函数定义

定义函数一个函数时,可以使用函数声明函数表达式Function构造函数 三种基本方式来定义定义JavaScript函数。而在ECMAScript 2015语言标准中,又扩展了Generator(生成器)函数箭头(=>)函数两种函数,所以在JavaScript中有以下定义函数的方式。

2.1 函数声明

函数声明(函数语名句)是指用指定的参数和函数体声明一个函数。使用函数声明定义一个函数语法如下:

function name([param,[, param,[..., param]]]) {
  [statements]
}
  • name - 函数名
  • param - 传递给函数的参数,最多有255个
  • statements - 函数体


2.2 函数表达式

函数表达式(function expression)和函数声明非常相似,不同的是函数表达式可以省略函数名,从而定义一个匿名函数。函数表达式语法结构如下:

function [name]([param1[, param2[, ..., paramN]]]) {
  statements
}
  • name - 函数名。可以省略,省略时即为匿名函数。
  • param - 传递给函数的参数,最多有255个
  • statements - 函数体

在使用函数表达式时,函数名(name)可有可无,可以省略函数名从而创建匿名函数。而无论有没有函数名,该函数名都不能在函数外部被调用,只能在函数体内部调用函数名。

如,使用函数表达式定义函数:

var x = function(y) {
  return y * y;
};


2.3 Function构造函数

和其它JavaScript内置对象类似,Function也可以使用new关键字创建对象实例:

new Function ([arg1[, arg2[, ...argN]],] functionBody)
  • arg1, arg2, ... argN - 函数使用的参数
  • functionBody - 一个表示函数定义的JavaScript语句的字符串。

注意:Function构造函数使用字符串做为函数体,这会阻止JS引擎的优化并带来一些其它问题。因此,并不建议使用这种函数定义方式。

更多关于函数声明、函数表达式、构造函数之间的比较,请参考:函数声明、Function构造函数、函数表达式的比较


2.4 生成器函数声明

生成器函数(Generator)是ES6中新增的一种函数,其声明方式为function*,即在function关键字后增加一个*号。

生成器函数声明语法格式如下:

function* name([param[, param[, ...param]]]) { 
  statements 
}
  • name - 函数名
  • param - 传递给函数的参数,最多有255个
  • statements - 函数体


2.5 生成器函数表达式

生成器函数同样可以使用表达式定义的方式:

function* [name]([param] [, param] [..., param]) { 
  statements 
}
  • name - 函数名。可以省略,省略时即为匿名函数。
  • param - 传递给函数的参数,最多有255个
  • statements - 函数体


2.6 生成器函数构造函数

生成器函数同样有构造函数的定义方式,生成器函数的构造函数是GeneratorFunction,可以使用new关键字来创建对象:

new GeneratorFunction (arg1, arg2, ... argN, functionBody)
  • arg1, arg2, ... argN - 函数使用的参数
  • functionBody - 一个表示函数定义的JavaScript语句的字符串。

注意:不推荐使用构造器函数GeneratorFunction来创建函数,因为它需要的函数体作为字符串做为函数体,可能会阻止一些JS引擎优化,也会引起其他问题。


2.7 箭头函数表达式

箭头函数是简写形式的函数表达式,并且它拥有词法作用域的this值(即不会新产生自己作用域下的this,argumentssupernew.target等对象),箭头函数总是匿名函数。

箭头函数的语法结构如下:

([param] [, param]) => { statements } param => expression
  • param - 函数参数
  • statements 或 expression - 声明多个语句时需要用大括号括起来,而单个表达式则不用。


3. arguments对象与ES6新增的参数表示方法

arguments是一个类似数组的对象,表示传递给一个函数的参数列表。arguments不是全局的,而是一个是函数内部的本地变量。在ES6语言标准中,对参数对象进行了扩展,增加了剩余(reset)参数默认(default)参数两种参数表示形式。


3.1 arguments对象

在JavaScript中,函数(function)参数被表示为一个类似数组(Array)对象arguments对象。该对象是一个参数列表,通过这个对象你可以向函数体传入已指定参数之外的额外参数,也就是说,你可以在创建参数时不指定任何参数而通过arguments对象对象访问所要传入的参数。

我们可以在函数内部使用arguments关键字,来获取该函数的参数。而访问一个函数参数,可以使用形参和arguments两种形式:

function myFun(arg1, arg2) {
  console.log(arg1);
  console.log(arg2);
  console.log(arguments[0]);
  console.log(arguments[1]);
}

myFun('itbilu.com', 'IT笔录');
//itbilu.com
//IT笔录
//itbilu.com
//IT笔录

arguments在对Function对象调用apply()方法时非常有用:

function show (site, domain) {
  console.log('%s-%s', site, domain);
}

function itbilu (){
  show.apply(this, arguments);
}
itbilu('IT笔录', 'itbilu.com');  //IT笔录-itbilu.com

arguments对象并不是一个真正的Array,它并没有数组所特有的属性和方法。如,没有.pop方法。但可以将其转换为一个数组:

var args = Array.prototype.slice.call(arguments);


3.2 默认参数

在JavaScript中,如果函数的实参未传入时,则参数的默认值是undefined。有时,我们可能需要设置一个不同的默认值,这时可以使用默认参数。

默认参数(默认值参数)又称做默认函数参数,是指如果一个形参没有传入对应的实参或者传入了undefined,则该形参会被赋予一个默认值。

如,在下面的乘法计算中,如果参数b未提供,那么其值是undefined,直接进行乘法运算的值会是NaN。所以需要对b的值进行判断,并将其值赋为1

function multiply(a, b) { b = typeof b !== 'undefined' ? b : 1; return a*b; } multiply(5); // 5

而如果使用默认值参数,我们就可以省去这些判断:

function multiply(a, b = 1) {
  return a*b;
}

multiply(5); // 5


3.3 剩余参数

剩余(reset)参数是将函数长度不确定的实参表示为一个数组,并以...参数名的形式做为函数最后一个参数提供。

可以像下面这样使用剩余参数:

function f(a, b, ...theArgs) {
  console.log(theArgs);  // [3, 4, 5]
}

f(1, 2, 3, 4, 5);

剩余参数与arguments对象有以下两点区别:

  • 剩余参数只包含没有提供形参的实参;而arguments包含了提供给函数的所有实参
  • arguments不是一个真正的数组;而剩余参数是一个Array对象


4. 定义方法函数

方法函数是指定义在对象内部的函数,对象方法分为属性方法和访问器方法(GetterSetter)。在ES6之前,只能使用Object.defineProperty()来定义访问器方法。而在ES6中增加了简短方法定义方式,可以在对象初始器中定义属性方法的语法,同样 这是一种把方法名直接赋给函数的简写方式。

4.1 对象的GetterSetter函数

在任何支持添中新属性的JavaScript标准的内置对象或用户定义的对象中,都可以定义访问器方法gettersetter。访问器方法,是对象提供给外部的用于访问或设置对象内置属性的访问接口。getter是访问器,它是一个访问方法,用于访问对象的内置属性。setter是设置器,它是一个设置方法,用于设置对象的内置属性。内定义getter(访问方法)和setter(设置方法)。

在ES6之前,我们会像下面这样定义访问器和设置器:

var site = {
  _name: 'IT笔录'
};

Object.defineProperty(site, "name", {
  get: function() {
    return this._name;
  },
  set: function(value) {
    this._name = value;
  }
});

而在ES6及之后,我们可以像下面这样定义访问器和设置器:

var site = {
  _name: 'IT笔录',
  get name() {
  	return this._name;
  },
  set name(value) {
  	this._name = value;
  }
};

site.name = 'http://itbilu.com';
site.name; // 'http://itbilu.com'

在ES6中,还增加了属性名表达式。在定义访问器方法时,同样可以使用属性名表达式:

var log = ['test'];
var obj = {
  get latest () {
    if (log.length == 0) return undefined;
    return log[log.length - 1]
  }
}
obj.latest; // "test"


4.2 ES6中的对象方法定义

gettersetter类似,从ECMAScript 6开始, 我们可以用更简短的语法定义自己的方法。

在ES6之前,我们会像下面这样定义对象方法:

var obj = {
  foo: function() {},
  bar: function() {}
};

而在ES6之后,定义对象方法可以更加简单:

var obj = {
  foo() {},
  bar() {}
};


5. 块级函数、条件定义函数

5.1 块级函数

块级函数是指定在一个语句块中的函数。

严格模式下,ES6之前与ES6及之后的块级函数定义没有什么区别。但并不建议在非严格模式下定义,因为非严格模式下的块级函数形为比较奇怪和不可控。

在严格模式下,在ES6之前,是禁止定义块级函数的,如果定义块级函数会抛出SyntaxError异常。ES6及之后,严格模式下定义的块级函数其作用域也被限制在所定义的块中:

'use strict';

function f() { 
  return 1; 
}

{  
  function f() {   // ES6之前会抛出 SyntaxError
    return 2; 
  }
  f();  // 2       // ES6 及之后
}

f() === 1; // true

// f() === 2 // 非严格模式下 2 


5.2 条件定义函数

条件定义函数类似于块级函数的定义。对于以下函数定义来说,由于'if (0)'的运算结果为false,所以zero函数永远不会被定义。而判断条件被换成'if (1)'就会被定义:

if (0) {
  function zero() {
    document.writeln("This is zero.");
  };
  zero();
}

一些JavaScript引擎不能很好的处理任何带有名称的函数表达式的函数定义,因此zero是否会被定义带有不确认性。有条件地定义函数的一个更安全的方法是,定义一个匿名函数并将它赋值给一个变量:

if (0) {
  var zero = function() {
    document.writeln("This is zero.");
  };
  zero();
}