第一天

 

Node简介

 

-什么是Javascript

+脚本语言

+运行在浏览器中

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

 

-JaveScript的运行环境?

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

   ,不够严谨

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

    

浏览器的作用

Node基础_html

-浏览器中的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基础_数据_02

 

 Node基础_客户端_03

Node基础_数据_04

Node在Web中的用途

 

·分发数据请求,渲染HTML

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

Node基础_html_05

常见命令行操作

 

Path变量

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

Node基础_Nodejs_06

*Windows默认使用反斜线\

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

 

 

Node REPL环境

 Node基础_json_07

Node基础_客户端_08

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

 

process.argv

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

 

process.stdout

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

 

 

 

Debug

 Node基础_json_09

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

 

  …

 

})

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

所以要trim掉

 

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

 

process.stdout.write();

 

 

 

异步操作

 

console.time(‘’)

console.timeEnd(‘’)

 

 

 

事件队列:

Node基础_Nodejs_10

var fs = require(‘fs’);

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

  if(err) throw err;

console.log(data);

})

 Node基础_json_11

 

第二天

 

错误优先的回调函数

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

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

Node基础_客户端_12

Node基础_html_13

 

进程和线程

进程:进行中的程序

Node基础_数据_14

 

 

 Node基础_html_15

Node基础_Nodejs_16

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

 

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

 

多线程都是假的,因为只有一个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基础_Nodejs_17

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

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

 

Node为什么能实现非阻塞,

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

 

调度。

Node基础_Nodejs_18

 

 Node基础_json_19

Node基础_Nodejs_20

 

 

非阻塞的优势

·提高代码的相应效率

·充分利用单核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基础_客户端_21

 

 

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

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

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

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

 

模块化的开始

Node基础_数据_22

 

 

//实现命令行计算器

 

//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基础_html_23

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

Node基础_Nodejs_24

 

 

CommonJS规范

在Node中实现的是CommonJS规范

 

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

Node基础_Nodejs_25

Node基础_Nodejs_26

文件模块 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基础_Nodejs_27

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

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

 

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

 

自己实现一个require

 

Node基础_数据_28

 

 

 

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基础_json_29

 

 

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

 

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

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

package.json中main指向的文件,

Node基础_客户端_30

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

 

 

 

 

 

require模块加载机制

Node基础_Nodejs_31

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

 

 

模块的require缓存

 Node基础_html_32

date.js:

module.exports =  new Date();

 

 

1.js:

//模块的缓存

 

setInterval(()=>{

 

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

  console.log(date.getTime());

 

},1000);

 

运行结果:

Node基础_数据_33

说明模块有缓存机制。

 

 

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基础_数据_34

Node基础_数据_35

 

NPM包管理工具

 

where node

npm config ls

 

Node基础_客户端_36

Node基础_html_37

全局配置文件 npmrc

Node基础_json_38

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基础_客户端_39

再看npm config ls

 

 

文件系统操作

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

fs:

path:

readline:

 

Windows默认编码 GBK

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

 

path模块操作

详见文档

path.relative(from,to)

path.resolve

同步调用和异步调用

fs.readFile

fs.readFileSync

 

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

buffer

 

Node基础_html_40

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

Node基础_客户端_41

 

Node基础_html_42

LE、BE

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

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

Node基础_json_43

Node基础_Nodejs_44

 

 

转换成Base64

Node基础_数据_45

通过Data:URL协议来打开看

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

一般的图片都会单独请求

Node基础_数据_46

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

 

 

文件编码的问题

 

滚动显示歌词

// 动态显示歌词

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基础_数据_47

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基础_数据_48

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基础_Nodejs_49

 

 

 

__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基础_Nodejs_50

装着字节的大水流

Node基础_数据_51

Node基础_Nodejs_52

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

 

Node基础_数据_53

 

 

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

 

// 文件流的方式复制

 

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基础_客户端_54

文件流写入

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

 

 

代码:

// 文件流的方式复制

 

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基础_json_55

 

 

(在这个教程中视频讲师说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基础_json_56

因为只有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基础_html_57

 

 

 Node基础_html_58

浏览器的本质作用:

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

建立与服务端的Socket链接

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

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

。。。。。。。。

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

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

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

 

 

浏览器就是一个socket客户端

 

 

Node基础_客户端_59

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

Node基础_Nodejs_60

 

 

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

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

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

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

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

身份证号:1234567

 

Node基础_json_61

 

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基础_数据_62

返回值是一个叫IncomingMessage的类

Node基础_数据_63

 

 Node基础_Nodejs_64

 

 

IncomingMessage继承于流。

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