我是卡卡卡颂


  • 首页

  • 归档

  • 标签

node源码实现——简单的promise实现

发表于 2016-09-08

promise是什么

顾名思义,promise就是一个承诺。承诺需要一段时间后兑现。在node中,这段时间就是异步操作执行到调用回调函数的时间。所以,promise的操作和异步操作有关。

promise的状态

  • Pending Promise对象实例创建时候的初始状态
  • Fulfilled 可以理解为成功的状态
  • Rejected 可以理解为失败的状态

    promise的简单应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //定义一个Promise实例,实例会立即执行传参。传参接收2个参数
    //resolve参数是一个函数,用以改变promise的状态以及执行成功状态的回调函数
    //reject参数是一个函数,用以改变promise的状态以及执行失败状态的回调函数
    var promise=new Promise((resolve,reject)=>{
    setTimeout(()=>{
    if (Math.random>0.5) resolve('success');
    else reject('fail');
    },1000)
    })
    //promise的实例的then方法接收两个函数参数,是对resolve与reject函数的定义
    promise.then(fullfiled,rejected)

源码实现思路

  1. 由于要实例化,所以Promise首先是一个类。constructor上有一个记录状态的变量。并且在实例化的过程中会立即执行传参。
  2. 原型上有一个then方法,接受两个参数,分别是成功与失败状态要调用的函数。但是由于这两个函数都有传参,所以还需要包装一下。
  3. 在contructor中定义resolve与reject函数,这两个函数接收传参,执行时会改变实例状态,并把传参赋给then上定义的方法并执行他。
  4. 在constructor中执行Promise中传入的函数,将包装好的resolve与reject传入。

源码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//1.定义Promise类
class Promise {
//2.接收一个函数为参数
constructor (fn) {
//3.保存状态,默认为'unfulfilled'
this._status='unfulfilled';
//5.包装好resolve
let resolve=(data)=>{
//改变状态
this._status='fulfilled';
this._resolve(data);
}
//6.包装好reject
let reject=(err)=>{
//改变状态
this._status='failed';
this._reject(err);
}
//7.立即执行
fn(resolve,reject);
}
//4.Promise.prototype上定义then方法,将传递的函数保存为内部方法
then (resolve,reject) {
this._resolve=resolve;
this._reject=reject;
}
}

node源码实现——嵌套文件夹的创建

发表于 2016-09-07

为什么需要手动实现嵌套文件夹的创建

node中fs模块原生提供了2种创建文件夹的方法:fs.mkdir (异步) 与fs.mkdirSync (同步)。但是原生的方法在创建嵌套文件夹时(如:a/b/c/d)时,必须在父级文件夹存在的情况下才能创建子级文件夹。所以不能一次创建a/b/c/d这样的嵌套文件夹。只能先创建a,再在a文件夹中创建b······

嵌套文件夹创建实现原理

递归调用创建函数,循环遍历路径,一层层判断文件夹是否存在,如果不存在就创建,存在就继续遍历下一层。

代码实现

提供2种方法,一种容易理解,一种比较复杂。

1
2
3
//首先引入依赖
var fs=require('fs');
var path=require('path')

  • 简单的版本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    //简单版本
    function makeDirp (path) {
    //分隔符的兼容
    var sep=path.sep;
    path=path.replace(/[\\/]/g,sep);
    var dir=path.split(sep);
    var index=1;
    make();
    //递归调用
    function make () {
    if (index>dir.length) return;
    //每次查找文件夹名+分隔符 是否存在
    var subdir=dir.slice(0,index++).join(sep);
    fs.exists(subdir,function (exist) {
    //存在则继续查找下一级
    if (exist) {
    make()
    //不存在则创建后查找下一级
    } else {
    fs.mkdir(subdir,function () {
    make();
    })
    }
    })
    }
    }
  • 复杂的版本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function mkdirp (p,callback) {
    //判断路径是否存在
    fs.exists(p,function (exist) {
    //存在则直接调用回调函数
    //不存在则递归调用自己,传入当前文件夹所在的文件夹路径
    return exist?callback():mkdirp(path.dirname(p),function () {
    fs.mkdir(p,callback);
    })
    })
    }

node源码实现——EVENT

发表于 2016-09-06

EVENT(事件)的作用

在node中很多异步方法需要监听事件,比如数据传输时需要监听流是否传输成功(data事件),数据是否传输完毕(end事件)。所以需要一个模块用来存储事件相关的操作(事件注册、事件监听、事件触发).

EVENT的机制

  1. 对于需要事件相关操作的函数,只需将其构造函数的原型指向事件的构造函数。这样其实例就能取得事件操作的方法。
  2. 对于事件库本身,提供事件的注册、监听、触发、取消绑定以及事件方法池。

    EVENT实现原理

  3. 设定一个事件方法池。
  4. 当监听事件时,将事件名与其对应回调函数放入方法池。
  5. 触发事件时,从方法池中找到相应事件名,依次执行事件名下保存的回调函数。

    代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    //事件构造函数
    function Event () {
    //事件方法池
    this._events={};
    }
    //事件的监听
    Event.prototype.on=function (eventName,callback) {
    var listeners=this._events[eventName];
    //如果事件方法池中存在对应方法名
    if (listeners) {
    //在对应方法名数组中新加入一个回调函数
    listeners.push(callback);
    } else {
    //事件方法池中对应方法名赋值为一个含回调函数的数组
    this._events[eventName]=[callback];
    }
    }
    //事件的触发
    Event.prototype.emit=function (eventName) {
    //第一项传参(也是形参)是事件名,从第二项开始就是传递给回调函数的参数,这里将这些参数保存在数组里
    var args=Array.prototype.slice.call(arguments,1);
    var listeners=this._events[eventName];
    //如果事件方法池中存在相应的方法名
    if (listeners) {
    for (var i=0;i<listeners.length;i++) {
    //依次执行方法名所对应的回调函数
    listeners[i].apply(null,args);
    }
    } else {
    console.log('对应方法不存在')
    }
    }
    //事件的移除
    Event.prototype.removeListener=function (eventName,callback) {
    this._events[eventName].filter(function (fn){
    return fn!=callback;
    })
    }
    //只执行一次的事件
    Event.prototype.once=function (eventName,callback) {
    //原理是执行完回调函数后移除这个事件
    //存储this指向,用以在one函数中调用指向once函数
    var that=this;
    function one () {
    //存储可能的传参
    var args=Array.prototype.slice.call(arguments);
    //执行回调函数
    callback.apply(that,args);
    //执行完后移除事件,这里的this指向的是事件实例
    this.removeListener(eventName,one);
    }
    //监听名为one的函数
    this.on(eventName,one);
    }
    module.exports=Event;

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
//引入继承模块
var util=require('util');
//A构造函数的实例继承事件库方法
function A () {
//A的实例取得Event的实例属性
Event.apply(this);
}
//A.prototype的原型指向Event.prototype
util.inherits(A,Event);
//这样A的实例就继承了事件库的方法

node源码实现——base64转换

发表于 2016-09-06

base64格式文件可以用来存放小容量的文件(如动态生成的验证码).node中实现了各种编码格式的转换(如utf8,base64).

字符串转换成base64格式流程

  1. 定义chars=’ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/‘.split(‘’);
  2. 将字符串转换成buffer。
  3. buffer每一项转换成二进制。
  4. 将得到的二进制拼接成字符串,再以6个为一组截取成段(因为二进制6个为一组转换成十进制最大为2^6-1=63,刚好能和chars一一对应)。
  5. 每一段转换成十进制,该十进制就对应chars中相应的索引。
  6. 将对应的索引拼接在一起就是base64格式内容。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function toBase64 (str) {
var chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
var buffer=new Buffer(str);
var str='';
var arr=[];
//遍历buffer的每一项,转换成二进制
for (var i=0;i<buffer.length;i++) {
var a= buffer[i].toString(2);
str+=a;
}
//对拼接的二进制6个为一组分割,转换成十进制,去chars中查找索引对应值
str.replace(/\d{6}/g,function () {
arr.push(chars[parseInt(arguments[0],2)]);
})
//将结果拼接并输出
return arr.join('');
}

express源码实现3——错误中间件及params

发表于 2016-09-05

上一篇内容

实现express中间件的源码

本篇文章的目的

实现错误中间件及params

思路

  • 错误中间件思路:
  1. 每次调用next方法,判断是否传递err参数。
  2. 如果err存在,则查询当前路由池中的索引是否为错误中间件。
  3. 如果是错误中间件,执行回调函数。
  4. 如果不是错误中间件,继续调用next并传递错误信息。
  • params思路:
  1. 判断请求路径是否含查询参数,如果包含将参数名提取出来并将路径转化成一个正则。
  2. 当请求到来时,将储存的正则与请求路径进行匹配。
  3. 如果匹配成功,将保存的参数名与对应的参数值组成对象赋给req。
  4. 如果匹配失败,执行next();

    代码

  • 错误中间件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //在next函数中
    if (err) {
    //判断是否是错误中间件, 依据是错误中间件有4个形参
    if (route.method=='middleware'&&route.fn.length==4) {
    route.fn(err,req,res,next);
    } else {
    //不是错误中间件,将错误继续传递下去
    next(err);
    }
    }
  • 查询参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    //首先在方法对应函数中设置对查询参数的保存
    methods.forEach(function (method) {
    app[method]=function (path,fn) {
    var config={method:method,path:path,fn:fn};
    }
    //如果路径中含查询参数
    if (path.includes(':')) {
    var params=[];
    config.path=path.replace(/:([^\/]+)/g,function () {
    //保存参数值
    params.push(arguments[1]);
    //返回一个正则,匹配非/ ,用来在请求中匹配
    return '([^\/]+)';
    })
    config.params=params;
    }
    app.routes.push(config);
    })
    //在next函数中,判断当前路由是否含查询参数
    if (route.params) {
    //当前请求与保存的带正则的路由路径匹配
    var matcher=req.path.match(new RegExp(route.path));
    //如果匹配上
    if (matcher) {
    //创建params对象
    var obj={};
    route.params.forEach(function (item,index) {
    obj[item]=matcher[index+1]
    })
    req.params=obj;
    route.fn(req,res);
    //没有匹配上
    } else {
    next();
    }
    }

express源码实现2——中间件实现

发表于 2016-09-03

上一篇内容

实现express中最主要的方法——创建服务器,监听端口,接收各种前端请求。

中间件是什么

  1. 中间件就是在路由过程中起过渡作用,用以检查权限,检查是否合法,最后再进入我们真正的路由
  2. 中间件有next方法,可以决定是否继续执行 如果调用next方法表示继续执行
  3. 中间件也可以匹配路径 只要路径开头匹配即可,默认是/等同于不写。比如中间件的/add可以匹配请求路径的/add/user
  4. 中间件中的req和res 和路由中的是同一个
  5. 一个页面中可以有多个中间件

    本篇文章的目的

    实现express中间件的源码

    思路

    之前路由的判断原理是将前端发送来的每个请求与路由池里保存的请求匹配,匹配上就执行相应回调函数。现在加入中间件,由于中间件不调用next()方法会阻塞接下来中间件/路由的执行,所以采用的原理是:讲中间件放入路由池,每当接收到前端请求,依次遍历路由池中的值,判断是中间件还是路由,分别处理。

流程

  1. 定义中间件函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    app.use=function (path,fn) {
    //当只传递回调函数不传递路径时
    if (typeof fn !='function') {
    fn=path;
    path='/';
    }
    var config={method:'middleware',path:path,fn:fn}
    app.routes.push(config);
    }
  2. 定义中间件的next方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    function express () {
    //首先在express函数上部定义一个索引
    var index=0;
    //执行一次next
    next();
    function next () {
    //判断是否遍历完都没有匹配到
    if (index>=app.routes.length) {
    res.end(`Cannot ${method} ${pathname}`)
    }
    //遍历到的路由项
    var route=app.routes[index++];
    //如果是中间件
    if (route.method=='middleware') {
    /**判断路径是否匹配
    *第一项匹配所有路径
    *第二项与请求路径完全匹配
    *第三项模糊匹配
    **/
    if (route.path=='/'||route.path==pathname||pathname.startsWith(route.path+'/')) {
    //匹配成功,执行中间件回调函数
    route.fn(req,res,next);
    } else {
    //没匹配到,进行下一次遍历
    next();
    }
    //如果是路由
    } else {
    //判断路由是否匹配
    if ((route.method==method||route.method=='all')&&(route.path==pathname||route.pathname=='*')) {
    //路由匹配成功
    route.fn(req,res);
    } else {
    //没匹配到,进行下次遍历
    next();
    }
    }
    }
    }

express源码实现1——基础实现

发表于 2016-09-03

express是什么

express之于nodeJS相当于jQuery之于JS。是一款后端框架(我个人觉得更应该是一个库),能用更简洁的代码量实现原生nodeJS中的效果。

本篇文章的目的

实现express中最主要的方法——创建服务器,监听端口,接收各种前端请求。

流程

  1. 分析express调用方法
    1
    2
    3
    4
    5
    6
    7
    var express=require('express');
    var app=express();
    //监听8080端口,启动服务器
    app.listen(8080);
    app.get('/',function (req,res) {
    //代码
    })

从调用方法来看,express是一个函数,我们需要取得的是express的返回值。该返回值上的listen方法启动服务器并监听端口。该返回值有get、post、put等方法用来接收相应方法的请求。

  • 代码实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    //1.定义express函数
    var express=function() {
    //5.定义app函数,因为被作为createServer()的回调函数传入,所以接收req、res两个参数
    var app=function (req,res) {}
    //6.路由路由池,存放各种请求,用以在适当时候加载
    app.routes=[];
    //7.定义各种请求方法对应的函数
    var methods=['get','post','put','options','delete','all'];
    methods.forEach(function (method) {
    //各种方法的函数接收两个参数 路径 与回调函数
    app[method]=function (path,fn) {
    //将方法,路径与回调函数全部放入方发池
    var config={method:method,path:path,fn:fn};
    routes.push(config);
    }
    })
    //4.定义返回值上的listen方法
    app.listen=function (port) {
    //启动服务器,监听端口,返回值作为createServer()的回调函数传入
    require('http').createServer(app).listen(port);
    }
    //3.返回方法
    return app;
    }
    //2.暴露接口
    module.exports=express;
  1. 当请求到来时将请求与路由池中保存的请求匹配,匹配上就执行相应回调函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //第5步定义的函数
    var app=function (req,res) {
    //取得请求中的方法名和路径
    var method=req.method;
    var urlObj=require('url').parse(req.url,true);
    var pathname=urlObj.pathname;
    //将请求的方法名与路径与路由池中保存的匹配
    var route=app.routes.find(function (route) {
    //返回路径与方法都匹配项,其中method=='all'能匹配所有方法,path=="*"能匹配所有路径
    return (route.method==method||route.method=="all")&&(route.path==pathname||route.path=="*")
    })
    //如果匹配到,就执行该路由的回调函数,传入req和res
    if (route) {
    route.fn(req,res);
    } else {
    //未匹配到则在页面上显示相应信息
    res.end(`Cannot ${method} ${pathname}`)
    }
    }

angular——多页面路由

发表于 2016-09-01

多页面路由与单页面路由的区别

之前的单页面路由是基于路径的路由,只能实现单页路由。接下来介绍基于状态的路由,为每个页面赋予不同的状态,就能实现多页面的路由。

多页面路由引入

  1. 引入angular-ui-router.js文件
  2. 模块中注入ui.router

    1
    var mod=angular.module('app',['ui.router']);
  3. 配置文件中注入$stateProvider和$urlRouterProvider,前者实现多页面路由,后者实现单页面路由中otherwise的功能

    1
    mod.config(function ($stateProvider,$urlRouterProvider) {})
  4. 单页路由配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //配置未定义的路径
    $urlRouterProvider.otherwise('/');
    //配置首页,状态为index
    $stateProvider.state('index',{
    url:'',
    templateUrl:'./index.html'
    }).state('about',{
    url:'/about',
    templateUrl:'./about.html',
    controller:function ($scope) {
    $scope.books=[{id:1,name:'王子与玫瑰'},{id:2,name:'夜莺与玫瑰'},{id:3,name:'王尔德'}]
    },
    //配置中注入的服务
    resolve:{
    }
    })
  5. 多页面路由配置

angular——表单控件

发表于 2016-08-30

什么是angular表单控件?

  • angular增强了常见的表单控件(如 url email date number),为其增加了一些默认的检测行为。比如input type为email类型时,输入的值不带有@符会默认为错误格式。

    几个常见的表单指令

  1. required 同ng-required 必填项
  2. ng-maxlength 控制字符最大长度
  3. ng-minlength 控制字符最小长度
  4. ng-pattern 传入正则,控制输入
    5.ng-trim:false 不去除首尾空格 (url email data number都有内置正则匹配)

    几个常见的表单属性

    刚才介绍的表单指令需要绑定在表单元素上,由事件触发。在触发时,他们会改变与其对应的表单属性。这些属性可以用在诸如ng-show这样的指令上。

    1. $pristine 文本框未发生任何变化时为true。
    1
    2
    3
    4
    5
    6
    7
    //注意取得相应表单属性需要表单控件有name属性
    <form name="myForm">
    <input type="text" name="myText" />
    <div ng-show="myForm.myText.$pristine" class='alert alert-danger'>
    请输入
    </div>
    </form>
    1. $dirty 文本框发生变化时的属性
    2. $invalid 验证失败时为true的属性
    3. $valid 验证成功时为true的属性
    4. $error 是一个对象,存储前面几个表单指令验证失败对应的属性。例:required项没填,则’表单name’.’组件name’.$error.required==true.
      -同样的还有$error.maxlength $error.minlength $error.pattern $error.trim

表单属性对应的样式

$pristine $dirty $valid $invalid都有对应的样式ng-pristine ng-dirty ng-valid ng-invalid样式,当相应的属性被触发发生改变,对应的样式也会被触发

再次提醒表单元素的属性都保存在 ‘表单name’.’组件name’下

angular——单页面路由

发表于 2016-08-29

##什么是路由

  • 路由就是不同的路径。不同的路径对应不同的路由

    angular单页路由的配置

  1. 需要引入angular-route这个包

    1
    <script src="../node_modules/angular-route.js"></script>
  2. angular模块依赖

    1
    var mod=angular.module('app',['ngRoute']);
  3. 对服务暴露出的api进行配置

    1
    2
    3
    mod.config(function ($routeProvider) {
    })

##具体流程 一个简单的案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var mod=angular.module('app',['ngRoute']);
mod.config(function ($routeProvider) {
//route的每个when方法里设置一个路由
$routeProvider.when('/',{
templateUrl:'index.html'
}).when('/about',{
templateUrl:'about.html',
controller:function ($scope) {
},
//resolve中定义服务,注意这里会嵌套两层
resolve:{
add:function () {
return function (a,b) {
return a+b;
}
}
}
}).when('/contact',{
templateUrl:'contact.html'
})
})

在DOM结构中,路由会显示在ng-view指令所在的标签内

1
<div ng-view></div>

1…345
BetaSu

BetaSu

一言不合撸源码

50 日志
© 2017 BetaSu
由 Hexo 强力驱动
主题 - NexT.Pisces