聊聊JS语言是如何从前端移到后台的
发布于 6 年前 作者 fanxiong 14064 次浏览 最后一次编辑是 4 年前

在cnodejs上看得比较多的主要是安装、配置、框架集成等话题。很少谈及js语言本身的讨论,今天我来起个头,希望大家能积极参与,碰撞出一些火花。

出处

js的出处大家都知道,先是netscape给自己浏览器写的,后来标准化了,集成到ECMA标准集中,成为ECMAScript 262标准,后来伴随html5制定了265版本。 师出同门的还有flex 所用的action script, 和photoshop所用的脚本,最近还听说有些硬件也用上了js。所以,大家今后其实还可以到这些领域去施展一下拳脚。

js起什么作用?

js 首先是一种计算机语言,其最核心的功能是:

  1. 按逻辑执行程序
  2. 操控数据
  3. 输入/输出

其次才是它的语法特性(比如OOP)、执行特性(比如异步)、易用性(比如自动内存管理)。 就其核心功能而言,js跟其它任何一门语言都无差别。

js的哪些特性是专为浏览器设计的?

js在浏览器中的运行,运行环境上下文是由浏览器程序赋予的,即window对象。因此,浏览器中运行时,this即是指window。 js对浏览器的主要用途有二:

  1. 实现DOM标准。如构造及保存节点树、提供访问和操控方法、响应节点事件(mouseOver, click…)
  2. 与浏览器环境交互。如与窗口对象(Window)、Location、History、Navigator、Screen交互

上面第1点中的事件问题一定要注意。我也是直到今天才知道这是DOM标准的一部分,而javascript当初为了实现这个标准,将响应事件的的能力内化在了语言中。也就成了后来大家所津津乐道的一大特性。其实,任何一种需要操控UI的语言都需要具备这种能力,或者通过别的方法变相实现这种能力(比如线程)。而那些不需要处理UI的语言就没怎么考虑过这个问题,如php, ruby, python, java,这些语言对事件的响应几乎都是通过线程实现。但javascript是单线程的,无法启动新线程,因此事件就成了变相多线程方式。

移植到浏览器以外的环境执行需要具备什么条件?

我以前不知道浏览器有webkit和V8的时候,以为浏览器是一个整体性的,不可分割的软件。知道后,才发现原来浏览器内UI元素的管理和程序执行环境是分别由不同的引擎在支撑。 既然程序由专门的引擎(虚拟机)在支撑,那这个虚拟机就可以移出来。但是,脱离了浏览器的环境,移出来做什么呢? 当然是做些常规程序做的事情:像perl一样做系统管理脚本,或是像php/java那样做服务器,或者像C一样无所不能

  1. 但既然移出来到操作系统所提供的环境运行,那程序本身的存储方式很大可能性就是文件,因此,操作文件的功能也必不可少。既然可以操作程序源文件或执行文件,那普通文件也自然不在话下,很多文件可以用来做为数据源或存储结果。
  2. 进一步考虑文件操作。文件如何读入?读入后如何存储?无论是源代码这样的ASCII字符还是图片、视频、word文档这种非ASCII数据,要读进来,就需要具备Read()方法,存起来就需要Write()方法。而文件读写肯定不能一个字节一个字节的操作,所以,还需要有二进制数据的容器,Buffer就是干这个的。考虑到不同的CPU和操作系统,以及网络传输规范等情况,还需要对大端序和小端序的支持。
  3. 要做服务器,那就必须能够打开一个网络端口进行监听。因此,网络操作能力也需要具备,其实这也就是扩展一个跟操作系统间的接口而已。这在浏览器上也是可以的,但是没必要。
  4. 进一步考虑网络操作,网络不能向磁盘一样预先知道其确切的传输量。而是一个数据流,因此,需要一个流式的读写方法。
  5. js虽然是可响应事件的,但事件是为DOM设计的,比如绑定对象必须是DOM节点(非HTML),事件会向父节点传递(冒泡)。对于更为常规的编程,不需要这样的事件机制,并且需要脱离DOM规范的设计。因此,需要实现一个额外的事件管理器。这就是NodeJs中的Event Class。
  6. 有时需要调用那些成熟软件,就需要跟安装在操作系统上的其它程序交互,通过在操作系统提供的shell环境,发起shell指令调用其它程序。这就是NodeJs中OS模块的作用。

总结一下,需要移出来到操作系统的大环境中运行,需要以下特性:

  1. 文件操作
  2. 流式读写
  3. 二进制数据容器
  4. 网络操作
  5. Event Class
  6. shell交互

如何操控DOM以外的对象?

  • 网络:
  • 数据库:
  • photoshop图层:
  • 文件:
  • 二进制数据:
  • 声音:
  • 串口:
  • USB设备:

总结:

无论哪种,它们都只是一个IO对象,这些对象都是二进制数据,或者是二进制数据驱动的。所以只要你能控制这些二进制数据,并能读取或写入到对应的地址或设备里去,那就可以操控它。对js而言,涉及两个问题:

  1. 二进制数据的操纵需要语言本身支持(其实几乎所有语言都支持二进制数据的,但js本身恰恰并未支持,所以NodeJs一上来就先解决了这个问题);
  2. 地址和设备的读写控制由操作系统管理,所以要额外添加一些接口来跟操作系统沟通。这也是NodeJs需要一个标准库的原因。

补充问题

  1. 标准库没涉及到的对象怎么办?比如遥控飞机、控制门锁、蓝牙通信、各类传感器数据获取等。这些设备的控制在标准库中没有,但属于操作系统可控的范围,因此,只要补充对应的操作库即可。这就是NodeJs留有C++接口的原因。
  2. 那些操作系统管不了的事情怎么办?比如photoshop图层、画笔、flex中的元素、动画等等。这些属于应用层之上的对象,就需要应用自己创建一个执行环境,并赋予执行上下文,定义各类对象和执行逻辑。这样就可以了。

跟其他语言有什么异同?

从最底层的运行逻辑上讲,js跟其它语言没有什么差异,都是以逻辑结构做为执行条件。 从执行环境层面来说,各种语言的执行顺序略有不同,有的语言是以顺序运行,有的靠触发,但根本上都是顺序执行的,只是执行的范围有所不同。因此,其执行是严格可预测的,而不能预测的只是事件发生的时间。

对JS应用的展望

js可以在浏览器上执行,也可以在服务器上执行,这是大家已经看到的。但其实它做为一种常规语言,可以做几乎任何事:

  • 做系统管理脚本。
  • 做普通GUI程序的控制语言。这需要扩展绘图模块,并在应用程序中集成js引擎。实际上现在linux上有个项目(Seed)就已经实现了,其图形库是GTK,控制语言使用javascript。
  • 做Android, ios平台上的普通app。实际上现在的phoneGap(Cordova)就是将webkit+js引擎预先集成到app中,开发者仅需像开发web一样开发即可。但这种app需要每个都集成一个webkit+v8(或其它js引擎)。更好的方式是整个大环境就是一个webkit+v8,然后每个app就只有相应的html, css, js代码即可,firefoxOS既是如此。要做手机app,则需要js引擎补充实现各种设备访问接口。如拨号、3D传感器、摄像头、GPS、蓝牙等。
  • 在带操作系统的微型PC上运行。比如前面帖子中有人编译到了openWRT上;还有一个控制auduino的NodeJs模块(名叫Johnny-Five)
  • 其它IO密集,但处理时效性要求高的领域里
  • 但也有例外,js也有不能做的事情:
    1. 单片机程序,或者实现一个裸机操作系统。因为js必须要一个虚拟机来运行,而虚拟机本身还不具备操作系统的功能。如设备管理、内存管理(这里可不是指自动垃圾回收机制,而是真正的物理内存)、任务调度(当然,单一功能的设备上,单线程的js无需理会这个需求)。
    2. 科学计算。(这个不知道我说的对不对,求指正)

由于时间有限,暂时写这么多。有空再补!也欢迎大家留言交流!

19 回复

楼主看JS语言的角度高,我们这些晚辈只能不断的学习来开阔自己的眼界。持续关注该贴···

有如何实现异步编程的就好了

一知半解,但似乎很厉害的样子!

我的理解也不一定完全正确,若有错误,希望大家能够指正。这对我加深理解也是非常有帮助的!

实现语言基于环境很简单, 拿linux, 其本身已经提供了完备的文件、tcp http、磁盘… node api只需要依靠c语言建立操作linux api的内核, 并提供可编程接口即可。

那是操作层面的内容,这个今后另起文章再写。

@fanxiong 我想楼主误会了,是我看得一知半解,但感觉未来通过javascript可以做到很多事情。

关于事件那块不妥,事件的监听和响应只是一种设计模式:观察者模式,与线程并无关联。另外提到的几种语言中,我知道的,java和python都可以做客户端,那显然需要处理UI。

你说的是实现的层面,事件的实现方式都是通过你说的观察者模式实现的。 只是有些实现是在虚拟机层面,有些是在库层面。这个我需要进一步了解或者测试一下。 js的实现即是在虚拟机层面,所以在js这个语言特性中没有线程的概念。但java在运行过程中,其事件是通过EventQueue在库这一层面利用线程来实现事件监听和分发的。所以其事件机制完全依赖语言提供的线程。如果java是单线程语言,那就无法实现事件监听了。 谢谢你提出的意见!

@fanxiong 虚拟机具体怎么跑的跟你应用无太大关联,应用本身如果需要自定义的事件功能一样需要自己实现。另外java要实现也是一样,至于具体是单线程还是多线程那得看相应的场景,想同步执行就单线程。事件只是某些场景下优化代码的一种模式而已,跟线程无关。

@fanxiong 说不太容易说清,贴个简单的事件的监听和相应实现代码吧。

function Event() {
    this._events = {};
}
Event.prototype.on = function(eventName, handler) {
    var existingHandlers = this._events[eventName];
    if (!existingHandlers) {
        existingHandlers = [];
        this._events[eventName] = existingHandlers;
    }
    existingHandlers.push(handler);
}

Event.prototype.trigger = function(eventName) {
    var handlers = this._events[eventName];
    if (handlers) {
        handlers.forEach(function(handler) {
            handler();
        })
    }
}

@chenboxiang 事件的监控与执行,需要依赖一个无限循环来进行检查,所以如果没有线程的话,是无法实现这个功能的。 然而,语言层面的线程,其效率无论如何也是没有系统的高。虚拟机可以充分利用系统的内核机制,如select, poll, epoll等。

上面是谈线程,下面说事件:

事件本也不属于ECMAScript标准,事件是属于DOM标准定义的(我想应该由webkit来实现)。浏览器环境需要将DOM与js引擎整合起来,让js能统一执行(即放到同一线程中运行)。下面这段话摘自ECMA262 4.1节:

A web browser provides an ECMAScript host environment for client-side computation including, for instance, objects that represent windows, menus, pop-ups, dialog boxes, text areas, anchors, frames, history, cookies, and input/output. Further, the host environment provides a means to attach scripting code to events such as change of focus, page and image loading, unloading, error and abort, selection, form submission, and mouse actions.
Web浏览器提供了一个ECMAScript主机环境的客户端计算包括,例如,代表窗口,菜单,弹出式窗口,对话框,文本区域,锚,框架,历史记录,cookies,以及输入/输出的对象。此外,主机环境提供了脚本代码附加到如改变焦点,页面和图像加载,卸载,错误和中止,选择,表单提交,和鼠标操作事件的手段。

以上说明事件的注册和发射是由浏览器来实现的,但限于DOM标准所定义的事件。自定义事件就需要js引擎自己来实现了。

@tulayang 你举的这个例子有点模糊。你说的是shell吗? 不过后一句话说得很对!一种语言要实现任何功能,基本就只需做两件事:对下跟操作系统打交道,对上跟程序员打交道。

@fanxiong 哈哈,sorry,你说的事件是指DOM事件,我说的事件就是指事件机制本身,看来我们是鸡同鸭讲了。DOM事件跟宿主相关,那自然是由浏览器实现了,这块无异议。至于线程嘛,我给出的代码已经实现了基本的事件监听和触发的功能,改成别的语言版本也是一样的,跟线程没什么关系。

@fanxiong 不是shelll. 我想你对linux和shell理解有些错误. shell其实只是一个界面工具, 他调用的是linux的api,跟x window一样. 也就是说shell能调用的,我们通过开放的api一样可以调用,而且可以调用许多shell调用不到的内核api.

一句话,JS就是胶水,其他的东东是积木……

@tulayang 我基本赞同你的说法。 shell为操作员提供一个操作界面,以获取输入和返回输出; shell是一个执行环境,为各应用程序执行时传递环境变量和执行参数; 它本身也支持一种编程语言,如bash, zsh, sh,以组合各应用程序的执行过程; 但是,对于它是如何让应用程序调用内核API的问题,我还不清楚。我想应该不完全是通过shell来透明转换,那样势必影响linux系统的整体效率。从pstree的结果来看,很多应用程序父进程都是init。 你引出的这个问题启发了我对linux系统的思考,我得再去了解一下这方面的细节。

回到顶部