Deno中有一段描述:“Aimstobebrowsercompatible”,可见Deno的目标是兼容浏览器。那么兼容浏览器在这里究竟意味着什么?我简单说说我的理解。首先,这里的兼容性绝对不是Deno直接运行在浏览器端。因为Deno是一个与浏览器处于同一级别的运行时。许多人仍然误解,兼容浏览器意味着Deno将提供“类似于Node.js的UMD编写”。首先要明确一点,这里的兼容并不仅仅指语法层面的兼容,并不是指兼容ES3和ES5。所以不要有这个误区,以为通过babel就可以兼容Node.js和Deno。在Deno'sRoadmap中,作者已经写到:Deno的目标是在任何方面都与Node的API兼容。Deno将导出一个单一的平面命名空间“deno”,在该命名空间下定义了所有核心功能。我们让用户包装Deno的命名空间以提供与Node.js的一些兼容性。这里的兼容性,我理解是兼容浏览器的API和生态。(等着被打脸)有个issuediscussion:structthebrowser-compatibleAPIs#82来讨论这个issue。issue中列出了一些希望兼容的浏览器API:HighlevelConsole?URL?File/FileList/FileReader/BlobXMLHttpRequest/FetchWebSocketURLSearchParams中级AudioContext/AudioBufferCanvasDiscussion还包括设置GPU对WebGL的支持。我们可以隐约猜到,Deno的目标之一就是让浏览器中的代码直接在Deno上运行。我的观点仍然是:Deno不是下一个Node.js。(再次等待被打脸)Deno是一个“V8上的安全TypeScript运行时”。如何理解基于V8的安全TypeScript运行时。浏览器可以被认为是一个安全的JavaScript运行时,所有的JavaScript代码都运行在一个沙箱(Sandbox)中。虽然你的电脑上安装了浏览器,但运行在浏览器中的JavaScript代码可能来自世界各地。也就是说,浏览器中运行的所有代码都是不可信的。是浏览器安全机制需要解决的问题。Node.js不像任何Web服务器一样,Node.js运行受信任的代码。在V8出现逃逸分析(EscapeAnalysis)安全漏洞之前,Chrome采取了应急措施,在下一版本中删除了逃逸分析相关的功能。相比之下,Node.js没有受到影响,因为Node.js运行的代码已经是可信的了。从这个角度来说,Deno和浏览器的定位很相似。V8团队的一个失误让整个互联网都变慢了,因为我们可以看到兼容的服务端生态和浏览器端生态是有区别的:浏览器运行的是不可信代码,服务端运行的是可信代码。从一个角度谈谈服务器生态。很多人还存在一个误区,认为上面提到的API是完全兼容Node.js的,比如Console、URL、XMLHttpRequest/Fetch等,很多API已经被Node.js实现了。开发Node.js的时候,还没有File、URL、Buffer这样的API浏览器。但是由于Node.js的定位是服务器端的运行平台,所以Node.js泛指其他的Web服务器或者Server编程语言。例如,文件系统(FileSystem)实现了一系列与POSIX(PortableOperatingSystemInterface,便携式操作系统接口)兼容的功能和功能。另外需要注意的是Node.js和浏览器的融合,比如在Node.js7中加入了URLAPI,现在主流的浏览器也都提供了这个API,因为Node.js也是使用WHATWGURL标准。WHATWG的全称是WebHypertextApplicationTechnologyWorkingGroup,Web超文本应用技术工作组。这是一个相当“重”的组织,当W3C决定放弃HTML并计划在未来专注于XHTML2.0时,WHATWG大力支持HTML并制定了下一代HTML的计划。最终WHATWG说服了W3C并发布了HTML5。今天Web的快速发展我们可以感谢这些组织的努力。那么现在有一个问题:如果Node.js、浏览器、Deno都使用这些标准,它们会变得一样吗?Deno会取代Node.js成为下一代Node.js吗?或者Deno会成为一个和Node.js一样但比Node.js更难用的平台,最终被边缘化或抛弃?惯于。因为性能和安全性不兼容,所以File/FileReader/Blob等API本质上是为浏览器端沙箱环境设计的(许多WHATWG标准是为浏览器设计的)。Node.js还没有提供相应的API,也不打算提供。毕竟我们在服务端环境中更需要的是文件系统,所以Node.js并没有拥抱WHATWG,而是选择了POSIX。说到这里,再深入一点,深入到底,看看为什么Node.js不用Blob和FileReader来读取文件。在浏览器端,文件通常来自网络,由url提供,或者由用户从表单中主动选择。无论哪种方式,浏览器都会完成对文件的读取,并将文件内容加载到内存缓冲区中。这时,JavaScript就可以通过Blob来操作文件了。但是,Node.js并没有实现这些API,而是在文件系统之上构建了Stream模块来实现这一点。看它的服务器端编程语言如Java和PHP也提供了Stream。如果我们使用Node.js作为Web服务器,那么我们将其与Nginx进行横向比较。如果用js开发静态文件服务器,那么Nginx可以轻松以十倍、一百倍的性能碾压Node.js。我们可以分析一下,为什么两者与底层的差异如此之大。这里介绍几个知识点:用户空间内核空间进程上下文中断上下文DMAZeroCopy为了安全起见,操作系统不允许用户代码直接操作硬件。为了保证操作系统内核的安全,将空间分为两部分,一部分是内核空间,一部分是用户空间。用户编写的代码运行在用户空间。当底层函数需要使用时,可以通过系统调用进入内核,比如文件读取。当用户进程通过系统调用从用户空间进入内核空间时,系统需要保存用户进程的上下文,当再次从内核空间返回用户空间时,系统恢复上下文。对于静态服务器,这一步大致是:调用read,将文件复制到内核缓冲区read函数返回,将文件从内核缓冲区复制到用户缓冲区write函数调用,从用户缓冲区复制文件向内核和套接字相关的缓冲区数据从套接字缓冲区复制到相关的协议引擎。可以看到整个过程中文件被复制了4次。Nginx底层使用sendfile,可以实现ZeroCopy(零拷贝)。整个过程变为:sendfile系统调用,将文件复制到内核缓冲区,从内核缓冲区复制到内核中的socket相关缓冲区,再从socket相关缓冲区复制到协议引擎。可以看出,在这个过程中,只有3个副本。并且没有用户空间和内核空间的切换,也不需要保存和恢复进程的上下文。其实上面还有优化的空间,因为内核中会发生buffer-to-buffer的copy。在Linux内核2.4版本之后,DMA模块将数据直接从内核缓冲区传递给协议引擎。虽然叫零拷贝,但数据还是从磁盘拷贝到内存中。从操作系统的角度来看,这是必要的。所谓零拷贝,就是内核中没有冗余数据,不需要在内核中拷贝数据。对于DMA模块,此过程完全独立于CPU。Nginx只需要2份数据拷贝,而Node.js需要4份。如果Node.js要使用零拷贝,也有办法,比如使用os模块的功能,或者直接使用C++扩展.从另一个角度来看,Node.js的性能损失不仅仅是4份副本和进程上下文的保存和恢复,还有数据和代码从C++到JavaScript的反复交叉。让我们回过头来讨论浏览器。对于浏览器来说,根本不需要这个特性。因为浏览器中的JavaScript代码不仅运行在内核空间,还运行在沙箱空间。所以与其猜测兼容的浏览器指的是什么,不如比较浏览器和服务器之间的差异:浏览器运行不受信任的代码,服务器运行受信任的代码浏览器遵循W3/WHATWG,服务器遵循POSIX浏览器关心API层的性能,服务器更关心操作系统层的性能。浏览器容量有限,但服务器容量不限。扫描二维码关注我公众号,每周推送原创前端内容
