声明JavaScript函数的六种方法
为什么80%的码农都做不了架构师?>>>
本文根据@Dmitri Pavlutin的《Six ways to declare JavaScript functions》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://rainsoft.io/6-ways-to-declare-javascript-functions/。
一个函数一次性定义的代码块可以多次调用。在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 declaration
function isEven (num) {return num % 2 === 0;
}
isEven(24); // => true
isEven(11); // => false
function isEven(num) {...}
是一个函数声明,定义了一个isEven
函数。用来判断一个数是不是偶数。
函数声明创建了一个变量,在当前作用域,这个变量就是函数的名称,而且是一个函数对象。这个函数变量存在变量生命提升,它会提到当前作用域的顶部,也就是说,在函数声明之前可以调用。
函数声明创建的函数已经被命名,也就是说函数对的name
属性就是他声明的名称。在调试或者错误信息阅读的时候,其很有用。
下面的示例,演示了这些属性:
// Hoisted variable
console.log(hello('Aliens')); // => 'Hello Aliens!'
// Named function
console.log(hello.name); // => 'hello'
// Variable holds the function object
console.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'); // => trueconsole.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'); // => trueconsole.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 expressionreturn this.numbers.reduce(function(acc, num) { // func. expressionreturn 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'); // => truereturn 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); // => truethis.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); // => truethis.array.push(number);}.bind(this);
//...
或者将上下文(context)存给一个变量var self = this
:
//...var self = this;return function(number) { console.log(self === numbersObject); // => trueself.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); // => 0
console.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); // => trueconsole.log(this === window); // => false
})();
如种方式最好
没有孰好孰坏,函数的声明类型的决定要视实际情况而定。但有一些规则还是值得大家一起遵循。
如果要在一个闭包内使用this
,那么箭头函数是一个很好的解决方案。另外回调函数是一个简短声明时,箭头函数也是一个很好的选择,因为它的代码短。
当在object literals上需要一个更短的语法时,方法声明是可取的。
new Function
这种方法一般不用来声明函数。主要因为它存在很多问题。
我认为这篇文章另一个作用是让大家写出更具可读性的代码,和减少函数使用的bug。因为他们像细胞一样存在任何一个应用程序当中。
转载于:https://my.oschina.net/wolfx/blog/716003
声明JavaScript函数的六种方法相关推荐
- 菜鸟教程 之 JavaScript 函数(function)
From:https://www.runoob.com/js/js-function-definition.html JavaScript 函数定义 JavaScript 使用关键字 function ...
- JavaScript函数,作用域以及闭包
JavaScript函数,作用域以及闭包 1. 函数 (1). 函数定义:函数使用function关键字定义,它可以用在函数定义表达式或者函数声明定义. a. 函数的两种定义方式: * functio ...
- javascript调用java的方法
这篇文章主要讲述如何在JavaScript脚本语言中调用Java类和接口,对大家的学习和工作有一定的参考借鉴价值,有需要的朋友们下面来一起看看吧. 前言 本文中所有的代码使用 JavaScript 编 ...
- JavaScript基础08-day10【函数的返回值、实参、立即执行函数、方法、全局作用域、函数作用域、提前声明、this、工厂方法创建对象、构造函数】
学习地址: 谷粒学院--尚硅谷 哔哩哔哩网站--尚硅谷最新版JavaScript基础全套教程完整版(140集实战教学,JS从入门到精通) JavaScript基础.高级学习笔记汇总表[尚硅谷最新版Ja ...
- 六种方法实现JavaScript数组去重
tip:有问题或者需要大厂内推的+我脉脉哦:丛培森 ٩( 'ω' )و [本文源址:http://blog.csdn.net/q1056843325/article/details/73277063 ...
- JavaScript 函数声明,函数表达式,匿名函数,立即执行函数之区别
函数声明:function fnName () {-};使用function关键字声明一个函数,再指定一个函数名,叫函数声明. 函数表达式 var fnName = function () {-};使 ...
- JavaScript进阶系列01,函数的声明,函数参数,函数闭包
本篇主要体验JavaScript函数的声明.函数参数以及函数闭包. □ 函数的声明 ※ 声明全局函数 通常这样声明函数: function doSth() { alert("可以在任何时候调 ...
- JavaScript函数之实际参数对象(arguments) / callee属性 / caller属性 / 递归调用 / 获取函数名称的方法...
函数的作用域:调用对象 JavaScript中函数的主体是在局部作用域中执行的,该作用域不同于全局作用域.这个新的作用域是通过将调用对象添加到作用域链的头部而创建的(没怎么理解这句话,有理解的亲可以留 ...
- JavaScript函数声明提升
首先,JavaScript中函数有两种创建方式,即函数声明.函数表达式两种. 1.函数声明. function boo(){console.log(123); } boo() 2.函数表达式. var ...
- asp.net中前台javascript与c#函数相互调方法
C#代码与javaScript函数的相互调用 问题: 1.如何在JavaScript访问C#函数? 2.如何在JavaScript访问C#变量? 3.如何在C#中访问JavaScript的已有变量? ...
最新文章
- Docker进阶(制作镜像,共享卷,网络通信,私有仓库)
- JPQL设置自增长、只读、文本类型等的注解
- Erlang中使用变量的简单示例
- 数据结构与算法--重建二叉树
- c语言 数组循环左移m位
- Qt实践录:常见控件操作示例2
- leetcode947. Most Stones Removed with Same Row or Column
- linux设置dns简单的,Linux下的DNS简单配置
- 直连路由、主机路由以及选择顺序
- LaTeX 常用符号命令大全
- 架构搜索文献笔记(5):《APQ:联合搜索网络结构、剪枝和量化策略》
- 面对面快传文件在服务器有痕迹,QQ面对面快传的文件存储在哪
- 恭主驾到:最新的汽车年审新规,都了解了吗?
- 2021-09-19
- 解决帆软中不能制作城市热力图的问题
- 红牛整装待发,功能饮料市场地位不可撼动
- javascript 生成汉字和拼音对照
- 基于MATLAB的全局多项式插值法(趋势面法)与逆距离加权(IDW)法插值与结果分析
- UI设计中标签设计总结
- RGB 转 edp 接口dp501 示范电路