小叶子

小叶子 查看完整档案

成都编辑西南交通大学  |  软件工程 编辑HUAWEI  |  前端 编辑填写个人主网站
编辑

一只萌萌的程序媛妹子,踏入前端领域不久,希望大家多指导,欢迎大家多多指出问题

个人动态

小叶子 发布了文章 · 2020-06-17

【面试系列】LazyMan的ES6实现

最近某次笔试看到了一个比较有意思的LazyMan问题,基于自己的一些基础做了一些解答,回来结合了一些相关资料,自己重新代码实现了一遍。

问题描述

实现一个LazyMan,可以按照以下方式调用:
LazyMan(“Hank”)输出:
Hi! This is Hank!
 
LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~
 
LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出
Hi This is Hank!
Eat dinner~
Eat supper~
 
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper
 
以此类推。

思路分析

看到这个题目,首先注意到一些关键点联想到对应的方案点。

  1. LazyMan(“Hank”)调用,而不是new LazyMan(“Hank”)创建 => 工厂方法返回new对象
  2. 链式调用实现 => 每次调用返回this
  3. sleep需要等待10s => setTimeout实现sleep
  4. setTimeout会放到事件列表中排队,继续执行后面的代码,但是题目中sleep需要阻塞后续操作。 => 考虑将sleep封装成promise,使用async/await等待sleep,实现阻塞。
  5. sleepFirst每次在最开始执行,考虑将sleepFirst插入到事件第一个执行。

因此,首先我们需要taskQueue记录事件列表,直到调用完成后再执行taskQueue里面的事件。怎么实现调用完成后才开始执行taskQueue的事件呢?
答案:setTimeout机制。setTimeout(function(){xxx},0)不是立马执行,这是因为js是单线程的,有一个事件队列机制,setTimeoutsetInterval的回调会插入到延迟时间塞入事件队列中,排队执行。

源码展示

class _LazyMan {
    constructor(name) {
        this.taskQueue = [];
        this.name = name;
        this.timer = null;
        this.sayHi();
    }
    // 每次调用时清楚timer,上一次设置的执行taskQueue就不会运行。
    // 重新设置timer,会在下一次调用完后进入执行。
    // 当所有调用结束后,就会顺利执行taskQueue队列里的事件
    next() {
        clearTimeout(this.timer);
        this.timer = setTimeout(async () => {
            // 执行taskQueue队列里的事件
            for (let i = 0; i < this.taskQueue.length; i++) {
                await this.taskQueue[i]();
            }
        });
        return this;
    }
    sayHi() {
        this.taskQueue.push(() => {
            console.log('Hi! This is ' + this.name);
        });
        return this.next();
    }
    eat(str) {
        this.taskQueue.push(() => {
            console.log('Eat ' + str);
        });
        return this.next();
    }
    beforSleep(time) {
        // unshift插入到事件的第一个
        this.taskQueue.unshift(() => this.sleepPromise(time));
        return this.next();
    }
    sleep(time) {
        this.taskQueue.push(() => this.sleepPromise(time));
        return this.next();
    }
    // sleep的Promise对象,用于给async/await来阻塞后续代码执行
    sleepPromise(time) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('wake up after ' + time);
                resolve();
            }, time * 1000);
        });
    }
}

function LazyMan(name) {
    return new _LazyMan(name);
}

调用测试:
LazyMan('Herry').beforSleep(1).eat('dinner').sleep(2).eat('check');
输出:
image.png

查看原文

赞 0 收藏 0 评论 0

小叶子 发布了文章 · 2020-06-14

原生JS利用transform实现banner的无限滚动

功能

image.png

  • 默认情况无限循环向右移动
  • 点击数字切换到对应图片
  • 点击左右切换可切换图片

原理

首先说下原理。

  1. 在布局上所有的图片都是重叠的,即只要保证Y方向对齐即可,当前可见的图z-index层级最高。
  2. 每隔3s中更换一张图片,使用setTimeout定时。
  3. 使用gIndex记录当前可视区域的展示的是哪张图片下标,每次更换,计算下一张图片的下标。
  4. 通过requestAnimationFrame实现一次图片切换的动画。

这种方法也可以做到整个页面始终只有2个img标签,而不必把所有的img节点全部创建出来,要点是每次更换不可见img的src。
image.png

动画的实现

  1. 首先定义一个timestap,这个值记录每个帧移动多少距离。定义初始step=0,记录移动的步数。
  2. 每次移动的距离moveWidth是timestamp*step,图片1向右移动增加moveWidth,图片2从左侧进入moveWidth。因此,图片1的transform是translate(moveWidth), 而图片2的transform则是translate(moveWidth-图片宽度)。
  3. step+1
  4. 如果moveWidth>图片宽度,步骤5,否则requestAnimationFrame请求下一次执行,继续2-4.
  5. 图片1和2都将位置放置在起始位置,图片2的z-index设置为最高。

这样就完成了一次移动的动画。

html代码

<header>
    <div class="box">
        <img data-original="imgs/banner1.jpg">
        <img data-original="imgs/banner2.jpg">
        <img data-original="imgs/banner3.jpg">
        <img data-original="imgs/banner4.jpg">
    </div>
    <div class="buttons">
        <div class="active">1</div>
        <div>2</div>
        <div>3</div>
        <div>4</div>
    </div>
    <div class="left">
        <div class="arrow"></div>
    </div>
    <div class="right">
        <div class="arrow"></div>
    </div>
</header>

JS代码

var timeout = null;
window.onload = function () {
    var oLeft = document.querySelector('.left');
    var oRight = document.querySelector('.right');
    var oButton = document.querySelector('.buttons');
    var oButtons = document.querySelectorAll('.buttons div');
    var oImgs = document.querySelectorAll('.box img');
    var imgWidth = oImgs[0].width;
    var gIndex = 0;
    begainAnimate();

    // 绑定左右点击事件
    oLeft.onclick = function () {
        clearTimeout(timeout);
        leftMove();
        begainAnimate();
    };
    oRight.onclick = function () {
        clearTimeout(timeout);
        rightMove();
        begainAnimate();
    };
    // 绑定数字序号事件
    oButton.onclick = function (event) {
        clearTimeout(timeout);
        var targetEl = event.target;
        var nextIndex = (+targetEl.innerText) - 1;
        console.log(nextIndex);
        rightMove(nextIndex);
        begainAnimate();
    }
    // 默认初始动画朝右边
    function begainAnimate() {
        clearTimeout(timeout);
        timeout = setTimeout(function () {
            rightMove();
            begainAnimate();
        }, 3000);
    }
    // 向左移动动画
    function leftMove() {
        var nextIndex = (gIndex - 1 < 0) ? oImgs.length - 1 : gIndex - 1;
        animateSteps(nextIndex, -50);
    }
    // 向右移动动画
    function rightMove(nextIndex) {
        if (nextIndex == undefined) {
            nextIndex = (gIndex + 1 >= oImgs.length) ? 0 : gIndex + 1;
        }
        animateSteps(nextIndex, 50);
    }
    // 一次动画
    function animateSteps(nextIndex, timestamp) {
        var currentImg = oImgs[gIndex];
        var nextImg = oImgs[nextIndex];
        nextImg.style.zIndex = 10;
        var step = 0;
        requestAnimationFrame(goStep);
        // 走一帧的动画,移动timestamp
        function goStep() {
            var moveWidth = timestamp * step++;
            if (Math.abs(moveWidth) < imgWidth) {
                currentImg.style.transform = `translate(${moveWidth}px)`;
                nextImg.style.transform = `translate(${moveWidth > 0 ? (moveWidth - imgWidth) : (imgWidth + moveWidth)}px)`;
                requestAnimationFrame(goStep);
            } else {
                currentImg.style.zIndex = 1;
                currentImg.style.transform = `translate(0px)`;
                nextImg.style.transform = `translate(0px)`;
                oButtons[gIndex].setAttribute('class', '');
                oButtons[nextIndex].setAttribute('class', 'active');
                gIndex = nextIndex;
            }
        }
    }
}
window.onclose = function () {
    clearTimeout(timeout);
}

css布局样式

<style>
    /* 首先设置图片box的区域,将图片重叠在一起  */
    header {
        width: 100%;
        position: relative;
        overflow: hidden;
    }
    .box {
        width: 100%;
        height: 300px;
    }
    .box img {
        width: 100%;
        height: 100%;
        position: absolute;
        transform: translateX(0);
        z-index: 1;
    }
    .box img:first-child {
        z-index: 10;
    }
    
    /* 数字序列按钮 */
    .buttons {
        position: absolute;
        right: 10%;
        bottom: 5%;
        display: flex;
        z-index: 100;
    }

    .buttons div {
        width: 30px;
        height: 30px;
        background-color: #aaa;
        border: 1px solid #aaa;
        text-align: center;
        margin: 10px;
        cursor: pointer;
        opacity: .7;
        border-radius: 15px;
        line-height: 30px;
    }

    .buttons div.active {
        background-color: white;
    }

    /* 左右切换按钮 */
    .left,
    .right {
        position: absolute;
        width: 80px;
        height: 80px;
        background-color: #ccc;
        z-index: 100;
        top: 110px;
        border-radius: 40px;
        opacity: .5;
        cursor: pointer;
    }

    .left {
        left: 2%;
    }

    .right {
        right: 2%;
    }

    .left .arrow {
        width: 30px;
        height: 30px;
        border-left: solid 5px #666;
        border-top: solid 5px #666;
        transform: translate(-5px, 25px) rotate(-45deg) translate(25px, 25px);
    }

    .right .arrow {
        width: 30px;
        height: 30px;
        border-left: solid 5px #666;
        border-top: solid 5px #666;
        transform: translate(50px, 25px) rotate(135deg) translate(25px, 25px);
    }
</style>
查看原文

赞 12 收藏 12 评论 0

小叶子 发布了文章 · 2020-04-29

【Web全栈课程8】express实现node服务器

express简介

以下介绍来自官网:

路由

定义如何处理浏览器的请求

  1. 例如app.get、app.post对应监听get、post请求,app.all监听所有http请求。
  2. 监听不同的路径,对指定路径进行处理。例如监听到'/login'进行登录处理,监听到'student_list',进行学生列表查询处理。
var express = require('express')
var app = express()
// respond with "hello world" when a GET request is made to the homepage
app.get('/', function (req, res) {
  res.send('hello world')
})

开发中间件

Express是一个路由和中间件Web框架,其自身的功能很少,Express应用程序本质上是一系列中间件函数调用。
中间件的功能是可以访问请求对象req),响应对象res)和应用程序的请求-响应周期中的下一个中间件功能的功能。下一个中间件功能通常由名为的变量表示next
中间件功能可以执行以下任务:

  • 执行任何代码。
  • 更改请求和响应对象。
  • 结束请求-响应周期。
  • 调用堆栈中的下一个中间件函数。

如果当前的中间件功能没有结束请求-响应周期,则必须调用next()将控制权传递给下一个中间件功能。否则,该请求将被挂起。
image.png

使用中间件

Express可以使用以下类型的中间件:

  • 应用层中间件
    通过app.use和app.method将应用层中间件绑定到express实例上,其中method是中间件处理的request请求的小写,例如get、post、put。
  • 路由器级中间件
    路由器级中间件与应用程序级中间件的工作方式相同,只不过它绑定到的实例express.Router()
  • 错误处理中间件
    与其他中间件函数相同的方式定义错误处理中间件函数,除了使用四个参数而不是三个参数(错误处理中间件始终采用四个参数。即使不需要使用该next对象,也必须指定它以维护签名。否则,next对象将被解释为常规中间件,并且将无法处理错误。)
  • 内置中间件
    express内置:express.static、express.json、express.urlencoded
  • 第三方中间件
    使用第三方中间件向Express应用程序添加功能

模板引擎

在运行期间,模板引擎将静态模板中的变量替换为运行期间产生的真实数据,并转换为html文件返回给客户端,这样使html的设计更加简单。
与Express配合使用的一些流行模板引擎是Pug,Mustache和EJS。Express默认使用Jade。

错误处理

Express带有默认的错误处理程序,不需要任何额外的工作。如果同步代码引发错误,则Express将捕获并处理该错误。例如:

app.get('/', function (req, res) {
  throw new Error('BROKEN') // Express will catch this on its own.
})

Express新建服务器

上一个章节中,分析了原生js书写一个简单的node服务器,非常的复杂麻烦。
使用express框架快速的构建一个上章节的Node服务。

新建html文件

<form action="http://localhost:8080/api" method="post"  enctype="multipart/form-data">
    用户:<input type="text" name="user"><br>
    密码:<input type="password" name="pwd"><br>
    <input type="file" name="file" multiple><br>
    <input type="submit" value="提交">
</form>

处理静态文件

使用express内置中间件static,如下请求静态资源的时候express会从www寻找并返回

server.use(express.static('./www'));

处理post的body

安装body-parser

npm i body-parser -D

引入body-parser插件,response的请求头中会自动新增body变量将body解析。

const body = require('body-parser');    //接收除文件外的body
server.use(body.urlencoded({extended: false}));

处理post上传的文件

安装multer

npm i multer -D

引入multer插件,设置上传路径为./upload,文件自动上传到upload目录,response的请求头中会新增files变量存储相关信息。

const multer = require('multer');   // 接收文件body
let multerObj = multer({dest: './upload'});
server.use(multerObj.any());

完整代码

const express = require('express');
const body = require('body-parser');    //接收除文件外的body
const multer = require('multer');   // 接收文件body
let server = express();
server.listen(8080);
// 中间件,用use吧服务加到中间件上,每个中间件加的方式看官网定义
server.use(body.urlencoded({extended: false}));
let multerObj = multer({dest: './upload'});
server.use(multerObj.any());
// 处理请求
server.get('/', (req, res) => {
    res.send('OK');
});
server.post('/api', (req, res) => {
    res.send('OK');
    // 原生和express都没有req.body,是中间件加上的body
    console.log(req.body);
    console.log(req.files);
});
// static,意思是请求静态资源的时候express会处理
server.use(express.static('./www'));

请求结果

  1. express对缓存已经做好了处理
    image.png
    再次访问
    image.png
  2. body提交的数据只通过req.body就正确解析,req.files里面包含了上传的文件信息,并且已经做好了文件名哈希。
    image.png
  3. 去upload文件夹下检验,上传的文件确实存在,并且正确
    image.png
查看原文

赞 2 收藏 2 评论 0

小叶子 赞了问题 · 2020-04-18

解决文章一直未发布,且不是第一篇

https://segmentfault.com/a/1190000021279506
文章发布之后过了老半天了还是未发布状态,之前不都是直接发布的嘛,这是怎么回事?

关注 4 回答 3

小叶子 发布了文章 · 2020-01-20

【Web全栈课程7】Node服务器和浏览器数据交互

Web服务器数据交互:

  1. 静态资源响应,返回文件
  2. 处理数据交互请求(GET、POST)
  3. 解析上传的文件数据
  4. 流式操作和gz压缩
  5. 数据库交互(后续再讲)

1. 返回文件

常用于response返回数据的方法:setHead, write, writeHead
基于请求文件返回服务器端对应文件,简单实现如下:

fs.readFile(`www${req.url}`, (err, data)=> {
    if (err){
        res.writeHead('404'); //设置状态码
        res.write('error');
    } else {
        res.write(data);
    }
    res.end();
})

从浏览器请求文件时,后端返回了对应的html文件。
image.png

2. 处理数据交互请求

get

新建一个简单的get请求的表单html,提交到我们本地的node服务器。

<form  action="http://localhost:8080/aaa"  method="get">
    用户:<input  type="text"  name="user"><br>
    密码:<input  type="password"  name="pass"><br>
    <input  type="submit"  value="提交">
</form>

get传递的数据在url中,通过node的url模块,可以将url解析成对象数据。

let obj = url.parse(req.url);

解析后的结果:
image.png

post

和get一样,新建一个简单的post请求的表单html。

<form  action="http://localhost:8080/aaa"  method="post">
…………

post传输的数据在body里面。post的数据传递,一个大数据包切成多个小包发送:

  1. 大数据包不切块发送,其他所有网络交互被阻塞,需要等待大数据包传输完成才能进行。
  2. 传输失败的时候只需要重传失败的一小段,速度快。

因此,我们服务器端可以监听收到的每小段数据包,以及发送结束的消息。

let str = '';
// 接收到一个一个分段的数据
req.on('data', data  => {
    str += data;
});
// 结束
req.on('end', () => {
    let post = querystring.parse(str);
    console.log(str, post);
}); 

解析后的结果:
image.png

url.parse 可以解析整个url
querystring 解析数据部分(a=**&b=**)

3. 文件数据处理

POST表单的3种enctype:

  1. text/plain 纯文本内容
  2. application/x-www-form-urlencoded 默认,url编码方式 xx=xx&&x=x(上面讲了如何解析)
  3. multipart/form-data 上传文件内容

解析multipart/form-data文件上传数据

3.1 编写一个使用post上传文件的form表单

<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
    用户:<input type="text" name="user"><br/>
    密码:<input type="password" name="password"><br/>
    文件:<input type="file" name="file"><br/>
    <input type="submit" value="提交">
</form>

得到简单的表单
image.png

3.2 接收buffer数据
上一篇文章中讲post到Node服务端的数据,使用了str来存储,如果上传二进制文件,使用str接收就会破坏二进制数据导致文件损坏。因此使用Buffer处理是比较合理的方式。

let arr =[];
req.on('data', data => {
    arr.push(data);
});
req.on('end', () => {
    const result = Buffer.concat(arr);
    res.end();
});

3.3 分析上传的内容
为了方便分析上传的内容,提交一个文本文件上传,将buffer转换为字符串,打印出来的结果如下:
image.png
------WebKitFormBoundaryT8xL9avSAHMZDI98作为一个分隔符号,换行使用\r\n表示,简化后如下图:

<分隔符>\r\n数据描述\r\n\r\n数据值\r\n
<分隔符>\r\n数据描述\r\n\r\n数据值\r\n
<分隔符>\r\n数据描述1\r\n数据描述2\r\n\r\n<文件内容>\r\n
<分隔符>--

一步步将数据解析开

  1. 用“<分隔符>”切开数据

    [
        null,
        \r\n数据描述\r\n\r\n数据值\r\n,
        \r\n数据描述\r\n\r\n数据值\r\n,
        \r\n数据描述1\r\n数据描述2\r\n\r\n<文件内容>\r\n,
        --
    ]

    “<分隔符>”怎么得到?分析请求的headers,发现“<分隔符>”就是content-type中的boundary(每次的boundary都重新生成),如下:
    image.png

  2. 丢弃数组的头尾,和丢弃每项头尾的\r\n

    [
        数据描述\r\n\r\n数据值,
        数据描述\r\n\r\n数据值,
        数据描述1\r\n数据描述2\r\n\r\n<文件内容>,
    ]
  3. 使用第一次出现的\r\n\r\n切分(如果文件中有空行,文件里也有\r\n\r\n,所以用第一个)

    [
        [数据描述, 数据值],
        [数据描述, 数据值],
        [数据描述1\r\n数据描述2, <文件内容>],
    ]
  4. 判断描述里有没有\r\n,就可以区分文件。

    没有 —— 普通数据:`[数据描述, 数据值]`
    有 —— 文件数据:`[数据描述1\r\n数据描述2, <文件内容>]`
  5. 分析“数据描述”,可以看出其中name的值是需要的数据:

    Content-Disposition: form-data; name="user"
    Content-Disposition: form-data; name="password"
    Content-Disposition: form-data; name="file"; filename="text.txt"
    Content-Type: text/plain

3.4 代码化实现
上述分析过程改写成代码:

let uploadArr = [];

// 接收到一个一个分段的数据
req.on('data', data => {
    uploadArr.push(data);
});

// 数据接收结束
req.on('end', () => {
    let bf = Buffer.concat(uploadArr);
    // 1. 找到分隔符,用分隔符切开数据
    let boundary = '--' + req.headers['content-type'].split(/;\s*/)[1].split('=')[1];
    let arr = bf.split(boundary);

    // 2. 丢弃数组的头尾,和丢弃每项头尾的`\r\n`
    arr.shift();
    arr.pop();
    arr = arr.map(ele => ele.slice(2, ele.length - 2));

    // 3. 使用第一次出现的`\r\n\r\n`切分
    arr = arr.map(ele => {
        let firstSpace = ele.indexOf('\r\n\r\n');
        return [ele.slice(0, firstSpace), ele.slice(firstSpace + 4)];
    });

    // 4. 判断描述里有没有`\r\n`,就可以区分文件,取出需要的数据
    let normalData = {};
    let fileData = {};
    arr = arr.map(ele => {
        let description = ele[0].toString();
        let content = ele[1];
        if (description.indexOf('\r\n') >= 0) {
            // 5. 文件数据,获得name和文件内容
            fileData['data'] = { data: content };
            description.split('\r\n')[0].split(/;\s*/).forEach(attr => {
                let [name, val] = attr.toString().split('=');
                if (name == 'name' || name == 'filename') {
                    fileData[name] = val.replace(/"/igm, '');
                }
            });
        } else {
            // 5. 普通数据,获得name
            let name = description.split(/;\s*/)[1].split('=')[1].replace(/"/igm, '');
            normalData[name] = content.toString();
        }
    });

    console.log(normalData);
    console.log(fileData);

    res.writeHead(200);
});

提取出来提交的表单数据如下,正是需要的结果:
image.png

3.5. 保存文件到服务器

// 写入文件,content是上传的文件buffer
fs.writeFile(`upload/${filename}`, content, (err) => {
    if (err){
        console.log('文件写入失败', err);
        res.writeHead(404);
        res.end();
    } else {
        console.log('文件写入成功');
        res.writeHead(200);
        res.end();
    }
});

运行后,分别提交了一个文本文件和图片,都能正常的保存和使用,到这里,post方式multipart/form-data类型的数据上传处理就完成了。
image.png

4. 流式操作和gz压缩

4.1 流式操作

readFile和writefile
先把所有数据全部读到内存中,然后回调,极其占用内存,资源利用非常不充分。流式操作读一部分发一部分,不会等待所有数据完成后再操作。

  1. 读取流 fs.createReadStream、request
  2. 写入流 fs.createWriteStream、response
  3. 读写流 压缩、加密

流式返回服务器端文件
请求文件返回服务器端对应文件,使用流的方式操作:

let rs= fs.createReadStream(`www${req.url}`);
rs.pipe(res);
rs.on('error', err=>{
    console.log('读取失败');
    res.writeHead(404);
    res.write('Not found');
    res.end();
});

4.2 gz压缩

没有压缩之前,请求1.html静态资源,返回的文件大小为:
image.png
在实际使用过程中,会有大量的静态资源,压缩能够降低带宽的消耗降低成本,和节省加载时间。
需要注意:要设置Content-Encoding通知浏览器文件内容是压缩过的

let rs= fs.createReadStream(`www${req.url}`);
// 创建zlib压缩对象
let gz = zlib.createGzip();
// 设置header通知浏览器文件内容是压缩过的
res.setHeader('Content-Encoding', 'gzip');
rs.pipe(gz).pipe(res);

压缩后请求相同资源,文件变小。
image.png

5. 数据库交互

数据库分类:
关系型数据库 —— MySQL、Oracle、SQL Server
文件型数据库 —— sqlite
文档型数据库 —— MongoDB

5.1 关系型数据库

常见常用,数据之间是有关系的

5.2 文件型数据库

特点是简单,轻量级、小巧

5.3 文档型数据库

存储异构数据,处理速度快,适用于频繁写入的数据。

NoSQL 没有复杂的关系,对性能有极高的要求。
例如redis、memcached、hypertable、bigtable

数据库操作放在后面结合数据库一起写。

查看原文

赞 1 收藏 1 评论 0

小叶子 发布了文章 · 2019-12-28

【Web全栈课程6】nodejs模块简介

node基础

官网:http://nodesjs.org

Node
前台JS能用的东西,后台都能用,只是Node里面不涉及样式层,不关注html、css;node提供了很多模块

  1. 开发相对小规模的web后台、中间层,无法取代java,java高并发的各种特性node无法替代
  2. 工具
    测试、构建(grunt、glup、WebPack……)、抓取

本地启动一个node服务器

  1. 实现一个简单的server.js文件:

    const http = require('http');
    // 创建服务器,得到一个服务器对象;每当有请求到这个服务器,都会回调这个方法
    let server = http.createServer((request,response)=>{
        console.log('node server recive message.');
        // 根据请求返回不同的内容
        switch (request.url) {
            case  '/aaa':
                response.write('abc');
                break;
            case  '/1.html':
                response.write('<html><head></head><body>this is 1.html</body></html>');
                break;
        }
        response.end();
    });
    // 开启HTTP服务器监听连接
    server.listen(3000);
  2. 运行NodeJS程序:
    进入到js文件所在的目录,执行: node server.js
  3. 当我们访问监听的接口时(如上代码监听的是3000,访问localhost:3000,可以看到server接收到了消息

clipboard.png
访问http://localhost:3000/1.html时,返回响应的内容到前端。
image.png

Q: NodeJs能处理并发吗?
NodeJs和Js一样的单线程、单进程。NodeJs采用非阻塞的异步交互。

Q:收到消息打印了2次?
我们使用ie7浏览器,发现只打印了一次,也就是说高级浏览器打印了2次。
打开高级浏览器的开发者工具,可以看到发送请求的时候,浏览器主动去请求了网站小图标,这就是为什么有2条的原因。
clipboard.png

assert - 断言

模块提供了一组简单的断言测试,可用于测试不变量,不满足条件的时候程序直接中断。
示例:

const assert = require('assert');
function sum(a, b) {
    assert(arguments.length == 2, '必须传2个参数');
    assert(typeof a == 'number', '第一个参数必须是数字');
    assert(typeof b == 'number', '第二个参数必须是数字');
}
console.log(sum(12));

image.png

Buffer - 缓冲器 & fs - 文件系统

Buffer:帮助处理二进制数据,用于在 TCP 流、文件系统操作、以及其他上下文中与八位字节流进行交互。
fs:提供与文件系统进行交互的功能,虽然几乎不使用同步,但任何一个操作同时提供了同步和异步。
读文件示例:

const fs = require('fs');
fs.readFile('1.txt', (err, data) => {
if (err){
    console.log('没有找到文件。');
} else {
    console.log(data);
}})

文件1.txt存放内容: 12 15 18 20,可以看到读出来的data并不是我们要的数据,而是显示成16进制的二进制流。
image.png

  1. 直接传递给前端是正常显示的,因为后端和浏览器通过二进制也可以进行正常交互。
  2. 文本文件,data.toString()可以看到原始的数据,注意如果是图片视频等二进制数据,toString就破坏了数据。

C/C++ Addons

可以使用C或C++书写插件,极大的提升性能。

多进程 process

涉及模块:child_process-子进程、cluste-集群、process-进程
进程拥有独立的存储空间,同一个进程内的所有线程共享一套存储空间。
image.png

  • 多进程:速度更慢,初始化和销毁都需要跟计算机申请。安全,进程之间隔离,写代码更简单。
  • 多线程:线程间通信更复杂,写代码更复杂,涉及到多线程同步和锁的问题。

crypto - 加密

crypto 模块提供了加密功能,包括对OpenSSL的哈希、HMAC、加密、解密、签名、以及验证功能的一整套封装。

const crypto = require('crypto');
function md5(str){
    let obj = crypto.createHash('md5');
    obj.update(str);
    return obj.digest('hex');
}
console.log(md5('123456'));// 16进制显示结果

我们将字符串'123456'使用md5加密,加密后的结果如下:
image.png

os - 操作系统

os 模块提供了与操作系统相关的实用方法和属性。

path - 路径

path 模块提供用于处理文件路径和目录路径的实用工具。例如basename、dirname、extname、normalize、relative等。

events - 事件触发器

构建node的异步事件驱动架构,EventEmitter实现自定义的异步事件。

const Event = require('events').EventEmitter;
let ev = new Event();
// 监听(接收)
ev.on('msg', function(a, b, c){
    console.log('收到msg事件:', a, b, c);
});
// 派发(发送)
ev.emit('msg', 12, 5, 8);

image.png

querystring - 查询字符串

发给服务器查询字符串(url问号后面的字符串)的相关处理,处理为一个json对象。

const querystring = require('querystring');
let obj = querystring.parse('wd=md5&rsv_spt=1&&issp=1&f=8&rsv_bp=1');
console.log(obj);

image.png
URL:url模块用于处理与解析 URL,大部分时候我们还是使用url解析,

const url = require('url');
let obj = url.parse('https://www.baidu.com/s?wd=md5&rsv_spt=1&rsv_iqid=0x82af93ad003be6e4&issp=1&f=8&rsv_bp=1');
console.log(obj);

image.png

网络

TCP - 稳定传输 Net
UDP - 快 UDP/Datagram

域名解析

dns - 域名服务器。使用示例:
服务器之间通过ip地址通信,查看一个域名的ip地址,可以直接ping查看IP地址。
image.png
dns的resolve实现了同上的域名解析。

const dns = require('dns');
dns.resolve('baidu.com',(err,res) => {
if (err){
    console.log('解析失败');
} else {
    console.log(res);
}})

image.png

stream - 流

连续的数据相关操作,例如:视频流、网络流、文件流、语音流。

TLS/SSL

为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层与应用层之间对网络连接进行加密。

zlib - 压缩

gz压缩,结合流使用,压缩后的数据减少传输带宽

查看原文

赞 0 收藏 0 评论 0

小叶子 发布了文章 · 2018-07-24

【Web全栈课程4】前后端交互方式简介

数据交互

对于服务器的处理来说,http所有的数据请求都是表单提交(除了websocket)

  1. 表单

    • 最基本 最简单
    • 提交表单的时候,刷新页面
  2. ajax

    • 提交的时候不用刷新页面,因此节约流量,(提交表单将整个页面提交)
    • 在网络慢的情况可能出现重复提交,可以通过禁用提交按钮等操作解决
    • 默认情况不支持跨域,需要服务器配置才能跨域
    • 单向通信,缺点:只能浏览器请求服务器,服务端数据改变无法通知客户端
  3. jsonp

    • 比较老的跨域的方式,由于安全性差,已经逐步被取代
  4. webSocket

    • 性能比ajax高,支持大批量数据通信
    • 双向通信(双工通信),例如订单系统商家接单了,订单状态变化了,或者有人发送消息给我,等都需要双向通信
    • 默认就可以跨域

http协议

1、3个版本:http1.0、http1.1、http2.0

  • http1.0 一次性连接
  • http1.1 保持连接
  • http2.0 强制https,自带双向通信,提供协议级的多路复用(即一个连接可以传输多个数据) (目前是草案)

2、http和https
http =>容易被攻击被窃听
https=http+secrity =>安全,https需要证书校验

rfc http 互联网上使用的所有协议都有rfc编号

3、三次握手与四次挥手
第4次的时候连接断开,http1.1要等待一段时间才会断开
clipboard.png

4、http请求消息叫request,返回消息叫response,消息体都有如下格式
clipboard.png

get在头里面传数据,post在body里传数据,因此get限制传递数据32k,post可以达到1G。
浏览器的一个请求里面,我们可以看到都有如下3部分
clipboard.png

具体内容含义,内容来自:https://www.cnblogs.com/good7... ,感谢

Request Headers请求头

  • Accept:告诉服务器,客户机支持的数据类型
  • Accept-Encoding:告诉服务器,客户机支持的数据压缩格式
  • Cache-Control:缓存控制,服务器通过控制浏览器要不要缓存数据
  • Connection:处理完这次请求,是断开连接还是保持连接
  • Cookie:客户机通过这个可以向服务器带数据
  • Host:访问的主机名
  • User-Agent:告诉服务器,客户机的软件环境

Response Headers响应头

  • Connection:处理完这次请求后,是断开连接还是继续保持连接
  • Content-Encoding:服务器通过这个头告诉浏览器数据的压缩格式
  • Content-Length:服务器通过这个头告诉浏览器回送数据的长度
  • Content-Type:服务器通过这个头告诉浏览器回送数据的类型
  • Date:当前时间值
  • Server:服务器通过这个头告诉浏览器服务器的类型
  • Vary:Accept-Encoding ——明确告知缓存服务器按照
  • Accept-Encoding 字段的内容,分别缓存不同的版本;
  • X-Powered-By:服务器告知客户机网站是用何种语言或框架编写的。

表单

  1. 属性
    action —— 提交到哪儿
    method —— 提交方式,GET、POST;PUT、HEADER、DELETE;自定义(服务器配置了就能识别)
    name —— 必须加(后端识别数据);多个相同name相同提交到后端是列表
    submit —— 提交
  2. 数据提交方法
    GET、POST的安全性完全一样,https才能更加安全
    GET

      1.数据放在url里
      2.容量有限(<32k)
      3.有缓存
      4.利于分享和收藏(相关参数都在url里面)

    POST

      1. 数据放在http-body里
      2. 容量较大(<1G)
      3. 不缓存
      4. 没法分享和收藏
    

Q: 表单重复提交怎么解决?
开始提交的时候,提交按钮禁用,结束后提交按钮恢复可用。

Q: 重定向、转发区别
重定向 通知浏览器,让浏览器去请求另一个地址——浏览器地址是会变的
转发 在服务器内部,把请求转交给另一个模块处理;对客户端是不可见的——浏览器地址不变


ajax

ajax的使用直接引入jq即可使用,可讲性不大。
我们尝试自己使用原生js简单封装了一个ajax,可参考 https://segmentfault.com/a/11...

查看原文

赞 1 收藏 0 评论 0

小叶子 提出了问题 · 2018-07-23

http请求在系统中Timing显示与单独请求显示的Timing不一致。

1、在系统中请求,看到的Timing是下面这种结果,同一时间只有3条请求并发。
从下面这个图看,是请求被挂起了,Waiting和Content Download总共134ms,非常快。
clipboard.png

2、新开浏览器窗口,直接通过url访问请求数据,Timing又是下图所示。
而这张图的结论是请求发出去之前挂起时间只有16ms,实际是Waiting和Content Download耗时了5s以上。
clipboard.png

3、很明显同一条接口不同的访问方式,显示出来的Timing完全不一样,甚至相反。
求大神解答。

关注 2 回答 1

认证与成就

  • 获得 20 次点赞
  • 获得 3 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 3 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-01-26
个人主页被 867 人浏览