JavaScript 函数

函数定义


函数的定义通常有两种方法:函数声明和函数表达式。

// 函数声明定义函数
function sayName() {
  alert('paper_crane');
}
// 函数表达式定义函数
var showName = function() {
  alert('paper_crane');
}

在上面的例子中,我们定义了两个函数,一个是通过函数声明定义的sayName()函数,一个是通过函数表达式定义的showName()函数。注意, 虽然以上两个函数都没有返回值(即使用return返回值), 但在JavaScript中,每个函数都会有返回值,若无规定的返回值,则返回undefined。上面两种函数的声明方式的区别在于解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问),即被放到了源代码树的顶部,称为 函数声明提升;至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行,所以不能在函数被定义之前调用此函数。

sayName();       // paper_crane
showName();      // 报错

// 函数声明定义函数
function sayName() {
  alert('paper_crane');
}
// 函数表达式定义函数
var showName = function() {
  alert('paper_crane');
}

参数


在ES规范中,函数接收的参数在内部是用一个类数组arguments来表示的,函数接收到的始终是这个数组。当显式的传入命名参数的时候,即可通过参数名来访问参数,又可使用数组来访问参数。这就导致,在你显式规定了该传入多少个命名参数之后,无论你传入多少个参数都不会因此导致错误,顶多是因为参数不足而是使运算结果不符合预期。

function sayHi() {
  alert('Hi,' + arguments[0] + ',' + arguments[1]);
}

sayHi();                                 // Hi,undefined,undefined
sayHi('paper_crane');                    // Hi,paper_crane,undefined
sayHi('paper_crane', 'crane');           // Hi,paper_crane,crane
sayHi('paper_crane', 'crane', 'paper');  // Hi,paper_crane,crane

上面的例子定义了一个函数sayHi(),没有显式传入参数,并且在内部访问了参数数组arguments。然后以0个参数,一个参数,两个参数,三个参数来调用此函数。然而不论用那种方式调用,都不会报错,当参数不足时,会默认其参数值为undefined,而参数过多也不会有任何影响。同时,如果定义函数的时候规定接收两个参数,那么无论是使用参数名还是arguments数组都能正确访问参数值。例如:

function sayHi(param1, param2) {
  alert('Hi,' + arguments[0] + ',' + arguments[1]);
  alert('Hi,' + param1 + ',' + param2);
}

sayHi();                                 // Hi,undefined,undefined
sayHi('paper_crane');                    // Hi,paper_crane,undefined
sayHi('paper_crane', 'crane');           // Hi,paper_crane,crane
sayHi('paper_crane', 'crane', 'paper');  // Hi,paper_crane,crane

没有重载


在JavaScript中,函数其实也是一个对象,每个函数都是Function类型的实例,而且和引用类型一样具有属性和方法。由于函数是对象,因此函数名也只是一个指向函数对象的指针,不与某个函数绑定。所以当给某个函数名重复定义函数体的时候,只是在不断的改变其指向的函数定义,那么也就不存在重载了。在重复的为一个函数名重定义函数体的情况下,其指向最后为其赋值的函数定义。

function addSomeNumber(num) {
  return num + 100;
}

function addSomeNumber(num) {
  return num + 200;
}

var addSomeNum = function(num) {
  return num + 100;
}

addSomeNum = function(num) {
  return num + 200;
}

alert(addSomeNumber(100));    // 300
alert(addSomeNumber(100));    // 300

在上面的例子中,我们分别使用了函数声明和函数表达式来定义了函数addSomeNumber(),addSomeNum()两次,而函数名只属于后定义的函数。

函数作为值被传递


因为函数名是一个指针,指向一个函数对象,那么函数名就可以被当做参数传入另一个函数中,并且在另一个函数内部调用此函数。

function sayHi(name) {
  alert('Hi, ' + name + '!');
}

function callFunction(callback) {
  var name = 'paper_crane';
  callback(name);
}

callFunction(sayHi);       // Hi, paper_crane!

上面的例子定义了一个sayHi()函数,同时声明了一个callFunction()函数,其中将sayHi当成参数传入callFunction函数并且在callFunction函数内部成功调用。此时sayHi()函数可以称为是callFunction()函数的回调函数,这种用法在解决异步处理程序很有用,除此之外,调用Array.sort()函数时,也常常将一个比较函数作为其的回调函数。

函数内部属性


在函数内部,有两个特殊的对象:arguments和this。其中this对象的话在此不打算讨论,有兴趣的读者可以看看笔者另一篇博文:《 JavaScript执行环境、作用域及this值》。本文在前面有提到过arguments对象,此对象是一个包含所有函数参数的类数组,即能够通过方括号索引来访问其中的元素,但却不是数组对象,其主要作用是保存函数参数。但是这个arguments对象还有一个属性callee,此属性是一个指向拥有此arguments对象的函数的指针。

function factorial(num) {
  if (num <= 1) {
    return num;
  } else {
    return factorial(num - 1) * num;
  }
}
function recursive(num) {
  if (num <= 1) {
    return num;
  } else {
    return arguments.callee(num - 1) * num;
  }
}

alert(factorial(5));       // 120
alert(recursive(5));       // 120

在上面的例子中,我们声明了两个函数factorial和recursive,这两个函数都是利用了递归实现阶乘。recursive函数使用arguments.callee来调用本身实现了 递归,用法还是比较简单的。factorial函数在返回值中使用其本身的函数名来调用自身实现递归,这种方法的缺点是:紧密耦合;如果我们需要修改函数名,那么返回值处也需要修改调用的函数名,维护成本较高;而且我们知道函数名只不过是一个指针,一旦将此函数赋值给另外一个函数名,那么将很危险:

function factorial(num) {
  if (num <= 1) {
    return num;
  } else {
    return factorial(num - 1) * num;
  }
}

var recursive = factorial;
factorial = null;

alert(recursive(5));       // Uncaught TypeError: factorial is not a function

上面的例子只是由上上个例子改写的,可以看到,将factorial赋值给recursive并将factorial指向null后,我们调用recursive就会报错,究其原因就是因为recursive内部调用了factorial,但是factorial已经指向空,也就是已经不是函数了,这种紧密耦合的方式给代码维护带来了极大的不便。

但是使用arguments.callee方法来消除紧密耦合也不是完全没问题的,在严格模式下或者在某些浏览器中,这种方式会报错,所以如果要实现递归,可以使用命名函数表达式的方法:

var factorial = (function f(num) {
  if (num <= 1) {
    return num;
  } else {
    return f(num - 1) * num;
  }
});

alert(factorial(5));          // 120

var recursive = factorial;
factorial = null;

alert(recursive(5));          // 120

在上面的例子中,创建一个命名函数f,f内部通过函数名f来调用本身达到递归的目的,由于我们无法更改此函数名称,所以无论将其赋值给factorial还是recursive,都能够正确的运行。

函数还有一个caller属性,此属性是指向调用此函数的函数的指针,在严格模式下,不能为其赋值,否则会报错。

function outer() {
  inner();
}
function inner() {
  alert(inner.caller);
}

outer();      // 返回outer函数的函数体

函数属性方法


每个函数都包含两个属性:length和prototype。其中,length属性表示函数希望接收的命名参数的个数。

function sayName(name) {
  alert(name);
}

function sum(num1, num2) {
  return num1 + num2;
}

function sayHi() {
  alert('hi');
}

alert(sayName.length);    // 1
alert(sum.length);        // 2
alert(sayHi.length);      // 0

每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值(查看更多有关this指针的内容可以查看笔者另一篇博文《 JavaScript执行环境、作用域及this值》)。这两个方法的区别在于:apply()方法接收两个参数:一个是在其中运行函数的作用域(this的指向),另一个是参数数组,第二个参数可以是Array的实例,也可以是arguments对象。而call()方法与apply()的区别在于接收参数的方式不同,接收的第一个参数是运行函数的作用域(this的指向),剩下的参数必须一一列举传入,而不是传入一个数组。

function sum(num1, num2) {
  return num1 + num2;
}

function callSum(num1, num2) {
  return sum.call(this, num1, num2);
}

function callSum1(num1, num2) {
  return sum.apply(this, arguments);
}

function callSum2(num1, num2) {
  return sum.apply(this, [num1, num2]);
}

alert(callSum(10, 10));      // 20
alert(callSum1(10, 10));     // 20
alert(callSum2(10, 10));     // 20

这两个方法除了传入参数的区别外,没有其他的区别,至于使用哪一种调用方法coder可自行选择。

然而,传递参数并非apply()和call()真正的用武之地;他们真正强大的地方是能够扩充函数赖以生存的作用域。看一看例子

window.color = 'red';
var obj = {color: 'blue'};

function sayColor() {
  alert(this.color);
}

sayColor();              // red
sayColor.call(this);     // red
sayColor.call(window);   // red
sayColor.call(obj);      // red

通过上面的例子我们可以看出,传入的参数能够改变函数内部的this对象的指向。第一次调用函数的时候,因为函数作为全局函数,此时内部函数this指针指向全局作用域window;第二次调用函数时传入参数this,此参数this指向的是window,函数内部this指向window;第三次调用函数时,传入的参数是window, 函数内部this指向window;第四次调用函数的时候,传入的参数是obj,那么函数内部this指向的是obj,所以输出了“blue”。

ES5还给函数定义了一个方法:bind()。这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。

window.color = 'red';
var o = {color:'blue'};

function sayColor() {
  alert(this.color);
}

var objectSayColor = sayColor.bind(o);
objectSayColor();        // blue

好了,函数的相关知识先说到这里,以后学习到新的知识再更新。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值