js开发-js开发外包-北京js开发外包

搜 索

Search:js开发


JavaScriptnodejs开发一个最简单的web服务器实例讲解

开发一个最简单的http服务开发一个最简单的http服务require 引入http模块创建http服务侦听端口实战案例实战案例vim server.jsvim ser开发一个最简单的http服务开发一个最简单的http服务require 引入http模块创建http服务侦听端口实战案例实战案例vim server.jsvim server.js// 使用JavaScript最严格的语法,防止出现一些问题'use strict' // 引入http模块var http = require("http");// 创建一个服务var app = http.createServer(function(req, res){ // 设置http头 res.writeHead(200, {"Content-Type": "text/plain"}); res.end("Hello World\n"); }).listen(8080, '0.0.0.0');// 使用JavaScript最严格的语法,防止出现一些问题'use strict' // 引入http模块var http = require("http");// 创建一个服务var app = http.createServer(function(req, res){ // 设置http头 res.writeHead(200, {"Content-Type": "text/plain"}); res.end("Hello World\n"); }).listen(8080, '0.0.0.0');forever,需要用npm进行安装npm install forever -g-g 意思是在全局安装这个命令forever,需要用npm进行安装npm install forever -g-g 意思是在全局安装这个命令# 启动程序node server.js# 后台启动程序方法nohub node server.js &forever start server.js # 启动程序node server.js# 后台启动程序方法nohub node server.js &forever start server.js 以上就是相关知识点内容,如果大家有任何补充内容可以联系小编,感谢大家对的支持。
标签:

js开发Node CLI构建微信小程序脚手架的示例js大全

本文介绍了 Node CLI 构建微信小程序脚手架的示例,分享给大家,具体如下:目的目的目的由于目前公司的 TOC 产品只要是微信小程序,而且随着业务的扩展, 会有的需求,创建的小程序,为了让本文介绍了 Node CLI 构建微信小程序脚手架的示例,分享给大家,具体如下:目的目的目的由于目前公司的 TOC 产品只要是微信小程序,而且随着业务的扩展, 会有的需求,创建的小程序,为了让团队避免每次开发前花费大量时间做比如工程化的一些配置,以及保持每个项目的一致性, 所以决定做一个 Node CLI 来创建微信小程序脚手架TOCNode CLI节省开发前期的大量时间,新项目可以很快开始业务开发保证项目统一性,有利于团队间的协作及工程化提升团队基建意识,从枯燥无味的业务开发中脱离出来,尝试新的东西,即使很基础很简单节省开发前期的大量时间,新项目可以很快开始业务开发保证项目统一性,有利于团队间的协作及工程化提升团队基建意识,从枯燥无味的业务开发中脱离出来,尝试新的东西,即使很基础很简单小程序选型小程序选型小程序选型小程序的第三方框架有很多, 我接触过的就有 taro / wepy / mpvue ,并且都有对应上线的项目。 在尝试这些框架的过程中,对比原生小程序,有一些感想想分享出来:tarowepympvue第三方框架语法贴近vue/react, 开发者可以根据自己的特点选择框架,学习成本相对较低原生框架在CSS预处理,多端复用,状态管理,自动构建这几块能力对比其他框架是欠缺的第三方框架额外的工具包会使打包体积变大,每次构建花费时间,同时性能不如原生第三方框架更新迭代很快,比如wepy@1.x/wepy@2, 导致旧项目的更新问题小程序的特性更新迭代速度较快, 第三方框架会相对滞后第三方框架语法贴近vue/react, 开发者可以根据自己的特点选择框架,学习成本相对较低原生框架在CSS预处理,多端复用,状态管理,自动构建这几块能力对比其他框架是欠缺的第三方框架额外的工具包会使打包体积变大,每次构建花费时间,同时性能不如原生第三方框架更新迭代很快,比如wepy@1.x/wepy@2, 导致旧项目的更新问题小程序的特性更新迭代速度较快, 第三方框架会相对滞后综上所述,由于我们目前没有多端复用的要求,并且有的小程序相对简单,需要很短时间内开发完成, 最重要的是,其他的框架我都试过了,原生的还没写过,一个字,新鲜感!!:smile: ,所以最终当仁不让地选择了原生小程序,不得不说,原生大法就是妙啊! :clap::clap::clap::clap:大体思路大体思路大体思路这个功能是相对很基础的,但是作为一个每天搬砖的业务仔来说,是个艰难的过程,也是个很好的学习机会。在做之前,想找找个社区比较:ox::beer:的学习(抄)一下,短暂考虑后,果断选择 taro-cli , 然后火速打开源码,一顿操作(完全蒙圈),学习了一点之后,才开始上手taro-cli这个具体的实现思路我想到两个git clone 远程仓库作为模版下载到本地,再根据用户输入配置修改 .json 文件(比如 appId )template 就放在当前目录中,直接`copy``, 之后的事等同git clone 远程仓库作为模版下载到本地,再根据用户输入配置修改 .json 文件(比如 appId )git clone.jsonappIdtemplate 就放在当前目录中,直接`copy``, 之后的事等同template权衡之后,打算使用 lerna 作为管理工具, 其中模版也作为一个 npm 包 ,用到的时候去 npm 下载,这么做我是为了方便管理,统一 push / publish , 就是为了省事 :smile:。lernanpm 包npmpushpublish最终思路:暴露命令 —> 用户交互输入配置 -> 集合配置下载模版 -> 根据配置修改 .json -> git init + 安装依赖.jsongit init开发 Node CLI开发 Node CLI开发 Node CLILerna 项目搭建Lerna 项目搭建知道 monorepo 的同学不需要我多说,其实就是把代码放在一个仓库里,结果包之间回想以来,发布繁琐等问题, 这里我们就用到了 lerna 这个神器帮助我们做包的统一管理monorepolerna// 创建项目mkdir modoo-mini-programcd modoo-mini-program// 初始化lerna initcd packagesmkdir modoo-scriptmkdir modoo-template-minimkdir modoo-mini // 安装 modoo-script 依赖用于测试,无其他实际用处lerna bootstrap // 安装依赖 + npm link// 创建项目mkdir modoo-mini-programcd modoo-mini-program// 初始化lerna initcd packagesmkdir modoo-scriptmkdir modoo-template-minimkdir modoo-mini // 安装 modoo-script 依赖用于测试,无其他实际用处lerna bootstrap // 安装依赖 + npm link安装依赖安装依赖安装依赖为了实现功能,我们需要安装一些依赖包commander 命令行工具,用于读取命令参数,作对应操作node-fs-extra 在 Node.js 的 fs 基础上增加了一些新的方法,更好用,还可以拷贝模板。chalk 可以用于控制终端输出字符串的样式, 调整颜色啥的inquirer 用户命令行交互,获取用户的交互配置数据,就像个提问板ora 实现加载中的状态是一个 Loading 加前面转起来的小圈圈,成功了是一个 Success 加前面一个小钩钩。log-symbols 日志彩色符号,用来显示√ 或 × 等的图标commander 命令行工具,用于读取命令参数,作对应操作node-fs-extra 在 Node.js 的 fs 基础上增加了一些新的方法,更好用,还可以拷贝模板。chalk 可以用于控制终端输出字符串的样式, 调整颜色啥的inquirer 用户命令行交互,获取用户的交互配置数据,就像个提问板ora 实现加载中的状态是一个 Loading 加前面转起来的小圈圈,成功了是一个 Success 加前面一个小钩钩。log-symbols 日志彩色符号,用来显示√ 或 × 等的图标获取命令获取命令获取命令首先第一步,要在用户全局安装之后,暴露出命令接口,需要在 packages.json 文件中加入如下内容packages.json"bin": {"modoo-script": "./bin/modoo-script.js"},"bin": {"modoo-script": "./bin/modoo-script.js"},之后在根目录下创建 bin 文件夹 + bin/modoo-script.jsbinbin/modoo-script.js#!/usr/bin/env nodeconst { program } = require("commander");program .version(require("../package").version) // modoo-script --version .usage(" [options]") // init 命令,床架项目 .command("init [projectName]", "Init a project with default templete") .parse(process.argv); // 解析命令参数#!/usr/bin/env nodeconst { program } = require("commander");program .version(require("../package").version) // modoo-script --version .usage(" [options]") // init 命令,床架项目 .command("init [projectName]", "Init a project with default templete") .parse(process.argv); // 解析命令参数然后需要注意的是, commander 支持 Git 风格的子命令处理,可以根据子命令自动引导到以特定格式命名的命令执行文件,文件名的格式是 [command]-[subcommand] ,例如:commanderGit[command]-[subcommand]modoo-script init => modoo-script-initmodoo-script build => modoo-script-buildmodoo-script init => modoo-script-initmodoo-script build => modoo-script-build所以为了实现 init 命令,可以直接在 bin 文件目录下添加 modoo-script-init.jsinitbinmodoo-script-init.js#!/usr/bin/env nodeconst { program } = require("commander");program .option("--name [name]", "项目名称") .option("--description [description]", "项目介绍") .option("--framework", "脚手架框架") .parse(process.argv);const args = program.args;// 获取命令参数const { name, description, framework } = program;const projectName = args[0] || name;......#!/usr/bin/env nodeconst { program } = require("commander");program .option("--name [name]", "项目名称") .option("--description [description]", "项目介绍") .option("--framework", "脚手架框架") .parse(process.argv);const args = program.args;// 获取命令参数const { name, description, framework } = program;const projectName = args[0] || name;......用户交互用户交互用户交互获取了命令参数后,根据参数转到用户交互界面,这里使用的是 inquirer 来处理命令行交互, 用法很简单inquirerconst inquirer = require('inquirer')if (typeof conf.description !== 'string') {prompts.push({type: 'input',name: 'description',message: '请输入项目介绍!'})}......inquirer.prompt(prompts).then(answers => {// 整合配置this.conf = Object.assign(this.conf, answers);})const inquirer = require('inquirer')if (typeof conf.description !== 'string') {prompts.push({type: 'input',name: 'description',message: '请输入项目介绍!'})}......inquirer.prompt(prompts).then(answers => {// 整合配置this.conf = Object.assign(this.conf, answers);})远程模块远程模块远程模块这里较为折腾,一开始说了,我把模版作为 npm包 ,具体查找,下载的过程如下npm包npm search 查找相应的模版 npm 包在用户选择框架后对应所需的包,获取它的详细信息,主要是 tarball用户输入完后,下载 tarball 到项目目录,并修改 .json 文件配置npm search 查找相应的模版 npm 包npm searchnpm 包在用户选择框架后对应所需的包,获取它的详细信息,主要是 tarballtarball用户输入完后,下载 tarball 到项目目录,并修改 .json 文件配置tarball.json部分代码如图所示// 一 npm search 查找相应的模版 npm 包const { execSync } = require("child_process");module.exports = () => { let list = []; try {const listJSON = execSync("npm search --json --registry http://registry.npmjs.org/ @modoo/modoo-template");list = JSON.parse(listJSON); } catch (error) {} return Promise.resolve(list);};// 一 npm search 查找相应的模版 npm 包const { execSync } = require("child_process");module.exports = () => { let list = []; try {const listJSON = execSync("npm search --json --registry http://registry.npmjs.org/ @modoo/modoo-template");list = JSON.parse(listJSON); } catch (error) {} return Promise.resolve(list);};// 二 返回 npm 数据const pkg = require("package-json");const chalk = require("chalk");const logSymbols = require("log-symbols");exports.getBoilerplateMeta = framework => { log(logSymbols.info,chalk.cyan(`您已选择 ${framework} 远程模版, 正在查询该模版...`) ); return pkg(framework, {fullMetadata: true }).then(metadata => {const {dist: { tarball },version,name,keywords} = metadata;log(logSymbols.success,chalk.green(`已为您找到 ${framework} 远程模版, 请输入配置信息`));return {tarball,version,keywords,name}; });};// 二 返回 npm 数据const pkg = require("package-json");const chalk = require("chalk");const logSymbols = require("log-symbols");exports.getBoilerplateMeta = framework => { log(logSymbols.info,chalk.cyan(`您已选择 ${framework} 远程模版, 正在查询该模版...`) ); return pkg(framework, {fullMetadata: true }).then(metadata => {const {dist: { tarball },version,name,keywords} = metadata;log(logSymbols.success,chalk.green(`已为您找到 ${framework} 远程模版, 请输入配置信息`));return {tarball,version,keywords,name}; });};// 三 下载 npm 包const got = require("got");const tar = require("tar");const ora = require("ora");const spinner = ora(chalk.cyan(`正在下载 ${framework} 远程模板仓库...`)).start();const stream = await got.stream(tarball);fs.mkdirSync(proPath);const tarOpts = { strip: 1, C: proPath};// 管道流传输下载文件到当前目录stream.pipe(tar.x(tarOpts)).on("close", () => {spinner.succeed(chalk.green("下载远程模块完成!"));......})// 三 下载 npm 包const got = require("got");const tar = require("tar");const ora = require("ora");const spinner = ora(chalk.cyan(`正在下载 ${framework} 远程模板仓库...`)).start();const stream = await got.stream(tarball);fs.mkdirSync(proPath);const tarOpts = { strip: 1, C: proPath};// 管道流传输下载文件到当前目录stream.pipe(tar.x(tarOpts)).on("close", () => {spinner.succeed(chalk.green("下载远程模块完成!"));......})// 四 遍历文件修改配置const fs = require("fs-extra");readFiles(proPath,{ignore: [".{pandora,git,idea,vscode,DS_Store}/**/*","{scripts,dist,node_modules}/**/*","**/*.{png,jpg,jpeg,gif,bmp,webp}"],gitignore: true},({ path, content }) => {fs.createWriteStream(path).end(template(content, inject));} ); // 递归读文件exports.readFiles = (dir, options, done) => { if (!fs.existsSync(dir)) {throw new Error(`The file ${dir} does not exist.`); } if (typeof options === "function") {done = options;options = {}; } options = Object.assign({},{cwd: dir,dot: true,absolute: true,onlyFiles: true},options ); const files = globby.sync("**/**", options); files.forEach(file => {done({path: file,content: fs.readFileSync(file, { encoding: "utf8" })}); });};// 配置替换exports.template = (content = "", inject) => { return content.replace(/@{([^}]+)}/gi, (m, key) => {return inject[key.trim()]; });};// 四 遍历文件修改配置const fs = require("fs-extra");readFiles(proPath,{ignore: [".{pandora,git,idea,vscode,DS_Store}/**/*","{scripts,dist,node_modules}/**/*","**/*.{png,jpg,jpeg,gif,bmp,webp}"],gitignore: true},({ path, content }) => {fs.createWriteStream(path).end(template(content, inject));} ); // 递归读文件exports.readFiles = (dir, options, done) => { if (!fs.existsSync(dir)) {throw new Error(`The file ${dir} does not exist.`); } if (typeof options === "function") {done = options;options = {}; } options = Object.assign({},{cwd: dir,dot: true,absolute: true,onlyFiles: true},options ); const files = globby.sync("**/**", options); files.forEach(file => {done({path: file,content: fs.readFileSync(file, { encoding: "utf8" })}); });};// 配置替换exports.template = (content = "", inject) => { return content.replace(/@{([^}]+)}/gi, (m, key) => {return inject[key.trim()]; });};下载依赖下载依赖下载依赖下载完毕并且修改完配置后, 默认执行 git init + 根据环境( yarn / npm / cnpm )安装依赖,这个就很简单了git inityarnnpmcnpmconst { exec } = require("child_process");const ora = require("ora");const chalk = require("chalk");// proPath 项目目录process.chdir(proPath);// git initconst gitInitSpinner = ora( `cd ${chalk.cyan.bold(projectName)}, 执行 ${chalk.cyan.bold("git init")}`).start();const gitInit = exec("git init");gitInit.on("close", code => { if (code === 0) {gitInitSpinner.color = "green";gitInitSpinner.succeed(gitInit.stdout.read()); } else {gitInitSpinner.color = "red";gitInitSpinner.fail(gitInit.stderr.read()); }});// installlet command = "";if (shouldUseYarn()) { command = "yarn";} else if (shouldUseCnpm()) { command = "cnpm install";} else { command = "npm install";}log(" ".padEnd(2, "\n"));const installSpinner = ora( `执行安装项目依赖 ${chalk.cyan.bold(command)}, 需要一会儿...`).start();exec(command, (error, stdout, stderr) => {if (error) {installSpinner.color = "red";installSpinner.fail(chalk.red("安装项目依赖失败,请自行重新安装!"));console.log(error);} else {installSpinner.color = "green";installSpinner.succeed("安装成功");log(`${stderr}${stdout}`);}});const { exec } = require("child_process");const ora = require("ora");const chalk = require("chalk");// proPath 项目目录process.chdir(proPath);// git initconst gitInitSpinner = ora( `cd ${chalk.cyan.bold(projectName)}, 执行 ${chalk.cyan.bold("git init")}`).start();const gitInit = exec("git init");gitInit.on("close", code => { if (code === 0) {gitInitSpinner.color = "green";gitInitSpinner.succeed(gitInit.stdout.read()); } else {gitInitSpinner.color = "red";gitInitSpinner.fail(gitInit.stderr.read()); }});// installlet command = "";if (shouldUseYarn()) { command = "yarn";} else if (shouldUseCnpm()) { command = "cnpm install";} else { command = "npm install";}log(" ".padEnd(2, "\n"));const installSpinner = ora( `执行安装项目依赖 ${chalk.cyan.bold(command)}, 需要一会儿...`).start();exec(command, (error, stdout, stderr) => {if (error) {installSpinner.color = "red";installSpinner.fail(chalk.red("安装项目依赖失败,请自行重新安装!"));console.log(error);} else {installSpinner.color = "green";installSpinner.succeed("安装成功");log(`${stderr}${stdout}`);}});主要的代码就是这些,其实只要知道思路,这些东西都很简单,虽然我写的有点 ️:chicken:,但是主要的逻辑还是能理清楚的一些的。更加详细的可以去:eyes:我发的源码,多谢指教。:pray::pray::pray:开发脚手架开发脚手架开发脚手架因为这是小程序的脚手架,它不像其他 web 框架一样需要很多 webpack 的配置,所以相对简单很多。webwebpack对于这个脚手架,相比于开发者工具创建的默认项目,我弥补了它的一些问题默认项目太过简单,只适合自己折腾,对于团队或者企业,缺乏相应的代码约定/规范,没有强制的约定会导致团队协作间的困难,提升code review的难度,所以我在原来的基础上加入了eslint,stylelint,prettier,commitlint等配置,以及git hook 在 pre-commit 时,执行校验,确保提交的代码尽量规范由于对 css 预处理的钟爱,另外加入了对 less 的支持,并且解决小程序背景图不支持本地图片的问题由于以上基本都是文件处理,所以选择 gulp 作为构建工具,这里是 v4, 与v3 写法上有一定的区别,不过关系不大默认项目太过简单,只适合自己折腾,对于团队或者企业,缺乏相应的代码约定/规范,没有强制的约定会导致团队协作间的困难,提升code review的难度,所以我在原来的基础上加入了eslint,stylelint,prettier,commitlint等配置,以及git hook 在 pre-commit 时,执行校验,确保提交的代码尽量规范由于对 css 预处理的钟爱,另外加入了对 less 的支持,并且解决小程序背景图不支持本地图片的问题由于以上基本都是文件处理,所以选择 gulp 作为构建工具,这里是 v4, 与v3 写法上有一定的区别,不过关系不大在根目录下创建 gulpfile.jsgulpfile.jsconst gulp = require('gulp');const chalk = require('chalk');const rename = require('gulp-rename');// 支持 lessgulp.task('less', () => { return gulp.src('./miniprogram/**/*.less').pipe(less()).pipe(postcss()) // 配置在 post.config.js.pipe(rename((path) => {path.extname = '.wxss';})).pipe(gulp.dest((file) => {return file.base; // 原目录}));});// 开发环境监听 lessif (env === 'development') { gulp.watch(['./miniprogram/**/*.less'], gulp.series('less')).on('change', (path) => {log(chalk.greenBright(`File ${path} was changed`)); });}// 一下代码注释掉了,依赖包下载太慢了,这主要负责图片的压缩const imagemin = require('gulp-imagemin');const cache = require('gulp-cache'); // 使用缓存gulp.task('miniimage', () => { return gulp.src('./miniprogram/**/*.{png,jpe?g,gif,svg}').pipe(cache(imagemin([imagemin.gifsicle({ interlaced: true }),imagemin.mozjpeg({ quality: 75, progressive: true }),imagemin.optipng({ optimizationLevel: 5 }),imagemin.svgo({plugins: [{ removeViewBox: true }, { cleanupIDs: false }],}),]))).pipe(gulp.dest((file) => {return file.base; // 原目录}));});const gulp = require('gulp');const chalk = require('chalk');const rename = require('gulp-rename');// 支持 lessgulp.task('less', () => { return gulp.src('./miniprogram/**/*.less').pipe(less()).pipe(postcss()) // 配置在 post.config.js.pipe(rename((path) => {path.extname = '.wxss';})).pipe(gulp.dest((file) => {return file.base; // 原目录}));});// 开发环境监听 lessif (env === 'development') { gulp.watch(['./miniprogram/**/*.less'], gulp.series('less')).on('change', (path) => {log(chalk.greenBright(`File ${path} was changed`)); });}// 一下代码注释掉了,依赖包下载太慢了,这主要负责图片的压缩const imagemin = require('gulp-imagemin');const cache = require('gulp-cache'); // 使用缓存gulp.task('miniimage', () => { return gulp.src('./miniprogram/**/*.{png,jpe?g,gif,svg}').pipe(cache(imagemin([imagemin.gifsicle({ interlaced: true }),imagemin.mozjpeg({ quality: 75, progressive: true }),imagemin.optipng({ optimizationLevel: 5 }),imagemin.svgo({plugins: [{ removeViewBox: true }, { cleanupIDs: false }],}),]))).pipe(gulp.dest((file) => {return file.base; // 原目录}));});其他的一些具体配置,可以看我的GitHub 仓库源码GitHub 仓库源码参考参考参考taro-clitaro-clipandora-clipandora-clilittle-bird-clilittle-bird-cli
标签:

JavaScript使用next.js开发网址缩短服务的方法

一、网址缩短服务的原理一、网址缩短服务的原理一、网址缩短服务的原理网址缩短服务,并不是压缩算法。而是把原网址存储在数据库中,用短的参数做key,届时取出原始url,并跳转。因此,短网址最适合用key/一、网址缩短服务的原理一、网址缩短服务的原理一、网址缩短服务的原理网址缩短服务,并不是压缩算法。而是把原网址存储在数据库中,用短的参数做key,届时取出原始url,并跳转。因此,短网址最适合用key/value数据库。那么,短网址的唯一参数,如何生成呢?其实用的就是10进制转62进制。function string10to62(number) { var chars = '0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ'.split(''),radix = chars.length,qutient = +number,arr = []; do {let mod = qutient % radix;qutient = (qutient - mod) / radix;arr.unshift(chars[mod]); } while (qutient); return arr.join('');}function string10to62(number) { var chars = '0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ'.split(''),radix = chars.length,qutient = +number,arr = []; do {let mod = qutient % radix;qutient = (qutient - mod) / radix;arr.unshift(chars[mod]); } while (qutient); return arr.join('');}相应地,每次生成后,都要让一个10进制自增,这样,每次就能根据唯一的10进制生成唯一的62进制。为什么用10进制转62进制呢?因为,10进制的100000000转为62进制,是6LAze。它很短。1000000006LAze二、next.js的动态路由如何获得参数二、next.js的动态路由如何获得参数二、next.js的动态路由如何获得参数建立文件:pages/[slug].js[slug]注意,该文件名由括号组成,代表是变量。这样,就可以通过http://ip/xxx 的形式,得到参数xxx。xxx关键代码:import { useRouter } from 'next/router';const router = useRouter();const { slug } = router.query;import { useRouter } from 'next/router';const router = useRouter();const { slug } = router.query;得到slug后,去数据库里找到并跳转即可:db.findOne({ slug: slug }, function (err, doc) { if(doc!=null){window.location.href=doc.url; }});db.findOne({ slug: slug }, function (err, doc) { if(doc!=null){window.location.href=doc.url; }});三、github及演示三、github及演示三、github及演示1、github地址:https://github.com/codetyphon ...https://github.com/codetyphon2、演示地址:https://nextshort.vercel.apphttps://nextshort.vercel.app总结总结总结
标签:

jsNodejs开发人员常见五个错误理解js大全

Nodejs 诞生于 2021 年,由于它使用了 JavaScript ,在这些年里获得了非常广泛的流行。它是一个用于编写服务器端应用程序的 JavaScript 运行时,但是 "它就是JavaScrNodejs 诞生于 2021 年,由于它使用了 JavaScript ,在这些年里获得了非常广泛的流行。它是一个用于编写服务器端应用程序的 JavaScript 运行时,但是 "它就是JavaScript" 这句话并不是 100% 正确的。JavaScript 是单线程的,它不是被设计用来实现要求可伸缩性的服务器端上运行的。借助 Google Chrome 的高性能 V8 JavaScript 引擎, libuv 的超酷异步 I/O 实现以及其他一些刺激性的补充, Nodejs 能够将客户端 JavaScript 引入服务器端,从而能够编写超快速的、能够处理成千上万的套接字连接的 Web JavaScript 服务器。NodeJS 是一个由大量有趣的基础模块构建的大型平台。但是,由于对 NodeJS 的这些内部组件的工作方式缺乏了解,因此许多 NodeJS 开发人员对 NodeJS 的行为做出了错误的理解,并开发了导致严重性能问题以及难以跟踪的错误的应用程序。在本文中,我将描述在许多 NodeJS 开发人员中很常见的五个错误理解。误解1 — EventEmitter 和事件循环相关误解1 — EventEmitter 和事件循环相关编写 NodeJS 应用程序时会大量使用 NodeJS EventEmitter ,但是人们误认为 EventEmitter 与 NodeJS Event Loop 有关,这是不正确的。NodeJS 事件循环是 NodeJS 的核心,它为 NodeJS 提供了异步的,非阻塞的 I/O 机制。它以特定顺序处理来自不同类型的异步事件的完成事件。相反, NodeJS Event Emitter 是一个核心的 NodeJS API ,它允许你将监听器函数附加到一个特定的事件,这个事件一旦触发就会被调用。这种行为看起来像是异步的,因为事件处理程序的调用时间通常比它最初作为事件处理程序注册的时间晚。EventEmitter 实例跟踪与 EventEmitter 实例本身内的事件相关联的所有事件和其实例本身。它不会在事件循环队列中调度任何事件。存储此信息的数据结构只是一个普通的老式 JavaScript 对象,其中对象属性是事件名称,属性的值是一个侦听器函数或侦听器函数数组。当在 EventEmitter 实例上调用 emit 函数时, emitter 将按顺序依次同步调所有注册到示例上的回调函数。看以下代码片段:const EventEmitter = require('events');const myEmitter = new EventEmitter();myEmitter.on('myevent', () => console.log('handler1: myevent was fired!'));myEmitter.on('myevent', () => console.log('handler2: myevent was fired!'));myEmitter.on('myevent', () => console.log('handler3: myevent was fired!'));myEmitter.emit('myevent');console.log('I am the last log line');const EventEmitter = require('events');const myEmitter = new EventEmitter();myEmitter.on('myevent', () => console.log('handler1: myevent was fired!'));myEmitter.on('myevent', () => console.log('handler2: myevent was fired!'));myEmitter.on('myevent', () => console.log('handler3: myevent was fired!'));myEmitter.emit('myevent');console.log('I am the last log line');以上代码段的输出为:handler1: myevent was fired!handler2: myevent was fired!handler3: myevent was fired!I am the last log linehandler1: myevent was fired!handler2: myevent was fired!handler3: myevent was fired!I am the last log line由于 event emitter 同步执行所有事件处理函数,因此 I am the last log line 在调用所有监听函数完成之后才会打印。误解2 - 所有接受回调的函数都是异步的误解2 - 所有接受回调的函数都是异步的函数是同步的还是异步的取决于函数在执行期间是否创建异步资源。根据这个定义,如果给你一个函数,你可以确定给定的函数是异步的:JavaScriptNodeJSsetTimeout,setInterval,setImmediate,process.nextTickNodeJS APIchild_process,fs,netPromiseAPIasync-await从 C++ 插件调用一个函数,该函数被编写为异步函数(例如 bcrypt )接受回调函数作为参数不会使函数异步。但是,通常异步函数的确接受回调作为最后一个参数(除非包装返回一个 Promise )。接受回调并将结果传递给回调的这种模式称为 Continuation Passing Style 。你仍然可以使用 Continuation Passing Style 编写同步功能。const sum = (a, b, callback) => { callback(a + b);};sum(1,2, (result) => { console.log(result);});const sum = (a, b, callback) => { callback(a + b);};sum(1,2, (result) => { console.log(result);});同步函数和异步函数在执行期间在如何使用堆栈方面有很大的不同。同步函数在执行的整个过程中都会占用堆栈,方法是禁止其他任何人占用堆栈直到return 为止。相反,异步函数调度一些异步任务并立即返回,因此将自身从堆栈中删除。一旦预定的异步任务完成,将调用提供的任何回调,并且该回调函数将再次占据该堆栈。此时,启动异步任务的函数将不再可用,因为它已经返回。考虑到以上定义,请尝试确定以下函数是异步还是同步。function writeToMyFile(data, callback) {if (!data) {callback(new Error('No data provided'));} else {fs.writeFile('myfile.txt', data, callback);}}function writeToMyFile(data, callback) {if (!data) {callback(new Error('No data provided'));} else {fs.writeFile('myfile.txt', data, callback);}}实际上,上述函数可以是同步的,也可以是异步的,具体取决于传递给的值 data 。如果 data 为 false, callback 则将立即调用,并出现错误。在此执行路径中,该功能是 100% 同步的,因为它不执行任何异步任务。如果 data 是 true ,它会将 data 写入 myfile.txt ,将调用回调完成的文件 I/O 操作之后。由于异步文件 I/O 操作,此执行路径是100%异步的。强烈建议不要以这种不一致的方式(在此功能同时执行同步和异步操作)编写函数,因为这会使应用程序的行为无法预测。幸运的是,这些不一致可以很容易地修复如下:function writeToMyFile(data, callback) {if (!data) {process.nextTick(() => callback(new Error('No data provided')));} else {fs.writeFile('myfile.txt', data, callback);}}function writeToMyFile(data, callback) {if (!data) {process.nextTick(() => callback(new Error('No data provided')));} else {fs.writeFile('myfile.txt', data, callback);}}process.nextTick 可以用来延迟 callback 函数的调用,从而使执行路径异步。或者,你可以使用 setImmediate 代替 process.nextTick ,这或多或少会产生相同的结果。但是,process.nextTick相对而言,回调具有更高的优先级,从而使其比 setImmediate 更快。误解3 - 所有占用大量CPU的功能都在阻止事件循环误解3 - 所有占用大量CPU的功能都在阻止事件循环众所周知, CPU 密集型操作会阻塞 Node.js 事件循环。尽管这句话在一定程度上是正确的,但并不是100%正确,因为有些 CPU 密集型函数不会阻塞事件循环。一般来说,加密操作和压缩操作是受 CPU 高度限制的。由于这个原因,某些加密函数和 zlib 函数的异步版本以在 libuv 线程池上执行计算的方式编写,这样它们就不会阻塞事件循环。其中一些功能是:crypto.pbkdf2()crypto.randomFill()crypto.randomBytes()所有 zlib 异步功能crypto.pbkdf2()crypto.randomFill()crypto.randomBytes()所有 zlib 异步功能但是,在撰写本文时,还无法使用纯 JavaScript 在 libuv 线程池上运行CPU密集型操作。但是,你可以编写自己的 C++ 插件,使你能够安排 libuv 线程池上的工作。有某些第三方库(例如 bcrypt ),它们执行CPU密集型操作并使用 C++ 插件来实现针对CPU绑定操作的异步API。误解4 - 所有异步操作都在线程池上执行误解4 - 所有异步操作都在线程池上执行现代操作系统具有内置的内核支持,可使用事件通知(例如, Linux 中的 epoll , macOS 中的 kqueue , Windows 中的 IOCP 等)以有效的方式促进网络 I/O 操作的本机异步。因此,不会在 libuv 线程池上执行网络 I/O 。但是,当涉及到文件 I/O 时,跨操作系统以及同一操作系统中的某些情况存在许多不一致之处。这使得为文件 I/O 实现通用的独立于平台的 API 极为困难。因此,在 libuv 线程池上执行文件系统操作以公开一致的异步 API 。dns.lookup() dns 模块中的函数是另一个利用 libuv 线程池的API。原因是,使用 dns.lookup() 功能将域名解析为IP地址是与平台有关的操作,并且此操作不是 100% 的网络 I/O 。误解5 - 不应使用NodeJS编写CPU密集型应用程序误解5 - 不应使用NodeJS编写CPU密集型应用程序这并不是真正的误解,而是关于 NodeJS 的一个众所周知的事实,现在由于在 Node v10.5.0 中引入 Worker Threads 而被淘汰了。尽管它是作为实验性功能引入的,但 worker_threads 自 Node v12 LTS 起,该模块现已稳定,因此适合在具有CPU密集型操作的生产应用程序中使用。每个 Node.js 工作线程将拥有其自己的v8运行时的副本,事件循环和 libuv 线程池。因此,执行阻塞CPU密集型操作的一个工作线程不会影响其他工作线程的事件循环,从而使它们可用于任何传入的工作。但是,在撰写本文时,IDE对 Worker Threads 的支持还不是最大。某些IDE不支持将调试器附加到在主线程以外的其他线程中运行的代码。但是,随着许多开发人员已经开始采用辅助线程进行CPU绑定的操作(例如视频编码等),开发支持将随着时间的推移而成熟。以上就是本文的全部内容,希望对大家的学习有所帮助。
标签: