这一部分是系列内容中的第三部分-下,也是最后一个部分。前两个部分在:
JS开发者应知的100个概念-上: juejin.cn/post/730226...
JS开发者应知的100个概念-中: juejin.cn/post/730378...
▢ 0x45: CallBack Function, 回调函数
回调函数并不是js独有的概念,虽然确实在js中使用的最为广泛,但基本上所有的编程语言都可以实现回调函数。在技术上而言,回调函数就是一个可以通过函数指针调用的函数,它可以作为一个参数,传入另一个函数,然后在那个函数中,在某个合适的时机和场合,调用并执行这个函数,从而达成某种过程场景驱动执行的效果。显然,回调函数不是声明时立即执行的函数,而是由其他函数或者代码,在合适的时候调用以实现某种业务或操作。
在js中,回调函数是其异步执行的基础和核心。js广泛的将其应用在如消息事件处理、异步网络请求、定时器、异步文件操作、异步数据库操作等等场景中。
▢ 0x46: Callback Hell, 回调地狱
js程序中,可能会大量使用异步函数和回调方法。如果调用层级过多(在复杂的应用和很长的业务流程中,这很有可能),在编写代码的时候,我们就会看到一层层的回调函数套着回调函数,对代码的编写、理解、修改和移植都会造成困扰,这种情况通常被称为回调地狱(图)。
要缓解这种情况,总体的思路是合理规划功能模块,或者将回调函数重写为同步执行的形式(其实只是形式),常用的模式包括async/await,promise等等。
▢ 0x47: Promise, 承诺
在JavaScript中,Promise是处理异步操作的一种机制。它的初始设计目的是为了避免回调地狱,编写更加直观而易于理解的异步执行代码。Promise将异步操作队列化,并支持链式操作和统一的错误捕获,还支持多方法的全部执行(和竞争执行方式(Promise.all和Promise.race)。
经过一段时间的发展,Promise已经成为比较成熟、应用广泛的异步代码编写模式。ES也在ES6版本后,将其引入ES作为内置的特性。如果不使用Promise或者比较早期的js程序,要使用这个模式,可能需要外部第三方技术方案(如async npm)。
技术实现方面,在js中Promise会作为一个对象,具有pending(初始),fullfiled(成功)和rejected(失败)三种状态。该对象通过构造函数创建,它需要传入一个执行器函数并立即执行。该执行器函数会接收resolve和reject两个函数参数,函数内容就是业务操作代码,当然可以调用异步方法;在异步操作成功时,调用resolve可以将promise状态置为fulfilled,并且可以使用参数返回操作结果;当异步操作失败时,调用reject将状态置为rejected,并返回错误信息。Promise的then方法用于获取promise成功的值或失败的原因。它接受两个可选回调函数,第一个回调函数在状态改变为fulfilled时调用,第二个回调函数在状态改变为rejected时调用。Promise也可以使用catch方法,捕获操作失败事件,并进行处理。
▢ 0x48: Resolve/Reject, 解决/拒绝
Resolve是构建promise时,设置的用于返回成功结果时,使用的回调函数对象参数。在promise中,异步操作成功时,可以执行调用这个方法,设置成功状态,并返回异步执行的结果。
Reject是构建promise时,设置的另一个回调函数参数对象,用于在promise中,异步操作失败时,调用执行这个方法,设置失败状态并返回错误信息。
▢ 0x49: Then/Catch, 继续/错误捕获
Promise.then方法可以用于获取Promise执行器的结果并链接下一个异步操作。Then可以级联来处理多个异步操作,就达到了顺序调用异步操作的效果,并且可以避免回调地狱。
Promise.catch方法提供了在连接多个异步操作过程中,一致的错误处理机制。无论哪个步骤出错,都可以用这个方法捕获并进行统一的错误处理。
▢ 0x4A: Async/Await, 异步/等待
一般认为,asnyc/await本质上是Promise的语法糖。可以用这两个关键字,对异步函数进行修饰,也可以达到形式上将异步函数进行同步调用的效果。
async用于声明一个函数,这时可能有两个作用。第一,在这个函数内,可以进行异步函数的调用和执行,其实就是可以用await来执行了。第二,此函数本身,也可以被其他函数用异步的方式调用。
在async函数内,调用另一个异步函数(被async修饰过的),要达到形式上同步执行的效果,可以使用await的方式调用。这时,应该不使用回调函数,而是使用函数返回值返回处理结果。
await还可以处理promise,使用await 修饰promise调用,可以返回成功的处理结果。由于可能有需要处理错误的情况,一般情况下,await执行需要和try/catch错误处理机制配合使用。
▢ 0x4B: Try/Catch, 尝试/捕获
try/catch是经典的错误处理机制和代码结构。这个机制的目标是避免程序或者数据错误导致的程序崩溃,使程序能够容错,更加健壮。
以JS为例,这个结构,一般由try和catch引导的两个代码块结合构成。try代码中块放置可能出错的代码,当此处的语句执行出错时,执行环境会抛出一个错误对象,程序控制流会跳转到catch代码块,并将错误对象传入catch;在catch代码块中,就有机会对错误进行处理,比如打印日志、显示错误信息给用户等。当然也可以完全不处理,这时出现的错误不会像普通的系统级错误一样,引起程序的总体崩溃。如果程序正常运行没有抛出错误,则不会执行catch块。
JS常见使用try/catch的场景包括数据转化(Parse)、加密解密、async/await等。因为完全无法限定和预料可能输入的数据。
▢ 0x4C: ES Modules, ES模块
ES Modules是ECMA Script 2015中引入的一种模块系统,用于实现JavaScript的模块化,可以用于浏览器和nodejs等运行环境中。
ES模块使用import和export关键字实现导入和导出。模块自动采用严格模式,支持按需导入。每个导入模块只会被执行一次,并且可以处理循环依赖。ES模块可以在模块间建立静态稳定的导入关系,提高了代码可靠性和可优化性,并可以利用js打包工具提升性能。
虽然nodejs已经可以支持ES模块,但需要一些特别的设置。在以CommonJS为主的生态系统中,笔者觉得并没有很好的实现兼容和融合,特别对对于已有的一些应用,迁移和实际使用起来有一些问题和限制,增加了系统向ES演进的障碍。
▢ 0x4D: Default Export, 默认导出
默认导出是ES6模块语法中的一个常用模式。它允许在一个模块中导出一个默认值,然后当模块导入时,就直接使用这个默认值,而不需要指定命名。
每个模块只能有一个默认导出。默认导出可以让我们明确定义每个模块的默认功能,在很多场景中可以简化模块的导入和使用,同时避免命名的冲突。
▢ 0x4E: Name Export, 命名导出
如果模块有多个功能方法需要导出,又不希望使用分离的模块文件,比如这些方法有逻辑的关联或者分类关系,或者模块内有需要共享的代码,就可以使用单一的模块文件,但使用命名导出这些不同的功能方法。
命名方法可以导出变量、函数、类等等,并使用名称进行表示和区隔。导入时,可以使用此名称引用导入的内容,并可选导入部分的导出项目,为避免和导入模块内的变量重复,还可以在导入时对导出项目进行重命名。ES模块支持同时使用命名导出和默认导出。
▢ 0x4F: Import, 引用导入
有Export,很自然的就有Import。在ES模块系统中,使用import来导入其他模块导出的接口。
import可以导入默认或者多个命名的导出,一个很重要和实用的特性是import可以结合关键字as对导入的模块接口进行重命名。import还支持动态按需异步加载模块。
import来源于ES6规范,而先前广泛使用的require方法基于CommonJS规范。所以笔者认为,import设计的更体现以模块为中心的概念。
▢ 0x50: NPM, Node Package Manager, nodejs软件包管理器
NPM是nodejs默认的JavaScript包管理工具。笔者甚至认为,NPM是除了V8之外,nodejs能够成功的另一个核心和关键。基于NPM,nodejs才能构建生机勃勃而又强大的生态系统,从而发展成为主流的Web应用开发平台。
从某种意义上而言,NPM改变了传统开发工具或者平台,对于外部程序库和程序扩展的概念和方式。NPM提供了标准命令可以方便的通过网络安装和维护各种开源外部软件包。NPM可以自动分析提供理这些软件包的版本和相互的依赖关系,查找并安装所依赖的软件包和模块,大大减轻了开发者的工作负担。使用配置文件和安装命令,NPM帮助开发者能够将项目在不同的系统和环境中进行迁移和移植。NPM也提供了简单方便的软件包发布方式,可以将自己开发的软件包发布到公共仓库中与社区共享。
▢ 0x51: Node Modules, Node模块
指nodejs程序引用和使用的模块。在nodejs项目和应用中,有一个node_modules的文件夹,在此保存通过NPM安装的项目所需要的所有外部软件包和模块。
在进行项目迁移的时候,开发者不需要迁移或者这个文件夹中的内容。因为相关的配置信息都保存在项目的package.json配置文件中了。开发者只需要在新的环境中,再次执行npm install命令,npm就会根据配置信息和所处环境,将外部node模块下载并安装到新的项目文件夹当中。安装完成之后,项目应用就又可以正常运行了。
▢ 0x52: package.json
package.json是标准的nodejs程序配置和定义文件。它位于nodejs程序项目的根目录,为json格式。它可以由开发工具和机制维护,开发者也可以手动编辑维护。
在比较新的nodejs环境中,还增加了一个package-lock.json,来记录npm包之间的版本依赖关系。这个机制可以相对固定相互关联的软件包和版本,避免和解决因为版本升级导致的兼容性问题。
▢ 0x53: Document Object Model, DOM, 文本对象模型
标准的HTML页面代码,使用的是文本对象模型。DOM提供了js对HTML和XML代码进行编程和操作的接口。DOM将HTML文档转换为一个由节点和对象(对应于结构化的文档)组成的结构体,并定义了表示和访问它所需的对象,接口和方法,比如获取各种元素,遍历文档树等(图为典型的DOM树结构)。
DOM的实现是由浏览器完成的,以此,js程序可以对网页进行操作,实现了js代码和HTML文本之间的关联。理解DOM对学习前端与编写复杂的Web应用是非常重要的。神奇的是js所在的script标签也是DOM的一个对象。
▢ 0x54: Document, 文档
在DOM模型中,Document一般指当前的这个HTML文件的内容和对象,也是模型的根节点。除此之外,Document还承载当前页面对象的URL、标题等文档基本全局信息。
Document继承了标准的Node接口,也拥有节点对象的常见属性和方法。如它可以将自己作为一个事件目标进行监听,处理如页面加载等事件,这些操作在页面生命周期管理中非常常见。
▢ 0x55: Cascading Style Sheets, 层叠样式表
层叠样式表,是一种用来控制网页样式和布局的计算机语言。笔者理解,CSS应该算是一种HTML的扩展。通过CSS,开发者可以通过语义化和指令化的方式,控制HTML页面和元素的布局排版、外观显示、对用户界面进行规范和美化。此处层叠的意思是可以将样式组合应用到同一个元素上,多种元素和样式有继承和组合的关系和效果。
CSS的实质是将内容和表现分离,使用CSS,开发者可以复用样式,减少开发和维护的工作量。CSS还提供了一定程度的标准化,保证在不同的设备和系统上,页面都具有相同一致的外观;CSS还提供了自适应性,编写良好的网页可以支持响应式设计,可以同时适用于桌面、平板、手机等多种设备和屏幕。
▢ 0x56: CSS Selector, 样式选择器
CSS的核心,其实是CSS样式选择器。因为在定义元素的属性之前,你需要先在DOM中找到特定的元素来定义。CSS就是这样一套元素选择的机制。它使用一套特定的语法,基于某些类型、标识和特征,在DOM文档树中查找能够匹配的节点和元素,并设置它们的样式(图为样式选择器代码)。
常见的CSS选择器支持包括元素类型、CSS类、ID、属性、后续和子类型等选择方式。CSS选择器的语法简单明了,可以支持结构化模式匹配,并可以组合使用复杂的匹配条件等,功能强大和灵活。
通过CSS选择器,开发者可以精确快速地获取文档树中的符合匹配条件的任意元素。这一操作的使用场景非常广泛,包括DOM操作、事件绑定、状态切换、UI自动化、页面内容分析等,值得深入学习,也是前端开发者必备的一项重要技能。
▢ 0x57: Query Selector/ Query Select ALl, 查询选择器
document.querySelector是浏览器提供的document的一个标准方法。它用于查找满足匹配条件的第一个元素。类似的,document.querySelectorAll方法,用于查找满足条件的所有元素。它们统称为查询选择器。
查询选择器使用CSS选择器语法进行工作。使用查询选择器结合CSS选择器语法,js程序才能够基于业务需求,从DOM模型中获取需要进行操作的元素对象,进行相关的操作。
▢ 0x58: Events, 事件
事件是软件设计中经常使用和非常重要的机制,它是在软件运行过程中,各种自身和外部动作,如UI操作或者系统状态变化等,在代码执行中的抽象和体现。
围绕事件进行编程的基本机制是,首先在系统中,会预先好定义和用户操作或者系统变化相关的事件;然后在编程时,编写事件处理代码,就是这些事件发生时要做些什么;然后将这个方法,同事件要发生的实体进行关联,称之为"侦听";关联后,程序运行,如果遇到事件发生,如点击了某个按钮,就会触发事件关联的方法,并执行这个方法对应的操作。从而完成这个事件从定义、侦听、触发、执行的完整的过程。
前端编程中场景的事件包括鼠标、键盘、表单、文档、触摸、手势等等。可以通过给DOM元素绑定事件处理函数的方式,来监听和关联这些事件。当这些事件发生时,就会触发并且执行所绑定的执行函数。
▢ 0x59: Element, 元素
在前端编程概念中,元素是用于构建用户界面的基本单位。HTML中的元素主要指标签元素,而在前端框架技术中,组件也是一种元素。
使用元素包括组件可以封装界面中的独立区域。元素可以跨页面和系统重复使用,并且可以组合、嵌套来构造更复杂的界面(就是组件)。元素可以管理自身行为和数据状态,同时处理用户交互,调用API更新界面。
这样,基于元素,构建界面不再需要复杂的DOM操作,只需要声明式地组合不同的元素,即可实现丰富的功能,大大简化了前端开发。
▢ 0x5A: Components, 组件
这里的组件,主要是前端模块化开发的概念。
流行的前端框架像React、Vue都采用了组件化的设计,开发者可以通过组合各种组件构建整个应用的界面。笔者理解,组件就是一个UI单元,但它将数据驱动呈现、操作事件响应、界面更新渲染等综合和封装了起来,只保留外部易于操作和集成的接口,非常类似于OOP的类的,更像机械工业里面的总成(如发动机总成、悬架总成等)的概念。组件是相互独立的模块,但可以复用、继承、组合,并且方便维护和测试。
组件和元素之间的关系,有点像部件和总成之间的关系。它们都是功能独立的实体,但组件可以由多个元素构成。
组件化开发已经成为开发现代 web 应用的主流方式。它提高了代码的复用性和维护性,降低了开发和测试的难度,很大程度上提升了前端开发的效率。
▢ 0x5B: Data Binding, 数据绑定
在前端开发的概念中,数据绑定将数据模型和UI界面之间建立动态有机连接的一种技术。它的主要概念和特点是当数据改变时,UI的呈现自动更新;而当UI改变如用户输入时,也会反映到数据模型中;这个过程无需编程手动操作DOM,而是通常使用简单清晰的声明式语法,易于理解和修改(图)。
当前流行的前端框架都有相应的数据绑定实现。数据绑定大幅简化了DOM操作逻辑,减少了相关的操作代码,增加了灵活性和可维护性,是非常重要的前端开发技术。
▢ 0x5C: Imperative, 命令式
命令式编程传统的编程范式,主要关注的是如何一步步地命令计算机系统执行特定的任务来实现结果。它需要指定执行流程、需要管理程序状态变化等。
命令式编程需要开发者完全控制执行流程,自行处理更多细节,这样导致代码量通常会比较多,但可以更加精细地表达算法,修改程序状态,也会更灵活,实现功能的弹性更大。
▢ 0x5D: Declarative, 声明式
声明式是一种编程范式,它的基本概念是主要关注的是要实现什么,而非具体如何实现。
前端领域使用声明式编程的例子包括CSS、GraphQL、VUE组件模板样式声明等等。后端领域使用声明式编程的典型就是Java中的注解,SQL广义上也是一种声明式编程。实现声明式的编程,可以抽象底层实现细节,简化状态管理,提高开发效率,并且使代码更加精简。同时,声明式编程让开发者暂时脱离技术实现细节,将注意力集中在业务逻辑之上,从而大大提高开发效率。
声明式和命令式编程并不是竞争而应该是合作和补充的关系。更多时候,需要开发者根据实际场景和需求,结合使用声明式和命令式编程,这样既能充分利用声明式编程简化状态管理逻辑,也可以利用命令式编程处理特定需求的实现细节。二者可以很好地结合,发挥各自的优势。
▢ 0x5E: Module Bundler, 模块打包工具
模块打包工具一般用于生产环境的前端发布代码的构建。在前端应用开发的一般流程中,前端开发所使用的代码,和最终部署到生产环境中的代码是不同的(虽然可能都是HTML、CSS和JS代码)。因为要考虑到它们在前端环境中加载、执行的性能和效率。
前端构建工作可能会包括模块合并、代码转换、依赖分析、文本压缩、资源封装、代码分割等操作。现在的应用程序日益复杂,这些工作基本上不可能由人来手工操作完成。就需要使用模块打包工具来完成和加速这些工作。常见的模块打包器有 webpack、Rollup.js、Parcel 等。图为模块打包工具的基本工作方式。
▢ 0x5F: Network Waterfall, 网络瀑布
网络瀑布,是在Chrome DevTools中(图),提供的一个网络性能分析工具和方法。它可以直观的展示网络请求的各个阶段和其消耗时间,可以帮助我们理解网络请求的各个构成阶段和工作,分析其中问题和瓶颈,用于指导页面加载和数据请求的性能优化。
▢ 0x60: Dynamic Imports, 动态导入
动态导入是ES2020中新增的JavaScript功能,顾名思义,它允许按需异步加载模块。ES2020中,使用Promise方式来实现动态加载,如果加载成功,则导入内容就在promise的resolved对象当中。
动态导入的好处很多,最典型的就是减少Web应用的首页加载工作,快速启动应用,提升用户体验。其他还有根据路由进行懒加载,减少网络请求和流量。并且简化打包操作等等。但负面影响就是需要细化程序和模块的设计,以及加载依赖关系等等。
▢ 0x61: Node.js
nodejs是开放源代码,跨平台的JavaScript运行时环境(官方定义)。
对开发者而言,nodejs是一个以JS作为编程语言,主要面向服务端的Web应用的开放平台(js本身源自浏览器前端应用开发)和生态系统。它的js执行引擎使用的是V8,并为其配套了相关的扩展库和功能如fs、crypto、http、npm等,使其更适合Web服务应用的开放(图为nodejs系统构成)。JS语言本身的简洁、高效,以及独特的非阻塞事件驱动的异步执行方式,配合V8支撑的高效执行,nodejs程序特别适合于Web应用这种大规模、随机、并发式、短流程、简单信息处理的业务场景。
除了平台本身之外,nodejs还通过npm建立的强大的Web应用开发社区和生态环境,经过长时间的发展和完善,已经成为主流的Web应用开发技术体系。
▢ 0x62: Cross Platform,跨平台
跨平台指软件或应用程序在不同的环境之间可以兼容运行的特性和能力。笔者认为,狭义的跨平台应当指程序在不同的平台上运行,完全不需要重新编译和额外的外部环境支持,如某些GO程序(Java严格来讲不算,因为需要安装执行环境);而相对广义的跨平台指代码在不同的平台上迁移和运行,不需要修改就可以执行,如JavaScript,但可能需要一定的支撑条件;最广义的跨平台,就是在不同的平台上迁移和运行,也不需要修改原代码,但是可能需要重新编译和配置,但这个过程比较方便。
对于js程序而言,可能需要运行的平台或者环境包括浏览器、nodejs系统、桌面应用内置的js环境(如VSCode)、移动应用内置js环境(如微信)、各种小程序、嵌入式操作系统等等。借助以V8为代表的js引擎的跨平台能力,js相比较其他应用程序,提供了更好的跨平台能力,也是其作为全栈语言得以流行的重要因素。
▢ 0x63: TypeScript
TypeScript(TS)是JavaScript的一个超集,它是由微软提出和实现的,目的是改善js作为脚本语言不太严谨和正规的问题(图为TS和JS的简单比较)。TypeScript的type就是类型的意思,意为这个语言更加重视数据类型的相关规范。.js文件可以直接重命名为 .ts 即可被TypeScript编译环境识别。
TS在JavaScript的基础上添加了可选的静态类型。这允许检测许多常见的错误如拼写、参数类型等等,可以提高开发效率和代码质量;TS内置对于新的ES6/7的支持,增加了一些新的语法特性如箭头函数、改进的类、模块系统等;TS改进了对面向对象编程模式的支持,包括接口、类、继承、访问修饰符、抽象类等特性;TS可以在代码中添加类型注解,这不会影响代码执行,但是会被IDE/编辑器用于验证和提示。
虽然TS提供了很多软件工程方面的改进,在前端开发的应用也日益广泛。但由于TS本身并不是真正的JS标准,最后在执行时,还是会被转化为标准的JS程序来进行执行,并没有特别本质上的区别。笔者个人觉得,很大程度是一个喜好的问题,TS会在一定程度上丧失JS的简单、灵活、高效,又把JS拉入了C#和Java的感觉当中。
▢ 0x64: ESLint
ESLint是流行的自动化JavaScript代码检查工具,可以用于JS代码的风格、规范、质量等等问题,提高在团队开发中代码的一致性和规范性。
ESLint一般集成在集成开发工具(IDE,如VSCode)中,并直接在编程时就作用在代码编辑器中。ESLint可以通过规则灵活的配置检查策略,并根据规则统一项目和团队的代码风格。ESLint还能自动修复一些常见的格式问题,并支持插件来检查特定程序框架的问题。图为ESLint工作时代码检查结果。
素材中的主要内容就到了这里,但撰写下来,笔者觉得好像还有一些值得一提的内容被遗漏了。后续有空的话,可能会总结一下,再增加一个拾遗的章节。