声明JavaScript函数的六种方法
来源:互联网 发布:asp.net与php的区别 编辑:程序博客网 时间:2024/05/22 08:25
一个函数一次性定义的代码块可以多次调用。在JavaScript中,一个函数有很多元素组成,同时也受很多元素影响:
- 函数体的代码
- 函数的参数列表
- 接受外部变量域的变量
- 返回值
- 当函数被调用时,
this
指上下文 - 命名和匿名函数
- 函数对象作为变量声明
arguments
对象(在ES6中的箭头函数中将丢弃这个)
这些元素都会影响到函数,但具体影响函数的行为还是取决于函数的声明类型。在JavaScript中常见的声明类型有以下几种方法:
- 函数声明
- 函数表达式
- 方法定义
- 箭头函数
- 函数生成器
- 函数构造器
函数声明类型对函数代码的影响只是轻微的。重要的是函数如何与外部组件交互功能(比如外部作用域、闭包、对象自身拥有的方法等)和调用方式(普通函数调用、方法调用和构造函数调用等)。
例如,你需要通过this
在一个函数调用封闭的下下文(即this
从外部函数继承过来)。最好的选择是使用箭头函数,很清楚的提供了必要的下下文。
比如下面示例:
class Names { constructor (names) { this.names = names; } contains(names) { return names.every((name) => this.names.indexOf(name) !== -1); }}var countries = new Names(['UK', 'Italy', 'Germany', 'France']); countries.contains(['UK', 'Germany']); // => true countries.contains(['USA', 'Italy']); // => false
箭头函数传给.every()
的this
(一个替代Names
类)其实就是一个contains()
方法。使用一个箭头(=>
)来声明一个函数是最适当的声明方式,特别是在这个案例中,上下文需要继承来自外部的方法.contains()
。
如果试图使用一个函数表达式来调用.every()
,这将需要更多的手工去配置上下文。有两种方式,第一种就是给.every(function(){...}, this)
第二个参数,来表示上下文。或者在function(){...}.bind(this)
使用.bind()
作为回调函数。这是额外的代码,而箭头函数提供的上下文透明度更容易让人理解。
这篇文章介绍了如何在JavaScript中声明一个函数的六种方法。每一种类型都将会通过简短代码来阐述。感偿趣?
函数声明(Function declaration)
函数声明通过关键词function
来声明,关键词后面紧跟的是函数的名称,名称后面有一个小括号(()
),括号里面放置了函数的参数(para1,...,paramN)
和一对大括号{...}
,函数的代码块就放在这个大括号内。
function name([param,[, param,[..., param]]]) { [statements]}
来看一个函数声明的示例:
// function declarationfunction isEven (num) { return num % 2 === 0;}isEven(24); // => trueisEven(11); // => false
function isEven(num) {...}
是一个函数声明,定义了一个isEven
函数。用来判断一个数是不是偶数。
函数声明创建了一个变量,在当前作用域,这个变量就是函数的名称,而且是一个函数对象。这个函数变量存在变量生命提升,它会提到当前作用域的顶部,也就是说,在函数声明之前可以调用。
函数声明创建的函数已经被命名,也就是说函数对的name
属性就是他声明的名称。在调试或者错误信息阅读的时候,其很有用。
下面的示例,演示了这些属性:
// Hoisted variableconsole.log(hello('Aliens')); // => 'Hello Aliens!'// Named functionconsole.log(hello.name); // => 'hello'// Variable holds the function objectconsole.log(typeof hello); // => 'function'function hello(name) { return `Hello ${name}!`;}
函数声明function hello(name) {...}
创建了一个hello
变量,并且提升到当前作用域最顶部。hello
变量是一个函数对象,以及hello.name
包括了函数的名称hello
。
一个普通函数
函数声明匹配的情况应该是创建一个普通函数。普通的意思意味着你声明的函数只是一次声明,但在后面可以多次调用它。它下的示例就是最基本的使用场景:
function sum (a, b) { return a + b;}sum(5, 6); // => 11([3, 7]).reduce(sum); // => 10
因为函数声明在当前作用域内创建了一个变量,其除了可以当作普通函数调用之外,还常用于递归或分离的事件侦听。函数表达式或箭头函数是无法创建绑定函数名称作为函数变量。
下面的示例演示了一递归的阶乘计算:
function factorial(n) { if (n === 0) { return 1; } return n * factorial(n - 1);}factorial(4); // => 24
有关于阶乘(Factorial)相关的详细介绍,可以点击这里。
在factorial()
函数做递归计算时调用了开始声明的函数,将函数当作一个变量:factorial(n - 1)
。当然也可以使用一个函数表达式,将其赋值给一个普能的变量,比如:var factorial = function (n) {...}
。但函数声明function factorial(n)
看起来更紧凑(不需要var
和=
)。
函数声明的一个重要属性是它的提升机制。它允许在相同的作用域范围内之前使用声明的函数。提升机制在很多情况下是有用的。例如,当你一个脚本内先看到了被调用的函数,但又没有仔细阅读函数的功能。而函数的功能实现可以位于下面的文件,你甚至都不用滚动代码。
你可以在这里了解函数声明的提升机制。
与函数表达式区别
函数声明和函数表达式很容易混淆。他们看起来非常相似,但他们具有不同的属性。
一个容易记住的规则:函数声明总是以function
关键词开始,如果不是,那它就是一个函数表达式。
下面就是一个函数声明的示例,声明是以function
关键词开始:
// Function declaration: starts with "function"function isNil(value) { return value == null;}
函数表达式不是以function
关键词开始(目前都一般出现在代码的中间地方):
// Function expression: starts with "var"var isTruthy = function(value) { return !!value;};// Function expression: an argument for .filter()var numbers = ([1, false, 5]).filter(function(item) { return typeof item === 'number';});// Function expression (IIFE): starts with "("(function messageFunction(message) { return message + ' World!';})('Hello');
条件中的函数声明
当函数声明出现if
、for
或while
这样的条件语句块{...}
时,在一些JavaScript环境内可能会抛出一个引用错误。让我们来看看在严格模式下,函数声明出现在一个条件语句块中,看看会发生什么。
(function() { 'use strict'; if (true) { function ok() { return 'true ok'; } } else { function ok() { return 'false ok'; } } console.log(typeof ok === 'undefined'); // => true console.log(ok()); // Throws "ReferenceError: ok is not defined"})();
当调用ok()
函数时,JavaScript抛出一个异常错误"ReferenceError: ok is not defined"
,因为函数声明出现在一个条件语句块内。注意,这种情况适用于非严格模式环境下,这让人更感到困惑。
一般来说,在这样的情况之下,当一个函数应该创建在基于某些条件内时,应该使用一个函数表达式,而不应该使用函数声明。比如下面这个示例:
(function() { 'use strict'; var ok; if (true) { ok = function() { return 'true ok'; }; } else { ok = function() { return 'false ok'; }; } console.log(typeof ok === 'function'); // => true console.log(ok()); // => 'true ok'})();
因为函数是一个普通对象,根据不同的条件,将其分配给一个变量,是一个不错的选择。调用ok()
函数也能正常工作,不会抛出任何错误。
函数表达式
函数表达式是由一个function
关键词,紧随其后的是一个可选的函数名,一串参数(para1,...,paramN)
放在小括号内和代码主体放在大括号内{...}
。
一些函数表达式的使用方法:
var count = function(array) { // Function expression return array.length;}var methods = { numbers: [1, 5, 8], sum: function() { // Function expression return this.numbers.reduce(function(acc, num) { // func. expression return acc + num; }); }}count([5, 7, 8]); // => 3 methods.sum(); // => 14
函数表达式创建了一个函数对象,可以用在不同的情况下:
- 当作一个对象赋值给一个变量
count = function(...) {...}
- 在一个对象上创建一个方法
sum: function() {...}
- 当作一个回调函数
.reduce(function(...) {...})
函数表达式在JavaScript中经常使用。大多数的时候,开发人员处理这种类型的函数,喜欢使用箭头函数。
命名函数表达式
当函数没有一个名称(名称属性是一个空字符串)时这个函数是一个匿名函数。
var getType = function(variable) { return typeof variable;};getType.name // => ''
getType
就是一个匿名函数,其getType.name
的值为''
。
当表达式指定了一个名称时,这就是一个命名函数表达式。它和简单的函数表达式相比具有一些额外的属性。
- 创建一个命名函数,其
name
属性就是函数名 - 在函数体中具有和函数对象相同名称的一个变量
我们使用上面的例子,不同的是在函数表达式内指定了一个名称:
var getType = function funName(variable) { console.log(typeof funName === 'function'); // => true return typeof variable;}console.log(getType(3)); // => 'number' console.log(getType.name); // => 'funName' console.log(typeof funName === 'function'); // => false
function funName(variable) {...}
是一个命名函数表达式。在函数作用范围内存一个funName
变量。函数对象的name
属性就是函数的名称funName
。
支持命名函数表达式
当变量赋值时使用一个函数表达式var fun = function() {}
,很多引擎可以推断这个变量的函数名。回调时常常给其传递的是一个匿名函数表达式,并没有存储到变量中,所以引擎不能确定它的名字。
在很多情况之下,使用命名函数和避免匿名函数似乎是很在理的。而且这也会带来一系列的好处:
- 在调试时,错误信息和调用堆栈时使用函数名能显示更详细的信息
- 调试时更舒服,可以减少
anonoymous
堆栈的名字出现的次数 - 函数名有助于快速理解其功能
- 在函数递归调用的范围内或事件监听时可以按名称来访问函数
方法定义
方法定义可以在object literals和ES6 class时定义。可以使用一个函数的名称,并紧随其后跟一对小括号放置参数列表(para1,...,paramN)
和函数主体代码放在一个大括内{...}
。
下面的示例是基于object literals上使用方法定义函数。
var collection = { items: [], add(...items) { this.items.push(...items); }, get(index) { return this.items[index]; }};collection.add('C', 'Java', 'PHP'); collection.get(1) // => 'Java'
add()
和get()
方法在collection
对象使用方法定义。这些方法可以像这样调用collection.add(...)
和collection.get(...)
。
方法定义和传统的属性定义有点类似,通一个冒号:
把名称和函数表达式连接在一起,比如add:function(...) {...}
。
- 更短的语法更易读和写
- 方法定义创建命名函数,和函数表达式刚好相反。有利于用于调试
注意,使用class
语法需要短形式方法来声明:
class Star { constructor(name) { this.name = name; } getMessage(message) { return this.name + message; }}var sun = new Star('Sun'); sun.getMessage(' is shining') // => 'Sun is shining'
计算属性名和方法
ES6中增加了一个很好的特性:在object literals和class中可以计算属性。
计算属性的方法和[methodNmae(){...}]
略有不同,其定义的方法这样的:
var addMethod = 'add', getMethod = 'get';var collection = { items: [], [addMethod](...items) { this.items.push(...items); }, [getMethod](index) { return this.items[index]; }};collection[addMethod]('C', 'Java', 'PHP'); collection[getMethod](1) // => 'Java'
[addMethod](...) {...}
和 [getMethod](...) {...}
使用了计算属性名快速方法声明。
箭头函数
箭头函数的定义是使用一对小括号,括号内是一系列的参数(param1,param2,...,paramN)
,后面紧跟=>
符号和{...}
,代码主体放置在这对大括号内。
当箭头函数只有一个参数时,可以省略这对小括号,另外它只包含一个声明时,大括号都可以省略。
下面的示例就是一个箭头函数的基本用法:
var absValue = (number) => { if (number < 0) { return -number; } return number;}absValue(-10); // => 10 absValue(5); // => 5
absValue
是一个箭头函数,这个函数主要功能就是计算一个数的绝对值。
函数声明使用箭头函数,其中=>
具有以下属性:
- 箭头函数不创建执行自己的上下文(函数表达式或函数声明式相反,创建不创建取决于
this
的调用) - 箭头函数是一个匿名函数:
name
是一个空字符串''
(函数声明式相反,它有一个名字) arguments
对象不可使用箭头函数(与其它声明类型相反,其他类型提供arguments
对象)
Context transparency
this
关键词的使用在JavaScript中让很多同学都感到困惑。(这篇文章详细介绍了this
关键词的使用)。
因为函数创建了自己的可执行的上下文(execution context),这也造成一般情况很难确定this
所指。
ES6引用箭头函数改善了这种用法(context lexically)。这是一个很好的特性,因为从现在开始函数需要封闭的上下文时没有必要使用.bind(this)
或者var self = this
。
来看一个示例,看this
如何继承外部函数:
class Numbers { constructor(array) { this.array = array; } addNumber(number) { if (number !== undefined) { this.array.push(number); } return (number) => { console.log(this === numbersObject); // => true this.array.push(number); }; }}var numbersObject = new Numbers([]); numbersObject.addNumber(1); var addMethod = numbersObject.addNumber(); addMethod(5);console.log(numbersObject.array); // => [1, 5]
Numbers
类有一个数字数组,并且提供了一个addNumber()
方法,将新数据插入到这个数组中。
当addNumber()
不带任何参数被调用时,则返回一个闭包,允许插入新的数据。这个闭包是一个箭头函数,它的this
就相当于numbersObject
。因为其上下文意思取自addNumbers()
方法。
如果没有箭头函数,那么需要我们自己手动去修复。这也意味着,要添加.bind()
方法:
//... return function(number) { console.log(this === numbersObject); // => true this.array.push(number); }.bind(this);//...
或者将上下文(context)存给一个变量var self = this
:
//... var self = this; return function(number) { console.log(self === numbersObject); // => true self.array.push(number); };//...
context transparency这个属性可以让你在一个封闭的环境内任意使用this
。
短回调
前面也说过了,当创建的箭头函数只有一个参数,或者主体只有一个声明时,小括号()
和花括号{}
都可以省去。这有助于创建一个非常短的回调函数。
让我们创建一个函数,如果数组只有0
这个元素,将它找出来。
var numbers = [1, 5, 10, 0]; numbers.some(item => item === 0); // => true
item => item === 0
是一个箭头函数,它看上去非常简单。
有时候嵌套短的箭头函数会让代码阅读起来增加困难。所以最方便的方式是当这它是一个回调函数(没有嵌套)可以使用短的箭头函数方式。如果有必要,添加花括号之来,这样有利于代码的阅读。
函数生成器
生成函数在JavaScript中会返回一个Generator对象。其语法类似于函数表达式、函数声明式和方法声明,不同的是,它需要在function
后添加一个*
符号。
生成器函数可以按以下这些方式来声明函数:
函数声明function* <name>()
:
function* indexGenerator() { var index = 0; while(true) { yield index++; }}var g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
函数表达式function* ()
:
var indexGenerator = function* () { var index = 0; while(true) { yield index++; }};var g = indexGenerator(); console.log(g.next().value); // => 0 console.log(g.next().value); // => 1
方法生成*<name>
:
var obj = { *indexGenerator() { var index = 0; while(true) { yield index++; } }}var g = obj.indexGenerator(); console.log(g.next().value); // => 0 console.log(g.next().value); // => 1
上面三种方式生成的函数都会返回一个生成器对象g
。然后g
可以生成一系列的数字。
函数构造器: new Function
在JavaScript函数中第一个类(class object)对象: 函数是一个普通的对象类型是function
。
这种声明的方式创建相同的函数对象类型,来看一个示例:
function sum1(a, b) { return a + b;}var sum2 = function(a, b) { return a + b;}var sum3 = (a, b) => a + b; console.log(typeof sum1 === 'function'); // => true console.log(typeof sum2 === 'function'); // => true console.log(typeof sum3 === 'function'); // => true
函数对象类型有一个构造器(constructor):Function
。
当Function
当作构造器(constructor)new Function(arg1,arg2,...,argN,bodyString)
,那么Function
构造器会创建一个新的 Function
对象(new Function
)。其中参数arg1,arg2,...,argN
会传递给构造器(constructor)成为新函数的参数,而且最后一个参数bodyString
用作函数体代码。
来看一个示例,创建一个函数,求两个数的和:
var numberA = 'numberA', numberB = 'numberB'; var sumFunction = new Function(numberA, numberB, 'return numberA + numberB');sumFunction(10, 15) // => 25
sumFunction
创建的Function
构造器调用了numberA
和numberB
两个参数,并且在函数主体内执行return numberA + numberB
。
这种方式创建的函数不能访问当前的作用域,因为没办法创建闭包。他们总是在全局作用域内创建的。
一个可能就用new Function
的最佳方式是浏览器或NodeJs脚本访问一个全局对象:
(function() { 'use strict'; var global = new Function('return this')(); console.log(global === window); // => true console.log(this === window); // => false})();
如种方式最好
没有孰好孰坏,函数的声明类型的决定要视实际情况而定。但有一些规则还是值得大家一起遵循。
如果要在一个闭包内使用this
,那么箭头函数是一个很好的解决方案。另外回调函数是一个简短声明时,箭头函数也是一个很好的选择,因为它的代码短。
当在object literals上需要一个更短的语法时,方法声明是可取的。
new Function
这种方法一般不用来声明函数。主要因为它存在很多问题。
我认为这篇文章另一个作用是让大家写出更具可读性的代码,和减少函数使用的bug。因为他们像细胞一样存在任何一个应用程序当中。
本文根据@Dmitri Pavlutin的《Six ways to declare JavaScript functions》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://rainsoft.io/6-ways-to-declare-javascript-functions/。
如需转载,烦请注明出处:http://www.w3cplus.com/javascript/6-ways-to-declare-javascript-functions.html
原文链接: http://www.w3cplus.com/javascript/6-ways-to-declare-javascript-functions.html
- 声明JavaScript函数的六种方法
- javascript中定义声明函数的三种方法
- ES6声明变量的六种方法
- javascript 函数声明的三种方式
- JavaScript两种函数声明的区别
- JavaScript函数的声明方式
- JavaScript 实现继承的六种方法
- JavaScript的函数声明和函数表达式
- 函数指针的声明方法
- 【javascript笔记】声明函数的三种方式<二>
- JavaScript——函数的三种声明方式
- JavaScript几种函数声明方式的区别
- JavaScript函数声明、变量声明及赋值的优先级问题
- javascript的变量声明和函数声明提升
- javascript的变量声明和函数声明提升
- 关于javascript变量声明、函数声明提升的问题
- JavaScript中函数的声明和表达式
- Javascript声明式函数的一点问题
- android下载, 断点续传, 在关闭activity或杀进程后,可继续下载,保证android下载速度
- DSP移植-全局阈值分割
- 搜索引擎选择: Elasticsearch与Solr
- ACM HDU 1263水果
- Linux_Linux命令_wc_统计命令_Word Count
- 声明JavaScript函数的六种方法
- HDOJ 1242 Rescue(优先队列+BFS)
- 扩展欧几里得原理与模板
- Gaussian Blurring
- H.264中整数DCT变换,量化,反量化,反DCT究竟是如何实现的?(无代码,无真相)
- 关于main(int argc, char* argv[])
- 新浪SAE启动报class org.springframework.web.filter.CharacterEncodingFilter is not a javax.servlet.Filter
- Selenium - (new Actions(driver)).moveToElement(ele).click().perform() 页面没反应
- 杭电ACM 1280 前m大的数