速通《了不起的Node.js》(一)

这是《了不起的Node.js》简记的第一篇,主要摘要一些JavaScript的基础知识和必须要注意的Node.js概念。

速通《了不起的Node.js》(一)
理解一个工具最好的方式是首先搞明白为什么会有这个工具

这是《了不起的Node.js》简记的第一篇,主要摘要一些JavaScript的基础知识和必须要注意的Node.js概念。当然,一些过于基础的知识我就不写出来了,相信对这几篇文章感兴趣的同好都有了解。

Node.js简述

2019年年末,Ryan Dahl在柏林的一个JavaScript大会上宣布了一项名为Node.js的新技术。这项技术是关于在服务器端运行JavaScript的,所有人在听闻后的第一个想法都是:

如果成真的话,以后开发Web应用就只需要一种语言了。

毫无疑问,Node.js引起了轰动,我还记得前几年很火的一句话:“所有能用JS写的东西都会使用JS”。虽然十来年过去了,这个目标并没有实现,但是Node.js带来的变化依旧在持久、缓慢的改变着技术圈子,拓宽着前端程序员的发展边界。

Node.js改变了Web开发模式,被称为是一个将设计网络应用导向正确道路的特殊工具。它快速又高效,这样的特性得益于一种叫事件轮询(event loop)的技术,以及其构建与V8之上,V8是Google为Chrome Web浏览器设计的JS解释器和虚拟机,它运行JS非常快。

JavaScript概览

JavaScript是基于原型、面向对象、弱类型的动态脚本语言。它从函数式语言中借鉴了一些强大的特性,如闭包和高阶函数。从技术层面上说,JavaScript是根据ECMAScript语言标准来实现的。

JavaScript基础

类型

JavaScript类型可以简单分为基本数据类型复杂数据类型。

  • 基本数据类型包括number、string、boolean、undefined、null和Symbol
  • 复杂数据类型包括array、function和object

简单来说,访问JavaScript的基本类型,访问的是值;访问复杂类型,访问的是对值的引用。

特定的false值

在条件表达式中,一些特定的值会被判定为false:null、undefined、' '、0。

改变this

js中的this可以使用以下方法:

  • .call 接受参数列表
  • .apply 接受参数数组
  • .bind 接受参数列表,改变this的引用

bind是返回对应函数,便于稍后调用;其他两者理解调用

闭包

自执行函数是一种机制,通过这种机制声明和调用一个匿名函数,能够达到仅定义一个新作用域的作用。自执行函数对声明私有变量是很有用的,这样可以让私有变量不被其他代码访问。——原书
在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。——阮一峰

类相关

JavaScript在出现class语法糖前,是使用函数来定义和模仿类的。其中的继承特性则是通过基于原型的继承来模拟类继承。

// 继承
子类.prototype = new 父类();
// 上面的继承的缺陷是还需要初始化父类,可以参照下面的方法
子类.prototype.__proto__ = 父类.prototype
// 为子类定义属性和方法
子类.prototype.属性 = ""
// 重写父类函数
子类.prototype.方法 =  function (a) {
    父类.prototype.方法.call(this, a);
    // 子类特有逻辑
    // ...
}

Node.js的特别之处

阻塞与非阻塞IO

Node为JavaScript引入了一个复杂的概念:共享状态的并发。

Node采用一个长期运行的进程,相反,Apache会产生多个线程(每个请求一个线程),每次都会刷新状态。

事件轮询

Node会先注册事件,随后不停的询问内核这些事件是否已经分发。当时间分发时,对应的回调函数就会被触发,然后继续执行下去。如果没有事件触发,则继续执行其他代码,直到有新事件时,再去执行对应的回调函数。

总的来说:JavaScript中的事件循环机制是一种执行代码、处理事件和实现异步编程的方式。事件循环机制使得JavaScript能够处理异步操作,如setTimeout、Promise等,从而在单线程环境中实现非阻塞的行为。

事件循环机制主要包括以下几个部分:

  1. 调用栈(Call Stack):负责执行函数调用和执行上下文的管理。当一个函数被调用时,其执行上下文被推入调用栈;当函数执行完毕,其执行上下文从调用栈中弹出。
  2. 任务队列(Task Queue):负责存储待处理的任务。例如,当setTimeout到达指定延迟时间时,其回调函数就会被添加到任务队列中。
  3. 微任务队列(Microtask Queue):负责存储待处理的微任务,如Promise的then回调。微任务队列优先级高于任务队列,因此在执行任务队列中的任务之前,需要先清空微任务队列。
  4. 事件循环(Event Loop):事件循环是整个机制的核心。它的作用是检查调用栈是否为空,然后从任务队列或微任务队列中取出任务并执行。具体流程如下:

  a. 首先检查调用栈是否为空,如果不为空,则执行栈顶的函数。
  b. 如果调用栈为空,检查微任务队列是否有任务。如果有,执行并清空微任务队列中的所有任务。
  c. 如果调用栈为空且微任务队列也为空,检查任务队列是否有任务。如果有,将任务队列中的第一个任务放入调用栈并执行。
  d. 重复以上步骤。

这样,JavaScript可以在执行同步代码的同时,处理异步操作的回调,实现非阻塞行为。值得注意的是,虽然事件循环使得JavaScript可以执行异步操作,但过多的异步操作仍然可能导致性能问题。因此,在实际开发中,需要合理安排任务,以避免阻塞主线程。

Node中的JavaScript

global对象

我们知道,在浏览器中,全局对象指的是window对象。在window对象中定义的任何内容都可以被全局访问到。

Node中有两个类似但却各自代表着不同含义的对象:

  • global:和window一样,任何global对象上的属性都可以被全局访问到。
  • process:所有全局执行上下文中的内容都在process对象中。举例来说,在浏览器中,窗口的名字是window.name,类似的,在Node中进程的名字是process.title。

process.nextTick函数可以将一个函数的执行时间规划到下一个事件循环中。

事件

Node.js中的基础API之一就是EventEmitter。无论是在Node中还是是浏览器中,大量代码都依赖于所监听或者分发的事件。

浏览器中负责处理事件相关的DOMAPI主要包括addEventListener/removeEventListener以及dispatchEvent。

在Node中,你也可以通过Event EmitterAPI来随处进行事件的监听和分发。该API上定义了on/emit和removeListener方法,它以process.EventEmitter形式暴露出来。

事件是Node非阻塞设计的重要体现。Node通常不会直接返回数据(因为这样可能会在等待某个资源的时候发生线程阻塞),而是采用分发事件来传递数据的方式。在Node中,事件机制可以通知你尚未发生但即将要发生的事情。

buffer

除了模块之外,Node还补全了语言的另一个不足之处——对二级制数据的处理。

buffer是一个表示固定内存分配的全局对象(也就是说,要放到缓冲区中的字节数需要提前定下),它就好比是一个由八位字节元素组成的数组,可以有效地在JavaScript中表示二进制数据。

在Node.js中,绝大部分进行数据IO操作的API都用buffer来接受和返回数据。

注:文中还提到了base64,base64是一种仅用ASCII字符书写二进制数据的方式,换句话说,它可以让你用简单的英文字母来表示图片这样的复杂事物。

其他说明

一些已经过时或被废弃的东西比如存取器我并没有在文章里提到,留作之后单章特地说明。

END

本章入门篇到这里就结束了,这一篇主要是回顾了以下JavaScript的基础以及初探Node.js的部分奥妙,更深更好的内容接下来会慢慢揭晓,我会加快更新速度的!