-
方式三:使用nvm-windows安装首先请参考此处(cid:link_0)安装nvm-windows,然后执行如下命令安装对应的版本:nvm node_mirror https://mirrors.huaweicloud.com/nodejs/nvm npm_mirror https://mirrors.huaweicloud.com/npm-software/nvm install latestnvm use latest按照上面的方法设置后在CMD中执行 nvm install latest报错:Error retrieving "https://mirrors.huaweicloud.com/nodejs/latest/SHASUMS256.txt": HTTP Status 418各位大佬们,这个需要怎么解决呢?
-
launch.json文件launch.json 文件是 Visual Studio Code (VS Code) 中用于配置调试会话的文件。它定义了调试器如何启动和运行程序。以下是 launch.json 文件的详细配置说明,包括常见的属性及其用途。基本结构launch.json 文件通常位于 .vscode 目录下,具有以下基本结构:主要属性每个配置块代表一个调试配置,包含多个属性。以下是一些常见属性的说明:type: 调试器类型,如 python, cppdbg, node, java, 等。request: 调试请求类型,通常为 launch(启动)或 attach(附加)。name: 配置名称,用户可以在调试配置列表中看到。program: 要调试的程序路径或文件。args: 传递给程序的命令行参数,数组形式。cwd: 当前工作目录。env: 环境变量设置。sourceMaps: 是否启用源映射(通常用于 JavaScript 调试)。preLaunchTask: 调试前要执行的任务(通常用于编译等)。postDebugTask: 调试结束后要执行的任务。stopOnEntry: 是否在程序入口处停止。console: 控制台类型,如 integratedTerminal, externalTerminal, 或 internalConsole。justMyCode: 是否只调试用户代码(用于 Python)。pythonPath: Python 可执行文件的路径(用于 Python)。常见配置项1. Pythonprogram: 要调试的 Python 文件。pythonPath: Python 解释器路径。args: 传递给 Python 脚本的命令行参数。env: 环境变量。console: 控制台类型。2. C++program: 可执行文件路径。args: 命令行参数。stopAtEntry: 是否在程序入口处暂停。cwd: 当前工作目录。environment: 环境变量。externalConsole: 是否使用外部控制台。MIMode: 调试器模式(如 gdb, lldb)。miDebuggerPath: 调试器路径。3. Node.jsprogram: 要调试的 Node.js 文件。args: 命令行参数。runtimeExecutable: Node.js 可执行文件路径。runtimeArgs: 传递给 Node.js 的参数。env: 环境变量。sourceMaps: 是否启用源映射。outFiles: 编译输出文件路径。tasks.jsontasks.json 文件是 Visual Studio Code (VS Code) 中用于配置任务(Tasks)的文件。这些任务可以是编译代码、运行测试、构建项目等自动化任务。以下是 tasks.json 文件的详细配置说明,包括常见的属性及其用途。主要属性每个任务配置块代表一个任务,包含多个属性。以下是一些常见属性的说明:label: 任务的名称或标签,用于在任务列表中标识任务。type: 任务类型,例如 shell 或 process。shell 表示任务将在 shell 中运行,process 表示任务将作为独立的进程运行。command: 要执行的命令,可以是编译器、构建工具、脚本等。args: 传递给命令的参数,数组形式。group: 任务分组,可以设置为 build 或 test,用于标识构建任务或测试任务。presentation: 控制任务输出的呈现方式,例如是否显示在终端中,是否清除之前的输出等。problemMatcher: 配置错误和警告的匹配器,用于从任务输出中解析错误和警告。options: 任务执行的选项,例如环境变量、当前工作目录等。常见配置项label任务的标签名称,用于在 VS Code 任务列表中标识任务。1"label": "build"type任务类型,可以是 shell 或 process。shell 表示任务将在 shell 中运行,process 表示任务将作为独立的进程运行。1"type": "shell"command要执行的命令,例如编译器、脚本或构建工具。1"command": "g++"args传递给命令的参数,数组形式。123456"args": [ "-g", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}.out"]group任务分组,用于标识任务的类别,可以是 build 或 test。1234"group": { "kind": "build", "isDefault": true}presentation控制任务输出的呈现方式。123456"presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared"}problemMatcher用于解析任务输出中的错误和警告。VS Code 内置了多种匹配器,例如 $gcc, $eslint 等。1"problemMatcher": ["$gcc"]options任务执行的选项,例如环境变量、当前工作目录等。
-
最近在维护一个小后台项目,有段JS需要压缩上传到CDN存储服务器。由于之前压缩的JS文件都比较少,都是手动压缩的。这次需要压缩的文件比较多,所以用了批量压缩。特此记录一下,方便大家和自己以后再用到的时候备忘。回到顶部v准备工作安装nodejs首先在本地安装node.js和npm,一般npm集成于nodejs,即安装nodejs,同时也安装了npm。node.js下载地址,下载以后直接不停下一步就行,全部使用默认选项即可。下载完成后打开CMD,node -v检测是否安装成功,安装成功则会显示nodejs版本号。安装uglify插件在cmd命令行执行:npm install uglify-js -g回到顶部v开始压缩压缩的时候将下面的代码拷贝下来,然后生成bat文件,再运行bat文件(有些电脑可能需要windows管理员身份运行),然后依次输入当前的JS文件目录。再输入生成输出压缩后JS的目录即可。@ECHO OFF setlocal enabledelayedexpansion set source_path=%1 set target_dir=%2 IF [%1]==[] ( rem echo please input javascript file or directory set /p source_path=please input javascript file or directory: ) IF [%2]==[] ( rem echo please input output directory set /p target_dir=please input output directory: ) rem source path exists? FOR %%i IN (%source_path%) DO SET FileAttrib=%%~ai if "%FileAttrib%" equ "" ( rem not found file attribute, source path not exist echo source path ^(%source_path%^) doesn't exist exit /b 0 ) ELSE IF "%FileAttrib:~0,1%" equ "d" ( rem source path is directory and not end with \, append \ to source path IF %source_path:~-1% neq \ ( set source_path=%source_path%\ ) ) echo source path is %source_path% rem target path exists? FOR %%i IN (%target_dir%) DO SET fa=%%~ai IF [%fa%]==[] ( rem target path not exist, make it mkdir %target_dir% ) IF %target_dir:~-1% neq \ ( rem append \ to target path set target_dir=%target_dir%\ ) echo target path is %target_dir% IF [%FileAttrib:~0,1%]==[d] ( for /r %source_path% %%I in (*.js) do ( set file_name=%%~nI set parent=%%~dpI set target_parent=%target_dir%!parent:%source_path%=! if not exist !target_parent! mkdir !target_parent! cd !target_parent! if [!file_name:~-4!] neq [.min] ( set w= uglifyjs %%I -m -c -O ascii_only=true -o !target_parent!%%~nI.min.js rem uglify .js file echo uglifyjs from "%%I" to "!target_parent!%%~nI.min.js" start cmd /c "!w!" ) else ( rem copy min.js file echo copy file from "%%~dpnI.js" to "!target_parent!%%~nI.js" start cmd /c "copy %%~dpnI.js !target_parent!%%~nI.js" ) ) ) else ( for %%I in (%source_path%) do ( IF "%%~xI" EQU ".js" ( set file_name=%%~nI if [!file_name:~-4!] neq [.min] ( rem uglify .js file set val=%target_dir%%%~nI.min.js echo uglifyjs from "%%I" to "!val!" start cmd /c "uglifyjs %%I -m -c -O ascii_only=true -o !val!" ) else ( rem copy min.js file echo copy file from "%%I" to "%target_dir%%%~nI.js" start cmd /c "copy %%I %target_dir%%%~nI.js" ) ) ) ) echo done
-
起因很多人都知道,setTimeout是有最小延迟时间的,根据MDN 文档 setTimeout:实际延时比设定值更久的原因:最小延迟时间中所说:在浏览器中,setTimeout()/setInterval() 的每调用一次定时器的最小间隔是 4ms,这通常是由于函数嵌套导致(嵌套层级达到一定深度)。在HTML Standard规范中也有提到更具体的:Timers can be nested; after five such nested timers, however, the interval is forced to be at least four milliseconds.简单来说,5 层以上的定时器嵌套会导致至少 4ms 的延迟。用如下代码做个测试:12345678910111213141516171819202122232425let a = performance.now();setTimeout(() => { let b = performance.now(); console.log(b - a); setTimeout(() => { let c = performance.now(); console.log(c - b); setTimeout(() => { let d = performance.now(); console.log(d - c); setTimeout(() => { let e = performance.now(); console.log(e - d); setTimeout(() => { let f = performance.now(); console.log(f - e); setTimeout(() => { let g = performance.now(); console.log(g - f); }, 0); }, 0); }, 0); }, 0); }, 0);}, 0);在浏览器中的打印结果大概是这样的,和规范一致,第五次执行的时候延迟来到了 4ms 以上。探索假设就需要一个「立刻执行」的定时器呢?有什么办法绕过这个 4ms 的延迟吗,在 MDN 文档的角落里有一些线索:如果想在浏览器中实现 0ms 延时的定时器,可以参考这里所说的window.postMessage()。这篇文章里的作者给出了这样一段代码,用postMessage来实现真正 0 延迟的定时器:12345678910111213141516171819202122232425(function () { var timeouts = []; var messageName = 'zero-timeout-message'; // 保持 setTimeout 的形态,只接受单个函数的参数,延迟始终为 0。 function setZeroTimeout(fn) { timeouts.push(fn); window.postMessage(messageName, '*'); } function handleMessage(event) { if (event.source == window && event.data == messageName) { event.stopPropagation(); if (timeouts.length > 0) { var fn = timeouts.shift(); fn(); } } } window.addEventListener('message', handleMessage, true); // 把 API 添加到 window 对象上 window.setZeroTimeout = setZeroTimeout;})();由于postMessage的回调函数的执行时机和setTimeout类似,都属于宏任务,所以可以简单利用postMessage和addEventListener('message')的消息通知组合,来实现模拟定时器的功能。这样,执行时机类似,但是延迟更小的定时器就完成了。再利用上面的嵌套定时器的例子来跑一下测试:全部在 0.1 ~ 0.3 毫秒级别,而且不会随着嵌套层数的增多而增加延迟。测试从理论上来说,由于postMessage的实现没有被浏览器引擎限制速度,一定是比 setTimeout 要快的。设计一个实验方法,就是分别用postMessage版定时器和传统定时器做一个递归执行计数函数的操作,看看同样计数到 100 分别需要花多少时间。实验代码:1234567891011121314151617181920212223242526272829303132333435363738394041424344function runtest() { var output = document.getElementById('output'); var outputText = document.createTextNode(''); output.appendChild(outputText); function printOutput(line) { outputText.data += line + '\n'; } var i = 0; var startTime = Date.now(); // 通过递归 setZeroTimeout 达到 100 计数 // 达到 100 后切换成 setTimeout 来实验 function test1() { if (++i == 100) { var endTime = Date.now(); printOutput( '100 iterations of setZeroTimeout took ' + (endTime - startTime) + ' milliseconds.' ); i = 0; startTime = Date.now(); setTimeout(test2, 0); } else { setZeroTimeout(test1); } } setZeroTimeout(test1); // 通过递归 setTimeout 达到 100 计数 function test2() { if (++i == 100) { var endTime = Date.now(); printOutput( '100 iterations of setTimeout(0) took ' + (endTime - startTime) + ' milliseconds.' ); } else { setTimeout(test2, 0); } }}实验代码很简单,先通过setZeroTimeout也就是postMessage版本来递归计数到 100,然后切换成 setTimeout计数到 100。直接放结论,这个差距不固定,在 mac 上用无痕模式排除插件等因素的干扰后,以计数到 100 为例,大概有 80 ~ 100 倍的时间差距。在硬件更好的台式机上,甚至能到 200 倍以上。Performance 面板只是看冷冰冰的数字还不够过瘾,打开 Performance 面板,看看更直观的可视化界面中,postMessage版的定时器和setTimeout版的定时器是如何分布的。这张分布图非常直观的体现出了上面所说的所有现象,左边的postMessage版本的定时器分布非常密集,大概在 5ms 以内就执行完了所有的计数任务。而右边的setTimeout版本相比较下分布的就很稀疏了,而且通过上方的时间轴可以看出,前四次的执行间隔大概在 1ms 左右,到了第五次就拉开到 4ms 以上。作用也许有同学会问,有什么场景需要无延迟的定时器?其实在 React 的源码中,做时间切片的部分就用到了。12345678910111213141516171819const channel = new MessageChannel();const port = channel.port2;// 每次 port.postMessage() 调用就会添加一个宏任务// 该宏任务为调用 scheduler.scheduleTask 方法channel.port1.onmessage = scheduler.scheduleTask;const scheduler = { scheduleTask() { // 挑选一个任务并执行 const task = pickTask(); const continuousTask = task(); // 如果当前任务未完成,则在下个宏任务继续执行 if (continuousTask) { port.postMessage(null); } },};React 把任务切分成很多片段,这样就可以通过把任务交给postMessage的回调函数,来让浏览器主线程拿回控制权,进行一些更优先的渲染任务(比如用户输入)。为什么不用执行时机更靠前的微任务呢?关键的原因在于微任务会在渲染之前执行,这样就算浏览器有紧急的渲染任务,也得等微任务执行完才能渲染。总结可以了解如下几个知识点:setTimeout的 4ms 延迟历史原因,具体表现。如何通过postMessage实现一个真正 0 延迟的定时器。postMessage定时器在 React 时间切片中的运用。为什么时间切片需要用宏任务,而不是微任务。本文转载于:https://juejin.cn/post/7229520942668824633
-
Node.js 团队近期新发布20.6.0版本,在今年4月的版本上增强了安全性和API引入,同时也增加对新操作系统的支持以及对 TypeScript 兼容性的优化,虽然现在该版本处于测试阶段,你觉得Node.js在未来会呈现怎样的发展态势呢?Node.js新版本发布,你觉得哪一性能提升最实用?你觉得Node.js与JAVA相比,其优势在哪里?未来会超越JAVA吗?
-
最近项目中要用到node写接口然后连接公司现有的sql.server数据库,再把执行结果返回给前端(还是我),因为之前一直做前端这块,后端这方面不是很懂,花了很长的时间终于研究出来了(还是太菜了,走了很多弯路),所以写个博客,一是复习巩固,二是给其他有需要的小伙伴一个参考,尽量少走弯路,废话不多说,直接上代码 1.首先在自己的数据库新建一张表,用于接下来的测试 2.在自己的项目文件夹中建两个文件,config.js & mssql.js 3.封装数据库信息,config.js //config.js let app = { user: 'xxxxxx', //这里写你的数据库的用户名 password: 'xxxxxx',//这里写数据库的密码 server: 'localhost', database: 'testDB', // 数据库名字 port: 1433, //端口号,默认1433 options: { encrypt: false, //加密,设置为true时会连接失败 Failed to connect to localhost:1433 - self signed certificate enableArithAbort: false }, pool: { min: 0, max: 10, idleTimeoutMillis: 3000 } } module.exports = app 4.对sql语句的二次封装 , mssql.js //mssql.js /** *sqlserver Model **/ const mssql = require("mssql"); const conf = require("./config.js"); const pool = new mssql.ConnectionPool(conf) const poolConnect = pool.connect() pool.on('error', err => { console.log('error: ', err) }) /** * 自由查询 * @param sql sql语句,例如: 'select * from news where id = @id' * @param params 参数,用来解释sql中的@*,例如: { id: id } * @param callBack 回调函数 */ let querySql = async function (sql, params, callBack) { try { let ps = new mssql.PreparedStatement(await poolConnect); if (params != "") { for (let index in params) { if (typeof params[index] == "number") { ps.input(index, mssql.Int); } else if (typeof params[index] == "string") { ps.input(index, mssql.NVarChar); } } } ps.prepare(sql, function (err) { if (err) console.log(err); ps.execute(params, function (err, recordset) { callBack(err, recordset); ps.unprepare(function (err) { if (err) console.log(err); }); }); }); } catch (e) { console.log(e) } }; /** * 按条件和需求查询指定表 * @param tableName 数据库表名,例:'news' * @param topNumber 只查询前几个数据,可为空,为空表示查询所有 * @param whereSql 条件语句,例:'where id = @id' * @param params 参数,用来解释sql中的@*,例如: { id: id } * @param orderSql 排序语句,例:'order by created_date' * @param callBack 回调函数 */ let select = async function (tableName, topNumber, whereSql, params, orderSql, callBack) { try { let ps = new mssql.PreparedStatement(await poolConnect); let sql = "select * from " + tableName + " "; if (topNumber != "") { sql = "select top(" + topNumber + ") * from " + tableName + " "; } sql += whereSql + " "; if (params != "") { for (let index in params) { if (typeof params[index] == "number") { ps.input(index, mssql.Int); } else if (typeof params[index] == "string") { ps.input(index, mssql.NVarChar); } } } sql += orderSql; console.log(sql); ps.prepare(sql, function (err) { if (err) console.log(err); ps.execute(params, function (err, recordset) { callBack(err, recordset); ps.unprepare(function (err) { if (err) console.log(err); }); }); }); } catch (e) { console.log(e) } }; /** * 查询指定表的所有数据 * @param tableName 数据库表名 * @param callBack 回调函数 */ let selectAll = async function (tableName, callBack) { try { let ps = new mssql.PreparedStatement(await poolConnect); let sql = "select * from " + tableName + " "; ps.prepare(sql, function (err) { if (err) console.log(err); ps.execute("", function (err, recordset) { callBack(err, recordset); ps.unprepare(function (err) { if (err) console.log(err); }); }); }); } catch (e) { console.log(e) } }; /** * 添加字段到指定表 * @param addObj 需要添加的对象字段,例:{ name: 'name', age: 20 } * @param tableName 数据库表名 * @param callBack 回调函数 */ let add = async function (addObj, tableName, callBack) { try { let ps = new mssql.PreparedStatement(await poolConnect); let sql = "insert into " + tableName + "("; if (addObj != "") { for (let index in addObj) { if (typeof addObj[index] == "number") { ps.input(index, mssql.Int); } else if (typeof addObj[index] == "string") { ps.input(index, mssql.NVarChar); } sql += index + ","; } sql = sql.substring(0, sql.length - 1) + ") values("; for (let index in addObj) { if (typeof addObj[index] == "number") { sql += addObj[index] + ","; } else if (typeof addObj[index] == "string") { sql += "'" + addObj[index] + "'" + ","; } } } sql = sql.substring(0, sql.length - 1) + ") SELECT @@IDENTITY id"; // 加上SELECT @@IDENTITY id才会返回id ps.prepare(sql, function (err) { if (err) console.log(err); ps.execute(addObj, function (err, recordset) { callBack(err, recordset); ps.unprepare(function (err) { if (err) console.log(err); }); }); }); } catch (e) { console.log(e) } }; /** * 更新指定表的数据 * @param updateObj 需要更新的对象字段,例:{ name: 'name', age: 20 } * @param whereObj 需要更新的条件,例: { id: id } * @param tableName 数据库表名 * @param callBack 回调函数 */ let update = async function (updateObj, whereObj, tableName, callBack) { try { let ps = new mssql.PreparedStatement(await poolConnect); let sql = "update " + tableName + " set "; if (updateObj != "") { for (let index in updateObj) { if (typeof updateObj[index] == "number") { ps.input(index, mssql.Int); sql += index + "=" + updateObj[index] + ","; } else if (typeof updateObj[index] == "string") { ps.input(index, mssql.NVarChar); sql += index + "=" + "'" + updateObj[index] + "'" + ","; } } } sql = sql.substring(0, sql.length - 1) + " where "; if (whereObj != "") { for (let index in whereObj) { if (typeof whereObj[index] == "number") { ps.input(index, mssql.Int); sql += index + "=" + whereObj[index] + " and "; } else if (typeof whereObj[index] == "string") { ps.input(index, mssql.NVarChar); sql += index + "=" + "'" + whereObj[index] + "'" + " and "; } } } sql = sql.substring(0, sql.length - 5); ps.prepare(sql, function (err) { if (err) console.log(err); ps.execute(updateObj, function (err, recordset) { callBack(err, recordset); ps.unprepare(function (err) { if (err) console.log(err); }); }); }); } catch (e) { console.log(e) } }; /** * 删除指定表字段 * @param whereSql 要删除字段的条件语句,例:'where id = @id' * @param params 参数,用来解释sql中的@*,例如: { id: id } * @param tableName 数据库表名 * @param callBack 回调函数 */ let del = async function (whereSql, params, tableName, callBack) { try { let ps = new mssql.PreparedStatement(await poolConnect); let sql = "delete from " + tableName + " "; if (params != "") { for (let index in params) { if (typeof params[index] == "number") { ps.input(index, mssql.Int); } else if (typeof params[index] == "string") { ps.input(index, mssql.NVarChar); } } } sql += whereSql; ps.prepare(sql, function (err) { if (err) console.log(err); ps.execute(params, function (err, recordset) { callBack(err, recordset); ps.unprepare(function (err) { if (err) console.log(err); }); }); }); } catch (e) { console.log(e) } }; exports.config = conf; exports.del = del; exports.select = select; exports.update = update; exports.querySql = querySql; exports.selectAll = selectAll; exports.add = add; 5.接口代码,api/user.js //user.js const express = require('express'); const db = require('../utils/mssql.js'); const moment = require('moment'); const router = express.Router(); /* GET home page. */ router.get('/info', function (req, res, next) { db.selectAll('userInfo', function (err, result) {//查询所有userInfo表的数据 res.send(result.recordset) // res.render('userInfo', { results: result.recordset, moment: moment }); }); }); router.post('/delete', function (req, res, next) {//删除一条id对应的userInfo表的数据 console.log(req.body, 77); const { UserId } = req.body const id = UserId db.del("where id = @id", { id: id }, "userInfo", function (err, result) { console.log(result, 66); res.send('ok') }); }); router.post('/update/:id', function (req, res, next) {//更新一条对应id的userInfo表的数据 var id = req.params.id; var content = req.body.content; db.update({ content: content }, { id: id }, "userInfo", function (err, result) { res.redirect('back'); }); }); module.exports = router; 6.启动文件,server.js //1.导入模块 const express = require('express') //2.创建服务器 let server = express() server.use(express.urlencoded()) //中间件要写在启动文件里面 const cors = require('cors') server.use(cors()) const user = require('./api/user.js') server.use('/', user) //3.开启服务器 server.listen(8002, () => { console.log('服务器已启动,在端口号8002') }) 7.启动服务器 8.用postman测试接口 数据成功返回~~~ ———————————————— 版权声明:本文为CSDN博主「快乐的小鱼儿12」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_67296095/article/details/126326217
-
node版本管理器:nvm 和 n区别在 node 的版本管理工具中,nvm 自然声名远扬,然而我们也不能忘了来自 TJ 的 n。这两种,是目前最主流的方案。关于这两个工具如何安装和使用,这里不再多说,请见它们各自的主页:creationix/nvmTJ接下来我们着重关注一下 nvm 和 n 的运作机制和特性。nn 依赖于 noden 是一个需要全局安装的 npm package。npm install -g n这意味着,我们在使用 n 管理 node 版本前,首先需要一个 node 环境。我们或者用 Homebrew 来安装一个 node,或者从官网下载 pkg 来安装,总之我们得先自己装一个 node——n本身是没法给你装的。然后我们可以使用 n来安装不同版本的node。在安装的时候,n会先将指定版本的node存储下来,然后将其复制到我们熟知的路径/usr/local/bin,非常简单明了。当然由于n会操作到非用户目录,所以需要加 sudo 来执行命令。所以这样看来,n 在其实现上是一个非常易理解的方案。but ,n会出现全局模块无法更新的问题nvm我们再来看 nvm。不同于 n,nvm 不是一个 npm package,而是一个独立软件包。这意味着我们需要单独使用它的安装逻辑:curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.29.0/install.sh | bash或者使用 Homebrew 来安装。安装完后,还需要修改一下 shell 配置(~/.zshrc or whatever),具体参见官方文档。然后我们可以使用 nvm 来安装不同版本的 node。在安装的时候,nvm 将不同的 node 版本存储到 ~/.nvm// 下,然后修改 $PATH,将指定版本的node路径加入,这样我们调用的 node 命令即是使用指定版本的 node。nvm 显然比 n 要复杂一些,但是另一方面,由于它是一个独立软件包,因此它和 node 之间的关系看上去更合乎逻辑:nvm 不依赖 node 环境,是 node 依赖 nvm;而不像 n 那样产生类似循环依赖的问题。如何选择?这样看下来,nvm 和 n 的差异还是比较大的,具体体现在:安装简易度。nvm 安装起来显然是要麻烦不少;n 这种安装方式更符合 node 的惯性思维。见仁见智吧。系统支持。注意, nvm 不支持 Windows。对全局模块的管理。n 对全局模块毫无作为,因此有可能在切换了 node 版本后发生全局模块执行出错的问题;nvm 的全局模块存在于各自版本的沙箱中,切换版本后需要重新安装,不同版本间也不存在任何冲突。关于 node 路径。n 是万年不变的 /usr/local/bin; nvm 需要手动指定路径。所以,如何选择?真心见仁见智了,不过这里可以给出大体的建议:1. 如果你使用 Windows,那没得选了,使用 n,或者换一台 Mac。2. 如果你会频繁切换 node 版本(比如本地经常测试最新版的特性,同时又要兼顾代码在生产环境的兼容性),那么从全局模块兼容性的角度考虑,只能使用 nvm。3. 如果你是一个轻量级的用户,不需要担心兼容性的问题,更关心 node 安装和使用上的体验,那么选择 n。4. 你如果要问,博主最终选用了谁?我会说,我选择了更流行的那一个。
-
在Node.JS中使用多进程非常简单,合理使用多进程,可以解放硬件的能力,让软件的运行效率得到肉眼可见的提升。本文详细讲解了Node.js使用多进程提高任务执行效率的方法,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下最近我有个学员写了个 Node.js 脚本程序,定时从某个服务器下载文件,并向另一个云服务商上传文件。但是每次只能先下载一个文件,再去上传一个文件。效率比较低。于是他向我请教,怎么样才能提高效率?我告诉他应该用 Node 的多进程技术。什么是 Node 多进程?Node 是在单个线程中运行,我们虽然没办法开启额外的线程,但是可以开启进程集群。这样可以让下载任务和上传任务同时进行。使用多进程进行初步代码优化简单看了一下学员的代码,大概是这样:123456789101112const dl = require('./download.js')const ul = require('./upload.js')const source = require('./source.js')async function runTask() { const { originUrl, targetUrl } = source.getNext() const { data } = await dl(originUrl) await ul(targetUrl, data) runTask()}runTask()这个代码逻辑上是没问题的,但是它只能在 1 个 CPU 核心中运行。我们完全可以使用 Node.js 的多进程来利用 CPU 的多核心来增加这个程序的吞吐量。怎么改造呢?也非常简单。1234567891011121314151617181920212223242526const os = require('os')const cluster = require('cluster')const dl = require('./download.js')const ul = require('./upload.js')const source = require('./source.js')function run() { if(cluster.isMaster) { const numCPUs = os.cpus().length; for(let idx = 0; idx < numCPUs; idx++) { cluster.fork(); } } else { runTask() }}async function runTask() { const { originUrl, targetUrl } = source.getNext() const { data } = await dl(originUrl) await ul(targetUrl, data) runTask() }}run()在上面的代码中,我添加了 os 和 cluster 模块。os 模块可以告诉我们运行环境的 CPU 信息,我们可以通过它来做为创建进程数量的限制条件。然后通过 cluster.isMaster 来判断是否是主进程,因为只有主进程才拥有 fork 的能力。worker和master通信其实上面的代码还可以继续做更深层次的优化,仔细分析一下,下载速度和上传速度其实是不一致的。通常来说,下载速度会很慢,但上传速度会很快。我们可以让其他进程去下载文件,当下载成功之后,让主进程去上传文件。Node 中的多进程之间不会共享内存,所以我们可以通过消息传递的方式,让下载进程通知主进程去上传文件。12345678910111213141516171819202122232425262728const os = require('os')const cluster = require('cluster')const dl = require('./download.js')const ul = require('./upload.js')const source = require('./source.js')function run() { if(cluster.isMaster) { const numCPUs = os.cpus().length; for(let idx = 0; idx < numCPUs; idx++) { const worker = cluster.fork(); worker.on('message', ({ targetUrl, data }) => { ul(targetUrl, data) }) } } else { runTask() }}async function runTask() { const { originUrl, targetUrl } = source.getNext() const { data } = await dl(originUrl) process.send({ targetUrl, data }) runTask()}run()可以在主进程中通过 worker.on('message', (msg)=>{}) 的方式来监听子进程发送的消息。在子进程中通过 process.send 来向主进程发送消息。总结在 NodeJS 中使用多进程非常简单,合理使用多进程,可以解放硬件的能力,让软件的运行效率得到肉眼可见的提升。转载自https://www.jb51.net/article/263647.htm
-
Node.js前端工程在arm机器安装依赖时出现很多前端组件找不到arm版的问题,很多依赖不兼容arm,请问是否有arm版本的前端组件库?
-
今天新启动一个项目,在 npm install 安装依赖项时出现报错,所以下面这篇文章主要给大家介绍了关于npm install安装报错:gyp info it worked if it ends with ok的解决方法,需要的朋友可以参考下目录• 1.可能原因1:node和node-sass版本冲突, • 2.node.js卸载与安装 • 3.node-sass卸载安装, • 4.安装好后对应版本的node和node-sass后启动 • 总结 前端vue新项目 npm install安装报错 gyp info it worked if it ends with ok1.可能原因1:node和node-sass版本冲突,对应的node和node-sass的版本如下2.node.js卸载与安装之前有安装过弄过node.js的如果node.js版本过高需要重新安装低版本node.js,在控制面板卸载node.js,再在node官网 下载对应的msi包安装到之前的安装过node.js的文件夹下。低版本的node.js 只需直接下载高版本的msi包安装无需卸载之前的低版本node.js,这样不需要再去配置环境变量啥的,3.node-sass卸载安装,node-sass卸载:1npm uninstall node-sassnode-sass安装可以指定版本:npm install node-sass 布置的版本直接安装npm install node-sass@4.14.0 指定4.14版本的,如果安装报错试试换下淘宝镜像,一个好的解决方案在是直接在项目根目录下创建一个 .npmrc文件指定淘宝镜像文件内容:sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ phantomjs_cdnurl=https://npm.taobao.org/mirrors/phantomjs/ electron_mirror=https://npm.taobao.org/mirrors/electron/ registry=https://registry.npm.taobao.org4.安装好后对应版本的node和node-sass后启动再npm install , npm run dev 没有报错啦总结到此这篇关于npm install安装报错:gyp info it worked if it ends with ok解决的文章就介绍到这了转载自https://www.jb51.net/article/255441.htm
-
*学习敏捷上云开发要点,提升就业机会**体验Java,Node.JS,C#真实应用上云开发案例**收获华为无线耳机、拍立得、机械键盘、京东卡等丰富好礼* △适合人群:对敏捷DevOps开发有转型需求,渴望提升云上研发能力的开发者△活动时间:2022.5.16-2022.6.30△活动参与方式: 报名活动:点此链接,填写报名表即完成报名,点此了解活动详细规则;加入学习社群:完成本报名后请务必扫码进入学习群,专家全程跟踪辅导,伙伴比肩共同进步; 完成学习任务,赢取积分奖励本帖为Node.js项目上云实践“Node.js项目上云全流程:搭建NodeClub社区系统”截图回复帖,如实验顺利完成,只提交部署成功的截图即可。请按实验手册示例进行截图,截图右上角露出华为云账号,将截图回复至本帖。如无法完成实验,做到哪一步提交哪一步的截图即可,将根据实验完成进度奖励积分。回复要求:华为云账号+截图加油~~
-
原文链接:https://blog.csdn.net/mengyidan/article/details/122947823在昨日的情人节当天,OpenJS基金会为保证JavaScript开源社区的持续增长,正式宣布收购Node.js徽标和商标的所有权。即日起,为Node.js提供长期赞助与管理的Joyent公司将把Node.js商标的所有权与管理权转让给OpenJS基金会,后者将负责Node.js的商标管理与维护工作。图源:OpenJS基金会官网Node.js是基于Chrome 的V8 JavaScript 引擎构建的JavaScript 运行时,由美国软件工程师瑞安·达尔开发。起初,Node.js的开发和维护工作是由达尔主持,其当时所在的公司Joyen提供赞助,随后达尔离开,Node.js社区也经历了一些分支与合并事件。目前Node.js有OpenJS基金会持有和维护,当下的最新版本为Node.js 17.5.0,其中LTS版本为Node.js 16.14.0。Node.js官网:https://nodejs.org/en/其实在6年前,OpenJS基金会已经获得了Node.js商标的免费永久许可使用权,包括那个六边形图形,商标转让并不会对贡献者产生任何影响。就开源产品而言,商标不仅可以保护开源项目,还能识别代码的具体来源。据OpenJS官博介绍,他们的目标是确保OpenJS商标政策在法律上尽可能的灵活和容易理解,同时保证Node.js以及OpenJS其它产品的服务和质量。Joyent总裁兼首席运营官Sung Whan Moon也表示,Joyent一直相信开源能给开发者和企业创造更多机会,很高兴能看到Node.js为这么多开发者、企业的经济增长带来支撑。随着Node.js进入第2个十年,把它放在一个中立的、并且在需要时能够制定商标限制,充分保证了项目的完整性,OpenJS基金会会是Node.js商标所有权的最佳容身之处。Node.js作为一个健康社区,得到了许多公司的广泛支持,这些公司扩大了这个项目的规模和商业应用,包括彭博社、美国国家航空航天局、Netflix等等。Node.js刚刚交付了Node.js 17,并将Node.js 16移至长期支持(LTS)。Joyent商业集团负责人和OpenJS董事会白金董事Sean Johnson提到,Node.js的采用前景将比以往任何时候都要光明。IBM开放技术和开发者倡导副总裁、OpenJS基金会董事会主席Todd Moore表示,"就推动JavaScript技术的广泛采用以及关键Node.js解决方案和相关技术的持续发展的使命,OpenJS基金会已经做好了准备。"Node.js的维护者们也正在为Node.js未来十年的战略方向进行合作。关于OpenJS基金会OpenJS基金会致力于支持JavaScript生态系统和网络技术的健康发展,提供一个中立的组织来主持和维持项目,并为整个社区的利益合作资助活动。OpenJS基金会由38个开源JavaScript项目组成,包括Appium、Dojo、jQuery、Node.js和webpack,并得到30个企业和最终用户成员的支持,包括GoDaddy、Google、IBM、Intel、Joyent和Microsoft。这些成员认识到JavaScript生态系统的相互关联性,以及为代表重大共享价值的项目提供一个中央家园的重要性。关于Linux基金会Linux基金会成立于2000年,由1000多个成员支持,是世界上领先的开源软件、开放标准和开放硬件的合作之家。Linux基金会的项目,如Linux、Kubernetes、Node.js等,被认为对世界上最重要的基础设施的发展至关重要。其开发方法利用既定的最佳实践,并解决贡献者、用户和解决方案提供者的需求,以创造可持续的开放合作模式。
-
>摘要: 网络是通信互联的基础,Node.js提供了net、http、dgram等模块,分别用来实现TCP、HTTP、UDP的通信,本文主要对使用Node.js的TCP通信部份进行实践记录。本文分享自华为云社区[《一文搞懂如何使用Node.js进行TCP网络通信》](https://bbs.huaweicloud.com/blogs/300544?utm_source=zhihu&utm_medium=bbs-ex&utm_campaign=other&utm_content=content),作者:lwq1228 。 # 1、构建TCP服务器 ## 1.1、使用Node.js创建TCP服务器 为了使用Node.js创建TCP服务器,首先要调用require(‘net’)来加载net模块,然后调用net模块的createServer方法就可以轻松地创建一个TCP服务器,语法格式如下: net.createServer([options][, connectionListener]) options是一个对象参数值,有两个布尔类型的属性allowHalfOpen和pauseOnConnect。这两个属性默认都是false; connectionListener是一个当客户端与服务端建立连接时的回调函数,这个回调函数以socket端口对象作为参数。 ## 1.2、监听客户端的连接 使用TCP服务器的listen方法就可以开始监听客户端的连接,语法格式如下: server.listen(port[, host][, backlog][, callback]); port:为需要监听的端口号,参数值为0的时候将随机分配一个端口号; host:服务器地址; backlog:连接等待队列的最大长度; callback:回调函数。 以下代码可以创建一个TCP服务器并监听8001端口: //引入net模块 const net = require('net'); //创建TCP服务器 const server = net.createServer(function (socket) { console.log('有新的客户端接入'); }); //设置监听端口 server.listen(8001, function () { console.log('服务正在监听中。。。') }); 运行这段代码,可以在控制台看到执行了listen方法的回调函数,如图所示: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/03/101037r6ftcnpmytewdq7y.png) 可以使用相应的TCP客户端或者调试工具来连接这个已经创建好的TCP服务器。例如,要使用Windows的Telnet就可以用以下命令来连接: `telnet localhost 8001` 连接成功后可以看到控制台打印了“有新的客户端接入”字样,表明createServer方法的回调函数已经执行,说明已经成功连接到这个创建好的TCP服务器。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/03/101103m2r5z7vxw1udupia.png) server.listen()方法其实触发的是server下的listening事件,所以也可以手动监听listening事件,代码如下: //设置监听端口 server.listen(8001); //设置监听时的回调函数 server.on('listening', function () { console.log("服务正在监听中。。。") }); 除了listening事件外,TCP服务器还支持以下事件: connection:当有新的链接创建时触发,回调函数的参数为socket连接对象。 close:TCP服务器关闭的时候触发,回调函数没有参数。 error:TCP服务器发生错误的时候触发,回调函数的参数为error对象。 下列代码通过net.Server类来创建一个TCP服务器,添加以上事件: //引入net模块 const net = require('net'); //实例化一个服务器对象 const server = new net.Server(); //监听connection事件 server.on('connection', function (socket) { console.log('有新的客户端接入'); }); //设置监听端口 server.listen(8001); //设置监听时的回调函数 server.on('listening', function () { console.log('服务正在监听中。。。'); }); //设置关闭时的回调函数 server.on('close', function () { console.log('服务已关闭'); }); //设置出错时的回调函数 server.on('error', function (err) { console.log('服务运行异常', err); }); ## 1.3、查看服务器监听的地址 当创建了一个TCP服务器后,可以通过server.address()方法来查看这个TCP服务器监听的地址,并返回一个JSON对象,因为这个方法返回的是TCP服务器监听的地址信息,所以应该在调用了server.listen()方法或者绑定了事件listening中的回调函数中调用该方法。这个对象的属性有: port:TCP服务器监听的端口号; family:说明TCP服务器监听的地址是IPv6还是IPv4; address:TCP服务器监听的地址。 代码如下: //引入net模块 const net = require('net'); //创建TCP服务器 const server = net.createServer(function (socket) { console.log('有新的客户端接入'); }); //设置监听端口 server.listen(8001); //设置监听时的回调函数 server.on('listening', function () { //获取地址信息 let address = server.address(); //获取地址详细信息 console.log("服务器监听的端口是:" + address.port); console.log("服务器监听的地址是:" + address.address); console.log("服务器监听的地址类型是:" + address.family); }); 运行结果如图: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/03/101232bvfcuix6qlupwa3o.png) ## 1.4、连接服务器的客户端数量 创建一个TCP服务器后,可以通过server.getConnections()方法获取连接这个TCP服务器的客户端数量。这个方法是一个异步的方法,回调函数有两个参数: 第一个参数为error对象。 第二个参数为连接TCP服务器的客户端数量。 除了获取连接数外,也可以通过设置TCP服务器的maxConnections属性来设置这个TCP服务器的最大连接数。当连接数超过最大连接数的时候,服务器将拒绝新的连接。如下代码设置这个TCP服务器的最大连接数为3。 //引入net模块 const net = require('net'); //创建TCP服务器 const server = net.createServer(function (socket) { console.log('有新的客户端接入'); //设置最大连接数量 server.maxConnections = 3; server.getConnections(function (err, count) { console.log("当前连接的客户端个数为:" + count); }); }); //设置监听端口 server.listen(8001, function () { console.log("服务正在监听中。。。") }); 运行这段代码,并尝试用多个客户端连接。可以发现当客户端连接数超过3的时候,新的客户端就无法连接这个服务器了,如图所示: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/03/101341lkrbbbuscutnlsh1.png) 1.5、获取客户端发送的数据 createServer方法的回调函数参数是一个net.Socket对象(服务器所监听的端口对象),这个对象同样也有一个address()方法,用来获取TCP服务器绑定的地址,同样也是返回一个含有port、family、address属性的对象。通过socket对象可以获取客户端发送的流数据,每次接收到数据的时候触发data事件,通过监听这个事件就可以在回调函数中获取客户端发送的数据,代码如下: //引入net模块 const net = require('net'); //创建TCP服务器 const server = net.createServer(function (socket) { //监听data事件 socket.on("data", function (data) { //打印数据 console.log("接收到数据:" + data.toString()); }); }); //设置监听端口 server.listen(8001, function () { console.log("服务正在监听中。。。") }); 测试结果如下: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/03/101359sey88z16zzwercn5.png) socket对象除了有data事件外,还有connect、end、error、timeout等事件。 1.6、发送数据给客户端 调用socket.write()可以使TCP服务器发送数据,这个方法只有一个必需参数,就是需要发送的数据;第二个参数为编码格式,可选。同时,可以为这个方法设置一个回调函数。当有用户连接TCP服务器的时候,将发送数据给客户端,代码如下: //引入net模块 const net = require('net'); //创建TCP服务器 const server = net.createServer(function (socket) { //设置消息内容 const message = "Hello Client......"; //发送数据 socket.write(message, function () { const writeSize = socket.bytesWritten; console.log("数据发送成功,数据长度为:" + writeSize); }); //监听data事件 socket.on("data", function (data) { const readSize = socket.bytesRead; //打印数据 console.log("接收到数据为:" + data.toString(), ";接收的数据长度为:" + readSize); }); }); //设置监听端口 server.listen(8001, function () { console.log("服务正在监听中。。。") }); 测试结果如下: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/03/1014289szjyty70obklol6.png) 在上面这段代码中还用到了socket对象的bytesWritten和bytesRead属性,这两个属性分别代表着发送数据的字节数和接收数据的字节数。除了上面这两个属性外,socket对象还有以下属性: socket.localPort:本地端口的地址; socket.localAddress:本地IP地址; socket.remotePort:进程端口地址; socket.remoteFamily:进程IP协议族; socket.remoteAddress:进程IP地址。 # 2、构建TCP客户端 Node.js在创建一个TCP客户端的时候同样使用的是net(网络)模块。 ## 2.1、使用Node.js创建TCP客户端 为了使用Node.js创建TCP客户端,首先要调用require(‘net’)来加载net模块。创建一个TCP客户端只需要创建一个连接TCP客户端的socket对象即可: //引入net模块 const net = require('net'); //创建TCP客户端 const client = new net.Socket(); 创建一个socket对象的时候可以传入一个json对象。这个对象有以下属性: fd:指定一个存在的文件描述符,默认值为null; readable:是否允许在这个socket上读,默认值为false; writeable:是否允许在这个socket上写,默认值为false; allowHalfOpen:该属性为false时,TCP服务器接收到客户端发送的一个FIN包后,将会回发一个FIN包;该属性为true时,TCP服务器接收到客户端发送的一个FIN包后不会回发FIN包。 ## 2.2、连接TCP服务器 创建了一个socket对象后,调用socket对象的connect()方法就可以连接一个TCP服务器,代码如下: //引入net模块 const net = require('net'); //创建TCP客户端 const client = new net.Socket(); //设置连接的服务器 client.connect(8001, '127.0.0.1', function () { console.log("连接服务器成功"); }); 连接成功如下图所示: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/03/101536kxduvsr8pn6sjaez.png) ## 2.3、获取从TCP服务器发送的数据 socket对象有data、error、close、end等事件,因可以通过监听data事件来获取从TCP服务器发送的数据,代码如下: //引入net模块 const net = require('net'); //创建TCP客户端 const client = new net.Socket(); //设置连接的服务器 client.connect(8001, '127.0.0.1', function () { console.log("连接服务器成功"); }); //监听data事件 client.on("data", function (data) { //打印数据 console.log("接收到数据为:" + data.toString()); }); 先启动TCP服务端,再运行上面客户端,可以发现命令行中已经输出了来自服务端的数据,说明此时已经实现了服务端和客户端之间的通信: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/03/101604mnpkt4rtvxzjxtdv.png) ## 2.4、向TCP服务器发送数据 因为TCP客户端是一个socket对象,所以可以使用以下代码来向TCP服务器发送数据: //引入net模块 const net = require('net'); //创建TCP客户端 const client = new net.Socket(); //设置连接的服务器 client.connect(8001, '127.0.0.1', function () { console.log("连接服务器成功"); //给服务端发送数据 client.write("Hello Server......"); }); //监听data事件 client.on("data", function (data) { //打印数据 console.log("接收到数据为:" + data.toString()); }); //监听end事件 client.on("end", function () { console.log("客户端发送数据结束") }); 客户端控制台输出: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/03/101633kpjtrqlzbs54yawl.png) 服务端控制台输出: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/03/101639opss6jdlnpuorkba.png) 至此使用Node.js进行TCP网络通信完成,如有不对的地方欢迎指正
-
>摘要:包与NPM Node组织了自身的核心模块,也使得第三方文件模块可以有序的编写和使用。本文分享自华为云社区[《NodeJs深入浅出之旅:包与NPM》](https://bbs.huaweicloud.com/blogs/307034?utm_source=zhihu&utm_medium=bbs-ex&utm_campaign=other&utm_content=content),作者:空城机。 # 包与NPM Node组织了自身的核心模块,也使得第三方文件模块可以有序的编写和使用。 但是在第三方模块中,模块与模块之间仍然是散列在各地的,**相互之间不能直接引用** 所以在模块外,包和NPM是将模块联系起来的机制。 - 包组织模块示意图 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202111/27/141323rxr3v28syu8prqok.png) CommonJS 的包规范定义其实也很简单,由包结构和包描述文件两部分组成。 # 包结构 用于组织包中的各种文件,是一个存档文件,即一个目录直接打包为.zip或tar.gz格式的文件。 符合规范的包目录: - package.json: 包描述文件 - bin: 用于存放可执行二进制文件的目录 - lib: 用于存放JavaScript代码的目录 - doc: 用于存放文档的目录 - test: 用于存放单元测试用例的代码 # package.json包描述文件 NPM所有行为都与包描述文件的字段息息相关 一些字段: - name: 包名。 规范定义需要用小写的字母和数字组成,不允许出现空格。 包名必须是唯一的,以免对外公布时产生重名冲突 - description: 包简介 - version: 版本号,关于其介绍在《Node.js学习(一)——简介》也有提及 - keywords: 关键字数组, NPM中主要用来作分类搜索。 - maintainers: 包维护者列表。 每个维护者由name、email和web这3个属性组成。 NPM通过这个属性进行权限认证。 格式: ```"maintainers":[{ "name":"kongchengji", "email":"111@.com", "web":"[http:](https://blog.csdn.net/qq_36171287)" }]``` - contributors: 贡献者列表,格式与维护者列表相同 - bugs: 一个可以反馈bug的网页地址或邮件地址 - licenses: 当前包所使用的许可证列表,表示包在哪些许可证下使用 格式: "licenses":[{ "type": "GPLv2", "url":"" }] // 或者 "license": "ISC" - repositories: 托管源代码的位置列表,表明可以通过哪些方式和地址访问包源代码。 - 格式: - "repository": { - "type": "git", - "url": "git+https://github.com/kongchengji/UiSelfMade.git" - }, - dependencies: 使用当前包所需要依赖的包列表。 这个属性非常重要 - homepage: 当前包的网站地址 - os: 操作系统支持列表, 如果列表为空,则不对操作系统做任何假设 - cpi: CPU架构支持列表 - engine:支持的JavaScript引擎列表 - directories:包目录说明 - implements: 实施规范的列表。 标志当前包实现了CommonJS哪些规范 - scripts: 脚本说明对象。 主要用于被包管理器用来安装、编译、测试和卸载包 "scripts": { "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run dev", "lint": "eslint --ext .js,.vue src", "build": "node build/build.js" }, NPM与包规范区别在于多了四个字段: - author: 包作者 :ok_man: - bin: 一些包作者希望包可以作为命令行工具使用。 - main: 模块引入包时,会有限检查这个字段,并将其作为包中其余模块的入口模块。 如果不存在,require会查找包目录下的index.js、index.node、index.json作为默认入口 - devDependencies: 一些模块只在开发时需要依赖。 devDependencies:开发环境使用 dependencies:生产环境使用 # 前后端共用模块 JavaScript在Node出现后,有一项优势 --> 一些模块可以在前后端实现共用。 但是前后端上始终还是有一些差别的 :sweat_drops: # 前后端模块侧重点 前后端JavaScript分别搁置在HTPP的两端,扮演的角色并不同。 浏览器端的JavaScript需要经历从同一个服务器端分发到多个客户端执行,瓶颈是带宽,从网络加载代码 服务器端的JavaScript是相同代码需要多次执行,瓶颈是CPU和内存等资源,从磁盘中加载 在前端JavaScript中,主要还是应用**AMD规范**。 CommonJS并不完全适用于前端JavaScript,比如Node的模块引入基本是同步的,但是前端引入如果使用同步引入,UI在初始化过程中需要花费很多时间等待脚本加载完成。 # AMD规范 AMD规范 是CommonJS规范的一个延伸,全称:Asynchronous Module Definition。 是异步模块定义 模块定义:define(id?, dependencies?, factory); id 是模块的名字,它是可选的参数。 dependencies 指定了所要依赖的模块列表,它是一个数组,也是可选的参数 AMD需要在声明模块时指定所有的依赖,通过形参传递依赖到模块内容中: define(['./a', './b'], function (dep1, dep2) { a.doSomethimg() b.doSomething() }); # CMD规范 与AMD规范相对的还有CMD规范,全称:Common Module Definition。 是公共模块定义 这是由国内的玉伯(也是一位大佬)提出的 模块定义:define(factory) CMD支持动态引入: define(function(require, exports, module) { var a=require('./a') a.doSomethimg() var b=require('./b') b.doSomething() }) 在需要依赖模块时, 随时调用require()引入即可 CMD 推崇依赖就近; AMD 推崇依赖前置 CMD 是延迟执行; AMD 是提前执行 CMD性能好,因为只有用户需要的时候才执行; AMD用户体验好,因为没有延迟,依赖模块提前执行了 AMD和CMD最大的区别是对依赖模块的执行时机处理不同 # 兼容多种模块规范 创建一个hello方法,让hello方法能在不同运行环境中运行,兼容Node、AMD、CMD和常见浏览器 匿名函数前加一个;是个好习惯 name是方法名,definition是方法体 通过typeof检测环境是否为AMD或CMD还是Node环境 可以将模块执行结果挂载在window变量中,这样可以直接调用 // 匿名函数前加一个;是个好习惯 name是方法名,definition是方法体 ;(function (name, definition) { //检查环境是否是AMD或CMD var hasDefine = typeof define === 'function', // 检查环境是否为Node hasExports = typeof module !== 'undefined' && mudule.exports; if(hasDefine) { define(definition); } else if (hasExports) { module.exports = definition(); } else { // 将模块的执行结果挂在window变量中,在浏览器中this指向window对象 this[name] = definition(); } })('hello', function () { var hello = function () { console.log('hello'); } return hello; });
-
Kubernetes 的节点可以按照节点的资源容量进行调度,默认情况下 Pod 能够使用节点全部可用容量。这样就会造成一个问题,因为节点自己通常运行了不少驱动 OS 和 Kubernetes 的系统守护进程。除非为这些系统守护进程留出资源,否则它们将与 Pod 争夺资源并导致节点资源短缺问题。 当我们在线上使用 Kubernetes 集群的时候,如果没有对节点配置正确的资源预留,我们可以考虑一个场景,由于某个应用无限制的使用节点的 CPU 资源,导致节点上 CPU 使用持续100%运行,而且压榨到了 kubelet 组件的 CPU 使用,这样就会导致 kubelet 和 apiserver 的心跳出问题,节点就会出现 Not Ready 状况了。默认情况下节点 Not Ready 过后,5分钟后会驱逐应用到其他节点,当这个应用跑到其他节点上的时候同样100%的使用 CPU,是不是也会把这个节点搞挂掉,同样的情况继续下去,也就导致了整个集群的雪崩,集群内的节点一个一个的 Not Ready 了,后果是非常严重的,或多或少的人遇到过 Kubernetes 集群雪崩的情况,这个问题也是面试的时候镜像询问的问题。 要解决这个问题就需要为 Kubernetes 集群配置资源预留,kubelet 暴露了一个名为 Node Allocatable 的特性,有助于为系统守护进程预留计算资源,Kubernetes 也是推荐集群管理员按照每个节点上的工作负载来配置 Node Allocatable。 本文的操作环境为 Kubernetes V1.17.11 版本,Docker 和 Kubelet 采用的 cgroup 驱动为 systemd。 Node AllocatableKubernetes 节点上的 Allocatable 被定义为 Pod 可用计算资源量,调度器不会超额申请 Allocatable,目前支持 CPU, memory 和 ephemeral-storage 这几个参数。 我们可以通过 kubectl describe node 命令查看节点可分配资源的数据:$ kubectl describe node ydzs-node4......Capacity: cpu: 4 ephemeral-storage: 17921Mi hugepages-2Mi: 0 memory: 8008820Ki pods: 110Allocatable: cpu: 4 ephemeral-storage: 16912377419 hugepages-2Mi: 0 memory: 7906420Ki pods: 110......可以看到其中有 Capacity 与 Allocatable 两项内容,其中的 Allocatable 就是节点可被分配的资源,我们这里没有配置资源预留,所以默认情况下 Capacity 与 Allocatable 的值基本上是一致的。下图显示了可分配资源和资源预留之间的关系: Node AllocatableKubelet Node Allocatable 用来为 Kube 组件和 System 进程预留资源,从而保证当节点出现满负荷时也能保证 Kube 和 System 进程有足够的资源。目前支持 cpu, memory, ephemeral-storage 三种资源预留。Node Capacity 是节点的所有硬件资源,kube-reserved 是给 kube 组件预留的资源,system-reserved 是给系统进程预留的资源,eviction-threshold 是 kubelet 驱逐的阈值设定,allocatable 才是真正调度器调度 Pod 时的参考值(保证节点上所有 Pods 的 request 资源不超过Allocatable)。节点可分配资源的计算方式为:Node Allocatable Resource = Node Capacity - Kube-reserved - system-reserved - eviction-threshold配置资源预留Kube 预留值首先我们来配置 Kube 预留值,kube-reserved 是为了给诸如 kubelet、容器运行时、node problem detector 等 kubernetes 系统守护进程争取资源预留。要配置 Kube 预留,需要把 kubelet 的 --kube-reserved-cgroup 标志的值设置为 kube 守护进程的父控制组。 不过需要注意,如果 --kube-reserved-cgroup 不存在,Kubelet 不会创建它,启动 Kubelet 将会失败。 比如我们这里修改 node-ydzs4 节点的 Kube 资源预留,我们可以直接修改 /var/lib/kubelet/config.yaml 文件来动态配置 kubelet,添加如下所示的资源预留配置:apiVersion: kubelet.config.k8s.io/v1beta1......enforceNodeAllocatable:- pods- kube-reserved # 开启 kube 资源预留kubeReserved: cpu: 500m memory: 1Gi ephemeral-storage: 1GikubeReservedCgroup: /kubelet.slice # 指定 kube 资源预留的 cgroup修改完成后,重启 kubelet,如果没有创建上面的 kubelet 的 cgroup,启动会失败:$ systemctl restart kubelet$ journalctl -u kubelet -f......Aug 11 15:04:13 ydzs-node4 kubelet[28843]: F0811 15:04:13.653476 28843 kubelet.go:1380] Failed to start ContainerManager Failed to enforce Kube Reserved Cgroup Limits on "/kubelet.slice": ["kubelet"] cgroup does not exist上面的提示信息很明显,我们指定的 kubelet 这个 cgroup 不存在,但是由于子系统较多,具体是哪一个子系统不存在不好定位,我们可以将 kubelet 的日志级别调整为 v=4,就可以看到具体丢失的 cgroup 路径:$ vi /var/lib/kubelet/kubeadm-flags.envKUBELET_KUBEADM_ARGS="--v=4 --cgroup-driver=systemd --network-plugin=cni"然后再次重启 kubelet:$ systemctl daemon-reload$ systemctl restart kubelet再次查看 kubelet 日志:$ journalctl -u kubelet -f......Sep 09 17:57:36 ydzs-node4 kubelet[20427]: I0909 17:57:36.382811 20427 cgroup_manager_linux.go:273] The Cgroup [kubelet] has some missing paths: [/sys/fs/cgroup/cpu,cpuacct/kubelet.slice /sys/fs/cgroup/memory/kubelet.slice /sys/fs/cgroup/systemd/kubelet.slice /sys/fs/cgroup/pids/kubelet.slice /sys/fs/cgroup/cpu,cpuacct/kubelet.slice /sys/fs/cgroup/cpuset/kubelet.slice]Sep 09 17:57:36 ydzs-node4 kubelet[20427]: I0909 17:57:36.383002 20427 factory.go:170] Factory "systemd" can handle container "/system.slice/run-docker-netns-db100461211c.mount", but ignoring.Sep 09 17:57:36 ydzs-node4 kubelet[20427]: I0909 17:57:36.383025 20427 manager.go:908] ignoring container "/system.slice/run-docker-netns-db100461211c.mount"Sep 09 17:57:36 ydzs-node4 kubelet[20427]: F0909 17:57:36.383046 20427 kubelet.go:1381] Failed to start ContainerManager Failed to enforce Kube Reserved Cgroup Limits on "/kubelet.slice": ["kubelet"] cgroup does not exist注意:systemd 的 cgroup 驱动对应的 cgroup 名称是以 .slice 结尾的,比如如果你把 cgroup 名称配置成 kubelet.service,那么对应的创建的 cgroup 名称应该为 kubelet.service.slice。如果你配置的是 cgroupfs 的驱动,则用配置的值即可。无论哪种方式,通过查看错误日志都是排查问题最好的方式。现在可以看到具体的 cgroup 不存在的路径信息了:The Cgroup [kubelet] has some missing paths: [/sys/fs/cgroup/cpu,cpuacct/kubelet.slice /sys/fs/cgroup/memory/kubelet.slice /sys/fs/cgroup/systemd/kubelet.slice /sys/fs/cgroup/pids/kubelet.slice /sys/fs/cgroup/cpu,cpuacct/kubelet.slice /sys/fs/cgroup/cpuset/kubelet.slice]所以要解决这个问题也很简单,我们只需要创建上面的几个路径即可:$ mkdir -p /sys/fs/cgroup/cpu,cpuacct/kubelet.slice$ mkdir -p /sys/fs/cgroup/memory/kubelet.slice$ mkdir -p /sys/fs/cgroup/systemd/kubelet.slice$ mkdir -p /sys/fs/cgroup/pids/kubelet.slice$ mkdir -p /sys/fs/cgroup/cpu,cpuacct/kubelet.slice$ mkdir -p /sys/fs/cgroup/cpuset/kubelet.slice创建完成后,再次重启:$ systemctl restart kubelet$ journalctl -u kubelet -f......Sep 09 17:59:41 ydzs-node4 kubelet[21462]: F0909 17:59:41.291957 21462 kubelet.go:1381] Failed to start ContainerManager Failed to enforce Kube Reserved Cgroup Limits on "/kubelet.slice": failed to set supported cgroup subsystems for cgroup [kubelet]: failed to set config for supported subsystems : failed to write 0 to hugetlb.2MB.limit_in_bytes: open /sys/fs/cgroup/hugetlb/kubelet.slice/hugetlb.2MB.limit_in_bytes: no such file or directory可以看到还有一个 hugetlb 的 cgroup 路径不存在,所以继续创建这个路径:$ mkdir -p /sys/fs/cgroup/hugetlb/kubelet.slice$ systemctl restart kubelet重启完成后就可以正常启动了,启动完成后我们可以通过查看 cgroup 里面的限制信息校验是否配置成功,比如我们查看内存的限制信息:$ cat /sys/fs/cgroup/memory/kubelet.slice/memory.limit_in_bytes1073741824 # 1Gi现在再次查看节点的信息:$ kubectl describe node ydzs-node4......Addresses: InternalIP: 10.151.30.59 Hostname: ydzs-node4Capacity: cpu: 4 ephemeral-storage: 17921Mi hugepages-2Mi: 0 memory: 8008820Ki pods: 110Allocatable: cpu: 3500m ephemeral-storage: 15838635595 hugepages-2Mi: 0 memory: 6857844Ki pods: 110......可以看到可以分配的 Allocatable 值就变成了 Kube 预留过后的值了,证明我们的 Kube 预留成功了。系统预留值我们也可以用同样的方式为系统配置预留值,system-reserved 用于为诸如 sshd、udev 等系统守护进程争取资源预留,system-reserved 也应该为 kernel 预留 内存,因为目前 kernel 使用的内存并不记在 Kubernetes 的 pod 上。但是在执行 system-reserved 预留操作时请加倍小心,因为它可能导致节点上的关键系统服务 CPU 资源短缺或因为内存不足而被终止,所以如果不是自己非常清楚如何配置,可以不用配置系统预留值。 同样通过 kubelet 的参数 --system-reserved 配置系统预留值,但是也需要配置 --system-reserved-cgroup 参数为系统进程设置 cgroup。请注意,如果 --system-reserved-cgroup 不存在,kubelet 不会创建它,kubelet 会启动失败。驱逐阈值上面我们还提到可分配的资源还和 kubelet 驱逐的阈值有关。节点级别的内存压力将导致系统内存不足,这将影响到整个节点及其上运行的所有 Pod,节点可以暂时离线直到内存已经回收为止,我们可以通过配置 kubelet 驱逐阈值来防止系统内存不足。驱逐操作只支持内存和 ephemeral-storage 两种不可压缩资源。当出现内存不足时,调度器不会调度新的 Best-Effort QoS Pods 到此节点,当出现磁盘压力时,调度器不会调度任何新 Pods 到此节点。 我们这里为 ydzs-node4 节点配置如下所示的硬驱逐阈值:# /var/lib/kubelet/config.yaml......evictionHard: # 配置硬驱逐阈值 memory.available: "300Mi" nodefs.available: "10%"enforceNodeAllocatable:- pods- kube-reservedkubeReserved: cpu: 500m memory: 1Gi ephemeral-storage: 1GikubeReservedCgroup: /kubelet.slice......我们通过 --eviction-hard 预留一些内存后,当节点上的可用内存降至保留值以下时,kubelet 将尝试驱逐 Pod,$ kubectl describe node ydzs-node4......Addresses: InternalIP: 10.151.30.59 Hostname: ydzs-node4Capacity: cpu: 4 ephemeral-storage: 17921Mi hugepages-2Mi: 0 memory: 8008820Ki pods: 110Allocatable: cpu: 3500m ephemeral-storage: 15838635595 hugepages-2Mi: 0 memory: 6653044Ki pods: 110......配置生效后再次查看节点可分配的资源可以看到内存减少了,临时存储没有变化是因为硬驱逐的默认值就是 10%。也是符合可分配资源的计算公式的:Node Allocatable Resource = Node Capacity - Kube-reserved - system-reserved - eviction-threshold到这里我们就完成了 Kubernetes 资源预留的配置。 原文链接:https://mp.weixin.qq.com/s/oYBzI9Zh-_RtKgtyXCO8kQ
上滑加载中
推荐直播
-
TinyEngine低代码引擎系列第2讲——向下扎根,向上生长,TinyEngine灵活构建个性化低代码平台
2024/11/14 周四 16:00-18:00
王老师 华为云前端开发工程师,TinyEngine开源负责人
王老师将从TinyEngine 的灵活定制能力出发,带大家了解隐藏在低代码背后的潜在挑战及突破思路,通过实践及运用,帮助大家贴近面向未来低代码产品。
回顾中 -
华为云AI入门课:AI发展趋势与华为愿景
2024/11/18 周一 18:20-20:20
Alex 华为云学堂技术讲师
本期直播旨在帮助开发者熟悉理解AI技术概念,AI发展趋势,AI实用化前景,了解熟悉未来主要技术栈,当前发展瓶颈等行业化知识。帮助开发者在AI领域快速构建知识体系,构建职业竞争力。
去报名 -
华为云软件开发生产线(CodeArts)10月新特性解读
2024/11/19 周二 19:00-20:00
苏柏亚培 华为云高级产品经理
不知道产品的最新特性?没法和产品团队建立直接的沟通?本期直播产品经理将为您解读华为云软件开发生产线10月发布的新特性,并在直播过程中为您答疑解惑。
去报名
热门标签