翻译:道奇
作者:Yash Agrawa
原文:blog.logrocket.com/defining-fu…
一般来说,函数是一串指令或是一段可以被该函数内部或外部代码调用的“子程序”,本质上,函数“封装”了一个特定的任务。函数是JavaScript中一个基础模块,真正理解函数可以帮助理解JavaScript的一些奇怪的点。
JavaScript中的函数
需要注意的是,函数是JavaScript的重要的一类对象,这意味着JavaScript中的函数可以像其他对象一样进行处理,并且像其他变量一样被引用或作为参数传入函数中。
函数甚至有属性和方法,就像其他JavaScript对象一样。函数和其他对象的关键区别在于函数可以调用。
JavaScript中的每个函数是个Function对象,可以进到控制台然后尝试以下脚本:
function typeCheck() {};
typeCheck instanceof Function // Logs True
Function对象有一些内置的方法和属性,如apply, call, bind, isGenerator 等等,而其他对象就没有。
在JavaScript中定义函数有好几种方式,不同的定义方式会影响函数最后的行为,下面就挨个看一下。
函数声明
这是最常见的定义函数的方式了,函数声明包括关键字function,其后是一个函数名,接着是一对必需要有的括号,括号内是可选的参数列表。
function sum(param1, param2) {
return param1 + param2;
}
用这种方式定义函数有两件事需要注意一下:
- 在当前作用域下会创建一个包含这个函数对象的变量,变量的标识名就是函数的名称,在本例中指的就是
sum。 - 变量是会被提升到当前作用域的顶层域的。可以在这里查看更多内容 为了更好的理解提升,看个例子:
console.log(notYetDeclared()); // Logs 'Here!'
function notYetDeclared() {
return 'Here';
}
我们可以在定义notYetDeclared之前就调用这个函数。
函数表达式
函数表达式在语法上和函数声明很相似,主要的不同在于函数表达式不需要函数名。
let sum = function(param1, param2) {
return param1 + param2;
};
函数表达式是一条语句的一部分,上面的例子中,函数表达式就是sum变量赋值的一部分, 不同于函数声明的是函数表达式不会进行提升。
console.log(notYetDeclared); // Logs 'undefined'
let notYetDeclared = function() {
return 'Here';
}
函数表达式的一个有意思的使用场景是创建IIFE或是立即调用函数表达式的能力,很多时候我们想定义一个函数然后立即调用它,但是其他地方不会再调用。
当然,使用函数声明也可以做到,但为了让代码可读性更好,并且让其他程序不会意外访问到它,可以使用IIFE,看下面的代码:
function callImmediately(foo) {
console.log(foo);
}
callImmediately('foo'); // Logs 'foo'
上面的代码创建了callImediately函数,该函数包含参数foo,并把参数foo打印到控制台,然后立即调用它。下面这样做可以达到同样的结果:
(function(foo) {
console.log(foo);
})('foo'); // Logs 'foo'
两者关键的不同在于第一个例子中函数声明污染了全局命名空间,并且命名函数callImmediately在使用后很长一段时间依然随处可调用。但IIFE是匿名的,因此以后是不能调用的。
箭头函数
箭头函数是ES6新增的,是函数表达式的语法紧凑版,箭头函数通过使用一对包含参数列表的括号进行定义,跟着一个双箭头=>,然后是由大括号{}包裹函数语句。
let sum = (param1, param2) => {
return param1 + param2;
};
由于箭头函数背后的主要动机之一是语法紧凑,如果箭头函数中只有return语句,可以去掉大括号和return关键字,像下面这样:
let sum = (param1, param2) => param1 + param2;
同样,如果只有一个参数,大括号也可以去掉:
let double = param1 => param1 * 2;
这种函数定义的形式需要注意的是:
- 箭头函数没有自己的
this,它使用的是封闭词法作用域,可以在这里阅读更多关于this的内容
let foo = {
id: 10,
logIdArrow: () => { console.log(this.id) },
logIdExpression: function() {
console.log(this.id);
}
}
foo.logIdArrow(); // Logs 'undefined'
foo.logIdExpression(); // Logs '10'
上面的例子中,foo对象中定义了一个箭头函数和一个函数表达式,两者都调用this记录了foo.id。
- 箭头函数没有
prototype属性。
let foo = () => {};
console.log(foo.prototype); // Logs 'undefined'
arguments对象在箭头函数内是不可用的,可以在这里阅读更多关于arguments对象的内容
Function 构造器
前面提到过,JavaScript中每个函数是Function对象,所以要定义函数,可以直接调用Function对象的构造器。
let sum = new Function('param1', 'param2', 'return param1 + param2');
参数以逗号分隔的字符串'param1', 'param2', 'param3', ..., 'paramN'传入,函数体作为最后一个参数传入。
从性能方面考虑的话,这种定义函数的方式没有函数声明或函数表达式那么高效,使用Function构造器定义函数在每次构造器被调用的时候,构造器都会被解析一次,因为字符串的函数体需要每次都需要重新进行解析,不像其他方式那样,只需要解析其他的代码就可以了。
这种方式定义函数的一种用途是在浏览器中通过Node或window对象访问global对象。这些函数经常在全局作用域中创建并且不需要在当前作用域中访问。
Generator函数
Generator函数是ES6新增的。Generator函数是一种特殊的函数类型,就意义上而言它不像传统的函数,Generator函数会在每个请求的基础上生成多个值,并在这些请求之间暂停它们的执行。
function* idMaker() {
let index = 0;
while(true)
yield index++;
}
let gen = idMaker();
console.log(gen.next().value); // Logs 0
console.log(gen.next().value); // Logs 1
console.log(gen.next().value); // Logs 2
function*和yield关键字对于generator函数来说是唯一的。Generator函数通过在function尾部添加一个 * 来定义Generator函数,这让我们在generator函数体中可以用yield在请求上生成多个值。可以在这里查看更多的细节。
总结
选用哪种定义类型取决于实际情况和你想要实现什么。以下是几个可以考虑的点:
- 如果想利用函数提升,使用函数声明,例如,为了清晰起见顶部仅仅是抽象流,而将函数实现细节放在底部。
- 箭头函数同样适用于短回调函数,更重要的是使用this时是封闭函数。
- 尽量避免使用
Function构造器来定义函数,如果讨厌的语法不足以让你远离他,那么你需要了解它的速度非常慢的,每次调用时都会解析一次。