函数声明

之前说的三种函数声明中(参见JavaScript快速入门(二)——JavaScript变量),使用Function构造函数的声明方法比较少见,我们暂时不提。function func() { }和var func = function() { }除了在声明提升中有所不同之外也没有其他不同,我们合并起来一起看。我们在这里着重讲一个东西——匿名函数。

匿名函数顾名思义,就是没有名字的函数。它的形式就是function() { }。请注意和之前说的两种方式的区别,这里并没有赋值给任何变量,也就是说,没有指向这个函数的引用,我们无法在其他地方调用这个函数,也就是说,这种函数的使用价值只有一次。当我们把匿名函数赋值给其他变量时,就变成了var func = function() { }。是不是很熟悉?没错,就是我们之前说的变量声明的方法。而如果func是隐式声明的话,那么,这个函数就变成了全局函数。

匿名函数使用非常广泛,它常用于只执行一次的函数,例如排序函数,我们可以这样来写:

var a = [2,1,4,7,5];
a.sort(function(num1, num2) {
return num1 > num2;
})

我们传了个匿名函数作为参数,因为这个函数只适用于这个地方,而无法用在其他地方。但如果是类似的地方也用了类似的函数,例如我们需要两次排序:

var a = [2,1,4,7,5],
b = [4,2,6,4,1];
function sortDesc(num1, num2) {
return num1 > num2;
}
a.sort(sortDesc);
b.sort(sortDesc);

我们就可以把这个匿名函数剥离出来赋给一个function类型的变量,达到重复利用的目的。

由上面这个例子我们可以看出匿名函数的优劣。坏处很明显,就是无法再次利用;好处是减少了声明的消耗(当然,如果有两次以上的利用的话,当然是声明的消耗更少)。

函数调用

函数的调用和C中差不多,但形式可能有点不同。JavaScript的函数调用形式为:(函数)(参数列表)或者函数名(参数列表)。后者和C是一样的,但前者和C是迥然不同的,因为C中函数声明和函数调用是区分开的。先来看下例子:
function add(num1, num2) {
    return num1 + num2;
}
var a = add(1, 2); // 3

这种方式就是函数名(参数列表)的形式,我们在C中经常见到,就不详细说了,我们可以把上面那个换种形式来展现:

var a = (function add(num1, num2) {
return num1 + num2;
})(1, 2);
console.log(a); // 3
这样我们就能实现声明和执行一块处理。

但是这样有个问题,我们再看一种情况:

var a = (function add(num1, num2) {
return num1 + num2;
})(1, 2);
var b = add(1, 2); // error,add is not defined
console.log(a); // 3
console.log(b); // undefined

这是因为add这个变量的作用域仅限于括号内,这个在之后的作用域讲解中将讲到。

这样的调用,一般是在匿名函数中,为了让函数立即执行才使用这种方式,又或者,利用它的不足,利用JavaScript的作用域特点,将函数内的变量全部转为局部变量,达到封装和防止污染全局的目的。
 

函数嵌套

JavaScript的函数理论上是可以无限嵌套的,当然并不推荐嵌套太多,原因有很多,无论是性能还是代码简洁要求,都要求不应该嵌套太多。我们举一个嵌套的例子:
function getAbs(num) {
    function isNegative(num) {
        return num < 0;
    }
    return isNegative(num) ? -num : num;
}
var a = getAbs(-1); // 1
记住一句话,有嵌套就有父子关系(相互嵌套的不在参考范围内,也极度不推荐)。在上面的例子中,父函数即为getAbs,子函数为isNegative。
在JavaScript的嵌套中,涉及到作用域的问题,我们先不讲太复杂的,简单的可以记成:父函数中声明的所有变量,或者说,父函数中能使用的变量,都能在子函数中使用,但反过来,子函数中显式声明的所有变量,都不能在父函数中使用。下面会讲到caller和callee来帮助理解嵌套。
 

arguments对象

函数中,有一个默认的对象,不需要你去声明,也不需要你去赋值,它叫做arguments,它是一个数组,保存着参数列表。先来看一个例子:
function add(num1, num2) {
console.log(arguments); // [1, 2]
return num1 + num2;
};
var b = add(1, 2);

注意,arguments对象保存的是实参。接下来,我们要展示JavaScript中非常有意思的一个东西,也是JavaScript灵活性的一大体现。在这之前,我们先来谈下C中的函数重载。

维基中的定义为:函数重载(Function overloading),是Ada、C++、C#、D和Java等编程语言中具有的一项特性,这项特性允许创建数项名称相同但功能的输入输出类型不同的子程序,它可以简单地称为一个单独功能可以执行多项任务的能力。
在函数重载中,输出类型可相同可不同,但参数列表一定要不一样,可以是数量不一样或者类型不一样,或者两者都不一样。
但在JavaScript这类弱类型语言中,类型无法预定义,即输入和输出类型无法从函数定义看出来。那么只剩一项了,参数列表的长度,即参数数量。但这真的有影响吗?
实际上,JavaScript没有函数重载,实参比形参长的后果仅仅是没有给实参一个别名而已。不懂?我们来看下例子:
function add(num1, num2) {
    console.log(arguments); // [1, 2, 3]
    return num1 + num2 + arguments[2];
};
var b = add(1, 2, 3); // 6

我们可以巧妙的利用arguments对象,来达到我们的目的。我们甚至可以对上面的做个扩展,让它能把所有参数的和返回,即使形参列表为空。

function add() {
var sum = 0;
for(var count = 0, length = arguments.length; count < length; count++) {
sum += arguments[count];
}
return sum;
};
var b = add(1, 2, 3, 4); // 10

那如果相反,形参列表长度比实参列表长呢?

function add(num1, num2) {
console.log(num1); // 1
console.log(num2); // undefined
return num1 + num2;
};
var b = add(1); // NaN

我们可以看到,超出实参长度的形参部分,就会是undefined,从而返回我们并不想要的结果(NaN表示应该是个number类型结果却是其他类型)。我们可以稍作修改:

function add(num1, num2) {
num2 = num2 || 0;
return num1 + num2;
};
var b = add(1); // 1

利用逻辑操作符的特性来将形参实例化,保证使用时形参不为undefined。当然,这样也有个别问题,如果传入的实参逻辑值也是false(例如0、undefined、null)等等,我们就需要用全等符号进行判断了,在此例中不做要求。

 

caller和callee

这两个对象,是用于判断函数调用和执行的对象函数的。其中,arguments.callee返回当前正在执行的函数,func.caller返回函数的调用体所在函数。而arguments.caller永远返回undefined。如果调用函数是在全局进行,那么func.caller将返回null。注意,在严格模式下这两个对象将被禁用。
我们举刚才的一个代码为例:
function getAbs(num) {
function isNegative(num) {
console.log(isNegative.caller); // getAbs
console.log(arguments.callee); // isNegative
return num < 0;
}
return isNegative(num) ? -num : num;
}
var a = getAbs(-1);

你可以将这段代码运行一下,会发现,arguments.callee永远指向函数本身,而函数名.caller将指向调用该函数的代码所在函数,例如本例中即为getAbs。不过如果通过函数名.caller来寻找的话,耦合度太高。我们可以把两个结合起来,

function getAbs(num) {
function isNegative(num) {
console.log(arguments.callee)
console.log(arguments.callee.caller)
return num < 0;
}
return isNegative(num) ? -num : num;
}
var a = getAbs(-1);

有人问这个有什么用?这个严格的来说不是太有用,而且其安全性有问题,否则严格模式也不会禁用掉这两个对象了。但说没用也是不可能的,要不然也不会出现这两个东西了。

首先,这个在调试的时候特别有效,可以帮我们理清代码执行顺序,或者寻找bug;
其次,可以用这两个变量实现一些花哨的技巧,例如我们实现斐波那契数,正常做法是这样:
function fib(num) {
if(num == 1 || num == 2) {
return 1;
}
return fib(num - 1) + fib(num - 2);
}
var b = fib(6); // 8

但是这样的坏处在于我们如果要更改个函数名,我们将同时修改三个地方(调用的暂时不论)。我们可以用我们刚学到的东西来解决这个问题:

function fib(num) {
if(num == 1 || num == 2) {
return 1;
}
return arguments.callee(num - 1) + arguments.callee(num - 2);
}
var b = fib(6); // 8

但是,投机取巧也是有其弊端的,这会让别人在看你的代码的时候很费劲。用不用,取决于具体情况。

JavaScript快速入门(四)——JavaScript函数的更多相关文章

  1. JavaScript快速入门-ECMAScript函数

    JavaScript函数(定义.参数.返回值.闭包.匿名函数) 一.函数定义 function functionName(arg0, arg1, ... argN) { statements } 函数 ...

  2. Web开发初探之JavaScript 快速入门

    本文改编和学习自 A JavaScript Primer For Meteor 和 MDN Web教程 前文 Web开发初探 概述 本文以介绍 JavaScript 为主,初学者掌握本文的内容后,将能 ...

  3. 快速入门上手JavaScript中的Promise

    当我还是一个小白的时候,我翻了很多关于Promise介绍的文档,我一直没能理解所谓解决异步操作的痛点是什么意思 直到我翻了谷歌第一页的所有中文文档我才有所顿悟,其实从他的英文字面意思理解最为简单粗暴 ...

  4. javascript快速入门2--变量,小学生数学与简单的交互

    变量 对于变量的理解:变量是数据的代号.如同人的名字一样. var num;//在JavaScript中使用关键字var声明一个变量 在JavaScript中,使用上面的语法,就可以声明一个变量,以便 ...

  5. JavaScript快速入门(一)——JavaScript概览

    JavaScript是什么? JavaScript的诞生 在1995年前后,当时世界上的主流带宽为28.8Kbps,现在世界平均下载带宽为21.9Mbps(数据来源于http://www.netind ...

  6. javascript快速入门1--JavaScript前世今生,HelloWorld与开发环境

    JavaScript历史 大概在1992年,一家称作Nombas的公司开始开发一种叫做C--(C-minus-minus,简称Cmm)的嵌入式脚本语言. Cmm背后的理念很简单:一个足够强大可以替代宏 ...

  7. javascript快速入门22--Ajax简介

    Ajax是什么? 首先,Ajax是什么?一个很酷的新兴词汇!仅仅是某种早就有了的技术的一种新说法而已! Ajax是指一种创建交互式网页应用的网页开发技术.要谈到网页应用程序,则必须从WEB的历史来讲: ...

  8. AndroidStudio快速入门四:打造你的开发工具,settings必备

    http://blog.csdn.net/jf_1994/article/details/50085825 前言:这里是使用AS的基本设置,适合新入手的朋友阅读,将这里介绍的设置完基本使用无忧啦. 1 ...

  9. javascript快速入门21--DOM总结

    跨浏览器开发 市场上的浏览器种类多的不计其数,它们的解释引擎各不相同,期待所有浏览器都一致的支持JavaScript,CSS,DOM,那要等到不知什么时候,然而开发者不能干等着那天.历史上已经有不少方 ...

随机推荐

  1. 基于visual Studio2013解决算法导论之012计数排序

     题目 计数排序 解决代码及点评 #include <stdio.h> #include <stdlib.h> #include <malloc.h> #in ...

  2. 浅析Java中的反射机制原理

    反射反射,程序员的快乐! Java中反射机制使用的还是比较广泛的,系统的灵活性.可扩展性大都都是通过反射等方式来加载外部插件,使得系统与插件解耦的同时,增加了功能.但是很多人都只是会用,却是不知道它的 ...

  3. android 从服务器上获取APK并下载安装

    简单的为新手做个分享.    网上有些资料,不过都是很零散,或是很乱的,有的人说看不懂.    一直有新手说 做到服务器更新APK时没有思路,这里做个简单的分享,希望有不同思路的可以讨论.     下 ...

  4. 一款简单的客户端安卓手机qq源码

    给大家分享一款比较简单的安卓手机qq应用源码,效果非常不错,大家可以借鉴一下,希望大家会喜欢. 1.png  (7.24 KB, 下载次数: 0) 02.png  (68.52 KB, 下载次数: 0 ...

  5. android平板Home键的监听

    关于android4.2平板Home键的监听 1.android4.0以上的平板Home键是不能屏蔽的,至少在应用层是这样.但是可以监听Home键的触发,主要是通过广播的监听来接受Hone键的触发广播 ...

  6. 在C++中使用C#编写的类2

    在那篇<在C#中使用C++编写的类>中我介绍了如何在C#中使用C++编写的类.可是由于C#在用户界面设计.数据库存储和XML文件读取等方面的优势,有时候也会出现要在C++中使用C#编写的类 ...

  7. SQLyog 注册码

    用户名: 随意填写 秘钥: ccbfc13e-c31d-42ce-8939-3c7e63ed5417a56ea5da-f30b-4fb1-8a05-95f346a9b20ba0fe8645-3916- ...

  8. 04-Foundation-NSSet、NSDictionary、block

    目录: 一.NSSet集合 二.NSDictionary字典 三.block代码块 回到顶部 一.NSSet集合 1 NSSet是一个无序的,管理对个对象的集合类,最大特点是集合中不允许出现重复对象, ...

  9. django目录下的各文件

    本文部分转载. 使用Python setup.py install命令从源代码安装完Django后,这些都会被拷贝到Python安装目录下的Lib/site-packages/django子目录中.之 ...

  10. 初探 FFT/DFT

    有用的学习链接&书籍 傅立叶变化-维基百科 离散傅立叶变化-维基百科·长整数与多项式乘法 维基百科看英文的更多内容&有趣的图 快速傅立叶变化-百度百科,注意其中的图! 组合数学(第4版 ...