写这篇文章的目的是想记录下NodeJs(后面简称node)与python的使用对比,希望看完之后大家对node跟python有个基本的认识。

(本文使用的node版本为v12.14.0,python为v3.8.3。)

简介

node 是一个基于 Chrome V8 引擎的 JavaScript(简称js) 运行时。简单的说就是通过v8引擎(由c++编写)解释并执行js代码,然后就能运行在服务器上。
python则是一门面向对象的解释型编程语言,目前最广泛的python解释器是CPython,就是通过C语言把python代码编译成字节码然后在虚拟机上运行。
node适用于前端代码的打包发布、服务端开发跟桌面端应用开发等。而python则适合科学计算、数据分析、自动化运维等场景。

数据结构

node的数组对应python的列表,都可以存放多种不同类型的数据。node对象则对应python的字典,都是使用key-value的形式。set结构也是类似的概念,都是没有重复元素的集合。node没有python中的元组类型,但是可以通过Object.freeze实现类似的效果。
node

let list = [1,2,3]list.push(4) // list [1,2,3,4]list.splice(2,1) // list [1,2,4]list.concat([5,6]) //  [1,2,4,5,6]list.slice(1) // [2,4]let [a,b,c] = list // a=1 b=2 c=4let tuple = [1,2]Object.freeze(tuple)tuple[0] = 3 // tuple [1,2]//一般可以使用for、forEach、for...of进行遍历list.forEach((item)=>{console.log(item)}) //1 2 4

python

list = [1,2,3]list.append(4) # list [1,2,3,4]del list[2] # list [1,2,4]list + [5,6] # [1,2,4,5,6]list[1:] # [2,4]a,b,c = list # a=1 b=2 c=4tuple = (1,2)tuple[0] = 3 # 报错 tuple (1,2)#通过for in遍历for item in list:    print(item) # 1 2 4

变量与作用域

node

node中全局变量在global对象上定义,可以在多个模块中访问。模块中声明变量可以通过var、let和const,其中let跟const在代码块(if、for等)内无法被外面的方法访问,而var可以。除了块级作用域外,还有函数作用域,函数作用域内的变量想被函数外访问则需要通过闭包。另外每个js文件就是一个模块,而模块最终会被一个匿名函数包裹(exports跟module就是匿名函数里的参数),所以模块里的变量也是局部变量。

// 代码块内的变量在代码块外面访问不了{    let a = 1    const PI = 3.14 //const用来定义常量}console.log(a, PI) // 无法访问代码块内的变量,会报错// 通过闭包访问函数内的变量function wrap(){    let n = 0    function inside(){        n = n + 1        return n    }    return inside}let count = wrap()count() // 1count() // 2
python

python没有像node这样的global对象,多个模块想要共享一个变量只能通过引入同一个模块的方式获取共享变量。python变量定义直接使用赋值的方式即可:value = 1。python中只有模块、类和函数会引入新的作用域,代码块不会引入新的作用域。python的闭包跟node有个明显的不同,假设函数inside在函数wrap内,内层函数inside想修改函数wrap的变量需要通过nonlocal关键字,而node可以直接使用函数wrap的变量。

# globalValue.py  存放全局变量的模块global_list = []#p1.pyfrom py2 import *import globalValueif __name__ == '__main__': # 表示如果主入口是p1.py文件    do_something()    print('py1.py')    print(globalVal.global_list)#p2.pyimport globalValdef do_something():    globalVal.global_list.append('a')    print('py2.py')    print(globalVal.global_list)#-------------------------------------def wrap():    n = 0    def inside():        nonlocal n # 需要使用nonlocal关键字        n = n + 1        return n    return insidecount = wrap()count() # 1count() # 2

模块

node

导入模块通过require方法。
导出模块可以使用exports跟module.exports,而require最终使用的是module.exports对象。需要注意的是exports跟module.exports虽然使用的是同一个内存地址,但如果对exports赋值了引用类型的值,那么就等于给exports使用了新的内存地址,使用require方法时就无法获取到exports的值。
通过文件夹中的package.json来表示包(package),package.json包含了包的描述信息、依赖项、运行命令等。

// a.js 跟 main.js在同一个目录exports = function name_a(){    console.log(1)}// main.jslet a = require('./a')console.log(a) //输出{} 如果改成module.exports则输出 [Function: name_a]

python

导入模块通过import或from package_name import module_name导入。模块导入还分为绝对导入跟相对导入,绝对导入要求使用导入模块的完整路径。相对导入则不需要。
导出模块并不需要特定语法,查看模块的变量可以通过dir方法。
python通过__init__.py文件来表示当前目录是package,当有外部import时,就执行里面的函数(pyton3.3后可以不用添加__init__.py文件)。__init__.py可以修改模块的引入方式。

#绝对导入from package.module import function#相对导入from . import module#查看当前模块含有的变量dir() #__init__.pyfrom package.module import function # 在导入的模块可以用function()。__all__ = ['module'] # 导入模块用 from package import * 后就能直接使用module里面的变量跟方法。

性能

在性能方面python比node差了不少,且不说CPython,同样有JIT的pypy也比node慢(用pypy3计算斐波那契数列,在n=40时大概比node慢了1秒多)。另外一般想提升多核CPU的利用率是通过开启多线程,但python的多线程由于CPython的GIL(全局解释器锁),一个CPU同一个时刻只能执行一个线程,所以在计算密集型任务通过开启多进程来优化。而node的worker_thread模块没有这个问题。

function fib(n) {    if (n === 0) {        return 0    } else if (n === 1) {        return 1    } else {        return fib(n - 1) + fib(n - 2)    }}console.time('fib')fib(40)console.timeEnd('fib')//python也是同样采用递归的方式,代码就省略了,大家可以在自己的电脑上试试。

总结

node跟python在各自领域都有不错的发展。对node来说前端打包构建、reactvue的同构应用这些场景很难被替代(采用js的好处)。而Deno如果在性能方面没有高过node很多的话也是不太可能取代node的,语言的生态是很重要的。python则由于有良好的开发效率、强大的库生态;并且随着近几年机器学习的热潮,python的语言热度一直保持在前几名。