理解 JavaScript 的函数

函数(Function )这个词对程序猿来说并不陌生. 哪怕你每天只写一点点代码, 你也得在下班前把正在创建或者修改的函数给close掉.

简单来说函数就是一组执行某些动作的一堆程序语句. 一个函数可能有一些输入参数(在方法体内使用)并且执行完成之后返回一个值.

JavaScript的函数也具有这些特点,不过他们可不止这些特点. 函数在JavaScript里面是对象. JavaScript里面几乎所有的东西都是对象,我在 《JavaScript 对象》 这篇文章里提到过这些内容.

作为一个对象,JavaScript的函数拥有属性和其他的函数(也就是方法)。让我们来看一个Javascript中典型的函数定义.


function myNotSoGreatFunc(visitor) {   
    console.log("Welcome to Code Morning Mr. " + visitor);
}

就是这样。上面的函数没有做太多的事,只是输出了一句欢迎访问者来到我的博客. 不过它已经演示了一个JavaScript应该长什么样子. 定义一个函数是以关键字 function 开头,后面跟着函数名然后括号里加入0个或多个参数). 然后实际的函数代码(JavaScript程序语句)包含在一对大括号里 { }. 函数可以有返回语句 return  这个语句是可选的. 不过函数都会返回一个值. 如果方法体中没有返回语句,那么方法会返回undefined(未定义).


理解 JavaScript 的函数

下面的代码使用访问者姓名作为参数传递函数并调用它.

myNotSoGreatFunc("Bob Martin");
// Output: 
// Welcome to Code Morning Mr. Bob Martin.

到这里,我们只是见识到了函数的基本特点. 接下来我们来近距离的观察下JavaScript函数的高级特性.

匿名函数

一个 JavaScript 函数可以是匿名的. 意思是说函数定义的时候你可以省略掉函数名. 不过,函数必须被存储在一个变量里,像下面这样.

var addNumbers = function (x, y) { 
    return x + y; 
}

 上面的语法也称为函数表达式。你可以把变量addNumbers当作函数名然后按照如下方式调用该函数。

var sum = addNumbers(2, 3);

函数表达式会很方便当你想要将一个函数作为参数传递给另一个函数时。让我们试着用一个简单的例子来理解这个。

var add = function(first, second) {
    return first + second
};

var multiply = function(first, second) {
    return first * second
};

function calculate(fun, a, b) {
    return fun(a, b);
}

我首先创建了两个匿名函数。第一个返回两个数的和,第二个返回两个数的积。这相当的简单,没有什么值得骄傲的。接着,我定义了一个函数calculate,它的第一个参数是函数类型,后面两个参数是数值类型。

我可以通过传递任何函数作为第一个参数来调用函数calculate。

var sum = calculate(add, 2, 3); 
// sum = 5var multiplication = calculate(multiply, 2, 3); 
// multiplication = 6

将函数通过参数形式传递是不是很简单. 这种方式在AJAX调用场景中被大量使用,比如你传入一个回调函数,当AJAX调用完成后用来处理成功或失败结果.

使用更多参数

JavaScript 在处理传递或接收函数参数时非常灵活. 我们来看下操作”函数参数“的几种方式.

丢失的参数

调用一个函数可以传递比预期更多或更少的参数。如果你调用一个方法,少传递了一个参数,那么没传递的那个参数的值会被设为undefined.

function callMe(a, b, c) {
    console.log("c is " + typeof c);
}

callMe("Code", "Morning");
// Output: "c is undefined"callMe("Learn", "JavaScript", "Functions"); 
// Output: "c is string"

参数对象

所有的Javascript函数都有一个特殊的对象 arguments  它是一个参数数组,在函数调用过程中被传递. 这个对象可以用来访问参数列表中的单独项,也可以获取参数传递至函数过程中的参数总个数.

function callMe() {
    var i;
    for (i = 0; i < arguments.length; i++) {
        console.log(arguments[i]);
    }
    console.log("Total arguments passed: " + arguments.length);
}

这个函数假设没有参数传递进来,就像我说的,在Javascript函数调用中,你可以传递任意数量的参数. 因此我也可以这样调用方法:

callMe("Code", "Morning", "Mr. Programmer");
// Output":
// Code
// Morning
// Mr. Programmer
// Total arguments passed: 3

所有的参数都可以在 arguments 对象数组中找到. 可以使用 arguments.length 属性获取传递参数的个数.

默认参数

你是 C++ 或者 C# 程序猿吗?  你见过有默认参数的函数吗? 可能见过! ECMAScript 6 把这个特性加入了Javascript,因此你就可以定义一个默认参数的函数.

function greetMyVisitors(name, profession = "The cool programmer") {
    alert("Welcome Mr. " + name + ", " + profession);
}

这个函数礼貌的向我的博客访问者问好. 它接收一个 name 和 profession 参数,然后显示一条欢迎信息. 第二个参数接收一个默认值(如果没有传递或者传递了一个undefined). 

greetMyVisitors("Justin Bieber", "The singer"); 
// Shows the message "Welcome Mr. Justin Bieber, The singer"greetMyVisitors("Bob Martin"); 
// Shows the message "Welcome Mr. Bob Martin, The cool programmer"greetMyVisitors("John Papa", undefined); 
// Shows the message "Welcome Mr. John Papa, The cool programmer"

函数嵌套

一个函数内部可以包含内部函数. 内部函数又再次可以包含内部函数。来看下代码:

function wakeUpAndCode() {
    function wakeUp() {
        console.log("I just woke up");
    }
    function code() {
        console.log("I am ready to code now");
    }

    wakeUp();
    code();
}

wakeUpAndCode(); // Output:// I just woke up// I am ready to code now

wakeUpAndCode 这个函数包含两个内部函数 wakeUp 和 code. 当调用 wakeUpAndCode 后, 开始执行方法体. 外部方法只有两条执行语句, 分别是对 wakeUp 和 code方法的调用 . 调用 wakeUp 函数会在控制台输出字符  “I just woke up” . 调用 code 函数会在控制台输出字符  “I am ready to code now” .

内部函数可以访问所有外部函数的变量和参数. 但是内部函数是函数内一种私有类型的实现,它不能被它所在的函数之外的函数访问. 关于使用内部函数与闭包问题将在其他文章讨论.

“ 自执行函数表达式 ” (IIFE, 读作"iffy" 好吧随你怎么叫)

IIFE (即时执行函数)是一个声明完成马上执行的匿名函数,它长这样:

(function() {
    // Your awesome code here
} ());

你只需要创建一个匿名函数,在方法定义结尾处加一对圆括号,然后把所有的代码再通过圆括号括起来. 最外层的括号里会把所有内容转化成表达式,因为JavaScript的括号里不能写程序语句. 函数定义后面的一对括号可以让函数立即执行。

任何在自执行函数体内定义的变量都是局部有效的,并且不能被任何此作用域之外的代码访问或改变.

看下面的代码. 这个函数不需要被调用就会自动执行.

(function() {
    console.log("I run on my own.");
} ());

把代码复制黏贴到 plunker 然后再浏览器控制台查看输出结果. 如果你不知道怎么打开浏览器控制台, 按F12键就会打开开发者工具. 然后点击 console 选项卡查看 console.log 语句的输出内容.

IIFE 是一个很好的在代码段中创建局部作用域的方法. 这么做的好处是可以保护你的变量和函数被应用程序中的其他部分复写掉. 那 IIFE 还有什么其他的优势? 还有他们怎么解决全局变量污染问题? 等我下一篇关于自执行表达式的文章.

构造函数

一个函数可以作为构造函数使用,并且可以使用构造函数来创建新的对象. 这个特性使Javascript更加面向对象. 使用构造函数的优势是你可以创建很多预定义对象和方法. 如果你把构造函数与其他语言的类和对象关联起来,那么你就对了。

我们来建立一个 Programmer 函数,它有函数构造器,并且有属性和方法.  你可以把它当作一个类如果你使用其他语言.

function Programmer(name, company, expertise) {
    this.name = name;
    this.company = company;
    this.expertise = expertise;
    this.writeCode = function() {
        console.log("Writing some public static thing..");
    }
    this.makeSkypeCall = function() {
        console.log("Making skype call..");
    }
    this.doSalsa = function() {
        console.log("I'm a programmer, I can only do Gangnam style..");
    }
    this.canWriteJavaScript = function() {
        return expertise === "JavaScript";
    }
}

 这个函数接收三个参数,并使用三个属性和四个方法来创建一个Programmer对象. 上面的代码不用说了吧没,很简单的. 我可以使用下面的代码创建很多programmer对象.

var javaProgrammer = new Programmer("Mohit Srivastava", "Infosys", "Java");
var dotnetProgrammer = new Programmer("Atul Mishra", "Prowareness", ".NET");

当然也可以使用普通的字面语法来创建具有相同属性和方法的对象, 但我们要多次编写重复代码,这是一种很不好的编程实践. 如果你知道编程原则中的 DRY(Don't repeat yourself,不要重复造轮子?),你就会同意我的观点了. 构造函数可以让你定义一次对象,然后再任何时候实例化他们.

注意!

要记得使用 new 关键字来创建新的函数对象. 如果你忘记使用 new 关键字创建了一个对象像这样 ->

var jsProgrammer = Programmer("Douglas Crockford", "Yahoo", "JavaScript")

它会把所有的属性方法都添加的全局 window 对象中,这是非常可怕的. 这就是为什么要明确使用 ‘this’ 关键字防止变量添加到全局 window 对象中. 使用 new 设置当前上下文到刚刚创建的对象中.

不过, 有个方法可以解决这个问题 . 你可以改变你的构造函数的实现方式,让它的内容对象是范围安全的,然后再创建新对象, 那么你就可以愉快地忽略 new 关键字了.查看下面修改后的构造函数代码. 已经移除了一些不必要的内容.

function Programmer(name, company, expertise) {
    if (! (this instanceof Programmer)) {
        return new Programmer(name, company, expertise);
    }
    this.name = name;
    this.company = company;
    this.expertise = expertise;
    this.writeCode = function() {
        console.log("Writing some public static thing..");
    }
}

if条件:判断当前的this对象是否是Programmer对象的一个实例,如果不是,就创建一个新的Programmer对象并且返回直接调用构造函数所调用的内容(指定this的指向并重新返回,有点递归的影子)。

注意:如果不使用 strict模式的话,这种方法就不能创建新对象。Strict模式将严格遵守执行标准,如果你编写一些不安全的代码,这么做会抛出异常。. 要启用 strict(严格) 方式, 只需要在代码前添加 ‘use strict’ 字符. 一般来讲,在代码中使用 strict 是比较好的。

'use strict'
 function doSomething() { ... }
 ....
 ....

这篇文章综合的介绍了关于函数的大多数内容. 函数是Javascript中的一个重要内容. 要成为Javascript大师理解函数是至关重要的. 希望你读完这篇文章后没有想要从头再看一遍,相信你已经了解了关于函数的细节内容. 如果你喜欢本文那么请你分享它。如果你不喜欢,那么请发表评论助我下次提高文章水平,稍后见啦。。。


  • 作者:Dheeraj Kumar Kesri

  • 链接:http://coyee.com/article/11179-understand-functions-in-javascript


原文链接:HelloWeb前端网 » 理解 JavaScript 的函数 » 感谢您的浏览,希望能有所帮助。

欢迎您加入“Helloweb” 学习交流群:HelloWeb-学习交流群 196291215 共同交流并结识同行,在这里说出您的收获与感想或有什么不同的观点,我们期待您的留言,分享,让我们一起进步!

喜欢 ()or分享