node -01
	node是什么
		node是用于编写服务端应用的语言
		javascripte 核心语法
		只是操作的对象不同
	前后端的关注点
		前端
			Bom
				文档对象
			BOM/DOM
				游览器对象
			XMLHttpRequest/fetch
				网络通讯
		后端
			os
				操作系统
			process 
				进程/ 子进程
			fs
				文件系统
			net
				网络通讯

node-02 运行和调试
	运行方式
		1 终端运行node  first.js 文件
		2 使用nodemon ,对文件修改进行监控自动执行
			1 全局安装  npm i nodemon -g
			2 终端运行  nodemon first.js 文件,文件信息修改后, 自动监控, 自动运行, 时刻监控,时刻运行
		3  使用debug 进行断点调试
			左侧点击断点位置, debugger  再选择node , 就可以了,也可以查看上下文环境
				运行调试
		4 单元测试jest 
			1  安装jest 库,  npm install jest -g
				1 进行全局安装
			运行异常设置npm init
				局部的启动项目
					npm inint -y
					npm install jest -d--dev
					"scripts": {
                                                "test": "jest"
                                         },
					npm run test
			在index.js 中, 同目录下增加__test__同文件夹
			增加同名, 例如: index.spec.js文件内
				test('测试Hello World!', () => {
                                        const helloWord = require('../index');
                                        console.log(helloWord, 'hello world');
                                    })
					自动识别
		/Users/dengyunfa/Desktop/quanzhang/kaikeba/nodeproject/helloWorld/__test__/index.spec.js
	test('测试Hello World!', () => {
        const t = require('../index');
        expect(t).toBe('Hello World!')
     });


					断言判断
			在外层设置 jest helloWold 执行测试
				在 helloWorld 这一层执行 jest helloWorld命令
				实时监控的命令  jest helloWorld --watch, 实时监控
					自动检测

自由主题

node-03文件自动的生成
	测试路径的生成
		const path = require('path');
// const path= require()

module.exports = class TestNow {
    /**
     * 生成测试文件名
     * @param {*} fileName  代码的文件名
     */
    getTestFileName(fileName) {
        // dirname/__test__/
        debugger;
        const dirName = path.dirname(fileName);  // 路径名称
        const baseName = path.basename(fileName);  // 文件名.扩展名   
        const extName =path.extname(fileName);  // 扩展名
        const testName = baseName.replace(extName, `.spec${extName}`)  // 修改文件名
        return path.format({
            root: dirName + '/__test__/',
            base: testName
        });
    }
}
/Users/dengyunfa/Desktop/quanzhang/kaikeba/nodeproject/helloWorld/testNow/index.js
test('测试文件名生成', () => {
    const src = new (require('../index'));
    const ret = src.getTestFileName('/abc/class.js');
    console.log('getTestFileName', ret);
    expect(ret).toBe('/abc/__test__/class.spec.js')
})
/Users/dengyunfa/Desktop/quanzhang/kaikeba/nodeproject/helloWorld/testNow/__test__/index.spec.js
testNow/__test__/index.spec.js

node-04 文件测试
	const path = require('path');
// const path= require()
const fs = require('fs');

module.exports = class TestNow {
    getJestSource(sourcePath = path.resolve('./')) {
        // 测试文件夹是否存在
        const testPath = `${sourcePath}/__test__`;
        // 文件夹是否存在
        if (!fs.existsSync(testPath)) {
            fs.mkdirSync(testPath);
        }
        // 遍历代码文件,下所有的文件
        let list = fs.readdirSync(sourcePath);
        // 路径进行拼接
        list
        .map(item => `${sourcePath}/${item}`)
        .filter(v => fs.statSync(v).isFile())  // 保存文件的部分
        // 排序测试代码, 函数spec 的文件
        .filter(v => v.indexOf('.spec') === -1)
        .map( v => this.getTestFile(v));  // 获取test 文件
    }

    getTestFile(filename) {
        console.log('filename', filename);
        const testFileName = this.getTestFileName(filename);
        // 判断此文件是否存在
        if (fs.existsSync(testFileName)) {
            console.log('该测试代码已经存在', testFileName);
            return 
        }
        const mod = require(filename);
        let source;
        if(typeof mod === 'object') {
            source = Object.keys(mod)
            .map(v => this.getTestSource(v, path.basename(filename), true))
            .join('\n')
            // 进行拼接
        } else if (typeof mod === 'function') {
            const basename = path.basename(filename);
            source = this.getTestSource(basename.replace('.js'), basename);
        }
        fs.writeFileSync(testFileName, source);
    }

    /**
     * 生成测试文件名
     * @param {*} fileName  代码的文件名
     */
    // 文件名称生成
    getTestFileName(fileName) {
        const dirName = path.dirname(fileName);  // 路径名称
        const baseName = path.basename(fileName);  // 文件名.扩展名   
        const extName =path.extname(fileName);  // 扩展名
        const testName = baseName.replace(extName, `.spec${extName}`)  // 修改文件名
        return path.format({
            root: dirName + '/__test__/',
            base: testName
        });
    }
    // 测试代码内容的生成
    getTestSource(methodName, classFile, isClass = false) {
        // methodName  测试方法名  classFile : 最后的一个路径名称 : 是否为对象
     //   console.log('getTestSource', methodName);
        return `test(${'Test' + methodName }, () => {
        const ${isClass ? '{' +  methodName  + ' }' : methodName} = require(${'../' + classFile });
        const ret = ${methodName}();
        // expect(ret)
        // .toBe('test return')
        })`
    }
}
module.exports = {
    fun01: () =>  'fun01 run',
    fun02: () =>  'fun02 fun'
}
		
test('测试文件名生成', ()=> {
    const TestNow = require('./index.js');
    const testNow = new TestNow();
    const ret = testNow.getTestFileName('/abc/class.js');
    console.log('getTestFileName', ret);
    expect(ret).toBe('/abc/__test__/class.spec.js');

});

test('测试测试代码生成', () => {
    const TestNow = require('./index.js');
    const testNow = new TestNow();
    const ret = testNow.getTestSource('fun', 'class', false);
    console.log('ret', ret);
    expect(ret).toBe(`
        test('Testfun' , () => {
            const fun = require('../class');
            const ret = fun();
            // expect(ret).toBe('test return ');
            // 
        })
        `)
});

test('集成测试 测试生成测试代码文件', () => {
    // 准备环境
    // 删除测试文件夹

    const fs = require('fs');
    // 文件夹内容的清除
    fs.rmdirSync(__dirname + '/data/__test__', {
        recursive: true
    });
    const TestNow = require('./index.js');
    const testNow = new TestNow();
    // 获取新的jest目录
    testNow.getJestSource(__dirname + '/data');
    
}); 
testNow/index.spec.js
test('测试文件名生成', ()=> {
 //   const TestNow = require('./index.js');
    const src = new (require('./index.js'))();
    const ret = src.getTestFileName('/abc/class.js');
    console.log('getTestFileName', ret);
    expect(ret).toBe('/abc/class.spec.js');

});
		const fs = require('fs');
const path = require('path');

module.exports = class TestNow {
    /**
     * 生成测试文件名
     * @param {*} fileName  代码文件名 
     * @returns 
     */
     getTestFileName(fileName) {
        // 目录名
        const dirName = path.dirname(fileName);
         // 文件名
        const baseName = path.basename(fileName);
        const extName= path.extname(fileName);
        const testName = baseName.replace(extName, `.spec${extName}`)
        return path.format({
            root: dirName + '/__test__/',
            base: testName
        });
    }

    getTestSource(methodName, classFile, isClass = false ) {  // 方法名称  文件路径, 是否为class
     //   console.log('getTestSource', methodName);
        return `
        test('Test${methodName}' , () => {
            const ${isClass ? '{' +methodName +'}' : methodName } = require('${'../' + classFile}');
            const ret = ${methodName}();
            // expect(ret).toBe('test return ');
            // 
        })
        `
    }

    getJestSource =(sourcePath = path.resolve('./')) => {  // 在当前目录
        debugger;
        const testPath = `${sourcePath}/__test__`;
        // 文件夹是否存在
        if (!fs.existsSync(testPath)) {
            fs.mkdirSync(testPath);
        }
        // 遍历代码文件
        let list = fs.readdirSync(sourcePath); // 读取内部的文件
        debugger;
        list
            .map(v => `${sourcePath}/${v}`)
            .filter(v => fs.statSync(v).isFile())  // 
            .filter(v =>  v.indexOf('.spec')  === -1)
            .map(v => this.getTestFile(v));
        // 过滤文件




    } 
    getTestFile = (fileName) => {
        debugger;
        console.log('fileName', fileName);
        const testFileName = this.getTestFileName(fileName);
        // 判断文件是否存在
        if (fs.existsSync(testFileName)) {
            console.log('该测试代码已经存在, ', testFileName);
            return ;
        }
        const mode = require(fileName);
        let source;
        if (typeof mode === 'object') {
            source = Object.keys(mode)
                .map( v => this.getTestSource(v, path.basename(fileName), true))
                .join('\n')
        } else if (typeof mode === 'function') {
            const baseName = path.basename(fileName);
            source = this.getTestSource(baseName.replace('.js', ''), baseName);
        }
        debugger;
        fs.writeFileSync(testFileName, source);

    }
 
}

index.js
			子主题 2
module.exports = {
    fun01 : () => 'fun01 run',
    fun02: () => 'fun02 run'
}
		testNow/data/class.js
	module.exports = () => 'function run'
		testNow/data/fun.js

node-06 异步
异步的编程 js的执行环境是单线程
I/O处理需要回调函数异步处理(异步I /O)
前端异步IO可以消除UI阻塞,提高用户体验
而放在后端可以提高CPU和内存的利用率
const logTime = (name) => {
    console.log(`Log ..... ${name}` + new Date().toLocaleDateString());
}

module.exports.callback = () => {
    setTimeout(() => {
        logTime('callback 1');
        setTimeout(() => {
            logTime('callback 2');
            setTimeout(() => {
                logTime('callback 3');
                setTimeout(() => {
                    logTime('callback 4');
                }, 100)
            }, 100)
        }, 100)
    }, 100);
}
Promise
Promise 对象用于异步操作, 它标示尚未完成且预计在未来完成的异步操作
		
const promise = (name, delay = 100) => new Promise(resolve => {
    setTimeout(() => {
        logTime(name);
        resolve();
    }, delay)
});
		
const promise = (name, delay = 100) => new Promise(resolve => {
    setTimeout(() => {
        logTime(name);
        resolve();
    }, delay)
});
	generagor函数
		
function * func() {
    console.log('one');
    yield '1';
    console.log('two');
    yield '2';
    console.log('three');
    yield '3';
    
}/*

const f = func();
console.log(f.next());
console.log(f.next());
console.log(f.next());
console.log(f.next());
*/
for (const [key, value] of func()) {
    console.log(`${key} :: ${value}`)
}
			
module.exports.generator = () => {
    const generator = function * (name) {
        yield promise(name + 1);
        yield promise(name + 2);
        yield promise(name + 3);
    }
    let co = generaFun => {
        if (it = generaFun.next().value ){
            it.then(res => {
                co(generaFun)
            })
        } else {
            return 
        }
    }
    co(generator('Co-Generator'))
}
	async /aswait
async await 是es6 推出的一套关于异步的终极解决方案
任何一个await 语句后面的Promise 对象变成为reject状态, 那么整个async函数都会中断执行
async 函数返回的promise 对象, 必须等到内部所有的await命令后面的Promise对象执行完成才能状态改变, 除非遇到return 语句或者抛出错误, 也就是说, 只有asymc函数内部的异步操作执行后,才能执行then方法执行的回调函数
		
module.exports.asyncAwait = async () => {
    await promise('Async/await 1');
    await promise('Async/await 2');
    await promise('Async/await 3');
    await promise('Async/await 4');
}
	事件监听模式处理
		任务的执行不取决于代码的顺序, 而是取决于事件是否发生
		
module.exports.event = async () => {
    const asyncFunc = name => event => {
        setTimeout(() => {
            logTime(name);
            event.emit('end');
        });
        return event;
    }
    const ary = [
        asyncFunc('event 1'),
        asyncFunc('event 2'),
        asyncFunc('event 3')
    ];
    const { EventEmitter} = require('events');
    const event = new EventEmitter();
    let i = 0;
    event.on('end', () => i < ary.length && ary[i++](event))
    event.emit('end');
}

自由主题

自由主题

04-07  node作业讲解

node-05 文件测试生成
	
    getTestSource(methodName, classFile, isClass = false ) {  // 方法名称  文件路径, 是否为class
     //   console.log('getTestSource', methodName);
        return `
        test('Test${methodName}' , () => {
            const ${isClass ? '{' +methodName +'}' : methodName } = require('${'../' + classFile}');
            const ret = ${methodName}();
            // expect(ret).toBe('test return ');
            // 
        })
        `
    }
	
test('测试测试代码生成', () => {
    const TestNow = require('./index.js');
    const testNow = new TestNow();
    const ret = testNow.getTestSource('fun', 'class', false);
    console.log('ret', ret);
    expect(ret).toBe(`
        test('Testfun' , () => {
            const fun = require('../class');
            const ret = fun();
            // expect(ret).toBe('test return ');
            // 
        })
        `)
});


node07 缓存
	/* 
http 缓存
web 缓存是什么
动机: 当游览器加载一个页面时, html 会引起引用的外部资源会加载,但这些外部资源比如
图片, css, js 都不经常变化, 如果每次都加载这些资源会带来资源的浪费, 而且加载时间
过长会影响用户的体验

http 缓存技术, 就是为了解决这个问题出现的。简单来说: http 缓存就是将静态资源
缓存在游览器内部, 下次请求相同的资源是可以直接使用
当然何时使用何时不实用要有一系列的策略来保证如果资源一旦更新, 缓存也会随之更新

作用: 提高首屏加载速度 -优化用户体验
流量小号
减轻服务器压力

http 的缓存分两类, 强缓存 / 协商缓存
强缓存: 更新的时间固定
 直接从本地副本对比读取, 不去请求服务器, 返回的状态码是200
这是有一个问题, 如果不去服务器请求, 如果静态资源有更新, 
使用定时器的方式, 就是强缓存可以设置静态资源的有效时间 
,如果超过有效期,则认为缓存失效

强缓存是设置参数
在http 1. 0 版本
在response 返回头中设置
expires: Thu, 03 Jan 2019 11:43: 04 GMT    格林威治的时间,
没有超过则不会更新
res.setHeader('Expires', new Date(Date.now() + 5 * 1000).toUTCString())

  http 1.0 版本
     第一次后, 10秒后过过期,Expires
        过期的时间来自服务器, 问题是客户机同服务器的时间有差异, 所以不好
 http 1.1 版本
    cache-control  http1.1 使用cache-control 来解决这个问题,
        /*
        publicc : 所有的内容都会被缓存(客户端和代理服务器都可以缓存)
        private: 内容只缓存在私有缓存中(客户端可以缓存)
        no-cache: 需要使用协商缓存来验证缓存数据, 不需要你来缓存
        no-store :  所有的内容都不会被缓存
        max-age =xxx(xxx:  缓存的内容 将在xxx 秒后失效, 这个选项只在http1.1
            is numeric)  上可以使用, 并如果和Last-Modified 一起使用时, 优先级较高
Last-Modified: 时间来自服务器, 比客户端的时间更精准
        
     */
/*
res.setHeader('Expires', new Date(Date.now() + 5 * 1000).toUTCString());
res.setHeader('Cache-Control', 'max-age=20'); // Cache-Control 相对于Expires 的优先级更高

*/

/*
强缓存的expires 和 Cache-Control 都会反问本地缓存直接验证是否过期,
如果没有过期, 则直接使用本地缓存, 并返回200,但如果设置了no-cache 和no-store
则本地缓存会被忽略, 会去请求服务器验证资源是否更新, 如果没有更新, 才继续使用
本地缓存, 此时返回的是304, 这就是协商缓存, 协商缓存主要报错
last-modified 和etg
协商缓存: 更新的时间不固定, 可能多次更新,所以需要询问后端是否更新
协商缓存:简单说就是游览器和服务器间就是否使用缓存来做协商



如果协商的结果是需要更新就会返回200, 并返回更新的内容。
如果不需要只需要返回状态码304, 不用返回内容, 这样虽然需要后端应答,但是后端
既不需要生成内容也不需要传输内容, 依然可以享受缓存的种种好处


协商的结果是用缓存还是不用缓存, 协商可能就在代理服务器上, 不会到应用服务器,
如果需要更新则返回200, 包含更新的内容
如果不需要更新则返回304,  不包含缓存的内容
协商缓存又可以分为 
last-modified  基于时间的
etag          基于内容的

第一种   last-modified  & if -Modified-since
这是一组通过协商修改时间的策略

客户端                                   服务器
 1 客户端请求index.js                      
  2  应答 index.js    
  响应头中增加 last-modified :  Mon, 06 Apr 2020 07:47:25 GMT
 3 第二次请求 index.js
 IF-Mofified-Since:    Mon, 06 Apr 2020 07:47:25 GMT         
   4  第二次应答
  (1) 已经过期:   200 index.js 
    (2) 为过期应答       304  , 没有内容    
 第二种方式
 另外一种是通过内容来判断, 一般的做法是嫁将返回的内容进行摘要(hash), 然后对比zhaiyoa 来判断内容是够更新        

        
 
客户端                                   服务器
 1 客户端请求index.js                      
 2  应答 index.js    
 响应头中增加  Etag  de721f9a8957ab45bb31
内容,进行进行哈希, 会有雪崩效应, 内容变化, 
  etag 会不同 
 3 第二次请求 index.js
 IF-None-Since:   de721f9a8957ab45bb31       
 4  第二次应答
 (1) 已经过期:   200 index.js 
  (2) 为过期应答       304  , 没有内容   
 服务器 会对新的内容,进行hash, 进行对比, 如果变化, 则返回新的内容                                    
                                          
*/
		
// 时间更新的函数
function updateTime() {

   this.timmer = this.timmer || setInterval(() => {
        this.time = new Date().toUTCString()
    }, 5000);
    return this.time;
}


// 缓存作为静态资源, 作为一个js 函数

const http = require('http');
// 当不使用缓存的时候, 前后的时间基本相等
http.createServer((req, res) => {
    const { url } = req;
    if (url === '/') {
        res.end(
            `
            <html>
                Html update Time ${updateTime()}
                <script src = 'main.js'></script>
            </html>

            `
        )
    } else if (url === '/main.js') {
       // debugger;
        const content = `document.writeln('<br/> JS UpdateTime${updateTime()}')`
        res.statusCode = 200;
        // http 1.0 版本
        // 第一次后, 10秒后过过期,Expires
        // 过期的时间来自服务器, 问题是客户机同服务器的时间有差异, 所以不好
        // http 1.1 版本
        // cache-control  http1.1 使用cache-control 来解决这个问题,
        /*
        publicc : 所有的内容都会被缓存(客户端和代理服务器都可以缓存)
        private: 内容只缓存在私有缓存中(客户端可以缓存)
        no-cache: 需要使用协商缓存来验证缓存数据, 不需要你来缓存
        no-store :  所有的内容都不会被缓存
        max-age =xxx(xxx:  缓存的内容 将在xxx 秒后失效, 这个选项只在http1.1
            is numeric)  上可以使用, 并如果和Last-Modified 一起使用时, 优先级较高
Last-Modified: 时间来自服务器, 比客户端的时间更精准
        
        */
        /*    
        强缓存的设置
        res.setHeader('Expires', new Date(Date.now() + 5 * 1000).toUTCString());
        res.setHeader('Cache-Control', 'max-age=20'); // Cache-Control 相对于Expires 的优先级更高
        */
       // 协商混存的设置
       res.setHeader('Cache-Control', 'no-cache'); // 表示不使用强缓存
      
       /*
       res.setHeader('last-modified', new Date().toUTCString());
       if (new Date(req.headers['if-modified-since']).getTime() + 5* 1000 >  Date.now()) {
            console.log('协商缓存命中');
            res.statusCode = 304;
            res.end();
            return 
       }

        res.end(content);
        */
        const crypto = require('crypto'); // 使用node.js 中crypto 进行hash
        const hash = crypto.createHash('sha1').update(content).digest('hex');
        // 获取内容进行hash, 并转化为16 进制度
        res.setHeader('Etag', hash);
        if (req.headers['if-none-match'] === hash) {  // 是否一致
            console.log('缓存命中');
            res.statusCode = 304;
            res.end();
            return ;

        }
        res.end(content);
    } else if (url === '/favicon.ico') {
        res.end('');
    }
}).listen(3000, () => {
    console.log('http Cache Test run at: ' + 3000);
})
Copy the code