为什么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');

条件中的函数声明

当函数声明出现ifforwhile这样的条件语句块{...}时,在一些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构造器调用了numberAnumberB两个参数,并且在函数主体内执行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函数的六种方法相关推荐

  1. 菜鸟教程 之 JavaScript 函数(function)

    From:https://www.runoob.com/js/js-function-definition.html JavaScript 函数定义 JavaScript 使用关键字 function ...

  2. JavaScript函数,作用域以及闭包

    JavaScript函数,作用域以及闭包 1. 函数 (1). 函数定义:函数使用function关键字定义,它可以用在函数定义表达式或者函数声明定义. a. 函数的两种定义方式: * functio ...

  3. javascript调用java的方法

    这篇文章主要讲述如何在JavaScript脚本语言中调用Java类和接口,对大家的学习和工作有一定的参考借鉴价值,有需要的朋友们下面来一起看看吧. 前言 本文中所有的代码使用 JavaScript 编 ...

  4. JavaScript基础08-day10【函数的返回值、实参、立即执行函数、方法、全局作用域、函数作用域、提前声明、this、工厂方法创建对象、构造函数】

    学习地址: 谷粒学院--尚硅谷 哔哩哔哩网站--尚硅谷最新版JavaScript基础全套教程完整版(140集实战教学,JS从入门到精通) JavaScript基础.高级学习笔记汇总表[尚硅谷最新版Ja ...

  5. 六种方法实现JavaScript数组去重

    tip:有问题或者需要大厂内推的+我脉脉哦:丛培森 ٩( 'ω' )و [本文源址:http://blog.csdn.net/q1056843325/article/details/73277063 ...

  6. JavaScript 函数声明,函数表达式,匿名函数,立即执行函数之区别

    函数声明:function fnName () {-};使用function关键字声明一个函数,再指定一个函数名,叫函数声明. 函数表达式 var fnName = function () {-};使 ...

  7. JavaScript进阶系列01,函数的声明,函数参数,函数闭包

    本篇主要体验JavaScript函数的声明.函数参数以及函数闭包. □ 函数的声明 ※ 声明全局函数 通常这样声明函数: function doSth() { alert("可以在任何时候调 ...

  8. JavaScript函数之实际参数对象(arguments) / callee属性 / caller属性 / 递归调用 / 获取函数名称的方法...

    函数的作用域:调用对象 JavaScript中函数的主体是在局部作用域中执行的,该作用域不同于全局作用域.这个新的作用域是通过将调用对象添加到作用域链的头部而创建的(没怎么理解这句话,有理解的亲可以留 ...

  9. JavaScript函数声明提升

    首先,JavaScript中函数有两种创建方式,即函数声明.函数表达式两种. 1.函数声明. function boo(){console.log(123); } boo() 2.函数表达式. var ...

  10. asp.net中前台javascript与c#函数相互调方法

    C#代码与javaScript函数的相互调用 问题: 1.如何在JavaScript访问C#函数? 2.如何在JavaScript访问C#变量? 3.如何在C#中访问JavaScript的已有变量? ...

最新文章

  1. Docker进阶(制作镜像,共享卷,网络通信,私有仓库)
  2. JPQL设置自增长、只读、文本类型等的注解
  3. Erlang中使用变量的简单示例
  4. 数据结构与算法--重建二叉树
  5. c语言 数组循环左移m位
  6. Qt实践录:常见控件操作示例2
  7. leetcode947. Most Stones Removed with Same Row or Column
  8. linux设置dns简单的,Linux下的DNS简单配置
  9. 直连路由、主机路由以及选择顺序
  10. LaTeX 常用符号命令大全
  11. 架构搜索文献笔记(5):《APQ:联合搜索网络结构、剪枝和量化策略》
  12. 面对面快传文件在服务器有痕迹,QQ面对面快传的文件存储在哪
  13. 恭主驾到:最新的汽车年审新规,都了解了吗?
  14. 2021-09-19
  15. 解决帆软中不能制作城市热力图的问题
  16. 红牛整装待发,功能饮料市场地位不可撼动
  17. javascript 生成汉字和拼音对照
  18. 基于MATLAB的全局多项式插值法(趋势面法)与逆距离加权(IDW)法插值与结果分析
  19. UI设计中标签设计总结
  20. RGB 转 edp 接口dp501 示范电路

热门文章

  1. minnet sample
  2. 关于python的闭包与装饰器的实验
  3. BZOJ 10628 Luogu 2633
  4. webstorm 去点右边白线
  5. PhpStorm快捷方式
  6. POJ 1984 Navigation Nightmare
  7. 第五话 Asp.Net MVC 3.0【MVC实战项目の一】
  8. xml 增 删 改 查
  9. 每日一课(4/75)逻辑运算指令
  10. delphi中单独编译pas生成dcu文件