html 定义 js 函数,声明JavaScript函数的六种方法

一个函数一次性定义的代码块可以多次调用。在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'); // => 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* ():

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

方法生成*:

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。因为他们像细胞一样存在任何一个应用程序当中。

8b555b46c6d95b49f226e7c33d8b94bf.png

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值