好程序员web前端分享详细了解JavaScript函数

简介: 好程序员web前端分享详细了解JavaScript函数,如果你曾经接触过JavaScript编程,你一定不会陌生如何定义并且调用一个函数。但是你知道在JavaScript中有多少种定义函数的方法吗?如果想要在Test262中编写和维护这些方法的测试,那可真是一个很大的挑战,尤其当一些新特性和现有函数语法相关,或者扩展了函数的API时。

好程序员web前端分享详细了解JavaScript函数,如果你曾经接触过JavaScript编程,你一定不会陌生如何定义并且调用一个函数。但是你知道在JavaScript中有多少种定义函数的方法吗?如果想要在Test262中编写和维护这些方法的测试,那可真是一个很大的挑战,尤其当一些新特性和现有函数语法相关,或者扩展了函数的API时。但是,想要断言新提出或被提案的语法、API有效时,测试所有既存变式又是非常必要的。 下面会针对JavaScript中已经存在的函数定义方式进行一个概述。本文不包含Class声明和表达式,因为这些方式创建的对象是“不可调用的”,本文旨在那些可生成“可调用”对象的函数定义方式。也就是说,我们不会研究那些复杂的参数列表(包含默认参数、结构赋值或者尾后逗号),因为那足够另起文章介绍了。

以前的方式

函数声明以及函数表达式

最为出名以及应用最广的同样也是这些旧方式:函数声明和函数表达式。前者设计(1995)和出现在第一版的规范(1997)(pdf)中。后者则是出现在第三版中(1999)(pdf)。仔细研究,你会从它们当中提取出三种不同的方式。

// 函数声明
function BindingIdentifier() {}

// 命名函数表达式
// (BindingIdentifier在函数外部是访问不到的)
(function BindingIdentifier() {});

// 匿名函数表达式
(function() {});
值得注意的是,匿名函数表达式仍然可能有名字,Mike Pennisi在什么是函数名称?中有深度解释。

Function构造函数

当研究一门语言的"function API"的时候,也就到了这门语言的底层。在这门语言的设计之初,函数声明方式可以被理解为是Function构造函数API的最直接实现。Function构造函数提供了一种定义函数的方式:通过指明Function的参数,其中最后一个参数就是函数的函数体(必须要说明的是,这是一种动态代码方式,可能存在安全问题)。在大多数情况下,这种方式是不合适的,所以用的人很少,但是在第一版的ECMAScript中,这种方式就出现了。

new Function('x', 'y', 'return x ** y;');
新的方式

自从ES2015发布以来,几种新的定义函数方式被引入进来,这些方式的变式更是非常繁多。

另类匿名函数

这是一种新式的匿名函数。如果你曾经接触过ES的模块化,那么你很有可能已经接触过这种定义函数的方式了。尽管这种方式看起来和匿名函数的定义方式很像,但是他确实有自己的名字:default

// 另类匿名函数声明
export default function() {}
顺便一提,这个名字并不是专属的标识,并没有进行绑定。

方法定义

下面这些方式定义的函数表达式、匿名函数或者命名函数,都是某个对象的属性。注意这些并不是新的语法,只是应用上面提及的那些语法,写在了某个对象的初始化器中。这种方式最早引入在ES3中。

let object = {
propertyName: function() {},
};
let object = {
// (BindingIdentifier不能再函数外部调用)
propertyName: function BindingIdentifier() {},
};
下面是存取器属性,引入在ES5。

let object = {
get propertyName() {},
set propertyName(value) {},
};
在ES2015中,JavaScript中提供了一种定义方法的简洁语法,不管是直接命名的方式还是计算属性名的方式,都可以使用,而且,存取器同样适用。

let object = {
propertyName() {},
["computedName"]() {},
get ["computedAccessorName"]() {},
set "computedAccessorName" {},
};
你也可以把这些定义属性或者方法的新方式应用在创建类时。

// 类声明
class C {
methodName() {}
["computedName"]() {}
get ["computedAccessorName"]() {}
set "computedAccessorName" {}
}

// 类表达式
let C = class {
methodName() {}
["computedName"]() {}
get ["computedAccessorName"]() {}
set "computedAccessorName" {}
};
...在定义静态方法时,同样可以使用。

// 类声明
class C {
static methodName() {}
static ["computedName"]() {}
static get ["computedAccessorName"]() {}
static set "computedAccessorName" {}
}

// 类表达式
let C = class {
static methodName() {}
static ["computedName"]() {}
static get ["computedAccessorName"]() {}
static set "computedAccessorName" {}
};
箭头函数

箭头函数首次出现在ES2015中,尽管起初饱受争议,但是现在已经被广泛应用了。箭头函数的定义根据是否简写有两种不同的语法:赋值表达式(在箭头后面没有花括号)和函数体(当函数包含零个或者多个表达式时)。语法规定,当函数只有一个参数时,可以不用小括号括起来,但是当没有参数或者多余一个参数时,就必须用小括号括起来了。(这种语法就决定了箭头函数会有多种定义形式)

// 零参数, 赋值表达式
(() => 2 ** 2);

// 一个参数, 可以省略小括号, 赋值表达式
(x => x ** 2);

// 一个参数, 可以省略小括号, 函数体
(x => { return x ** 2; });

// 多个参数, 赋值表达式
((x, y) => x ** y);
上面的最后一种形式中,参数是用参数列表来表示的,因为它们用小括号括了起来。类似于用小括号来标识参数列表的语法,还有其他形式,诸如({ x }) => x。 如果参数不用小括号括起来,那么只能给参数起一个独一的标识符名称,以在箭头函数中使用。当箭头函数被定义为异步函数或者Generator函数时,这个标识符名称还可以加上await 或者 yield的前缀,但那也已经是在不用括号情形中,考虑足够深远的了。 箭头函数可以并且也经常出现在初始化器或者对象属性的定义中,但是这种情况大部分使用的是上面介绍的赋值表达式形式,举例如下:

let foo = x => x ** 2;

let object = {
propertyName: x => x ** 2
};
Generators

Generator函数的语法是在其他定义函数的方式上加点东西,但箭头函数和存取器方法除外。你可以使用和之前函数声明,函数表达式,函数定义甚至是构造函数等相似的方式。所有方法列举如下:

// Generator 声明
function *BindingIdentifer() {}

// 另类匿名 Generator 声明
export default function *() {}

// Generator 表达式
// (BindingIdentifier只能在函数内部调用)
(function *BindingIdentifier() {});

// 匿名 Generator 表达式
(function *() {});

// 方法定义
let object = {
*methodName() {},
*["computedName"]() {},
};

// 在类声明中定义方法
class C {
*methodName() {}
*["computedName"]() {}
}

// 在类声明中定义静态方法
class C {
static *methodName() {}
static *["computedName"]() {}
}

// 在类表达式中定义方法
let C = class {
*methodName() {}
*["computedName"]() {}
};

// 在类表达式中定义静态方法
let C = class {
static *methodName() {}
static *["computedName"]() {}
};
ES2017

异步函数

经过几年的发展,异步函数将会发布ES2017——第八版EcmaScript语言规范——规范会在2017年6月在正式发布。但其实,很多开发者早已经开始使用异步函数了,这还要归功于Babel的支持。 异步函数语法提供了一个干净的、统一的方式来描述异步操作。当被调用时,异步函数会返回一个Promise对象。当异步执行结束后,这个Promise对象即会被相应执行。当函数中含有await表达式时,异步函数就会暂停执行,这时,await表达式结果就会作为异步函数的返回值。 异步函数的语法并没有太多的不同,只是在我们熟知的那些方式前面加上一个前缀:

// 异步函数声明
async function BindingIdentifier() { /**/ }

// 另类匿名异步函数声明
export default async function() { /**/ }

// 命名异步函数表达式
// (BindingIdentifier只能在函数内部调用)
(async function BindingIdentifier() {});

// 匿名异步函数表达式
(async function() {});

// 异步方法
let object = {
async methodName() {},
async ["computedName"]() {},
};

// 类声明中的异步方法
class C {
async methodName() {}
async ["computedName"]() {}
}

// 类声明中的静态异步方法
class C {
static async methodName() {}
static async ["computedName"]() {}
}

// 类表达式中的异步方法
let C = class {
async methodName() {}
async ["computedName"]() {}
};

// 类表达式中的静态异步方法
let C = class {
static async methodName() {}
static async ["computedName"]() {}
};
异步箭头函数

async 和 await并不是只局限在常规函数的声明或者表达式中,它们同样适用于箭头函数:

// 单一参数,赋值表达式
(async x => x ** 2);

// 单一参数,函数体
(async x => { return x ** 2; });

// 参数列表,赋值表达式
(async (x, y) => x ** y);

// 参数列表,函数体
(async (x, y) => { return x ** y; });
与ES2017结合

异步Generator函数

和ES2017结合,async 和 await将会被扩展支持异步函数。这一特性的发展可以追踪推荐的github仓储。正如你猜想到的,异步Generator函数的语法是结合async、await以及已经存在的Generator函数声明和表达式而来。当异步Generator函数被调用的时候,会返回一个迭代器,这个迭代器的next()方法会返回一个Promise对象来处理迭代器的返回对象,而不是直接返回迭代器的结果对象。 异步Generator函数已经开始在很多地方使用,你很有可能已经碰到过。

// 异步Generator声明
async function BindingIdentifier() { /*/ }

// 另类匿名异步Generator声明
export default async function *() {}

// 异步Generator表达式
// (BindingIdentifier只能在函数内部访问)
(async function *BindingIdentifier() {});

// 匿名异步Generator表达式
(async function *() {});

// 匿名异步Generator方法定义
let object = {
async *propertyName() {},
async *["computedName"]() {},
};

// 类声明中异步Generator原型方法定义
class C {
async *propertyName() {}
async *["computedName"]() {}
}

// 类表达式中异步Generator原型方法定义
let C = class {
async *propertyName() {}
async *["computedName"]() {}
};

// 类声明中异步Generator静态方法定义
class C {
static async *propertyName() {}
static async *["computedName"]() {}
}

// 类表达式中异步Generator静态方法定义
let C = class {
static async *propertyName() {}
static async *["computedName"]() {}
};

相关文章
|
16天前
|
前端开发 JavaScript 安全
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第27天】本文介绍了HTTP/2和HTTPS在前端性能调优中的应用。通过多路复用、服务器推送和头部压缩等特性,HTTP/2显著提升了Web性能。同时,HTTPS确保了数据传输的安全性。文章提供了示例代码,展示了如何使用Node.js创建一个HTTP/2服务器。
30 3
|
13天前
|
监控 前端开发 JavaScript
探索微前端架构:构建可扩展的现代Web应用
【10月更文挑战第29天】本文探讨了微前端架构的核心概念、优势及实施策略,通过将大型前端应用拆分为多个独立的微应用,提高开发效率、增强可维护性,并支持灵活的技术选型。实际案例包括Spotify和Zalando的成功应用。
|
11天前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
|
11天前
|
移动开发 前端开发 JavaScript
前端实训,刚入门,我用原生技术(H5、C3、JS、JQ)手写【网易游戏】页面特效
于辰在大学期间带领团队参考网易游戏官网的部分游戏页面,开发了一系列前端实训作品。项目包括首页、2021校园招聘页面和明日之后游戏页面,涉及多种特效实现,如动态图片切换和人物聚合效果。作品源码已上传至CSDN,视频效果可在CSDN预览。
18 0
前端实训,刚入门,我用原生技术(H5、C3、JS、JQ)手写【网易游戏】页面特效
|
16天前
|
JavaScript 前端开发 开发者
前端框架对比:Vue.js与Angular的优劣分析与选择建议
【10月更文挑战第27天】在前端开发领域,Vue.js和Angular是两个备受瞩目的框架。本文对比了两者的优劣,Vue.js以轻量级和易上手著称,适合快速开发小型到中型项目;Angular则由Google支持,功能全面,适合大型企业级应用。选择时需考虑项目需求、团队熟悉度和长期维护等因素。
22 1
|
17天前
|
前端开发 JavaScript
Bootstrap Web 前端 UI 框架
Bootstrap 是快速开发 Web 应用程序的前端工具包。
30 3
|
Web App开发 JavaScript 前端开发
|
移动开发 JavaScript 前端开发
|
1月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
100 3
|
16天前
|
设计模式 前端开发 数据库
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
103 45