当前位置: 首页 > 后端技术 > Node.js

《Node.js设计模式》欢迎来到Node.js平台

时间:2023-04-03 10:47:34 Node.js

本系列文章为《Node.js Design Patterns Second Edition》的原文翻译和阅读笔记。在GitHub上连续更新,同步翻译版本链接。欢迎关注我的专栏,以下博文将在专栏同步:Encounter的掘金专栏知乎的编程思维segmentfault专栏前端小站欢迎来到Node.js。js生态系统的开发由官方组织维护。Node.js的特点。小模块以包的形式尽可能多地复用模块。原则上每个模块的容量越小越好。原则:“小即是美”---小而精“让每个程序做好一件事”---单一职责原则因此,一个Node.js应用是由多个包构建而成,包管理器(npm)管理使得他们相互依存,没有冲突。如果你设计一个Node.js模块,尽量做到以下三点:易于理解和使用,易于测试和维护,兼顾对客户端(浏览器)更友好的支持,以及,Don'tRepeatYourself(DRY)重用性原则。每个以接口形式提供的Node.js模块都是一个函数(类也以构造函数的形式呈现),我们只需要调用相关的API,而无需知道其他模块的实现。创建Node.js模块是为了使用它们,不仅是为了可扩展性,也是为了可维护性和可用性。简单实用“Simplicityistheultimatecomplexity”——达尔文遵循KISS(KeepItSimple,Stupid)原则,即优秀简洁的设计可以更有效地传递信息。设计一定要非常简单,无论是实现还是接口,更重要的是实现要比接口简单。简单是一个重要的设计原则。我们制作的软件设计简单,功能齐全,但并不完美:需要更少的实施工作,以更少的资源实现更快的交付,可扩展,更易于维护和理解,促进社区贡献,允许软件本身用于Node.js。js,因为它支持JavaScript,简单的函数、闭包、对象等特性可以替代复杂的面向对象的类语法。比如单例模式和装饰器模式,它们在面向对象语言中都需要非常复杂的实现,但是对于JavaScript来说就比较简单了。在ES5引入Node.js6和ES2015的新语法let和const关键字之前,只有函数和全局作用域。if(false){varx="hello";}console.log(x);//undefined现在使用let创建词法作用域,会报错UncaughtReferenceError:xisnotdefinedif(false){letx="hello";}console.log(x);在循环语句中使用let也会报错UncaughtReferenceError:iisnotdefined:for(leti=0;i<10;i++){//dosomethinghere}console.log(i);使用let和const关键字可以使代码更安全,如果不小心访问了另一个范围内的变量,也更容易发现错误。变量是用const关键字声明的,因此它们不会被意外更改。constx='这永远不会改变';x='...';这里会报错UncaughtTypeError:Assignmenttoconstantvariable。但是对于对象属性的改变,const好像没有办法:constx={};x.name='John';上面的代码没有抛出错误,但是如果直接改变对象,还是会抛出错误。常量x={};x=空;在实践中,我们使用const来导入模块以防止意外更改:constpath=require('path');letpath='./some/path';上面的代码会报错,提醒我们不小心更改了模块。如果需要创建不可变对象,单纯使用const是不够的,需要使用Object.freeze()或者deep-freeze我看了源码,其实很少,直接使用Object.freeze()模块.exports=函数递归deepFreeze(o){Object.freeze(o);Object.getOwnPropertyNames(o).forEach(function(prop){如果(o.hasOwnProperty(prop)&&o[prop]!==null&&(typeofo[prop]==="object"||typeofo[prop]==="函数")&&!Object.isFrozen(o[prop])){deepFreeze(o[prop]);}});返回o;};箭头函数箭头函数比较容易理解,尤其是我们定义回调的时候:constnumbers=[2,6,7,8,1];consteven=numbers.filter(function(x){returnx%2===0;});使用箭头函数语法,更简洁:constnumbers=[2,6,7,8,1];consteven=numbers.filter(x=>x%2===0);ifmorethanAreturnstatementuses=>{}constnumbers=[2,6,7,8,1];consteven=numbers.filter((x)=>{if(x%2===0){console.log(x+'iseven');returntrue;}});最重要的是,箭头函数绑定了它的词法作用域,它的this和父代码块的this是一样的。functionDelayedGreeter(name){this.name=name;}DelayedGreeter.prototype.greet=function(){setTimeout(functioncb(){console.log('Hello'+this.name);},500);}constgreeter=newDelayedGreeter('世界');问候语。问候语();//'Hello'要解决此问题,请使用箭头函数或绑定函数DelayedGreeter(name){this.name=name;}DelayedGreeter.prototype.greet=function(){setTimeout(functioncb(){console.log('Hello'+this.name);}.bind(this),500);}constgreeter=newDelayedGreeter('World');greeter.greeter();//'HelloWorld'或箭头函数,与父块作用域相同:functionDelayedGreeter(name){this.name=name;}DelayedGreeter.prototype.greet=function(){setTimeout(()=>console.log('Hello'+this.name),500);}constgreeter=newDelayedGreeter('World');greeter.greeter();//'HelloWorld'类语法糖类是原型继承的语法糖,对于那些来自传统面向对象语言(如Java和C#)的开发者来说比较熟悉,新的语法并没有改变JavaScript的运行特性,通过原型更方便易读。传统的构造函数+原型的写法:functionPerson(name,surname,age){this.name=name;this.surname=姓氏;this.age=age;}Person.prototype.getFullName=function(){returnthis.name+''+this.surname;}Person.older=function(person1,person2){return(person1.age>=person2.年龄)?person1:person2;}类语法更简洁、方便、易用理解:classPerson{constructor(name,surname,age){this.name=name;this.surname=姓氏;这个。年龄=年龄;}getFullName(){returnthis.name+''+this.surname;}staticolder(person1,person2){return(person1.age>=person2.age)?人1:人2;但是上面的实现是可以互换的,然而,对于类语法最有意义的是extends和super关键字。classPersonWithMiddlenameextendsPerson{constructor(name,middlename,surname,age){super(name,surname,age);this.middlename=中间名;}getFullName(){returnthis.name+''+this.middlename+''+this.surname;}}这个例子是真正的面向对象的方式,我们声明一个我们要继承的类,定义一个新的构造函数,并使用super关键字调用父构造函数,并重写getFullName方法,使其支持middlename。对象字面量的新语法允许默认值:constx=22;常数y=17;constobj={x,y};允许省略方法名module.exports={square(x){returnx*x;},cube(x){返回x*x*x;},};keyconstnamespace='-webkit-'的计算属性;conststyle={[namespace+'box-sizing']:'border-box',[namespace+'box-shadow']:'10px10px5px#888',};定义getter和setter的新方法constperson={name:'George',surname:'Boole',getfullname(){returnthis.name+''+this.surname;},setfullname(fullname){letparts=fullname.split('');this.name=parts[0];this.surname=parts[1];}};console.log(person.fullname);//"GeorgeBoole"console.log(person.fullname='AlanTuring');//“艾伦图灵”console.log(person.name);//这里是“Alan”,第二个console.log触发了set方法。TemplateString其他ES2015语法函数默认参数剩余参数语法扩展运算符解构赋值new.target代理反射Symbolreactor模式Reactor模式是Node.js异步编程的核心模块,其核心概念是:单线程,非阻塞I/O,通过下面的例子,你可以看到reactor模式在Node.js平台上的体现。I/O慢在计算机的基本操作中,输入和输出肯定是最慢的。访问内存的速度是纳秒级(10e-9s),而访问磁盘上的数据或访问网络上的数据则更慢,处于毫秒级(10e-3s)。内存的传输速度一般认为以GB/s计算,但磁盘或网络的访问速度较慢,一般为MB/s。虽然I/O操作对CPU的资源消耗并不大,但是从发送I/O请求到操作完成之间总是有时间延迟的。此外,还要考虑人为因素。通常,应用程序的输入是人为产生的,例如:按钮点击、即时通讯工具发送消息。因此,输入输出的速度不是网络和磁盘访问速度慢造成的,而是有很多因素造成的。阻塞I/O在具有阻塞I/O模型的进程中,I/O请求会阻塞后续代码块的执行。在I/O请求操作完成之前,线程浪费了无限长的时间。(它可能以毫秒为单位,但甚至可以以分钟为单位,就像用户按住某个键的情况一样)。以下示例是一个阻塞I/O模型。//直到请求完成,数据可用,线程阻塞data=socket.read();//请求完成,数据可用print(data);我们知道阻塞I/O的服务器模型不能在一个线程中处理多个连接,每个I/O都会阻塞其他连接的处理。为此,传统的Web服务器会为每个需要处理的并发连接启动一个新的进程或线程(或重用线程池中的进程)。这样,当一个线程在I/O操作上被阻塞时,它不会影响另一个线程的可用性,因为它们是在单独的线程中处理的。通过下图:通过上图我们可以看出,每个线程都空闲了一段时间,等待从关联的连接接收新的数据。如果各种I/O操作都会阻塞后续的请求。比如连接数据库,访问文件系统,我们现在可以很快知道一个线程需要等待很多时间才能得到I/O操作的结果。不幸的是,线程占用的CPU资源并不便宜。它需要消耗内存并导致CPU上下文切换。因此,长期占用CPU且大部分时间不被使用的线程在资源利用率方面不予考虑。高效的选择。非阻塞I/O除了阻塞I/O之外,大多数现代操作系统还支持另一种访问资源的机制,即非阻塞I/O。在这种机制下,后续的代码块不会等到I/O请求数据返回后才执行。如果当前时刻没有所有数据可用,函数将首先返回一个预定义的常量值(如undefined),表示当前时刻没有可用数据。例如,在Unix操作系统中,fcntl()函数对现有文件描述符进行操作,将其操作模式更改为非阻塞I/O(通过O_NONBLOCK状态字)。一旦资源处于非阻塞模式,如果读文件操作没有数据可读,或者写文件操作被阻塞,则读或写操作返回-1和EAGAIN错误。非阻塞I/O最基本的模式是轮询获取数据,也称为busy-wait模型。看下面的例子,通过非阻塞I/O和轮询机制来获取I/O的结果。资源=[socketA,socketB,pipeA];while(!resources.isEmpty()){for(i=0;i