×

node.js 笔记

96
梁同桌
2017.11.30 18:59 字数 1120

https://github.com/nswbmw/N-blog/tree/master/book

启动服务

  • mongod --config /usr/local/etc/mongod.conf

  • cd /usr/local/bin 终端进去
  • ./mongo 查看并且链接

终止服务

  • pkill mongod

  • 引入:

    • import {firstName, lastName, year} from './profile';
  • 输出:

    • export {firstName, lastName, year};
  • 初始化:

    • npm init
  • 下载模块:

    • npm i express --save
var express = require('express');
var app = express();
app.use(express.bodyParser());//现实body
app.all('/', (req, res) => {
  res.send(req.body.title + req.body.text);
});

app.listen(3000);
  • res.send('Hello World') 比 res.end('Hello World')
    • 省去诸如添加Content-Length之类的事情
  • 每次修改代码保存后,就自自动启动
    • npm WARN checkPermissions Missing write access(权限问题

    • sudo npm install -g supervisor

    • 运行 supervisor --harmony index 启动程序 cd进去

    • kill node 关闭

    • supervisor start app

    • 停止进程: control+c

//localhost:3000/users/nswbmw
const express = require('express')
const app = express()

app.get('/',(req,res) => {
   res.send('hello, express ')
 })

app.get('/users/:name',(req,res) => {
    res.send('hello' + req.params.name)
})

app.listen(3000)
  • req.query: 解析后的 url 中的 querystring,如 ?name=haha,req.query 的值为 {name: 'haha'}
  • req.params: 解析 url 中的占位符,如 /:name,访问 /haha,req.params 的值为 {name: 'haha'}
  • req.body: 解析后请求体,需使用相关的模块,如 body-parser,请求体为 {"name": "haha"},则 req.body 为 {name: 'haha'}

模块加载

exports.Hello = Hello 
var Hello = require('./hello').Hello 来获取
--
module.exports = Hello
var Hello = require('./hello')


路由

index.js
'use strict'

const express = require('express')
const app = express()

const indexRouter = require('./routes/index')
const userRouter = require('./routes/users')

app.use('/',indexRouter)
app.use('/users',userRouter)

app.listen(3000)
router index.js
'use strict'

const express = require('express')
const router = express.Router()

router.get('/', (req, res) => {
  res.send('hello, express')
})

module.exports = router
router users.js
'use strict'

const express = require('express')
const router = express.Router()

router.get('/users/:name',(req,res) => {
 res.send('hello, '+req.params.name)
})

module.exports = router

  • npm i ejs --save

模版

index.js
'use strict'

const path = require('path')
const express = require('express')
const app = express()

const indexRouter = require('./routes/index')
const userRouter = require('./routes/users')

app.set('views', path.join(__dirname, 'views'))// 设置存放模板文件的目录,查找模版路径
app.set('view engine', 'ejs')// 设置模板引擎为 ejs

app.use('/',indexRouter)
app.use('/users',userRouter)

app.listen(3000)

routes/users.js
'use strict'

const express = require('express')
const router = express.Router()

router.get('/:name', (req, res) =>{
  res.render('users', {
    name: req.params.name
  })
})

module.exports = router

//通过调用 res.render 函数渲染 ejs 模板,res.render 第一个参数是模板的名字,这里是 users 则会匹配 views/users.ejs,第二个参数是传给模板的数据,这里传入 name,则在 ejs 模板中可使用 name。res.render 的作用就是将模板和数据结合生成 html,同时设置响应头中的 Content-Type: text/html,告诉浏览器我返回的是 html,不是纯文本,要按 html 展示。现在我们访问 localhost:3000/users/haha
views/users.ejs
<!DOCTYPE html>
<html>
  <head>
    <style type="text/css">
      body {padding: 50px;font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;}
    </style>
  </head>
  <body>
    <h1><%= name.toUpperCase() %></h1>
    <p>hello, <%= name %></p>
  </body>
</html>
上面html name.toUpperCase()大写
<% code %>:运行 JavaScript 代码,不输出
<%= code %>:显示转义后的 HTML内容
<%- code %>:显示原始 HTML 内容
        supplies: ['mop', 'broom', 'duster']
        //模版拼接
        <ul>
        <% for(var i=0; i<supplies.length; i++) {%>
           <li><%= supplies[i] %></li>
        <% } %>
        </ul>
        //形成的样式
        <ul>
          <li>mop</li>
          <li>broom</li>
          <li>duster</li>
        </ul>

模版引用

我们使用模板引擎通常不是一个页面对应一个模板,这样就失去了模板的优势,而是把模板拆成可复用的模板片段组合使用,如在 views 下新建 header.ejs 和 footer.ejs,并修改 users.ejs:

views/header.ejs
<!DOCTYPE html>
<html>
  <head>
    <style type="text/css">
      body {padding: 50px;font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;}
    </style>
  </head>
<body>
views/footer.ejs
</body>
</html>
views/users.ejs
拼接成网页<%-include('文件名字')%>
<%- include('header') %>
  <h1><%= name.toUpperCase() %></h1>
  <p>hello, <%= name %></p>
<%- include('footer') %>

中间件

index.js

const express = require('express');
const app = express();

app.use(function(req, res, next) {
  console.log('1');
  next();
});

app.use(function(req, res, next) {
  console.log('2');
  res.status(200).end();
});

app.listen(3000);
访问 localhost:3000,终端会输出:
1
2

错误处理

index.js
const express = require('express');
const app = express()

app.use(function(req, res, next) {
  console.log('1')
  next(new Error('haha'))
});

app.use(function(req, res, next) {
  console.log('2')
  res.status(200).end()
});

//错误处理
app.use(function(err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

app.listen(3000)
//此时访问 localhost:3000,命令行输出:1,浏览器会显示:Something broke!。

events 事件


var events = require('events');

var emitter = new events.EventEmitter()

emitter.on('someEvent', function(arg1, arg2) { 
   console.log('listener1', arg1, arg2)
})

emitter.on('someEvent', function(arg1, arg2) { 
   console.log('listener2', arg1, arg2)
})

emitter.emit('someEvent', 'byvoid', 1991)

//运行结果
//listener1 byvoid 1991
//listener2 byvoid 1991

以上例子中,emitter 为事件 someEvent 注册了两个事件监听器,然后发射了 someEvent 事件。运行结果中可以看到两个事件监听器回调函数被先后调用。这就是EventEmitter最简单的用法。接下来我们介绍一下EventEmitter常用的API。 
    EventEmitter.on(event, listener) 为指定事件注册一个监听器,接受一个字
符串event 和一个回调函数listener。
    EventEmitter.emit(event, [arg1], [arg2], [...]) 发射 event 事件,传
递若干可选参数到事件监听器的参数表。
    EventEmitter.once(event, listener) 为指定事件注册一个单次监听器,即
监听器最多只会触发一次,触发后立刻解除该监听器。
    EventEmitter.removeListener(event, listener) 移除指定事件的某个监听
器,listener 必须是该事件已经注册过的监听器。
    EventEmitter.removeAllListeners([event]) 移除所有事件的所有监听器, 如果指定 event,则移除指定事件的所有监听器。

fs.readfile

//异步的 fs.readFile()
//同步的 fs.readFileSync()

var fs = require('fs');
fs.readFile('content.txt', function(err, data) { 
    if (err) {
        console.error(err); 
    } else {
        console.log(data);
      }
});
//假设 content.txt 中的内容是 UTF-8 编码的 Text 文本文件示例,运行结果如下:
//<Buffer 54 65 78 74 20 e6 96 87 e6 9c ac e6 96 87 e4 bb b6 e7 a4 ba e4 be 8b>
    var fs = require('fs');
    fs.readFile('content.txt', 'utf-8', function(err, data) {
      if (err) {
        console.error(err);
      } else {
        console.log(data);
      }
    });


//那么运行结果则是:
//Text 文本文件示例

npm

无参数的 npm install 的功能就是 检查当前目录下的 package.json,并自动安装所有指定的依赖。

express 架构分离

image
  • 这是一个典型的 MVC 架构,浏览器发起请求,由路由控制器接受,根据不同的路径定 向到不同的控制器。控制器处理用户的具体请求,可能会访问数据库中的对象,即模型部分。控制器还要访问模板引擎,生成视图的 HTML,最后再由控制器返回给浏览器,完成一次请求。

express 路径匹配

如我们想要展示一个用户的个人页面,路径为 /user/[username],可以用下面的方法定义路由规则:

app.get('/user/:username', function(req, res) { 
    res.send('user: ' + req.params.username);
});

REST风格

  • 请求方式 安全 幂等
  • GET 是 是
  • POST 否 否
  • PUT 否 是
  • DELETE 否 是
  • 所谓安全是指没有副作用,即请求不会对资源产生变动,连续访问多次所获得的结果不受访问者的影响。而幂等指的是重复请求多次与一次请求的效果是一样的,比如获取和更新操作是幂等的,这与新增不同。删除也是幂等的,即重复删除一个资源,和删除一次是 一样的。

Express 对每种 HTTP 请求方法绑定:

    • GET
      app.get(path, callback)

    • POST
      app.post(path, callback)

    • PUT
      app.put(path, callback)

    • DELETE
      app.delete(path, callback)

    • PATCH
      app.patch(path, callback)

    • TRACE
      app.trace(path, callback)

    • CONNECT
      app.connect(path, callback)

    • OPTIONS
      app.options(path, callback)

    • 所有方法
      app.all(path, callback)

Express 路由控制权转移

Express 提供了路由控制权转移的方法,即回调函数的第三个参数next,通过调用 next(),会将路由控制权转移给后面的规则,例如:

app.all('/user/:username', function(req, res, next) { 
    console.log('all methods captured');
    next();
});

app.get('/user/:username', function(req, res) {
    res.send('user: ' + req.params.username);
});   

//如果调用next(“route”),则会跳过当前路由的其它中间件,直接将控制权交给下一个路由。

使用模版引擎

我们在 app.js 中通过以下两个语句设置了模板引擎和页面模板的位置:

 app.set('views', __dirname + '/views');
 app.set('view engine', 'ejs');

表明要使用的模板引擎是 ejs,页面模板在 views 子目录下。在 routes/index.js 的 exports.index 函数中通过如下语句调用模板引擎:

res.render('index', { title: 'Express' });

res.render 的功能是调用模板引擎,并将其产生的页面直接返回给客户端。它接受 两个参数,第一个是模板的名称,即 views 目录下的模板文件名,不包含文件的扩展名;第 二个参数是传递给模板的数据,用于模板翻译。index.ejs 内容如下:

   <h1><%= title %></h1>
   <p>Welcome to <%= title %></p>

上面代码其中有两处 <%= title %>,用于模板变量显示,它们在模板翻译时会被替换 成 Express,因为 res.render 传递了 { title: 'Express' }。

    ejs 的标签系统非常简单,它只有以下3种标签。
   <% code %>:JavaScript 代码。
   <%= code %>:显示替换过 HTML 特殊字符的内容。 
   <%- code %>:显示原始 HTML 内容。 我们可以用它们实现页面模板系统能实现的任何内容。

会话支持Cookie

  为了在无状态的 HTTP 协议之上实现会话,Cookie 诞生了。Cookie 是一些存储在客户 端的信息,每次连接的时候由浏览器向服务器递交,服务器也向浏览器发起存储 Cookie 的 请求,依靠这样的手段服务器可以识别客户端。我们通常意义上的 HTTP 会话功能就是这样 实现的。具体来说,浏览器首次向服务器发起请求时,服务器生成一个唯一标识符并发送给 客户端浏览器,浏览器将这个唯一标识符存储在 Cookie 中,以后每次再发起请求,客户端 浏览器都会向服务器传送这个唯一标识符,服务器通过这个唯一标识符来识别用户。

              

methodOverride

因为我们单击出发的是post,而服务器上是put事件.
现在想让服务器端依然是put接受,html上依然是post请求. 加app.use(express.methodOverride());  
http://www.bubuko.com/infodetail-503315.html

重定位

'use strict'

const express = require('express')
const app = express()

app.get('/', function (req, res) {
  
    console.log('1')

});

app.get('/one', function(req, res) {
    
    console.log('2')

    return res.redirect('/');//是重定向功能,

})
//访问http://localhost:3000/one 会输出:2,1

connect-flash


const express = require('express')
const session = require('express-session')
const flash = require('connect-flash');

const app = express()


app.use(session({
  secret: 'one',  //通过设置 secret 来计算 hash 值并放在 cookie 中,使产生的 signedCookie 防篡改
  cookie: {maxAge: 60000}, // 过期时间,过期后 cookie 中的 session id 自动删除

}))

app.use(flash());//中间件

app.get('/', function (req, res) {
  //取出来,然后就消失了
    console.log(req.flash('one'))
    res.send('success')

})

app.get('/one', function(req, res) {
    
    req.flash('one','1')//存 key value值
    return res.redirect('/')//是重定向功能,

})

//这是session 设置详情

'use strict'

const express = require('express')
const session = require('express-session')
const flash = require('connect-flash')
const MongoStore = require('connect-mongo')(session);

const app = express()

app.use(session({ 
    name: 'app'
    secret: 'one',  //通过设置 secret 来计算 hash 值并放在 cookie 中,使产生的 signedCookie 防篡改
    cookie: {maxAge: 60000}, // 即60000后session和相应的cookie失效过期除
    resave: false,//是指每次请求都重新设置session cookie,假设你的cookie是10分钟过期,每次请求都会再设置10分钟
    saveUninitialized: true//是指无论有没有session cookie,每次请求都设置个session cookie ,默认给个标示为 connect.sid

  //数据库储存
   store: new MongoStore({   //创建新的mongodb数据库
         host: 'localhost',    //数据库的地址,本机的话就是127.0.0.1,也可以是网络主机
         port: 27017,          //数据库的端口号
         db: 'test-app'        //数据库的名称。
     })

}))

app.use(flash());//中间件

app.get('/', function (req, res) {
  //取出来,然后就消失了
    console.log(req.flash('one'))
    res.send('success')

});

app.get('/one', function(req, res) {
    
    req.flash('one','1')//存 key value值
    return res.redirect('/')//是重定向功能,

})


// 监听端口,启动程序
app.listen(3000, function () {
  console.log(`success`)
})

require


我们要在 /home/byvoid/develop/foo.js 中使用 require('bar.js') 命令,Node.js会依次查找:
 /home/byvoid/develop/node_modules/bar.js
 /home/byvoid/node_modules/bar.js
 /home/node_modules/bar.js
 /node_modules/bar.js

循环便利,里面异步回调-有坑

var fs = require('fs');
var files = ['a.txt', 'b.txt', 'c.txt'];
for (var i = 0; i < files.length; i++) { 
    fs.readFile(files[i], 'utf-8', function(err, contents) {
        console.log(files);
        console.log(i);
        console.log(files[i]);
    }); 

}
运行结果如下:
    [ 'a.txt', 'b.txt', 'c.txt' ]
    3
    undefined
    [ 'a.txt', 'b.txt', 'c.txt' ]
    3
    undefined
    [ 'a.txt', 'b.txt', 'c.txt' ]
    3
    undefined
//三次输出的 i 的值都是 3,超出了 files 数组的下标 范围,因此 files[i] 的值就是 undefined 了。
//现在问题就明朗了:原因是3次读取文件的回调函数事实上是同一个实例,退出循环时 i 的值就是 files.length 的值。既然 i 的值是 3,引用到的 i 值是上面循环执行结束后的值,因此不能分辨。

//解决方案

var fs = require('fs');
var files = ['a.txt', 'b.txt', 'c.txt'];
files.forEach(function(filename) {
   fs.readFile(filename, 'utf-8', function(err, contents) {
   console.log(filename + ': ' + contents); 
   });
});
    

构造函数

//创建复杂的对象。
function User(name, uri) { 
   this.name = name;
   this.uri = uri; 
   this.display = function() {
       console.log(this.name); 
   }
}
//以上是一个简单的构造函数,接下来用new 语句来创建对象: 
 var someuser = new User('byvoid', 'http://www.byvoid.com');
//然后就可以通过someuser 来访问这个对象的属性和方法了。

this指针

//this 指针不属于某个函数,而是函数调用时所属的对象。
var someuser = { 
    name: 'byvoid', 
    func: function() {
        console.log(this.name); 
    }
 }; 
var foo = { 
    name: 'foobar'
};
someuser.func(); // 输出 byvoid
foo.func = someuser.func; 

foo.func(); // 输出 foobar
name = 'global';
func = someuser.func; func(); // 输出 global

call 和 apply

//call 和 apply 的功能是一致的,两者细微的差别在于 call 以参数表来接受被调用函 数的参数,而 apply 以数组来接受被调用函数的参数。
var someuser = {
    name: 'byvoid',
    display: function(words) {
    console.log(this.name + ' says ' + words); 
    }
};
var foo = { 
    name: 'foobar'
};
someuser.display.call(foo, 'hello'); // 输出 foobar says hello

输出了 foobar.someuser.display 是 被调用的函数,它通过 call 将上下文改变为 foo 对象,因此在函数体内访问 this.name 时,实际上访问的是 foo.name,因而输出了foobar。

bind绑定

bind 方法来永久地绑定函数的上下文,使其无论被谁调用,上 下文都是固定的
var someuser = { 
    name: 'byvoid', 
    func: function() {
    console.log(this.name);
    } 
};

var foo = { 
    name: 'foobar'
};
foo.func = someuser.func;
foo.func(); // 输出 foobar
foo.func1 = someuser.func.bind(someuser);
foo.func1(); // 输出 byvoid func = someuser.func.bind(foo);
func(); // 输出 foobar func2 = func;
func2(); // 输出 foobar


使用 bind 绑定参数表
bind 方法还有一个重要的功能:绑定参数表
var person = {
    name: 'byvoid', 7 says: function(act, obj) {
    console.log(this.name + ' ' + act + ' ' + obj); }
};
    person.says('loves', 'diovyb'); // 输出 byvoid loves diovyb
    byvoidLoves = person.says.bind(person, 'loves'); 
    byvoidLoves('you'); // 输出 byvoid loves you

可以看到,byvoidLoves 将 this 指针绑定到了 person,并将第一个参数绑定到 loves,之后在调用 byvoidLoves 的时候,只需传入第三个参数。这个特性可以用于创建 一个函数的“捷径”.

原型链

两个特殊的对象: Object 与 Function,它们都是构造函数,用于生成对象。Object.prototype 是所有对象的祖先,Function.prototype 是所有函数的原型,包括构造函数。
我把 JavaScript 中的对象分为三类:
一类是用户创建的对象,用户创建的对象,即一般意义上用 new 语句显式构造的对象。
一类是构造函数对象,构造函数对象指的是普通的构造函数,即通过 new 调用生成普通对象的 函数。
一类是原型对象,原型对象特指构造函数 prototype 属性指向的 对象。
这三类对象中每一类都有一个 __proto__ 属 性,它指向该对象的原型,从任何对象沿着它开始遍历都可以追溯到 Object.prototype。
构造函数对象有 prototype 属性,指向一个原型对象,通过该构造函数创建对象时,被创 建对象的 __proto__ 属性将会指向构造函数的 prototype 属性。原型对象有 constructor 属性,指向它对应的构造函数。
function Foo() {
}
Object.prototype.name = 'My Object'; 
Foo.prototype.name = 'Bar';
var obj = new Object();
var foo = new Foo();
console.log(obj.name); // 输出 My Object
console.log(foo.name); // 输出 Bar
console.log(foo.__proto__.name); // 输出 Bar 
console.log(foo.__proto__.__proto__.name); // 输出 My Object 
console.log(foo.__proto__.constructor.prototype.name); // 输出 Bar

[图片上传失败...(image-badd9-1512039587570)]

对象定义

尽量将所有的成员函数通过原型定义,将属性在构造函数内定义,然后对构造函数使用 new 关键字创建对象。绝对不要把属性作为原型定义,因为当要定义的属性是一个对象的 时候,不同实例中的属性会指向同一地址。
正确:
function FooObj(bar) { 
    //在构造函数中初始化属性 
    this.bar = bar; 
    this.arr = [1, 2, 3];
}
//使用原型定义成员函数 
FooObj.prototype.func = function() {
    console.log(this.arr); 
};
var obj1 = new FooObj('obj1'); 
var obj2 = new FooObj('obj2');
obj1.arr.push(4);
obj1.func(); // [1, 2, 3, 4]
obj2.func(); // [1, 2, 3]

node.js
Web note ad 1