第一天

 

Node简介

 

-什么是Javascript

+脚本语言

+运行在浏览器中

+一般用来做客户端页面的交互(Interactive)

 

-JaveScript的运行环境?

  +是不是运行在浏览器呢?

   ,不够严谨

  +是运行在浏览器内核中的JS引擎(Engine)(而不是浏览器,浏览器是一个大的概念)

    

浏览器的作用

Node基础

-浏览器中的JavaScript可以做什么?

+操作DOM(对DOM的增删改、注册事件)

+AJAX/跨域

+BOM(页面跳转、历史记录、console.log()、alert())

  +ECMAScript

 

-浏览器中的Javascript不可以做什么?

  +文件操作(文件和文件夹的CRUD)

  +没有办法操作系统信息

  +由于运行环境特殊,并不是在服务器执行,而是到不认识的人的浏览器客户端中执行

 

 

 

-在开发人员能力相同的情况下编程语言的能力取决于什么

  +语言本身?

  +语言本身只是提供定义变量,定义函数,定义类型,流程控制,循环结构之类的操作

  +取决于运行该语言的平台(环境)

  +对于JS来说,我们常说JS实际是ES,大部分能力都是由浏览器的执行引擎决定

  +BOM和DOM可以说是浏览器开发出来的接口

  +比如:Cordova中提供JS调用摄像头,操作本地文件的API

 

  +Java既是语言也是平台

  +Java运行在Java虚拟机(跨操作系统)

+PHP 即使语言也是平台

 

  +C#语言 平台: .net framework(Windows)

  +C#可以运行在Mono平台

  +因为有人需要将C#运行在Linux平台,所以出现了MONO

 

-JavaScript只可以运行在浏览器中吗?

  +不是

  +能运行在哪取决于,这个环境有没有特定的平台

 

### 什么是Node

 

 

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.

 

·Node就是服务器端的一个Javascript运行环境

 

Node基础

 

 Node基础

Node基础

Node在Web中的用途

 

·分发数据请求,渲染HTML

(因为NodeJS并发数相比传统平台很优秀)

Node基础

常见命令行操作

 

Path变量

添加到Path变量后可以直接在CMD中打开

Node基础

*Windows默认使用反斜线\

*在Linux标准中一般使用/正斜线(除法的那个斜线)

 

 

Node REPL环境

 Node基础

Node基础

浏览器中 全局对象是Window对象 在node中是没有window对象的。

 

process.argv

打印一个数组,第一个是node的路径,第二个是当前运行的程序的路径,后面的内容是数组中的参数。

 

process.stdout

process.stdout(msg) = console.log(`${msg}\n`);

 

 

 

Debug

 Node基础

process.stdin.on(‘data’,(input)=>{

 

  …

 

})

//输入的字符最后肯定是一个回车符

所以要trim掉

 

process.stdin.readline() //用户的操作是无状态的,所以一般都不用这个

 

process.stdout.write();

 

 

 

异步操作

 

console.time(‘’)

console.timeEnd(‘’)

 

 

 

事件队列:

Node基础

var fs = require(‘fs’);

fs.readFile(‘./typings/node/aa.ts’,’utf8’,(err,data)=>{

  if(err) throw err;

console.log(data);

})

 Node基础

 

第二天

 

错误优先的回调函数

因为操作大多数都是异步形式,没法通过trycatch来捕获异常

所以写错误优先 的回掉函数

Node基础

Node基础

 

进程和线程

进程:进行中的程序

Node基础

 

 

 Node基础

Node基础

创建线程,就像当项目经理招人一样。需要时间,并不是那么容易。

 

不要太较真,没有任何一个人是那么完美。

 

多线程都是假的,因为只有一个CPU(单核)

线程之间共享某些数据,同步某个状态都很麻烦

更致命的是:

-创建线程耗费时间

-线程数量有限

-CPU

 

单线程产品nginx  Redis

事实证明这些单线程产品比多线程产品性能要好。

 

Node.js的优势

事件驱动,非阻塞

 

如果使用Java或者PHP需要创建新线程,分配资源,需要的代码和其他资源也比较多而复杂,而Node实现起来非常简单。在每一个异步操作后都会有一个回调。

 

 

 

非阻塞I/O

Node的核心特性

 

const fs = require(‘fs’);

 

事件驱动:

const fs = require(‘fs’)

 

//判断是否存在list文件

fs.stat(‘./list.md’,(err,stats)=>{

  if(err) throw err;

  //存在删除

/* 

fs.unlink(‘./list.md’,(err,)=>{

if(err) throw err;});

*/ console.log(stats);

})

 

const fs = require('fs');

 

console.time('timer');

 

 fs.stat('./1.txt',(err,stats)=>{

    if(err) console.error(err);

    //创建

    

      fs.writeFile('./list.md',new Date(),(err)=>{

        if(err) console.error(err);

        console.log('创建成功');

       

        console.timeEnd('timer');

      })

 

 });

 

 

事件队列:  Event Queue

 

主线程执行,遇到异步事件(阻塞操作)会塞到事件队列里,

主线程执行完开始到事件队列中拿第一个异步事件进行执行

如果在这个异步事件的callback中有异步操作,那么把他放到事件队列中,

 

 

阻塞操作是交给内部线程池来完成的

Node基础

Node在底层维护了一个线程池(里面有很多线程)如果有需要耗时的操作,就会交给线程来操作。

不用的线程再次关回小黑屋(图片最下面的部分)

 

Node为什么能实现非阻塞,

原因是他在实现调度的工作,本身并没有实质的读文件 读网络的工作。

 

调度。

Node基础

 

 Node基础

Node基础

 

 

非阻塞的优势

·提高代码的相应效率

·充分利用单核CPU的优势(但是目前市场上大多数是多核CPU)

·改善I/O的不可预测带来的问题

·如何提高一个人的工作效率

 

web中的单线程

 

//Node 开发服务器的阻塞情况

const http = require('http');

 

let count = 0;

 

const server = http.createServer((req,res)=>{

//此回调回在有任何用户请求时触发 

res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'});

res.write(`你是第${count++}个访问用户`,);

res.end();

 

});

 

server.listen(2080,(err)=>{

   if(err) throw err;

   console.log('成功启动web服务,端口:2080');

});

 

 

 

下面把代码改成这样,

Node基础

 

 

因为事件队列对阻塞事件的处理,

在第十个请求的时候会一直卡死。

如果是PHP就没事儿,因为那个是不同的线程去执行的。

注意:node开发时一定要谨慎防止这种情况的出现。

 

模块化的开始

Node基础

 

 

//实现命令行计算器

 

//1.接收参数

const args = process.argv.slice(2);

//['node 执行程序所在路径 ','当前脚本所在路径',...参数]

 

//2.分析参数

if(args.length != 3){

  console.log('参数不合法');

  throw new Error('err'); //到这里跳出这个文件

}

 

//parameter

let p1 = args[0];

let operator = args[1];

let p2 = args[2];

 

let result;

 

switch (operator) {

 

  case '+':

    result = parseFloat(p1) + parseFloat(p2);

    break;

  case '-':

    result = parseFloat(p1) - parseFloat(p2);

    break;

  case 'x':

  case '*':

    result = parseFloat(p1) * parseFloat(p2);

    break;

  case '÷':

  case '/':

    result = parseFloat(p1) / parseFloat(p2);

    break;

  default:

    throw new Error('不被支持的操作符' + operator);

    break;

}

 

console.log(result);

 Node基础

其他的社区规范.. CMD规范

Node基础

 

 

CommonJS规范

在Node中实现的是CommonJS规范

 

CommonJS与CMD规范的区别就是不需要用define了。

Node基础

Node基础

文件模块 require(‘./../’)

核心模块 require(‘fs’)   //并没有写什么目录

 

所有的文件操作必须是绝对路径(物理路径)



module1.js

//获取当前脚本所在路径

console.log(__dirname);

//文件路径

console.log(__filename);

 

const fs = require('fs');

 

//所有的文件操作必须是绝对路径(物理路径)

fs.readFile(__dirname+'/../list.md','utf8',(err,content)=>{

  if (err) throw err;

    console.log(content);

 

});

 

2.js

//模块中的全局成员

 

const modul = require('./module/module1.js');

 

为什么文件操作必须是绝对路径?

答:因为require以后执行的话module1.js中的相对路径就会产生错误进而报错。

 

 

 

 

 

 

//module对象

 

console.log(module);

Module {

  id: '.',

  exports: {},

  parent: null,

  filename: 'C:\\Users\\BDSOFT\\Desktop\\nodelearn\\4.js',  loaded: false,

  children: [],

  paths:

   [ 'C:\\Users\\BDSOFT\\Desktop\\nodelearn\\node_modules',

     'C:\\Users\\BDSOFT\\Desktop\\node_modules',

     'C:\\Users\\BDSOFT\\node_modules',

     'C:\\Users\\node_modules',

     'C:\\node_modules' ]

}

 

 

Node.js 全局对象

JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。

在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global 对象的属性。

在 Node.js 我们可以直接访问到 global 的属性,而不需要在应用中包含它。

 

__filename

__dirname

setTimeout(cb, ms)

clearTimeout(t)

setInterval(cb, ms)

console

process

 

Node基础

为什么说是伪全局变量呢?

答:因为在Node REPL里输__dirname是没有的。__dirname存在于node模块(.js文件)内部。

 

模块本身就是一个封闭的作用域,没有必要写自执行函数。

 

自己实现一个require

 

Node基础

 

 

 

module4.js:

function say(msg){

    console.log(msg);

}

module.exports = {say};

 

 

1.js:

//自己写一个require函数

 

function $require(id){

 

// 1.先找到文件 如果文件不存在 Error: Cannot find module  'xxx.js'

// 2.读取文件内容 内容是JS代码

const fs = require('fs');

const path = require('path');

 

//要加载的js文件路径(完整路径)

const filename = path.join(__dirname,id);

const dirname = path.dirname(filename);

 

let code = fs.readFileSync(filename,'utf8');

 

// 3.执行代码,所要执行的代码 需要营造一个私有空间

// 定义一个数据容器,用容器去装模块导出的成员

let module = {id:filename,exports:{}};

let exports = module.exports;

code = `

(function($require,module,exports,__dirname,__filename){

     ${code}

})($require,module,exports,dirname,filename)`;

 

eval(code);

 

// 4.返回值

return module.exports;

 

}

 

 

var m4 = $require('./module/module4.js');

 

m4.say('hello');

 

(然而没有讲export是怎么实现的.)

 

require扩展名

Node基础

 

 

先加载js,如果没有按顺序往下面加在。

 

//require不仅仅可以载入js模块,也可以用来读取配置信息(JSON)

如果require了一个文件夹,会默认加载这个文件夹下的

package.json中main指向的文件,

Node基础

如果没有定义就会载入index.js

 

 

 

 

 

require模块加载机制

Node基础

从当前目录向上搜索node_modules目录中的文件(就近加载)

 

 

模块的require缓存

 Node基础

date.js:

module.exports =  new Date();

 

 

1.js:

//模块的缓存

 

setInterval(()=>{

 

  var date = require('./date.js');

  console.log(date.getTime());

 

},1000);

 

运行结果:

Node基础

说明模块有缓存机制。

 

 

console.log(module.require)

打印出一个对象,被引入的模块将被缓存在这个对象中。

 

 

缓存结构示例:

{ 'C:\\Users\\BDSOFT\\Desktop\\nodelearn\\require.js':

    Module {

      id: '.',

      exports: {},

      parent: null,

      filename: 'C:\\Users\\BDSOFT\\Desktop\\nodelearn\\require.js',

      loaded: true,

      children: [

            [Module

            ]

        ],

      paths: [ 'C:\\Users\\BDSOFT\\Desktop\\nodelearn\\node_modules',

         'C:\\Users\\BDSOFT\\Desktop\\node_modules',

         'C:\\Users\\BDSOFT\\node_modules',

         'C:\\Users\\node_modules',

         'C:\\node_modules'

        ]

    },

   'C:\\Users\\BDSOFT\\Desktop\\nodelearn\\date.js':

    Module {

      id: 'C:\\Users\\BDSOFT\\Desktop\\nodelearn\\date.js',

      exports: 2018-04-27T09: 31: 32.836Z,

      parent:

       Module {

         id: '.',

         exports: {},

         parent: null,

         filename: 'C:\\Users\\BDSOFT\\Desktop\\nodelearn\\require.js',

         loaded: true,

         children: [Array

            ],

         paths: [Array

            ]

        },

      filename: 'C:\\Users\\BDSOFT\\Desktop\\nodelearn\\date.js',

      loaded: true,

      children: [],

      paths: [ 'C:\\Users\\BDSOFT\\Desktop\\nodelearn\\node_modules',

         'C:\\Users\\BDSOFT\\Desktop\\node_modules',

         'C:\\Users\\BDSOFT\\node_modules',

         'C:\\Users\\node_modules',

         'C:\\node_modules'

        ]

    }

}

 

 

清空一个缓存试试:

    Object.keys(require.cache).forEach((key)=>{

        delete require.cache[key];

    });

*一般情况下不会手动清空它(闲得蛋疼),这里就是示例一下

 

 

加缓存:

 

function $require(id) {

 

    // 1.先找到文件 如果文件不存在 Error: Cannot find module  'xxx.js'

    // 2.读取文件内容 内容是JS代码

    const fs = require('fs');

    const path = require('path');

 

    //要加载的js文件路径(完整路径)

    const filename = path.join(__dirname, id);

   

    $require.cache  =$require.cache || {};

   

   

    if($require.cache[filename]){

        return $require.cache[filename].exports;

    }

 

    const dirname = path.dirname(filename);

 

    let code = fs.readFileSync(filename, 'utf8');

 

    // 3.执行代码,所要执行的代码 需要营造一个私有空间

    // 定义一个数据容器,用容器去装模块导出的成员

    let module = { id: filename, exports: {} };

    let exports = module.exports;

    code = `

    (function($require,module,exports,__dirname,__filename){

         ${code}

    })($require,module,exports,dirname,filename)`;

 

    eval(code);

 

    $require.cache[filename] = module;

   

    // 4.返回值

    return module.exports;

 

}

 

 

第三天

 

学编程要学会看API文档

Node基础

Node基础

 

NPM包管理工具

 

where node

npm config ls

 

Node基础

Node基础

全局配置文件 npmrc

Node基础

prefix前缀

设置npm install –g安装到哪里…

npm config set prefix C:\Develop\nvm\npm

npm config get prefix

 

NodeResourceManager介绍

npm install nrm –g

rm ls

这个星球所有的源!

nrm use cnpm

Node基础

再看npm config ls

 

 

文件系统操作

文件操作必须使用绝对路径

fs:

path:

readline:

 

Windows默认编码 GBK

笔记本保存的话显示ANSI,其实就是GBK

 

path模块操作

详见文档

path.relative(from,to)

path.resolve

同步调用和异步调用

fs.readFile

fs.readFileSync

 

读文件没有指定编码默认读取的是一个buffer(缓冲区)

buffer

 

Node基础

这个”瓢”只能装4个字节

Node基础

 

Node基础

LE、BE

little-endian 小字节序 符合人的思维,地址低位存储值的低位,地址高位存储值的高位。

big-endian 大字节序 最直观的字节序,地址低位存储值的高位,地址高位存储值的低位。

Node基础

Node基础

 

 

转换成Base64

Node基础

通过Data:URL协议来打开看

DATA-URI 是指可以在Web 页面中包含图片但无需任何额外的HTTP 请求的一类URI.

一般的图片都会单独请求

Node基础

(这里是一个简单的介绍)

 

 

文件编码的问题

 

滚动显示歌词

// 动态显示歌词

const fs = require('fs')

const path = require('path')

 

fs.readFile(path.join(__dirname, './nineteen.lrc'), (err, data) => {

 

    if (err) throw err;

 

    var lines = data.toString('utf8').split('\n');

 

    // [00:17.78]走过初识你的地点

    // 第一组,第二组,第三组,第四组

    var regex = /\[(\d{2})\:(\d{2})\.(\d{2})\](.+)/;

 

    var begin = new Date().getTime();

 

    //遍历  

    lines.forEach((line) => {

 

        var matches = regex.exec(line);

 

        // 如果匹配上了

        if (matches) {

            var m = parseFloat(matches[1]);

            var s = parseFloat(matches[2]);

            var f = parseFloat(matches[3]);

            var lyric = matches[4]; //当前行歌词其实不是立即执行

 

 

            //由于下达输出任务的时刻不同,设置定时器其实有一些几毫秒的误差

            var offset = new Date().getTime() - begin;

 

            setTimeout(() => {

                console.log(lyric);

            }, m * 60 * 1000 + s * 1000 + f - offset);

 

        } else {

            // 不是一行歌词 

            console.log(line);

        }

 

    });

 

});

 

 

流的方式读取

 Node基础

node会根据系统资源情况自动生成buffer大小 先把数据写到缓冲区,然后再从缓冲区写入磁盘中。

 

const fs = require('fs')

const path = require('path')

const readline = require('readline')

 

var filename = path.join(__dirname, './nineteen.lrc');

 

var streamReader = fs.createReadStream(filename);

 

var data = '';

 

streamReader.on('data', (chunk) => {

 

    //chunk只是文档的一个片段 不完整

    data += chunk.toString();

 

});

 

streamReader.on('end',()=>{

      //通知你已经结束了 此时data是完整的

   

    console.log(data);

 

});

利用ReadLine读取:

var streamReader = fs.createReadStream(filename);

 

//利用readline读取

var rl = readline.createInterface({input:streamReader})

 

var begin = new Date().getTime();

 

rl.on('line',(line)=>{

 

    //function()...

 

});

 

 

//经过console.log(typeof line)来看,

readline读出来的已经是string了,不是buffer。不是buffer

 

文件写入

//文件写入

 

const fs = require('fs');

const path = require('path');

 

//fs.writeFile();

 

//JSON.stringify 序列化

//JSON.parse 反序列化

 

/*

//默认覆盖文件

fs.writeFile(path.join(__dirname, './temp.txt'), { id: 10 }, (err) => {

    //要是写入对象,其实就是toString()了

 

    if (err) {

 

        //读文件是不存在报错

        //写文件可能出现的..意外错误

        //..文件权限问题

        //..文件夹找不到(不会自动创建文件夹)

 

        console.log('error');

 

    } else {

 

        console.log('success');

 

    }

 

});

 

*/

 

//fs.writeFileSync();

 

//java c

// try{

 

// } catch(error){

 

// }

 

//fs.createWriteStream();

 

// var streamWriter = fs.createWriteStream(path.join(__dirname,'./temp.txt'));

 

// setInterval(()=>{

 

// 不断的往里面写,代码操作的是内存中的数据,(不是磁盘中的数据),然后这个副本会写到磁盘中

 

//     streamWriter.write('hello',()=>{

//         console.log('+1');

//       });

     

   

// },1000);



/*追加

fs.appendFile(path.join(__dirname, './temp.txt'), JSON.stringify({ id: 10 }), (err) => {

    //要是写入对象,其实就是toString()了

 

    if (err) {

 

        //读文件是不存在报错

        //写文件可能出现的..意外错误

        //..文件权限问题

        //..文件夹找不到(不会自动创建文件夹)

 

        console.log('error');

 

    } else {

 

        console.log('success');

 

    }

 

});

 

*/

 

其他文件操作

fs.stat.isFile()…

.isDirectory…

重命名

fs.rename

fs.renameSync

 

删除文件

fs.unlink

fs.unlinkSync

//移动文件和重命名

//移动文件和重命名

 

const fs = require('fs');

const path = require('path');

 

var currentPath = path.join(__dirname,'./temp1.txt');

 

var targetPath = path.join(__dirname,'./temp2.txt');

 

fs.rename(currentPath,targetPath,(err)=>{

    if(err) {console.log(err)}

});

 

//和命令CMD中的mv操作一样。

 

//删除文件和rm操作差不多

 

打印当前目录文件列表

//打印当前目录所有文件

 

const fs = require('fs');

const path = require('path');

 

//获取当前有没有传入目标路径

 

var target = path.join(__dirname,process.argv[2] || './');





fs.readdir(target,(err,files)=>{

 

    files.forEach(file=>{

        //console.log(path.join(target,file));

   

        fs.statSync(path.join(target,file),(err,stats)=>{

         

 

          console.log(`${stats.mtime}\t${stats.size}\t${file})`);

 

        });

   

    });

 

});

 

 

递归目录树

//递归目录树

 

//1.先写一层的情况

//2.抽象出递归参数

//3.找到突破点(避免死循环)

//自己调自己, 某种情况(肯定会存在的)不调用

 

const fs = require('fs');

const path = require('path');

 

var target = path.join(__dirname, process.argv[2] || './');

 

var level = 0;

 

load(target,0);

 

function load(target,depth) {

    //depth 0 = ''

    //depth 1 = '| '

    //depth 2 = '| | '

    var prefix = new Array(depth+1).join('| ');

 

    var dirinfos = fs.readdirSync(target);

 

    var dirs = [];

    var files = [];

 

    dirinfos.forEach(info => {

 

        var stats = fs.statSync(path.join(target, info));

        if (stats.isFile()) {

            files.push(info);

        } else {

            dirs.push(info);

        }

 

    });

 

    //│└

    dirs.forEach(dir => {

        console.log(`${prefix}├─${dir}`);

 

        // 当前是一个目录 需要深入进去

        load(path.join(target,dir),depth+1);

 

    });

 

    var count = files.length;

    files.forEach(file => {

       

        console.log(`${prefix}${--count ? '├' : '└'}─${file}`);

    });

 

}


总结一下:

这部分最骚的操作就是递归,和打印目录列表

 

 

循环创建文件夹

Node基础

node中的文件创建并不能像CMD中的mkdir a/b这样连续创建文件夹,一次只能创建一个,所以这里写了一个函数,目的是能实现连续创建文件夹的效果。

1.js

const fs = require('fs');

const path = require('path');

 

const mkdirs = require('./mkdirs');

 

mkdirs('demo2/demo3');

 

 

mkdirs.js

//创建层及目录

 

const fs = require('fs');

const path = require('path');

 

//创建文件,定义模块成员,导出模块成员,载入模块,使用模块

 

function mkdirs(pathname, callback) {

 

    // 1.判断传入的是否是一个绝对路径

    // D:\a\demo2\demo3

    pathname = path.isAbsolute(pathname) ? pathname : path.join(__dirname, pathname);

 

    //获取要创建的部分

    // pathname = pathname.replace(__dirname,'');

    var relativepath = path.relative(__dirname,pathname);

   

    // ['demo2','demo3']

    var folders = relativepath.split(path.sep);

   

 

    try{

        var pre = '';

        folders.forEach(folder =>{

            fs.mkdirSync(path.join(__dirname,pre,folder));

            pre = path.join(pre,folder); //demo2

        });

        callback&&callback(null);

    }catch(error){

        callback && callback(error);

    }

 

}

 

module.exports = mkdirs;

 

 

为啥node_modules里面有那么多文件夹?

顺带一提:windows下如果有好多文件夹嵌套会产生好多奇怪的bug

node里面使用的是平行依赖。

平行依赖与递归依赖

Node基础

 

 

 

__dirname不能乱用

如果创立了一个module文件夹,然后把写好的mkdir放到里面,那么引用的时候当解析到mkdir.js的__dirname字段时会被认为是module文件夹,所以__dirname不能乱用,这里我们使用了path.dirname(module.parent.filename)。

 

最终的mkdirs.js

//创建层及目录

 

const fs = require('fs');

const path = require('path');

 

//创建文件,定义模块成员,导出模块成员,载入模块,使用模块

 

function mkdirs(pathname, callback) {

 

    //module.parent 拿到的是调用我的对象 02.js

   // console.log(module.parent);

   

     var root = path.dirname(module.parent.filename);

 

    //  console.log(root);

 

    // 1.判断传入的是否是一个绝对路径

    // D:\a\demo2\demo3

    pathname = path.isAbsolute(pathname) ? pathname : path.join(root, pathname);

 

    //获取要创建的部分

    // pathname = pathname.replace(root,'');

    var relativepath = path.relative(root,pathname);

   

    // ['demo2','demo3']

    var folders = relativepath.split(path.sep);

   

 

    try{

        var pre = '';

        folders.forEach(folder =>{

          

           try{

            //如果不存在则报错

            fs.statSync(path.join(root,pre,folder));

           

            } catch(error){

 

                fs.mkdirSync(path.join(root,pre,folder));

 

            }

          

          

            pre = path.join(pre,folder); //demo2

        });

        callback&&callback(null);

    }catch(error){

        callback && callback(error);

    }

 

}

 

module.exports = mkdirs;

 

 

监视文件变化变为HTML文件

利用文件监视实现markdown文件转换

4.js

//Markdown文件自动转换

 

/*

思路

1.利用fs模块的文件监视功能监视指定MD文件

2.当文件发生变化后,借助marked包提供的markdown to html功能改变后的MD文件转换为HTML

3.再将得到的HTML替换到模板中

4.最后利用BrowserSync模块实现浏览器自动刷新

*/

 

const fs = require('fs');

const path = require('path');

const marked = require('marked');

 

//接收需要转换的文件路径

const target = path.join(__dirname,process.argv[2]||'./readme.md');

 

//监视文件变化

fs.watchFile(target,(curr,prev)=>{

 

   // console.log(`current:${curr.size};previous:${prev.size}`)

  

   // 判断文件到底有没有变化

   if(curr.mtime === prev.mtime){

       return false;

   }

  

   // 读取文件 转换为新的HTML

   fs.readFile(target,'utf8',(err,content)=>{

      if(err){throw err;}

 

      var html = marked(content);

    

      console.log(html);

      html = template.replace('{{{content}}}',html);

      fs.writeFile(target.replace('.md','.html'),html,'utf8');

 

   });

 

var template = `

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <meta http-equiv="X-UA-Compatible" content="ie=edge">

    <title>Document</title>

</head>

<body>

     <div>

        {{{content}}}

     </div>

</body>

</html>

`;

 

});

 

 

文件流的概念

Node基础

装着字节的大水流

Node基础

Node基础

node中没有copy的方法,我们可以用流来复制。

 

Node基础

 

 

写文件及进度条功能的实现

 

// 文件流的方式复制

 

const fs = require('fs');

const path = require('path');

 

// 创建文件的读取流,并没有读出真实的数据,开始了读取文件的任务

 

console.time('start');

//执行到这里的时候已经从线程池中调度线程,往缓冲区中塞数据了

var reader = fs.createReadStream('D:\\1.txt');

console.timeEnd('start');

 

// 都文件内容与读文件信息不一样,都文件信息很快fsstat读取的是文件的元信息,所以很快

fs.stat('D:\\1.txt', (err, stats) => {

 

    if (err) throw err;

 

    if (stats) {

 

        var total = 0;

        //当缓冲区中塞了数据,就开始处理

        reader.on('data', (chunk) => {

            //chunk是一个buffer(字节数组)

 

            total += chunk.length;

            console.log('读了一点 进度:' + total / stats.size *100 + '%');

      

        });

    }

 

})

Node基础

文件流写入

在内存中再建立一个缓冲区,是写入的缓冲区。

 

 

代码:

// 文件流的方式复制

 

const fs = require('fs');

const path = require('path');

 

// 创建文件的读取流,并没有读出真实的数据,开始了读取文件的任务

 

//执行到这里的时候已经从线程池中调度线程,往缓冲区中塞数据了

var reader = fs.createReadStream('D:\\1.txt');

 

var writer = fs.createWriteStream('D:\\2.txt');

 

// 都文件内容与读文件信息不一样,都文件信息很快fsstat读取的是文件的元信息,所以很快

fs.stat('D:\\1.txt', (err, stats) => {

 

    if (err) throw err;

 

    if (stats) {

 

        var totalRead = 0;

        var totalWrite = 0;

        //当缓冲区中塞了数据,就开始处理

        reader.on('data', (chunk) => {

            //chunk是一个buffer(字节数组)

 

 

            writer.write(chunk,(err)=>{

                 totalWrite += chunk.length;

                console.log('写了一点 进度:' +  totalWrite/ stats.size *100 + '%');

      

            });

 

            totalRead += chunk.length;

            console.log('读了一点 进度:' + totalRead / stats.size *100 + '%');

      

        });

    }

 

})



pipe方法
之前的方式就像是拿一个水瓢(缓冲区),舀出来一点,到出去一点,现在介绍一种方法叫pipe,管道,直接拿个管子把俩连起来。

 

 

node progress 进度条

 

这里讲了一下node progress包

https://www.npmjs.com/package/progress

回顾一下npm命令

npm init –y

npm ls展示当前目录所有依赖包

npm ls –depth 0 展示第0层目录

 

 

学个新单词

Token令牌 标识

 

 

 

 

 

 

 

 

 

 

 

 

Socket基础

 

Socket

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。

建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。

Socket可以理解为两个插孔,中间连上一根线就可以打电话啦。

Node基础

 

 

(在这个教程中视频讲师说socket是这个双向的连接,这个双向连接叫socket,和百度百科定义不太一样)

 

*介绍一下nodemon 会根据文件变化自动运行,而不用每次都用node命令运行。

npm install nodemon –g

nodemon 4.js

(然而并没有什么卵用,后面还是用的node命令来运行)

 

一个简单的server和client利用socket通讯示例:

server.js:

// 建立一个Socket服务端

const net = require('net');

 

var server = net.createServer((socket)=> {

 

    console.log(socket); console.log(socket); console.log(socket);

   

    //有客户端连接时触发

    var clientIp = socket.address();

    console.log(`${clientIp.address} connectioned`);

 

    //监听数据

    socket.on('data', (chunk) => {

        process.stdout.write('\nclient> ');       

        console.log(chunk.toString());

        socket.write('你说啥?');

    });

});

 

var port = 2080;

 

server.listen(port, (err) => {

    if (err) {

        console.log('端口被占用了');

        return false;

    }

    console.log(`服务器正常启动监听【${port}】端口`);

});

 

 

client.js:

//建立socket客户端

const net = require('net');

 

const socket = net.createConnection({ port: 2080 }, () => {

    //'connect' listener

    console.log('Already connected to server');

 

    process.stdout.write('\nclient> ');

    process.stdin.on('data', (chunk) => {

        // 控制台输入回车

        var msg = chunk.toString().trim();

        socket.write(msg);

    });

});

 

socket.on('data', (data) => {

  process.stdout.write('\nserver> ');

  console.log(data.toString());

  process.stdout.write('\nclient> ');

  //socket.end();

});

 

// socket.on('end', () => {

//   console.log('disconnected from server');

// });

 

 

Usage:

node server.js

node client.js

 Node基础

因为只有Socket服务端启动了监听。Socket客户端没有启动监听,所以客户端之间不能互相通信。 上面例子完成了与服务器通信的简单功能,还可以通过指定一些特定的数据格式(协议),来实现更复杂的功能。

通过制定一个协议实现聊天室服务端

 sever.js

// 建立一个Socket服务端

const net = require('net');

 

// 用于存储所有的连接

var clients = [];

 

var server = net.createServer((socket) => {

   

    //socket对象push进去

    clients.push(socket);

 

    //广播方法

    function broadcast(signal){

          console.log(signal);

          // 肯定有用户名和消息

          var username = signal.from;

          var message = signal.message;

          // 我们要发给客户端的东西

          var send = {

              protocal:signal.protocal,

              from:username,

              message:signal.message

            };

 

          // 广播消息

        clients.forEach(client => {

          client.write(JSON.stringify(send));       

        });

 

    }

 

    socket.on('data', (chunk) => {

 

        // chunk:broadcast|张三|弄啥咧!

        //        协议     用户名 消息

        // chunk:{'protocal':'broadcast','from':'张三','message':''}

        // chunk:{'protocal':'p2p',from:'张三',to:'李四',message':''}

 

        try {

            var signal = JSON.parse(chunk.toString().trim());

            var protocal = signal.protocal;

 

            switch (protocal) {

                case 'broadcast':

                    broadcast(signal);

                    break;

                case 'p2p':

                    p2p(signal);

                    break;

                case 'shake':

                    shake(signal);

                    break;

                default:

                    socket.write('协议的protocal字段有问题!');

                    break;

            }

 

            // var username = signal.from;

            // var message = signal.message;

 

        }

        catch (err) {

            socket.write('数据格式有问题!');

            throw err;

        }

 

        // 有任何客户端发消息都会触发

        // var username = chunk.username;

        // var message = chunk.messge;

        // broadcast(username.message)

    });

 

});

 

var port = 2080;

 

server.listen(port, (err) => {

    if (err) {

        console.log('端口被占用了');

        return false;

    }

    console.log(`服务器正常启动监听【${port}】端口`);

});

 

 

client.js

//客户端

const net = require('net');

const readline = require('readline');

 

 

const rl = readline.createInterface({

    input: process.stdin,

    output: process.stdout

});

 

rl.question('What is your name? ', (name) => {

 

    name = name.trim();

    if (!name) throw new Error('姓名有问题!');

    //创建与服务端的连接

 

    var socket = net.createConnection({ port: 2080 }, () => {

        console.log(`Welcome ${socket.remoteAddress} ${name} to 2080 chatroom`);

      

        //监听服务端发过来的数据

        socket.on('data', (chunk) => {

            try {

                var signal = JSON.parse(chunk.toString().trim());

                var protocal = signal.protocal;

                switch (protocal) {

                    case 'broadcast':

                        console.log(`[broadcast]"${signal.from}"说了:${signal.message}`);

                        rl.prompt();

                        break;

                    default:

                        server.write('数据协议字段有问题');

                        break;

                }

            }

            catch (err) {

                socket.write('数据格式有问题!');

                throw err;

            }

 

        });





        rl.setPrompt(`${name}> `);

 

        rl.prompt();

 

        rl.on('line', (line) => {

 

            // chunk:{'protocal':'broadcast','from':'张三','message':''}

            var send = {

                protocal: 'broadcast',

                from: name,

                message: line.toString().trim()

            };

 

            socket.write(JSON.stringify(send));

 

            rl.prompt();

 

        }).on('close', () => {

            //console.log('Have a great day!');

            //process.exit(0);

        });

 

    });

 

});



HTTP是超文本传输协议

上面的例子也是一种协议。

 

 

第五天

CMD的netstat命令

 Netstat是在内核中访问网络连接状态及其相关信息的程序,它能提供TCP连接,TCP和UDP监听,进程内存管理的相关报告。

 

处理服务器异常

之前写的代码中,如果有客户机取消,那么所有都取消了,这个怎么处理呢?

 

答:很简单,只要在socket on data的监听事件中添加error监听事件,就不会全部取消了。

socket.on('error',(err)=>{

 

       //console.log(err) 这个错误也可以不log出来,只要捕获就没事儿

    });

 

 

最终写好的聊天程序,支持p2p协议。

server.js:

// 建立一个Socket服务端

const net = require('net');

 

// 用于存储所有的连接

var clients = [];

 

// var clients = [

//      {

//       name:..

//       socket:..

//      }

// ]

 

var signin = false;

 

var server = net.createServer((socket) => {



    //建立了连接,代表登录

    signin = true;

 

    var logName;

 

    //socket对象push进去

    clients.push({ socket: socket });

 

    socket.on('data', clientData).on('error', (err) => {

 

        for (let i = 0; i < clients.length; i++) {

            if (clients[i].socket == socket) {

                logName = clients[i].name;

                clients.splice(i, 1);

            }

        }

 

        console.log(`${socket.remoteAddress} ${logName} 下线了 当前在线${clients.length}人`);

 

    });



    //广播方法

    function broadcast(signal) {

        console.log(signal);

        // 肯定有用户名和消息

        var username = signal.from;

        var message = signal.message;

        // 我们要发给客户端的东西

        var send = {

            protocal: signal.protocal,

            from: username,

            message: message

        };

 

        // 广播消息

        clients.forEach( client  => {

            client.socket.write(JSON.stringify(send));

        });

 

    }

 

    //点对点消息

    function p2p(signal) {

        console.log(signal);

        // 肯定有用户名和消息

        var username = signal.from;

        var target = signal.to;

        var message = signal.message;

 

        // 我们要发给客户端的东西

        var send = {

            protocal: signal.protocal,

            from: username,

            message: message

        };

 

       // console.log(`${username}要发给${target}的内容是${message}`);



        clients.forEach(client=>{

 

        

            if(client.name == target)

            {

                client.socket.write(JSON.stringify(send));

            }

 

        })

        // clients[target].write(JSON.stringify(send));

 

    }



    function clientData(chunk) {

 

        //如果是第一次连接,就是在登录,在传入用户名

        if (signin == true) {

            //第一次回传入用户名

            var JSONname = chunk.toString();

            var Objname = JSON.parse(JSONname);

            logName = Objname.name;

 

            clients[clients.length - 1]['name'] = logName;

            signin = false;

 

            console.log(`${socket.remoteAddress} ${logName} 上线了,当前在线${clients.length}人`);

 

        }

 

       

 

        // chunk:broadcast|张三|弄啥咧!

        //        协议     用户名 消息

        // chunk:{'protocal':'broadcast','from':'张三','message':''}

        // chunk:{'protocal':'p2p',from:'张三',to:'李四',message':''}

 

        try {

            var signal = JSON.parse(chunk.toString().trim());

            var protocal = signal.protocal;

 

           // console.log(chunk.toString());

 

            switch (protocal) {

                case 'broadcast':

                    //console.log('要broadcast了');

                    broadcast(signal);

                    break;

                case 'p2p':

                    // console.log('要p2p了!');

                    p2p(signal);

                    break;

                // case 'shake':

                //     shake(signal);

                //     break;

                // default:

                //     socket.write('协议的protocal字段有问题!');

                //     break;

            }

 

            // var username = signal.from;

            // var message = signal.message;

 

        }

        catch (err) {

            socket.write('数据格式有问题!');

            throw err;

        }

 

        // 有任何客户端发消息都会触发

        // var username = chunk.username;

        // var message = chunk.messge;

        // broadcast(username.message)

    };

 

});

 

var port = 2080;

 

server.listen(port, (err) => {

    if (err) {

        console.log('端口被占用了');

        return false;

    }

    console.log(`服务器正常启动监听【${port}】端口`);

});

 

client.js:

//客户端

const net = require('net');

const readline = require('readline');

 

const rl = readline.createInterface({

    input: process.stdin,

    output: process.stdout

});

 

rl.question('What is your name? ', (name) => {

 

    name = name.trim();

    if (!name) throw new Error('姓名有问题!');

    //创建与服务端的连接

 

    //还可以传入一个参数host:192.xx...

    var socket = net.createConnection({ port: 2080 }, () => {

 

 

 

        console.log(`Welcome ${socket.remoteAddress} ${name} to 2080 chatroom`);

 

        //登录

        socket.write(JSON.stringify({name:name}));

 

    

 

        //监听服务端发过来的数据

        socket.on('data', (chunk) => {

            try {

                var signal = JSON.parse(chunk.toString().trim());

                var protocal = signal.protocal;

                switch (protocal) {

                    case 'broadcast':

                        console.log(`[broadcast]"${signal.from}"说了:${signal.message}`);

                        rl.prompt();

                        break;

                    case 'p2p':

                        console.log(`[p2p]${signal.from}说了:${signal.message}`);

                        rl.prompt();

                        break;

                    default:

                        server.write('数据协议字段有问题');

                        break;

                }

            }

            catch (err) {

                socket.write('数据格式有问题!');

                throw err;

            }

 

        });





        rl.setPrompt(`${name}> `);

 

        rl.prompt();

 

        rl.on('line', (line) => {

 

            line = line.toString().trim();

            var temp = line.split(':');

            var send;

 

            if (temp.length === 2) {

                //点对点消息

                //console.log('这是一个点对点消息');

 

                send = {

                    protocal: 'p2p',

                    from: name,

                    to: temp[0],

                    message: temp[1]

                };

 

            } else {

                //广播消息

 

                // chunk:{'protocal':'broadcast','from':'张三','message':''}

                send = {

                    protocal: 'broadcast',

                    from: name,

                    message: line.toString().trim()

                };

 

            }

 

            socket.write(JSON.stringify(send));

           

 

            rl.prompt();

 

        }).on('close', () => {

            console.log('Have a great day!');

            process.exit(0);

        });

 

    });

 

});

usage

node server.js

node client.js

 

 

总结一下,复习,自己写一下简单的net模块使用流程:

server.js:

const net = require('net');

 

var server = net.createServer((socket) => {

 

    console.log(`${socket.remoteAddress}连接上了我的服务器`);

 

    socket.on('data', (chunk) => {

 

        console.log(chunk.toString());

 

    }).on('err', (err) => { console.log(err) });

 

});

 

server.listen({ port: 2888 }, (err) => {

 

    if (err) throw err;

 

    console.log('在2888端口创立了服务器');

 

})

 

 

client.js:

const net = require('net');

const readline = require('readline');

const rl = readline.createInterface({

        input:process.stdin,

        output:process.stdout

});

 

rl.setPrompt('> ');

rl.prompt();

 

rl.question('what is your name?',(name)=>{

 

   name = name.toString().trim();

 

   var socket =  net.createConnection({port:2888},(err)=>{

 

    if(err) throw err;   

 

    console.log('连接上服务器了');

 

    rl.prompt();

 

    socket.write(name);

 

                rl.on('line',(line)=>{

 

                    socket.write(line.toString());

                    rl.prompt();              

               

                });

   });  

 

});




浏览器的作用

HTTP也是一种协议,

Node基础

 

 

 Node基础

浏览器的本质作用:

将用户输入的URL封装为一个请求报文。

建立与服务端的Socket链接

将刚刚封装好的请求报文通过socket发送到服务端。

socket.write(JSON.stringify(send));

。。。。。。。。

接收到服务端返回的响应报文。

解析响应报文(类似于我们写的那个JSON.parse…)

渲染内容到页面中(类似于我们的console.log(msg))

 

 

浏览器就是一个socket客户端

 

 

Node基础

CR+LF = \r \n 有一个回车符,有一个换行符

Node基础

 

 

URI(统一资源标识符)是URL(统一资源定位符)的超集

统一资源标志符URI就是在某一规则下能把一个资源独一无二地标识出来。

那统一资源定位符URL是什么呢。也拿人做例子然后跟HTTP的URL做类比,就可以有:

动物住址协议://地球/中国/浙江省/杭州市/西湖区/某大学/14号宿舍楼/525号寝/张三.人

可以看到,这个字符串同样标识出了唯一的一个人,起到了URI的作用,所以URL是URI的子集。URL是以描述人的位置来唯一确定一个人的。
在上文我们用身份证号也可以唯一确定一个人。对于这个在杭州的张三,我们也可以用:

身份证号:1234567

 

Node基础

 

http模块发送

 

const http = require('http');

const fs = require('fs');

 

//利用node做一个客户端 请求m.baidu.com

 

http.get('http://m.baidu.com',(response)=>{

 

var rawContent = '';

 

response.on('data',(chunk)=>{

 

 rawContent += chunk.toString();

 

});

 

response.on('end',()=>{

 

    //后面可以做爬虫哟

    fs.writeFile('./1.txt',rawContent,(err)=>{

 

        if(err) throw err;

   

        console.log('出来了');

    })



    console.log(response.headers['content-length']);

    //下面是不包含头的长度

    console.log(rawContent.length);

   

});




});

看一下get方法:

Node基础

返回值是一个叫IncomingMessage的类

Node基础

 

 Node基础

 

 

IncomingMessage继承于流。

所以会有on(‘data’)和on(‘end’)事件。

 

相关文章:

  • 2021-11-17
  • 2022-01-07
  • 2021-08-01
  • 2021-06-12
  • 2021-12-10
  • 2022-01-30
  • 2021-10-27
  • 2021-10-09
猜你喜欢
  • 2021-10-30
  • 2022-12-23
  • 2021-08-11
  • 2021-11-15
  • 2021-04-16
相关资源
相似解决方案