细说JavaScript的函数
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了细说JavaScript的函数相关的知识,希望对你有一定的参考价值。
这是第二篇博客,路还很漫长,我才刚开始。
javascript中几乎万物皆对象,函数也不例外。然而函数不只是把一堆属性打包在一起,函数是这门语言的核心。要理解它的核心还得从基础说起。
一、JavaScript中的代码块
要理解JavaScript函数,你得先理解代码块。JavaScript的代码块只是把语句组合在一起。代码块以左花括号“{”开头以右花括号“}”结束。简单来说,代码块让花括号中的语句在一起执行。代码块是JavaScript中最基本的控制结构。下面是JavaScript代码块的一些例子:
// Immediately invoked function expression ;!function () { var triumph = false, cake = false, satisfaction = 0, isLie, note; // Block used as part of a function expression var isLie = function (val) { return val === false; } // Block used as part of a conditional statement if (isLie(cake)) { triumph = true; makeNote(‘huge success‘); satisfaction += 10; } // Block used as part of a function declaration function makeNote(message) { note = message; } }();
正如你所看到的,函数本质上就是开发者可以调用的有名字的代码块。举个例子:
// The inline conditional block statement is executed only once per cycle. if (isLie(cake)) { ... } function makeNote(message) { ... } // The function declaration is executed as many times as it is called. makeNote("Moderate Success"); makeNote("Huge Success");
1. 函数参数
可以通过把参数传进函数体来初始化诸如控制语句(if,for,while...)的函数。在JavaScript中,变量要么是复合类型(如对象,数组),要么是基本类型(如字符串,整数)。当复合类型做参数时,它通过引用传递给函数体,JavaScript传递的是变量在内存堆中位置的指针,而不是变量的拷贝。相反,当给函数传达基本类型时,JavaScript按值进行传递。这点不同可能会导致微妙的bug,因为函数经常被当作概念上的黑盒,人们往往假定函数返回变量时只会影响封闭的作用域。当按引用传递时,即使函数没有返回值,参数对象也可能被修改。下面是通过引用传递和按值传递的例子:
var object = { ‘foo‘: ‘bar‘ }, num = 1; // Passed by reference 按引用传递 ;!function(obj) { obj.foo = ‘baz‘; }(object); // => Object {foo: "baz"} console.log(object); // Passed by value 按值传递 ;!function(num) { num = 2; }(num); // => 1 console.log(num);
(1) 参数对象—arguments
参数对象适用于不需要提前指定参数数量的函数。参数对象就像通配符,可以像数组一样遍历参数对象来访问任意数量的参数。比如:
var sum = function () { var len = arguments.length, total = 0; for (var x = 0; x < len; x++) { total += arguments[x]; } return total; }; // => 6 console.log(sum(1, 2, 3));
然而参数对象只是跟数组有些像而已,如果你用更多的数组方法来重写这段脚本就会产生错误:
var sum = function () { var len = arguments.length, total = 0; while (arguments.length > 0) { total += arguments.pop(); } return total; }; // Uncaught TypeError: arguments.pop is not a function sum(1, 2, 3);
幸运的是,ECMAScript 6改善了函数参数的使用方式,很少需要使用原来的参数对象了。我们可以看看参数里加入的几个新特性。
(2)默认参数 (ECMAScript 6)
许多编程语言允许在函数签名中为参数选择默认值。JavaScript会在ECMAScript 6中支持这个特性。
var join = function (foo = ‘foo‘, baz = (foo === ‘foo‘) ? join(foo + "!") : ‘baz‘) { return foo + ":" + baz; } // => hi:there console.log(join("hi", "there")); // Use the default parameter when not supplied // => hi:baz console.log(join("hi")); // Use the default parameter when undefined is supplied // => foo:there console.log(join(undefined, "there")); // Use an expression which has access to the current set of arguments // => foo:foo!:baz console.log(join(‘foo‘));
(3) 剩余参数rest (ECMAScript 6)
有时函数需要支持任意数量的参数,然而用参数对象来做会有些麻烦。
1 var dispatcher = { 2 join: function (before, after) { 3 return before + ‘:‘ + after 4 }, 5 sum: function () { 6 var args = Array.prototype.slice.call(arguments); 7 return args.reduce(function (previousValue, currentValue, index, array) { 8 return previousValue + currentValue; 9 }); 10 } 11 } 12 var proxy = { 13 relay: function (method) { 14 var args; 15 args = Array.prototype.splice.call(arguments, 1); 16 return dispatcher[method].apply(dispatcher, args); 17 } 18 }; 19 // => bar:baz 20 console.log(proxy.relay(‘join‘, ‘bar‘, ‘baz‘)); 21 // => 28 22 console.log(proxy.relay(‘sum‘, 1, 2, 3, 4, 5, 6, 7));
在前面的例子里,我们的proxy对象期待一个参数,这个参数作为方法在dispatcher上进行调用。它不知道在这个函数调用时还需要多少其他的参数。要知道参数对象并不是数组,也没有splice,map或reduce这样的方法。为了把剩下任意数量的参数传送给dispatcher,你必须用数组来处理它们。
有了剩余参数rest,你不必再像对暗号一样跟函数打交道。这里用剩余参数rest来重写前面的方法:
var dispatcher = { join: function (before, after) { return before + ":" + after }, sum: function (...rest) { return rest.reduce(function (previousValue, currentValue, index, array) { return previousValue + currentValue; }); } }; var proxy = { relay: function (method, ...goodies) { return dispatcher[method].apply(dispatcher, goodies); } }; // => bar:baz console.log(proxy.relay(‘join‘, ‘bar‘, ‘baz‘)); // => 28 console.log(proxy.relay(‘sum‘, 1, 2, 3, 4, 5, 6, 7));
2. 函数的类型
现在你已经对代码块和参数有了更好的理解,让我们进一步深入到函数声明和函数表达式,看看如何在JavaScript中使用这两种类型的函数。如果不仔细看,两者可能非常相似。
// Function Declaration function isLie(cake) { return cake === true; } // Function Expression var isLie = function(cake) { return cake === true; }
二者之间唯一的区别发生在赋值期间。解释器可以在语法解析期间访问到函数声明。而函数表达式是赋值表达式的一部分,在整个程序赋值完成前都不能执行。这点区别看起来很小,但影响很大。考虑一下这种情况:
// => Hi, I‘m a function declaration! declaration(); function declaration() { console.log("Hi, I‘m a function declaration!"); } // => Uncaught TypeError: expression is not a function expression(); var expression = function () { console.log("Hi, I‘m a function expression!"); }
就像前面例子里所看到的,函数表达式在被调用时抛出了异常,但函数声明执行正常。这个异常揭示了函数声明和函数表达式的关键不同。JavaScript可以理解声明的函数,并能在程序执行前将函数解析。因此,函数的调用在声明前后都没关系,因为JavaScript在幕后已经将函数提升到当前作用域的顶端。而函数表达式直到变量被赋值时才进行计算,因此当它被调用时仍然是undefined。所以优秀的代码风格应该在当前作用域的顶端定义所有变量。这样做的话,脚本的顺序就跟JavaScript语法解析时的顺序一样了。
前面省略了一个概念,当语法解析时,JavaScript会把所有函数声明移到当前作用域的顶端。所以声明式函数可以放在脚本中的任意位置。为了更进一步探索声明和表达式间的区别,看看这个例子:
function sayHi() { console.log("hi"); } var hi = function sayHi() { console.log("hello"); } // => hello hi(); // => hi sayHi();
你在看这段代码的时候可能会以为函数声明会崩溃,因为它跟函数表达式重名了。然而因为第二个函数是赋值变量的一部分,拥有自己的作用域,所以JavaScript把它们当成不同的实体。
以上是关于细说JavaScript的函数的主要内容,如果未能解决你的问题,请参考以下文章