Giter VIP home page Giter VIP logo

blog_abandoned's Introduction

39Er's profile

  • 🔭 I’m working at Alibaba

  • 🌱 I’m currently learning golang/java/spring cloud/hadoop/spark

  • stats

39Er's github stats

  • language-stats

Top Langs

  • project

ReadMe Card

blog_abandoned's People

Contributors

39er avatar

Watchers

 avatar

blog_abandoned's Issues

关于yarn

首先来说一说yarn解决了npm的一些问题:

  • 安装的时候无法保证速度/一致性
  • 安全问题,因为 npm 安装时允许运行代码

yarn的另外一些优势:

  • yarn.lock锁定了依赖包的版本,保证其它机子也安装相同版本的包,同时包含了 package.json 中定义的一系列允许的版本。

  • yarn install 的时候会先使用缓存,所以再次下载速度会比npm快一些

  • yarn采用并行安装的方式

六大特性:

  • 离线模式: 一次安装,永久使用,无需下载
  • 依赖确定性:安装依赖锁定,保证一致性
  • 更好的网络性能:下载包,优化网络请求,最大限度提高网络利用率
  • 多注册来源处理:不管依赖包被不同的库间接关联引用多少次,安装这个包时,只会从一个注册来源去装(npm/bower), 防止出现混乱不一致。
  • 网络弹性处理: 安装依赖时,不会因为某个单次网络请求的失败导致整个安装挂掉。当请求失败时会进行自动重试。
  • 扁平模式: 当关联依赖中包括对某个软件包的重复引用,在实际安装时将尽量避免重复的创建

yarn & npm 常用命令比较

npm install === yarn / yarn install

npm install xxx —save === yarn add xxx

npm uninstall xxx —save === yarn remove xxx

npm install xxx —save-dev === yarn add xxx —dev

npm update === yarn upgrade

npm install xxx -g === yarn global add xxx

但个人用过一段时间yarn之后有了这样的反思:

我真的需要yarn吗?

  在使用yarn时发现,但项目所依赖的包a如果依赖其他包b(semver '~')时,yarn会锁定这个b的版本,而当b包有小版本更迭时,直接使用yarn upgrade a 依旧不会更新 b的版本,这点就像npm update a 一样。这时如果删除node_modules,重新yarn intall 时,就会出现问题,因为yarn.lock已经锁定了b的版本,所以再次install依旧安装的是老版本的,也就是说这样下去可能只有等到a包的下一个版本更迭才能更新到b包的新版本,所以这时需要将yarn.lock也一并删除,或 yarn remove a,重新 install a,但这样与npm 无异,且一不小心可能会将b的老版本一直锁在yarn.lock中。在使用antd的时候就发现了这个问题,其中依赖rc-select包,当rc-select更新时,只有re-install antd 才能更新rc-select。

另外看了死马的这篇文章后加深了这种反思。也许在今后更多的使用中我会有新的发现和感想。

闭包 Closure

什么是Closure

wikipedia:

In programming languages, closures (also lexical closures or function closures) are techniques for implementing lexically scoped name binding in languages with first-class functions. Operationally, a closure is a record storing a function[a] together with an environment: a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.

MDN:

闭包是指这样的作用域,它包含有一个函数 ,这个函数可以调用被这个作用域所封闭的变量、函数或者闭包等内容。通常我们通过闭包所对应的函数来获得对闭包的访问。

通俗的讲:

闭包就是一个能够访问外部变量的函数

在JS中,每个function都是闭包,因为它总是能访问在它外部定义的数据。

Closure 样例

var generateClosure = function() {
	var count = 0;
	var get = function() {
		count ++;
		return count;
	};
	return get;
};
var counter = generateClosure();
console.log(counter()); // 输出 1
console.log(counter()); // 输出 2
console.log(counter()); // 输出 3

这段代码中, generateClosure() 函数中有一个局部变量count, 初值为 0。还有一个叫做 get 的函数, get 将其父作用域,也就是 generateClosure() 函数中的 count 变量增加 1,并返回 count 的值。 generateClosure() 的返回值是 get 函数。在外部我们通过 counter 变量调用了generateClosure() 函数并获取了它的返回值,也就是 get 函数,接下来反复调用几次 counter(),我们发现每次返回的值都递增了 1。

按照通常命令式编程思维的理解, count 是generateClosure 函数内部的变量,它的生命周期就是 generateClosure 被调用的时期,当 generateClosure 从调用栈中返回时, count 变量申请的空间也就被释放。问题是,在 generateClosure() 调用结束后, counter() 却引用了“已经释放了的” count变量,而且非但没有出错,反而每次调用 counter() 时还修改并返回了 count。

这正是所谓闭包的特性。当一个函数返回它内部定义的一个函数时,就产生了一个闭包,闭 包 不 但 包 括 被 返 回 的 函 数 , 还 包 括 这 个 函 数 的 定 义 环 境 。 上 面 例 子 中 ,当函数generateClosure() 的内部函数 get 被一个外部变量 counter 引用时, counter和generateClosure() 的局部变量就是一个闭包。

闭包用途

  1. 实现私有成员;
  2. 避免全局变量污染;
  3. 使一个变量常驻内存中

注:由于IE的js对象和DOM对象使用不同的垃圾收集方法,因此闭包在IE中会导致内存泄露问题,也就是无法销毁驻留在内存中的元素

跨域资源共享CORS

跨域资源共享CORS

一、CORS简介

    当 Web 资源请求由其它域名或端口提供的资源时,会发起跨域 HTTP 请求(cross-origin HTTP request)。比如,站点 http://domain-a.com 的某 HTML 页面通过 的 src 请求 http://domain-b.com/image.jpg。

出于安全考虑,浏览器会限制脚本中发起的跨域请求。比如,使用 XMLHttpRequest 和 Fetch 发起的 HTTP 请求必须遵循同源策略。

同源:如果协议,端口(如果指定了一个)和域名对于两个页面是相同的,则两个页面具有相同的源。

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。简单请求不会触发CORS预检请求。

若请求满足所有下述条件,则该请求可视为“简单请求”:

  • 使用下列方法之一:

    GET

    HEAD

    POST
  • Fetch 规范定义了对 CORS 安全的首部字段集合,不得人为设置该集合之外的其他首部字段。该集合为:

    Accept

    Accept-Language

    Content-Language

    Content-Type (需要注意额外的限制)

    DPR

    Downlink

    Save-Data

    Viewport-Width

    Width
  • Content-Type 的值属于下列之一:

    application/x-www-form-urlencoded

    multipart/form-data

    text/plain

当请求满足下述任一条件时,即应首先发送预检请求:

  • 使用了下面任一 HTTP 方法:

    PUT

    DELETE

    CONNECT

    OPTIONS

    TRACE

    PATCH
  • 人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:

    Accept

    Accept-Language

    Content-Language

    Content-Type (but note the additional requirements below)

    DPR

    Downlink

    Save-Data

    Viewport-Width

    Width
  • Content-Type 的值不属于下列之一:

    application/x-www-form-urlencoded

    multipart/form-data

    text/plain

二、简单请求样例

比如说,假如站点 http://foo.example 的网页应用想要访问 http://bar.other 的资源。http://foo.example 的网页中可能包含类似于下面的 JavaScript 代码:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/public-data/';
   
function callOtherDomain() {
  if(invocation) {
    invocation.open('GET', url, true);
    invocation.onreadystatechange = handler;
    invocation.send(); 
  }
}

客户端和服务器之间使用 CORS 首部字段来处理跨域权限:

分别检视请求报文和响应报文:

GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.example


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61 
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

[XML Data]

第 1~10 行是请求首部。第10行 的请求首部字段 Origin 表明该请求来源于 http://foo.exmaple。

第 13~22 行是来自于 http://bar.other 的服务端响应。响应中携带了响应首部字段 Access-Control-Allow-Origin(第 16 行)。使用 Origin 和 Access-Control-Allow-Origin 就能完成最简单的访问控制。本例中,服务端返回的 Access-Control-Allow-Origin: * 表明,该资源可以被任意外域访问。如果服务端仅允许来自 http://foo.example 的访问,该首部字段的内容如下:

Access-Control-Allow-Origin: http://foo.example

三、非简单请求样例

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/post-here/';
var body = 'Arun';

function callOtherDomain(){
  if(invocation)
    {
      invocation.open('POST', url, true);
      invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
      invocation.setRequestHeader('Content-Type', 'application/xml');
      invocation.onreadystatechange = handler;
      invocation.send(body); 
    }
}

上面的代码使用 POST 请求发送一个 XML 文档,该请求包含了一个自定义的请求首部字段(X-PINGOTHER: pingpong)。另外,该请求的 Content-Type 为 application/xml。因此,该请求需要首先发起“预检请求”。

OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

预检请求完成之后,发送实际请求:

POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: http://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: http://foo.example
Pragma: no-cache
Cache-Control: no-cache

<?xml version="1.0"?><person><name>Arun</name></person>


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain

[Some GZIP'd payload]

浏览器检测到,从 JavaScript 中发起的请求需要被预检。从上面的报文中,我们看到,第 1~12 行发送了一个使用 OPTIONS 方法的“预检请求”。 OPTIONS 是 HTTP/1.1 协议中定义的方法,用以从服务器获取更多信息。该方法不会对服务器资源产生影响。 预检请求中同时携带了下面两个首部字段:

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER

首部字段 Access-Control-Request-Method 告知服务器,实际请求将使用 POST 方法。首部字段 Access-Control-Request-Headers 告知服务器,实际请求将携带两个自定义请求首部字段:X-PINGOTHER 与 Content-Type。服务器据此决定,该实际请求是否被允许。

第1426 行为预检请求的响应,表明服务器将接受后续的实际请求。重点看第 1720 行:

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

首部字段 Access-Control-Allow-Methods 表明服务器允许客户端使用 POST, GET 和 OPTIONS 方法发起请求。该字段与 HTTP/1.1 Allow: response header 类似,但仅限于在需要访问控制的场景中使用。

首部字段 Access-Control-Allow-Headers 表明服务器允许请求中携带字段 X-PINGOTHER 与 Content-Type。与 Access-Control-Allow-Methods 一样,Access-Control-Allow-Headers 的值为逗号分割的列表。

最后,首部字段 Access-Control-Max-Age 表明该响应的有效时间为 86400 秒,也就是 24 小时。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。

四、附带身份凭证的请求

Fetch 与 CORS 的一个有趣的特性是,可以基于 HTTP cookies 和 HTTP 认证信息发送身份凭证。一般而言,对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位。

本例中,http://foo.example 的某脚本向 http://bar.other 发起一个GET 请求,并设置 Cookies:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';
    
function callOtherDomain(){
  if(invocation) {
    invocation.open('GET', url, true);
    invocation.withCredentials = true;
    invocation.onreadystatechange = handler;
    invocation.send(); 
  }
}

第 7 行将 XMLHttpRequest 的 withCredentials 标志设置为 true,从而向服务器发送 Cookies。因为这是一个简单 GET 请求,所以浏览器不会对其发起“预检请求”。但是,如果服务器端的响应中未携带 Access-Control-Allow-Credentials: true ,浏览器将不会把响应内容返回给请求的发送者。

客户端与服务器端交互示例如下:

GET /resources/access-control-with-credentials/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2
X-Powered-By: PHP/5.2.6
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
	
[text/plain payload]

即使第 11 行指定了 Cookie 的相关信息,但是,如果 bar.other 的响应中缺失 Access-Control-Allow-Credentials: true(第 19 行),则响应内容不会返回给请求的发起者。

附带身份凭证的请求与通配符

对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“*”。

这是因为请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为“*”,请求将会失败。而将 Access-Control-Allow-Origin 的值设置为 http://foo.example,则请求将成功执行。

另外,响应首部中也携带了 Set-Cookie 字段,尝试对 Cookie 进行修改。如果操作失败,将会抛出异常。

五、HTTP响应首部字段

1、Access-Control-Allow-Origin

响应首部中可以携带一个 Access-Control-Allow-Origin 字段,其语法如下:

Access-Control-Allow-Origin: | *
其中,origin 参数的值指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。

例如,下面的字段值将允许来自 http://mozilla.com 的请求:

Access-Control-Allow-Origin: http://mozilla.com

如果服务端指定了具体的域名而非“*”,那么响应首部中的 Vary 字段的值必须包含 Origin。这将告诉客户端:服务器对不同的源站返回不同的内容。

2、Access-Control-Expose-Headers

Access-Control-Expose-Headers 首部字段指定了服务端允许的首部字段集合。

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

服务器允许请求中携带 X-My-Custom-Header 和 X-Another-Custom-Header 这两个字段。

3、Access-Control-Max-Age

Access-Control-Max-Age 首部字段指明了预检请求的响应的有效时间。

Access-Control-Max-Age:

delta-seconds 表示该响应在多少秒内有效。

4、Access-Control-Allow-Credentials

Access-Control-Allow-Credentials 首部字段用于预检请求的响应,表明服务器是否允许 credentials 标志设置为 true 的请求。请注意:简单 GET 请求不会被预检;如果对此类请求的响应中不包含该字段,浏览器不会将响应返回给请求的调用者。

Access-Control-Allow-Credentials: true

上文已经讨论了附带身份凭证的请求。

5、Access-Control-Allow-Methods

Access-Control-Allow-Methods 首部字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。

Access-Control-Allow-Methods: <method>[, <method>]*

6、Access-Control-Allow-Headers

Access-Control-Allow-Headers 首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字段。

Access-Control-Allow-Headers: <field-name>[, <field-name>]*

六、HTTP请求首部

1、Origin

Origin 首部字段表明预检请求或实际请求的源站。

Origin: <origin>

origin 参数的值为源站 URI。它不包含任何路径信息,只是服务器名称。

Note: 有时候将该字段的值设置为空字符串是有用的,例如,当源站是一个 data URL 时。
注意,不管是否为跨域请求,ORIGIN 字段总是被发送。

2、Access-Control-Request-Method

Access-Control-Request-Method 首部字段用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器。

Access-Control-Request-Method: <method>
相关示例见这里。

3、Access-Control-Request-Headers

Access-Control-Request-Headers 首部字段用于预检请求。其作用是,将实际请求所携带的首部字段告诉服务器。

Access-Control-Request-Headers: <field-name>[, <field-name>]*

以上内容主要摘自 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS


接下来是关于node.js中跨域中间件

CORS

Express cors middleware

1、Installation

$ npm install cors

2、一些基础用法样例

为所有的请求设置跨域:

var express = require('express')
var cors = require('cors')
var app = express()

app.use(cors())

app.get('/products/:id', function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for all origins!'})
})

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

为特定路由设置跨域:

var express = require('express')
var cors = require('cors')
var app = express()

app.get('/products/:id', cors(), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for a Single Route'})
})

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

配置跨域参数:

var express = require('express')
var cors = require('cors')
var app = express()

var corsOptions = {
  origin: 'http://example.com',
  optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}

app.get('/products/:id', cors(corsOptions), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for only example.com.'})
})

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

配置动态origin参数:

var express = require('express')
var cors = require('cors')
var app = express()

var whitelist = ['http://example1.com', 'http://example2.com']
var corsOptions = {
  origin: function (origin, callback) {
    if (whitelist.indexOf(origin) !== -1) {
      callback(null, true)
    } else {
      callback(new Error('Not allowed by CORS'))
    }
  }
}

app.get('/products/:id', cors(corsOptions), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for a whitelisted domain.'})
})

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

允许预检请求:

var express = require('express')
var cors = require('cors')
var app = express()

app.options('/products/:id', cors()) // enable pre-flight request for DELETE request
app.del('/products/:id', cors(), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for all origins!'})
})

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
});

  或者为所有路由设置预检请求:

app.options('*', cors()) // include before other routes

异步配置跨域:

var express = require('express')
var cors = require('cors')
var app = express()

var whitelist = ['http://example1.com', 'http://example2.com']
var corsOptionsDelegate = function (req, callback) {
  var corsOptions;
  if (whitelist.indexOf(req.header('Origin')) !== -1) {
    corsOptions = { origin: true } // reflect (enable) the requested origin in the CORS response
  }else{
    corsOptions = { origin: false } // disable CORS for this request
  }
  callback(null, corsOptions) // callback expects two parameters: error and options
}

app.get('/products/:id', cors(corsOptionsDelegate), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for a whitelisted domain.'})
})

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

3、配置选项

  • origin:配置Access-Control-Allow-Origin首部,可能的值:

    Boolean:true表示允许来源URI访问,false表示禁用CORS跨域

    String:指定一个URI可以跨域访问此域

    RegExp:符合正则表达式的域可以访问

    Array:设置一组可以访问域的数组

    Function:自定义逻辑设置函数,第一个参数为来源的uri,第二个参数为一个回调函数callback(err,boolean)

  • methods:设置 Access-Control-Allow-Methods首部,例如:字符串('GET,PUT,POST')或数组
    (['GET','PUT','POST'])

  • allowedHeaders:设置Access-Control-Allow-Headers 首部,用于预检请求的响应,(ex: 'Content-Type,Authorization')或(ex: ['Content-Type', 'Authorization']),如果不指定则反射请求中的Access-Control-Request-Headers首部

  • exposedHeaders: 设置 Access-Control-Expose-Headers 首部,指定了服务端允许的首部字段集合, (ex: 'Content-Range,X-Content-Range') 或 (ex: ['Content-Range', 'X-Content-Range']).

  • credentials: 设置 Access-Control-Allow-Credentials 首部,设为true启用

  • maxAge:设置 Access-Control-Max-Age首部,int

  • preflightContinue: 将预检请求传递给下一个处理器;

  • 为成功的请求提供一个状态码

默认的配置选项:

{
  "origin": "*",
  "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
  "preflightContinue": false,
  "optionsSuccessStatus": 204
}

Code Screenshots

会将一些我认为经典的代码片段放在这里,没事拿出来学习一下,像TJ大神一下多看别人的代码,想一想如果是我来做会怎样来实现。
(由于截图都会放在评论里,所以本文禁止评论...)

Stream 简介

Stream 是Node.js 的一个基础模块,继承了EventEmitter,是很多模块的基础,几乎所有的I/O操作都与其有关。

Stream 的分类包括Readable、Writable、Duplex、Transform

通过 Stream 源码 可了解具体分类:

const Stream = module.exports = require('internal/streams/legacy');

Stream.Readable = require('_stream_readable');
Stream.Writable = require('_stream_writable');
Stream.Duplex = require('_stream_duplex');
Stream.Transform = require('_stream_transform');
Stream.PassThrough = require('_stream_passthrough');

// Backwards-compat with node 0.4.x
Stream.Stream = Stream;

1. Readable

常见的Readable 如下:

  • http response(客户端)
  • http request(服务器端)
  • fs.ReadStream
  • zlib
  • crypto
  • net.Socket
  • child precess的stdout和stderr
  • process.stdin

new stream.Readable([options])

    options <Object>

        highWaterMark <number> :The maximum number of bytes to store in the internal buffer before ceasing to read from the underlying resource. Defaults to 16384 (16kb), or 16 for objectMode streams

        encoding <string>: If specified, then buffers will be decoded to strings using the specified encoding. Defaults to null

        objectMode <boolean>: Whether this stream should behave as a stream of objects. Meaning that stream.read(n) returns a single value instead of a Buffer of size n. Defaults to false

        read <Function> Implementation for the stream._read() method.

        destroy Implementation for the stream._destroy() method.

Readable stream有两种模式:

  • flowing:在该模式下,会尽快获取数据向外输出。因此如果没有事件监听,也没有pipe()来引导数据流向,数据可能会丢失。
  • paused:默认模式。在该模式下,需要手动调用stream.read()来获取数据。

可以通过以下几种方法切换到following模式:

  1. 为data事件添加监听器:
  2. 调用resume()
  3. 调用pipe()

可以通过以下几种方法切换到paused模式:

  1. 如果没有pipe,则调用pause()即可
  2. 如果有pipe,那么需要移除data事件的所有监听器,并通过unpipe()移除所有的pipe

paused 例子:

var Readable = require('stream').Readable;
var rs = new Readable();

rs.on('readable', function() {
var chunk = rs.read();
console.log('get data:', chunk ? chunk.toString() : null);
});

rs.on('end', function() {
console.log('stream end');
});

rs.push('hello stream');
rs.push('hello alex');
rs.push(null);

flowing 例子:

var Readable = require('stream').Readable;
var rs = new Readable();

rs.on('data', function(chunk) {
console.log('get data:', chunk.toString());
});

rs.on('end', function() {
console.log('stream end');
});

rs.push('hello stream');
rs.push('hello alex');
rs.push(null);

在实际情况下,如果要实现一个自定义的Readable stream类,往往是通过定义其_read方法来进行数据的处理。看如下例子:

var Readable = require('stream').Readable;

function MyReadable(data, options) {
if (!(this instanceof MyReadable)) {
return new MyReadable(data, options);
}
Readable.call(this, options);
this.data = data || [];
this.index = 0;
}

MyReadable.prototype.__proto__ = Readable.prototype;

MyReadable.prototype._read = function() {
if (this.index >= this.data.length) {
this.push(null);
} else {
setTimeout(function() {
this.push(this.data[this.index++]);
}.bind(this), 1000);
}
};

var data = ['California Dreaming', 'Hotel California', 'Californication'];
var rs = MyReadable(data);

rs.on('data', function(chunk) {
console.log('get data:', chunk.toString());
});

实现一个Readable,需要实现_read()方法:

const { Readable } = require('stream');

class MyReadable extends Readable {
  constructor(options) {
    // Calls the stream.Readable(options) constructor
    super(options);
    // ...
  }
  _read(size){
	// ...
  }
}

通过readable.push(chunk[, encoding])方法来向缓冲区中写入数据

2. Writable

常见的如下几种:

  • http request(客户端)
  • http response(服务器端)
  • fs.WriteStream
  • zlib
  • crypto
  • net.Socket
  • child process的stdin
  • process.stdout
  • process.stderr

Constructor: new stream.Writable([options])

    options <Object>

        highWaterMark <number> Buffer level when stream.write() starts returning false. Defaults to 16384 (16kb), or 16 for objectMode streams.

        decodeStrings <boolean> Whether or not to decode strings into Buffers before passing them to stream._write(). Defaults to true

        objectMode <boolean> Whether or not the stream.write(anyObj) is a valid operation. When set, it becomes possible to write JavaScript values other than string, Buffer or Uint8Array if supported by the stream implementation. Defaults to false

        write <Function> Implementation for the stream._write() method.

        writev <Function> Implementation for the stream._writev() method.

        destroy <Function> Implementation for the stream._destroy() method.

        final <Function> Implementation for the stream._final() method.

当创建一个Writable stream的时候,我们需要实现其_write()方法。看下面例子:

var Writable = require('stream').Writable;

var ws = Writable();

ws._write = function(chunk, encoding, cb) {
console.log(chunk.toString());
cb();
}

ws.on('finish', function() {
console.log('on finish');
});

ws.write('hello world');
ws.write('hello alex');
ws.end();

实现Writable:

const { Writable } = require('stream');

class MyWritable extends Writable {
  constructor(options) {
    // Calls the stream.Writable() constructor
    super(options);
    // ...
  }
}

3. Duplex

Duplex 同时实现了 Readable 和 Writable。

new stream.Duplex(options)

    options <Object> Passed to both Writable and Readable constructors. Also has the following fields:

        allowHalfOpen <boolean> Defaults to true. If set to false, then the stream will automatically end the readable side when the writable side ends and vice versa.

        readableObjectMode <boolean> Defaults to false. Sets objectMode for readable side of the stream. Has no effect if objectMode is true.

        writableObjectMode <boolean> Defaults to false. Sets objectMode for writable side of the stream. Has no effect if objectMode is true.

常见的Duplex stream有:

  • zlib
  • crypto
  • net.Socket

如果要实现一个Duplex stream,需要实现它的_read()和_write()方法。

如下例子,实现了一个比较简单的双向流:

var util = require('util'),
stream = require('stream'),
Readable = stream.Readable,
Duplex = stream.Duplex;

function MyReadable(options) {
  if (!(this instanceof MyReadable)) {
    return new MyReadable(options);
  }
  Readable.call(this, options);
  this._cur = 1;
  this._max = 20;
}

util.inherits(MyReadable, Readable);

MyReadable.prototype._read = function() {
  if (this._cur > this._max) {
    this.push(null);
  } else {
    this.push('' + this._cur++);
  }
}

function MyDuplex(options) {
  if (!(this instanceof MyDuplex)) {
    return new MyDuplex(options);
  }
  Duplex.call(this, options);
  this._data = [];
}

util.inherits(MyDuplex, Duplex);

MyDuplex.prototype._read = function() {
  if (this._data.length) {
    this.push(this._data.shift());
    this.push('\n');
  } else {
    this.push(null);
  }
}

MyDuplex.prototype._write = function(chunk, encoding, cb) {
  console.log('write data:', chunk.toString());
  this._data.push(chunk);
  cb();
};

var rs = MyReadable(),
ds = MyDuplex();

rs.pipe(ds).pipe(process.stdout);

4. Transform

Transform是一种特殊的Duplex stream,它可以对数据进行转换,也就是说,它的输出是将输入根据某种规则计算而成的。常见的Transform stream有:

  • zlib
  • crypto

new stream.Transform([options])

    options <Object> Passed to both Writable and Readable constructors. Also has the following fields:

        transform <Function> Implementation for the stream._transform() method.

        flush <Function> Implementation for the stream._flush() method.

如果要实现一个Transform stream,需要实现它的_transform()方法。

如下例子,实现了一个比较简单的转换流:

var util = require('util'),
stream = require('stream'),
Readable = stream.Readable,
Transform = stream.Transform;

function MyReadable(options) {
  if (!(this instanceof MyReadable)) {
    return new MyReadable(options);
  }
  Readable.call(this, options);
  this._cur = 1;
  this._max = 20;
}

util.inherits(MyReadable, Readable);

MyReadable.prototype._read = function() {
  if (this._cur > this._max) {
    this.push(null);
  } else {
    this.push('' + this._cur++);
  }
}

function MyTransform(options) {
  if (!(this instanceof MyTransform)) {
    return new MyTransform(options);
  }
  Transform.call(this, options);
}

util.inherits(MyTransform, Transform);

MyTransform.prototype._transform = function(chunk, encoding, cb) {
  var val = Number(chunk.toString());
  this.push('' + val * 2 + '\n');
  cb();
};

var rs = MyReadable(),
ts = MyTransform();

rs.pipe(ts).pipe(process.stdout);

5. Duplex与Transform的区别

Duplex 虽然同事具备可读流和可写流,但两者是相对独立的;Transform 的可读流的数据会经过一定的处理过程自动进入可写流。

Redux初阶

image
redux数据流

image
react生命周期

Redux的适用场景:多交互、多数据源

例:
用户的使用方式复杂
不同身份的用户有不同的使用方式(比如普通用户和管理员)
多个用户之间可以协作
与服务器大量交互,或者使用了WebSocket
View要从多个来源获取数据

三大原则

1. 单一数据源

整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。

2. State 是只读的

惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件
的普通对象。

3. 使用纯函数来执行修改

Redux 组成(TODO)

1. Action

把数据从应用传到store的有效载荷,是store的唯一数据来源。

我们应该尽量减少在Action中传递的数据。

store里能直接通过store.dispath()调用dispatch()方法,但是大多数情况下会使用react-redux提供的
connect()帮助器来调用。bindActionCreators()函数可以把多个action创建函数绑定到dispatch()方法上。

2. Reducer

指明应用如何更新state。

在Redux应用中,所有state都被保存在一个单一对象中。建议在写代码前先向下这个对象的结构。


开发复杂的应用时, 不可避免会有一些数据相互引用。 建议你尽可能地把 state 范式化, 不存在嵌套。 把所有数据放到一个对象里, 每个数据以 ID 为主键, 不同实体或列表间通过 ID 相互引⽤数据。 把应用的state 想像成数据库。

Reducer是一个纯函数,接收旧的state和action,返回新的state。

永远不要在reducer中做这些操作:

  • 修改传入参数;
  • 执行有副作用的操作,如API请求和路由跳转;
  • 调用非纯函数,如 Date.now() 或 Math.random()

只要传入参数相同, 返回计算得到的下一个 state 就一定相同。 没有特殊情况、 没有副作用, 没有 API 请求、 没有变量修改, 单纯执行计算。

注意每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。

3. Store

Store是将Action和Reducer联系起来的。职责:

  • 维持应用的 state;
  • 提供 getState() 方法获取 state;
  • 提供 dispatch(action) 方法更新 state;
  • 通过 subscribe(listener) 注册监听器;
  • 通过 subscribe(listener) 返回的函数注销监听器。

再次强调一下:Redux 应用只有一个单一的 store 。

import { createStore } from 'redux'
import todoApp from './reducers'

let store = createStore(todoApp)

数据流

严格的单向数据流是 Redux 架构的设计核心。

Redux 应用中数据的生命周期遵循下面 4 个步骤:

1. 调用 store.dispatch(action)

2. Redux store 调用传入的 reducer 函数

3. 根reducer把多个子reducer输出合并成一个单一的state树

4. Redux store 保存了根 reducer 返回的完整 state 树

这个新的树就是应用的下一个 state!所有订阅 store.subscribe(listener) 的监听器都将被调用;监听器里可以调用 store.getState() 获得当前 state。

Redux 与 React Router结合

import React, { PropTypes } from 'react';
import { Provider } from 'react-redux';
import { Router, Route, browserHistory } from 'react-router';
import App from './App';
const Root = ({ store }) => (
  <Provider store={store}>
    <Router history={browserHistory}>
      <Route path="/(:filter)" component={App} />
    </Router>
  </Provider>
);
Root.propTypes = {
  store: PropTypes.object.isRequired,
};
export default Root;

final、static使用总结

一、final

        根据程序上下文环境,Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变:设计或效率。

        final类不能被继承,没有子类,final类中的方法默认是final的。

        final方法不能被子类的方法覆盖,但可以被继承。

        final成员变量表示常量,只能被赋值一次,赋值后值不再改变。

        final不能用于修饰构造方法。

        注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。
 

1、final类

        final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。

2、final方法

        如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。
        使用final方法的原因有二:
        第一、把方法锁定,防止任何继承类修改它的意义和实现。
        第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。
        例如:

    public class Test1 { 
		public static void main(String[] args) { 
		    // TODO 自动生成方法存根 
		} 
		public void f1() { 
		    System.out.println("f1"); 
		} 
		//无法被子类覆盖的方法 
		public final void f2() { 
		    System.out.println("f2"); 
		} 
		public void f3() { 
		    System.out.println("f3"); 
		} 
		private void f4() { 
		    System.out.println("f4"); 
		} 
		} 
		public class Test2 extends Test1 { 
		    
		public void f1(){     
		    System.out.println("Test1父类方法f1被覆盖!"); 
		} 
		public static void main(String[] args) { 
		    Test2 t=new Test2(); 
		    t.f1();    
		    t.f2(); //调用从父类继承过来的final方法 
		    t.f3(); //调用从父类继承过来的方法 
		    //t.f4(); //调用失败,无法从父类继承获得 
		} 
	}

3、final变量(常量)

        用final修饰的成员变量表示常量,值一旦给定就无法改变!

        final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。

        从下面的例子中可以看出,一旦给final变量初值后,值就不能再改变了。

        另外,final变量定义的时候,可以先声明,而不给初值,这中变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。但是,final空白在final关键字final的使用上提供了更大的灵活性,为此,一个类中的final数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。

 
package org.leizhimin;

public class Test3 { 
        private final String S = "final实例变量S"; 
        private final int A = 100; 
        public final int B = 90; 

        public static final int C = 80; 
        private static final int D = 70; 

        public final int E; //final空白,必须在初始化对象的时候赋初值 

        public Test3(int x) { 
                E = x; 
        } 

        /** 
         * @param args 
         */ 
        public static void main(String[] args) { 
                Test3 t = new Test3(2); 
                //t.A=101;    //出错,final变量的值一旦给定就无法改变 
                //t.B=91; //出错,final变量的值一旦给定就无法改变 
                //t.C=81; //出错,final变量的值一旦给定就无法改变 
                //t.D=71; //出错,final变量的值一旦给定就无法改变 

                System.out.println(t.A); 
                System.out.println(t.B); 
                System.out.println(t.C); //不推荐用对象方式访问静态字段 
                System.out.println(t.D); //不推荐用对象方式访问静态字段 
                System.out.println(Test3.C); 
                System.out.println(Test3.D); 
                //System.out.println(Test3.E); //出错,因为E为final空白,依据不同对象值有所不同. 
                System.out.println(t.E); 

                Test3 t1 = new Test3(3); 
                System.out.println(t1.E); //final空白变量E依据对象的不同而不同 
        } 

        private void test() { 
                System.out.println(new Test3(1).A); 
                System.out.println(Test3.C); 
                System.out.println(Test3.D); 
        } 

        public void test2() { 
                final int a;     //final空白,在需要的时候才赋值 
                final int b = 4;    //局部常量--final用于局部变量的情形 
                final int c;    //final空白,一直没有给赋值.    
                a = 3; 
                //a=4;    出错,已经给赋过值了. 
                //b=2; 出错,已经给赋过值了. 
        } 
}

 

4、final参数

        当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。
 

public class Test4 { 
        public static void main(String[] args) { 
                new Test4().f1(2); 
        } 

        public void f1(final int i) { 
                //i++;    //i是final类型的,值不允许改变的. 
                System.out.print(i); 
        } 
}

二、static

        static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是Java语言中没有全局变量的概念。

        被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。

        用public修饰的static成员变量和成员方法本质是全局变量和全局方法,当声明它类的对象市,不生成static变量的副本,而是类的所有实例共享同一个static变量。

 
        static变量前可以有private修饰,表示这个变量可以在类的静态代码块中,或者类的其他静态成员方法中使用(当然也可以在非静态成员方法中使用--废话),但是不能在其他类中通过类名来直接引用,这一点很重要。实际上你需要搞明白,private是访问权限限定,static表示不要实例化就可以使用,这样就容易理解多了。static前面加上其它访问权限关键字的效果也以此类推。

 
        static修饰的成员变量和成员方法习惯上称为静态变量和静态方法,可以直接通过类名来访问,访问语法为:

  类名.静态方法名(参数列表...)

  类名.静态变量名

        用static修饰的代码块表示静态代码块,当Java虚拟机(JVM)加载类时,就会执行该代码块(用处非常大,呵呵)。

 

1、static变量

        按照是否静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量;另一种是没有被static修饰的变量,叫实例变量。两者的区别是:
        对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
        对于实例变量,没创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。
 

2、静态方法

        静态方法可以直接通过类名调用,任何的实例也都可以调用,因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。因为实例成员与特定的对象关联!这个需要去理解,想明白其中的道理,不是记忆!!!
        因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。
 

3、static代码块

        static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。例如:
 
public class Test5 {
        private static int a;
        private int b;

        static { 
                Test5.a = 3; 
                System.out.println(a); 
                Test5 t = new Test5(); 
                t.f(); 
                t.b = 1000; 
                System.out.println(t.b); 
        } 

        static { 
                Test5.a = 4; 
                System.out.println(a); 
        } 

        public static void main(String[] args) { 
                // TODO 自动生成方法存根 
        } 

        static { 
                Test5.a = 5; 
                System.out.println(a); 
        } 

        public void f() { 
                System.out.println("hhahhahah"); 
        } 
}
 
运行结果:
3
hhahhahah
1000
4
5

  利用静态代码块可以对一些static变量进行赋值,最后再看一眼这些例子,都一个static的main方法,这样JVM在运行main方法的时候可以直接调用而不用创建实例。

 

4、static和final一块用表示什么

static final用来修饰成员变量和成员方法,可简单理解为“全局常量”!

 对于变量,表示一旦给值就不可修改,并且通过类名可以访问。

 对于方法,表示不可覆盖,并且可以通过类名直接访问。
      
特别要注意一个问题:

    对于被static和final修饰过的实例常量,实例本身不能再改变了,但对于一些容器类型(比如,ArrayList、HashMap)的实例变量,不可以改变容器变量本身,但可以修改容器中存放的对象,这一点在编程中用到很多。

    也许说了这么多,反倒把你搞晕了,还是看个例子吧:

public class TestStaticFinal { 
        private static final String strStaticFinalVar = "aaa"; 
        private static String strStaticVar = null; 
        private final String strFinalVar = null; 
        private static final int intStaticFinalVar = 0; 
        private static final Integer integerStaticFinalVar = new Integer(8); 
        private static final ArrayList<String> alStaticFinalVar = new ArrayList<String>(); 

        private void test() { 
                System.out.println("-------------值处理前----------\r\n"); 
                System.out.println("strStaticFinalVar=" + strStaticFinalVar + "\r\n"); 
                System.out.println("strStaticVar=" + strStaticVar + "\r\n"); 
                System.out.println("strFinalVar=" + strFinalVar + "\r\n"); 
                System.out.println("intStaticFinalVar=" + intStaticFinalVar + "\r\n"); 
                System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + "\r\n"); 
                System.out.println("alStaticFinalVar=" + alStaticFinalVar + "\r\n"); 


                //strStaticFinalVar="哈哈哈哈";        //错误,final表示终态,不可以改变变量本身. 
                strStaticVar = "哈哈哈哈";                //正确,static表示类变量,值可以改变. 
                //strFinalVar="呵呵呵呵";                    //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。 
                //intStaticFinalVar=2;                        //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。 
                //integerStaticFinalVar=new Integer(8);            //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。 
                alStaticFinalVar.add("aaa");        //正确,容器变量本身没有变化,但存放内容发生了变化。这个规则是非常常用的,有很多用途。 
                alStaticFinalVar.add("bbb");        //正确,容器变量本身没有变化,但存放内容发生了变化。这个规则是非常常用的,有很多用途。 

                System.out.println("-------------值处理后----------\r\n"); 
                System.out.println("strStaticFinalVar=" + strStaticFinalVar + "\r\n"); 
                System.out.println("strStaticVar=" + strStaticVar + "\r\n"); 
                System.out.println("strFinalVar=" + strFinalVar + "\r\n"); 
                System.out.println("intStaticFinalVar=" + intStaticFinalVar + "\r\n"); 
                System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + "\r\n"); 
                System.out.println("alStaticFinalVar=" + alStaticFinalVar + "\r\n"); 
        } 

        public static void main(String args[]) { 
                new TestStaticFinal().test(); 
        } 
}
 
运行结果如下:
-------------值处理前----------
strStaticFinalVar=aaa
strStaticVar=null
strFinalVar=null
intStaticFinalVar=0
integerStaticFinalVar=8
alStaticFinalVar=[]
-------------值处理后----------
strStaticFinalVar=aaa
strStaticVar=哈哈哈哈
strFinalVar=null
intStaticFinalVar=0
integerStaticFinalVar=8
alStaticFinalVar=[aaa, bbb]
Process finished with exit code 0

 
看了上面这个例子,就清楚很多了,但必须明白:通过static final修饰的容器类型变量中所“装”的对象是可改变的。这是和一般基本类型和类类型变量差别很大的地方。

Zookeeper简介

zookeeper简介

       Zookeeper是一个分布式的、开源的分布式应用协调服务。它暴露了一组简单的基础原件,分布式应用可以在这些原件之上实现更高级别的服务,如同步、配置维护、群组、和命名。它的设计非常容易编程实现,并且使用一个常见的文件系统的树型结构的数据模型。它运行在Java中,并且绑定了Java和C。

基于观察者模式设计的分布式服务管理框架

负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper 就将负责通知已经在 Zookeeper 上注册的那些观察者做出相应的反应,从而实现集群中类似 Master/Slave 管理模式

主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。

zookeeper架构

Zookeeper客户端/服务端架构

ZooKeeper 有一个类似于文件系统的数据模型,由 znodes 组成。可以将 znodes(ZooKeeper 数据节点)视为类似 UNIX 的传统系统中的文件,但它们可以有子节点。另一种方式是将它们视为目录,它们可以有与其相关的数据。每个这些目录都被称为一个 znode。

znode 层次结构被存储在每个 ZooKeeper 服务器的内存中。这实现了对来自客户端的读取操作的可扩展的快速响应。每个 ZooKeeper 服务器还在磁盘上维护了一个事务日志,记录所有的写入请求。因为 ZooKeeper 服务器在返回一个成功的响应之前必须将事务同步到磁盘,所以事务日志也是 ZooKeeper 中对性能最重要的组成部分。可以存储在 znode 中的数据的默认最大大小为 1 MB。因此,即使 ZooKeeper 的层次结构看起来与文件系统相似,也不应该将它用作一个通用的文件系统。相反,应该只将它用作少量数据的存储机制,以便为分布式应用程序提供可靠性、可用性和协调。

数据模型

Zookeeper 数据结构

Zookeeper 这种数据结构有如下这些特点:

  1. 每个子目录项如 NameService 都被称作为 znode,这个 znode 是被它所在的路径唯一标识,如 Server1 这个 znode 的标识为 /NameService/Server1
  2. znode 可以有子节点目录,并且每个 znode 可以存储数据,注意 EPHEMERAL 类型的目录节点不能有子节点目录
  3. znode 是有版本的,每个 znode 中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据
  4. znode 可以是临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除,Zookeeper 的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为 session,如果 znode 是临时节点,这个 session 失效,znode 也就删除了
  5. znode 的目录名可以自动编号,如 App1 已经存在,再创建的话,将会自动命名为 App2
  6. znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是 Zookeeper 的核心特性,Zookeeper 的很多功能都是基于这个特性实现的。

再次强调:Zookeeper 并不是用来专门存储数据的,它的作用主要是用来维护和监控你存储的数据的状态变化。

Zookeeper非常简单和高效。因为它的目标就是作为建设复杂服务的基础,比如同步。zookeeper提供了一套保证,他们包括:

  • 顺序一致性 - 来自客户端的更新会按顺序应用。
  • 原子性 - 更新成功或者失败,没有局部的结果产生。
  • 唯一系统映像 - 客户端不管连接到哪个服务端都会看到同样的视图。
  • 可靠性- 一旦一个更新被应用,它将从更新的时间开始一直保持到一个客户端重写更新。
  • 时效性 - 系统中的客户端视图在特定的时间点保证是最新的。

Zookeeper常用接口

方法名 方法描述
Stringcreate(String path, byte[] data, List acl, CreateMode createMode) 创建一个给定的目录节点 path, 并给它设置数据,CreateMode 标识有四种形式的目录节点,分别是 PERSISTENT:持久化目录节点,这个目录节点存储的数据不会丢失;PERSISTENT_SEQUENTIAL:顺序自动编号的目录节点,这种目录节点会根据当前已近存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名;EPHEMERAL:临时目录节点,一旦创建这个节点的客户端与服务器端口也就是 session 超时,这种节点会被自动删除;EPHEMERAL_SEQUENTIAL:临时自动编号节点
Statexists(String path, boolean watch) 判断某个 path 是否存在,并设置是否监控这个目录节点,这里的 watcher 是在创建 ZooKeeper 实例时指定的 watcher,exists方法还有一个重载方法,可以指定特定的 watcher
Statexists(String path, Watcher watcher) 重载方法,这里给某个目录节点设置特定的 watcher,Watcher 在 ZooKeeper 是一个核心功能,Watcher 可以监控目录节点的数据变化以及子目录的变化,一旦这些状态发生变化,服务器就会通知所有设置在这个目录节点上的 Watcher,从而每个客户端都很快知道它所关注的目录节点的状态发生变化,而做出相应的反应
void delete(String path, int version) 删除 path 对应的目录节点,version 为 -1 可以匹配任何版本,也就删除了这个目录节点所有数据
List getChildren(String path, boolean watch) 获取指定 path 下的所有子目录节点,同样 getChildren方法也有一个重载方法可以设置特定的 watcher 监控子节点的状态
StatsetData(String path, byte[] data, int version) 给 path 设置数据,可以指定这个数据的版本号,如果 version 为 -1 怎可以匹配任何版本
byte[] getData(String path, boolean watch, Stat stat) 获取这个 path 对应的目录节点存储的数据,数据的版本等信息可以通过 stat 来指定,同时还可以设置是否监控这个目录节点数据的状态
void addAuthInfo(String scheme, byte[] auth) 客户端将自己的授权信息提交给服务器,服务器将根据这个授权信息验证客户端的访问权限。
Stringcreate(String path, byte[] data, List acl, CreateMode createMode) 创建一个给定的目录节点 path, 并给它设置数据,CreateMode 标识有四种形式的目录节点,分别是 PERSISTENT:持久化目录节点,这个目录节点存储的数据不会丢失;PERSISTENT_SEQUENTIAL:顺序自动编号的目录节点,这种目录节点会根据当前已近存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名;EPHEMERAL:临时目录节点,一旦创建这个节点的客户端与服务器端口也就是 session 超时,这种节点会被自动删除;EPHEMERAL_SEQUENTIAL:临时自动编号节点
Statexists(String path, boolean watch) 判断某个 path 是否存在,并设置是否监控这个目录节点,这里的 watcher 是在创建 ZooKeeper 实例时指定的 watcher,exists方法还有一个重载方法,可以指定特定的 watcher
Statexists(String path, Watcher watcher) 重载方法,这里给某个目录节点设置特定的 watcher,Watcher 在 ZooKeeper 是一个核心功能,Watcher 可以监控目录节点的数据变化以及子目录的变化,一旦这些状态发生变化,服务器就会通知所有设置在这个目录节点上的 Watcher,从而每个客户端都很快知道它所关注的目录节点的状态发生变化,而做出相应的反应
void delete(String path, int version) 删除 path 对应的目录节点,version 为 -1 可以匹配任何版本,也就删除了这个目录节点所有数据
List getChildren(String path, boolean watch) 获取指定 path 下的所有子目录节点,同样 getChildren方法也有一个重载方法可以设置特定的 watcher 监控子节点的状态
StatsetData(String path, byte[] data, int version) 给 path 设置数据,可以指定这个数据的版本号,如果 version 为 -1 怎可以匹配任何版本
byte[] getData(String path, boolean watch, Stat stat) 获取这个 path 对应的目录节点存储的数据,数据的版本等信息可以通过 stat 来指定,同时还可以设置是否监控这个目录节点数据的状态
void addAuthInfo(String scheme, byte[] auth) 客户端将自己的授权信息提交给服务器,服务器将根据这个授权信息验证客户端的访问权限。
StatsetACL(String path, List acl, int version) 给某个目录节点重新设置访问权限,需要注意的是 Zookeeper 中的目录节点权限不具有传递性,父目录节点的权限不能传递给子目录节点。目录节点 ACL 由两部分组成:perms 和 id。Perms 有 ALL、READ、WRITE、CREATE、DELETE、ADMIN 几种而 id 标识了访问目录节点的身份列表,默认情况下有以下两种:ANYONE_ID_UNSAFE = new Id("world", "anyone") 和 AUTH_IDS = new Id("auth", "") 分别表示任何人都可以访问和创建者拥有访问权限。
List getACL(String path, Stat stat) 获取某个目录节点的访问权限列表

配置详解

1. 单机模式

tickTime=2000 
dataDir=D:/devtools/zookeeper-3.2.2/build 
clientPort=2181
  • tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。
  • dataDir:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
  • clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。

当这些配置项配置好后,你现在就可以启动 Zookeeper 了,启动后要检查 Zookeeper 是否已经在服务,可以通过 netstat – ano 命令查看是否有你配置的 clientPort 端口号在监听服务。

2. 集群模式

initLimit=5 
syncLimit=2 
server.1=192.168.211.1:2888:3888 
server.2=192.168.211.2:2888:3888
  • initLimit:这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 5*2000=10 秒
  • syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 2*2000=4 秒
  • server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。

除了修改 zoo.cfg 配置文件,集群模式下还要配置一个文件 myid,这个文件在 dataDir 目录下,这个文件里面就有一个数据就是 A 的值,Zookeeper 启动时会读取这个文件,拿到里面的数据与 zoo.cfg 里面的配置信息比较从而判断到底是那个 server。

参考资料:

  1. https://www.ibm.com/developerworks/cn/data/library/bd-zookeeper/
  2. https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/index.html
  3. http://zookeeper.majunwei.com/document/3.4.8/Overview.html

jwt认证

JWT简介

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

与传统cookie/session认证

传统的Session认证通常是将sessionId保存在客户端的cookie中,session存储在服务器端的内存或redis等中。当请求时,通过sessionId与服务器端的session进行对比认证。

传统cookie/session认证的弊端:

  1. 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
  2. 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。(可存储在redis中)
  3. 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

区别

一句话概括与Session认证的区别:jwt只需保存在客户端,session认证需要将sessionId保存在cookie中,session保存在服务器中。

jwt认证流程

流程上是这样的:

  • 用户使用用户名密码来请求服务器
  • 服务器进行验证用户的信息
  • 服务器通过验证发送给用户一个token
  • 客户端存储token,并在每次请求时附送上这个token值
  • 服务端验证token值,并返回数据

JWT的构成

第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

header

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

playload

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后将其进行base64加密,得到Jwt的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW

express-jwt

express jwt认证中间件

github主页:https://github.com/auth0/express-jwt

server.js

app.use(expressJwt({secret:"secret"}).unless({path:['/login']}));                                                                                     
                                                                                                                                                      
app.use(function(err,req,res,next){                                                                                                                   
  if(err.name === 'UnauthorizedError'){                                                                                                               
    console.log(err);                                                                                                                                 
    return res.status(401).send("invalid token");                                                                                                     
  }                                                                                                                                                   
  next();                                                                                                                                             
});   

客户端应用

一般是在请求头里加入Authorization,并加上Bearer标注:

fetch('api/user/1', {
  headers: {
    'Authorization': 'Bearer ' + token
  }
})

node-jsonwebtoken

生成jwt

github主页:https://github.com/auth0/node-jsonwebtoken

Usage

jwt.sign(payload, secretOrPrivateKey, [options, callback])

var authToken = jwt.sign({username: username}, "secret");

JWT优点

  • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
  • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
  • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
  • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

安全相关

  • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
  • 保护好secret私钥,该私钥非常重要。
  • 如果可以,请使用https协议

尾调用

尾调用

在函数的执行过程中,如果最后一个动作是一个函数的调用,即这个调用的返回值被当前函数直接返回,则称为尾调用

如:

function a(x) {
  return b(x);
}

而如下则不是尾调用:

function f(x) {  
  return 1 + g(x)
}

原因是它的最后一步操作是将 g 函数调用的返回值和 1 进行加法操作,而不是调用其他函数,所以它不是尾调用。

尾调用的重要性:它不会在调用栈上增加新的堆栈帧,而是直接更新调用栈,调用栈所占空间始终是常量,节省了内存,避免了爆栈的可能性。

    函数调用会在内存形成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用记录上方,还会形成一个B的调用记录。等到B运行结束,将结果返回到A,B的调用记录才会消失。如果函数B内部还调用函数C,那就还有一个C的调用记录栈,以此类推。所有的调用记录,就形成一个"调用栈"(call stack)。

    尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用记录,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用记录,取代外层函数的调用记录就可以了。

尾递归

    顾名思义,在一个尾调用中,如果函数最后的尾调用位置上是这个函数本身,则被称为尾递
归。递归很常用,但如果没写好的话也会非常消耗内存,导致爆栈。但是对于尾递归来说只
存在一个调用栈,便永远不会发生“栈溢出”错误。
就以求一个给出数的阶乘来探索吧。在传统的做法中利用n的递减乘上原函数,这样复杂度
便会很高,数据量一大便会发生“栈溢出”的错误了。

传统的解决办法

function f(n) {
  if(n === 1) {
	return 1;
  }
  return n * f(n -1);
}

尾调用

function a(n, t) {
  if(n === 1) {
	return t;
  }
  return a(n - 1, n * t)
}

对比两个解决办法的复杂度就知道,尾调用只保留了一个记录。

尾递归优化

上面的尾递归方法虽然可以避免发生“栈溢出”的错误,但只是一个普通的阶乘方法,传递两个参数,第二个参数不容易让调用者理解,可读性差,优化方式有两种

  1. 柯里化:将多参数函数转换成单参数函数

     function currying(fn,n){
     	return function(m){
     		fn.call(this,m,n);
     	}
     }
     const b = currying(a,1);
     b(n);
    

2、使用ES6初始化参数默认值特性

    function a(n,t=1){
		if(n === 1) {
			return t;
		}
		return a(n - 1, n * t)
	}

参考文章:阮老师的这篇文章
    还有:https://www.keephhh.com/2017/05/02/tc/

前端知识网络图

(盗图。。。)
image

看到这张图忽然引发了自己的困惑,自己的定位到底是什么呢?前端Or后端?Node.js的定位又算是什么呢?搞了两年多java和node.js,也一直在写着一些前端页面,对热门前端框架(react、vue、angular)也学了不少,
感觉自己现在的定位十分尴尬...难道要发展成传说中的“全沾工程师”了吗...
另对现在主要使用的node.js的前景不是很明了,node.js到底算是前端还是后端呢?问出这句话肯定很多人会喷node.js肯定是服务器语言呀,不知道还说自己是搞node的,等等....但是,仔细想想,node在后端的大规模应用感觉还是不是很现实,因为node自身现在还是极不成熟的,js也存在其语言本身的一些弊病,正如偶像TJ大神都去搞Go了一样。。。感觉真正的后端工程师也是不屑于使用这门性能不高的语言的...反而适合于前端工程师们快速去适应一门服务器语言,可以提供一些快速的webserver。个人感觉nodejs十分适合于作为api的提供方,去调用其他后端处理,反馈给前端页面。所以感觉node.js没有核心地位,不像java、Ruby、Python那么稳...

以上纯属个人头脑发热,欢迎指点...

js常用工具方法备忘

1. 【Array】 reduce() 方法:

对累加器和数组的每个值 (从左到右)应用一个函数,以将其减少为单个值。
例1:

              let sum = [0, 1, 2, 3].reduce(function(acc, val) {
		  return acc + val;
		}, 0);
		console.log(sum);
		// 6
例2(TJ—only):
                //Return whitelisted properties of an object
		//筛选obj中特定的properties,reduce合并为一个新json对象返回
		module.exports = function(obj, keys){
		  obj = obj || {};
		  if ('string' == typeof keys) keys = keys.split(/ +/);
		  return keys.reduce(function(ret, key){
		    if (null == obj[key]) return ret;
		    ret[key] = obj[key];
		    return ret;
		  }, {});
		};

		var obj = {
		  name: 'tobi',
		  last: 'holowaychuk',
		  email: '[email protected]',
		  _id: '12345'
		};
		var user = only(obj, 'name last email');

【Object】Object.assign() 方法

用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

Object.assign(target, ...sources)
      参数:
        target 目标对象。
        sources (多个)源对象。
      返回值:目标对象。

Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象身上。
针对深度拷贝,需要使用其他方法,因为 Object.assign() 拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。

【Object】Object.is() 方法

Object.is() 方法确定两个值是否是 相同的值。

Object.is(value1, value2);
参数
	value1 需要比较的第一个值。
	value2 需要比较的第二个值。
返回值
	一个布尔值指示两个参数是否相同的。

Object.is() 会在下面这些情况下认为两个值是相同的:

  • 两个值都是 undefined
  • 两个值都是 null
  • 两个值都是 true 或者都是 false
  • 两个值是由相同个数的字符按照相同的顺序组成的字符串
  • 两个值指向同一个对象
  • 两个值都是数字并且
    • 都是正零 +0
    • 都是负零 -0
    • 都是 NaN
    • 都是除零和 NaN 外的其它同一个数字

== 运算符会对它两边的操作数做隐式的类型转换(如果它们是不同类型的值的话),然后才进行相等性比较,(所以才会有类似 "" == false 为 true 的现象),但 Object.is 不会做这种类型转换。

严格相等运算符 === 也不会对操作数进行类型转换,但是它会把 -0 和 +0 这两个数值视为相同的,还会把两个 NaN 看成是不相等的。

Object.is(5,'5'); 	//false
5 == '5'			//true
5 === '5'			//false

Object.is(NaN,NaN);	//true
NaN === NaN			//false

Object.is(-0,+0);	//false
-0 === +0			//true

ES6中的 Set & Map

Set

ES5中的set通常如此表示:

var set = Object.create(null);
set.foo = true;
// checking for existence
if (set.foo) {
	// code to execute
}

ES6:

let set = new Set();
set.add(5);
set.add("5");
console.log(set.size); // 2

ES6的Set去重内部使用的是Object.is()方法,所以5和'5'是两个不同的子元素。

Set方法包括:

  • add() 新增元素
  • delete() 删除元素
  • clear() 清空set
  • has() 校验是否存在元素
  • forEach() 遍历set

可以利用set的特性对数组进行去重:

function eliminateDuplicates(items) {
	return [...new Set(items)];
}
let numbers = [1, 2, 3, 3, 3, 4, 5],
noDuplicates = eliminateDuplicates(numbers);
console.log(noDuplicates); // [1,2,3,4,5]

Set存在的问题:

let set = new Set(),
key = {};
set.add(key);
console.log(set.size); // 1
// eliminate original reference
key = null;
console.log(set.size); // 1
// get the original reference back
key = [...set][0];

set中的对象元素置空后,仍然可以通过set获取到元素原来的引用,这样容易造成内存泄漏。
于是ES6引入了WeakSet。

WeakSet

let set = new WeakSet(),
key = {};
// add the object to the set
set.add(key);
console.log(set.has(key)); // true
// remove the last strong reference to key (also removes from weak set)
key = null;

当WeakSet中的对象元素置空后,WeakSet中的元素也被移除了。

注意: WeakSet中只能保存对象元素

WeakSet与Set的不同:

  • 在WeakSet中,add()、delete()、has()方法接收到非对象参数时会报错;
  • WeakSet不是可迭代的,所以不能使用for-of循环;
  • WeakSet不显示任何迭代器,没有keys()和values(),所以没有办法确定一个WeakSet的内容;
  • WeakSet没有forEach()方法;
  • WeakSet没有size属性

Map

ES5 中的 Map实现:

var map = Object.create(null);
map.foo = "bar";
// retrieving a value
var value = map.foo;
console.log(value); // "bar"

ES5中map实现的问题:

var map = Object.create(null);
map[5] = "foo";
console.log(map["5"]); // "foo"

var map = Object.create(null),
	key1 = {},
	key2 = {};
map[key1] = "foo";
console.log(map[key2]); // "foo"

在ES5中,会自动将数字key转换为string,所以map[5]和map['5']的值是一样的。

key1和key2的value值相同,因为key1和key2被转换为string,对象属性必须是string,因为[object Object]是对象的默认string值,所以key1和key2都转换为这个string值,所以他们的value相同。

ES6 Map:

let map = new Map();
map.set("title", "Understanding ECMAScript 6");
map.set("year", 2016);
console.log(map.get("title")); // "Understanding ECMAScript 6"
console.log(map.get("year")); // 2016

在ES6的Map中每一个对象都被看成是唯一的。

Map方法:

  • set() 增加、修改键值对
  • has(key) 校验是否存在键值
  • get(key) 获取key的value
  • delete(key) 删除键
  • clear() 清空map
  • forEach() 遍历map
  • keys() 返回键数组
  • values() 返回值数组

Map的key也存在和Set同样的内存泄漏隐患。

WeakMap

let map = new WeakMap(),
element = document.querySelector(".element");
map.set(element, "Original");
let value = map.get(element);
console.log(value); // "Original"
// remove the element
element.parentNode.removeChild(element);
element = null;
// the weak map is empty at this point

WeakMap的key只能为Object

WeakMap的方法:

  • delete(key);
  • get(key);
  • set(key);
  • has(key);

关于Promise异常

Promise异常有两种捕获方式:一种是就近捕获,还有一种是最后catch()统一捕获。

Promise.resolve()
  .then( () => {
    // 使 .then() 返回一个 rejected promise
    throw 'Oh no!';
  })
  .then( () => {
    console.log( 'Not called.' );
  }, reason => {
    console.error( 'onRejected function called: ', reason );
});

只会捕获第一个then()中的异常,如第二个then()中抛出异常不会捕获。
优点:可以灵活处理每一个error;
缺点:如果有未捕获的异常,会报错: UnhandledPromiseRejectionWarning

Promise.resolve()
  .then( () => {
    // 使 .then() 返回一个 rejected promise
    throw 'Oh no!';
  })
  .then( () => {
    console.log( 'Not called.' );
  })
  .catch((reason) =>{
	console.error('onRejected function called: ', reason);
  });

catch()会捕获前面所有then()中抛出的异常。
优点:不会出现没有捕获的异常;
缺点: 进行个性化处理时会很复杂

建议:灵活混用这两种方法,需要特别处理时就近处理,不关注的error放在最后统一捕获处理。

child_process中的exec与spawn

exec()官方示例:

const exec = require('child_process').exec;
exec('cat *.js bad_file | wc -l', (error, stdout, stderr) => {
  if (error) {
    console.error(`exec error: ${error}`);
    return;
  }
  console.log(`stdout: ${stdout}`);
  console.log(`stderr: ${stderr}`);
});

spawn()官方示例:

const spawn = require('child_process').spawn;
const ls = spawn('ls', ['-lh', '/usr']);

ls.stdout.on('data', (data) => {
  console.log(`stdout: ${data}`);
});

ls.stderr.on('data', (data) => {
  console.log(`stderr: ${data}`);
});

ls.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
});

exec() 和 spawn() 的区别:

    1. 从return来看,exec()返回string或buffer;spawn()返回stream
    1. spawn()是“异步的异步”,也就是说在子进程开始执行时,它就开始从一个流将数据从子进程返回给Node;exec()是“同步的异步”,意思是虽然exec()是异步的,它一定要等到子进程结束然后尝试一次性返回所有的缓存数据,如果exec的buffer体积设置的不够大,它将会以一个“maxBuffer exceeded”错误失败告终。
    1. exec()默认参数:

         const defaults = {
           encoding: 'utf8',
           timeout: 0,
           maxBuffer: 200*1024,
           killSignal: 'SIGTERM',
           cwd: null,
           env: null
         };
      

spawn()默认参数:

	const defaults = {
	  cwd: undefined,
	  env: process.env
	};

exec是对execFile的封装,execFile又是对spawn的封装。

详见源码:

exports.exec = 
	function(command /*, options, callback*/) {
  		var opts = normalizeExecArgs.apply(null, arguments);
  		return exports.execFile(opts.file,
                          		opts.args,
                          		opts.options,
                          		opts.callback);
};

    exec对于execFile的封装是进行参数处理

    处理的函数:

    normalizeExecArgs

if (process.platform === 'win32') {
    file = process.env.comspec || 'cmd.exe';
    args = ['/s', '/c', '"' + command + '"'];
    // Make a shallow copy before patching so we don't clobber the user's
    // options object.
    options = util._extend({}, options);
    options.windowsVerbatimArguments = true;
  } else {
    file = '/bin/sh';
    args = ['-c', command];
  }

感觉这个处理和我的想法很像...

将简单的command命名做一个,win和linux的平台处理。

此时execFile接受到的就是一个区分平台的command参数。

然后重点来了,继续debug,execFile中:

var options = {
    encoding: 'utf8',
    timeout: 0,
    maxBuffer: 200 * 1024,
    killSignal: 'SIGTERM',
    cwd: null,
    env: null
};

有这么一段,设置了默认的参数。然后后面又是一些参数处理,最后调用spawn方法启动子进程。

上面的简单流程就是启动一个子进程。到这里都没有什么问题。

继续看,重点又来了:

用过子进程应该知道这个child.stderr

下面的代码就解答了为什么子进程会挂掉。

child.stderr.addListener('data', function(chunk) {
    stderrLen += chunk.length;

    if (stderrLen > options.maxBuffer) {
      ex = new Error('stderr maxBuffer exceeded.');
      kill();
    } else {
      if (!encoding)
        _stderr.push(chunk);
      else
        _stderr += chunk;
    }
});

逻辑就是,记录子进程的log大小,一旦超过maxBuffer就kill掉子进程。

原来真相在这里。我们在使用exec时,不知道设置maxBuffer,默认的maxBuffer是200K,当我们子进程日志达到200K时,自动kill()掉了。

这也正是exec()与spawn()最大的区别: exec()限制了maxBuffer默认为200k,超过时会自动kill子进程,并报"maxBuffer exceeded"异常

参考文献:https://www.hacksparrow.com/difference-between-spawn-and-exec-of-node-js-child_process.html

使用node子进程spawn-exec踩过的坑

使用phabricator方法

  • 1. 先创建个帐号,登录名密码与邮箱相同。
  • 2.  加入特定project。
  • 3.  安装arc, 见https://secure.phabricator.com/book/phabricator/article/arcanist/ 。 在Windows上安装稍微麻烦一点,具体见https://secure.phabricator.com/book/phabricator/article/arcanist_windows/
  • 4.  在工作目录,创建个随意的commit,尝试使用arc diff。第一次使用的时候会让你装证书。照着屏幕指令走就行了。
  • 5.  在arc diff的时候,在编辑器里会让你写summary, test plan, reviewers, subscribers。 前三项必须写。test plan一定要有。 Reviewers和subscribers里可以写多个人,用逗号隔开,既可以是人名(邮箱名),也可以是project名。Project名前面需要加#。
  • 6.  如果diff被接受了,直接arc land或者git push就行了
  • 7.  如果reviewer有建议,diff被拒绝,原作者需要做出改进并重新测试。然后使用 git commit --amend修改上次commit,然后再一次arc diff来更新上次的diff。  原则上没被accept的diff不允许push到remote。

提交步骤:
git add(新文件) --> arc diff --> arc land

纯函数

纯函数的定义

纯函数是指不依赖于且不改变它作用域之外的变量状态的函数。

纯函数的返回值只由它调用时的参数决定,它的执行不依赖于系统的状态(比如:何时、何处调用它)

使用纯函数的好处

最主要的好处是没有副作用。纯函数不会修改作用域之外的状态,做到这一点,代码就变得足够简单和清晰:当你调用一个纯函数,你只要关注它的返回值,而不用担心因为别处的问题导致错误。

纯函数是健壮的,改变执行次序不会对系统造成影响,因此纯函数的操作可以并行执行。

纯函数非常容易进行单元测试,因为不需要考虑上下文环境,只需要考虑输入和输出。

最后,尽可能使用纯函数让你的代码保持简单和灵活。

纯函数的应用

典型应用1: Redux中的Reducer设计原则就是必须为纯函数

只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。

典型应用2: React函数式无状态组件

优点

相比于 class 创建组件

  • 语法更简洁
  • 占内存更小(class 有 props context _context 等诸多属性),首次 render 的性能更好
  • 可以写成无副作用的纯函数
  • 可拓展性更强(函数的 compose,currying 等组合方式,比 class 的 extend/inherit 更灵活)

缺点

  • 无生命周期函数

一个组件就是一个函数,函数应该是谈不上生命周期的,但是组件却是有生命周期,stateless functions 没有生命周期。当然了,我们其实可以使用 高阶组件 去实现生命周期

  • 没有 this

在 stateless functions 中,this 是 undefined,所以是不能使用 this 变量。不过换个角度思考,this 是在运行时随时可以被修改或重新赋值,跟外界环境有着密切的联系,正是不使用this才会让组件变得更纯。

ES5和ES6中的继承

ES5

ES5中的继承,看图:
image

function Super() {}
 
function Sub() {}
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
 
var sub = new Sub();
 
Sub.prototype.constructor === Sub; // ② true
sub.constructor === Sub; // ④ true
sub.__proto__ === Sub.prototype; // ⑤ true
Sub.prototype.__proto__ == Super.prototype; // ⑦ true

ES6

ES6中的继承,看图:
image

class Super {}
 
class Sub extends Super {}
 
var sub = new Sub();
 
Sub.prototype.constructor === Sub; // ② true
sub.constructor === Sub; // ④ true
sub.__proto__ === Sub.prototype; // ⑤ true
Sub.__proto__ === Super; // ⑥ true
Sub.prototype.__proto__ === Super.prototype; // ⑦ true

所以

ES6和ES5的继承是一模一样的,只是多了class 和extends ,ES6的子类和父类,子类原型和父类原型,通过__proto__ 连接。

原文链接:ES5和ES6中的继承

cookie session杂谈

what is cookie ?

    通俗的讲就是一个用户通过http协议访问一个服务器的时候,这个服务器会将一些key/value键值对返回给客户端浏览器,并给这些数据加上一些限制条件,在条件符合时这个用户下次访问这个服务器的时候,数据又被完整的带回给服务器。

set cookie: name=value; domain=.mozilla.org; expires=Feb, 13-Mar-2018 11:47:50; path=/; secure

  • Domain:域,表示当前cookie所属于哪个域或子域下面。

    此处需要额外注意的是,在C#中,如果一个cookie不设置对应的Domain,那么在CookieContainer.Add(cookies)的时候,会死掉。对于服务器返回的Set-Cookie中,如果没有指定Domain的值,那么其Domain的值是默认为当前所提交的http的请求所对应的主域名的。比如访问 [http://www.example.com],返回一个cookie,没有指名domain值,那么其为值为默认的www.example.com。

  • Path:表示cookie的所属路径。
  • Expire time/Max-age:表示了cookie的有效期。expire的值,是一个时间,过了这个时间,该cookie就失效了。或者是用max-age指定当前cookie是在多长时间之后而失效。如果服务器返回的一个cookie,没有指定其expire time,那么表明此cookie有效期只是当前的session,即是session cookie,当前session会话结束后,就过期了。对应的,当关闭(浏览器中)该页面的时候,此cookie就应该被浏览器所删除了。
  • secure:表示该cookie只能用https传输。一般用于包含认证信息的cookie,要求传输此cookie的时候,必须用https传输。
  • httponly:表示此cookie必须用于http或https传输。这意味着,浏览器脚本,比如javascript中,是不允许访问操作此cookie的。

发向服务器的所有 cookie 的最大数量(空间)仍旧维持原始规范中所指出的:4KB。所有超出该限制的 cookie 都会被截掉并且不会发送至服务器。

由于cookie存在客户端,可能被篡改。

cookie本身并没有会话cookie和持久化cookie之分,只是取决于是否设置了存活时间。

cookie-parser

cookie parsing middleware: github地址

Installation

$ npm install cookie-parser

API

var express = require('express')
var cookieParser = require('cookie-parser')

var app = express()
app.use(cookieParser())

cookieParser(secret, options)

secret:(String/Array)签名cookie,不指定则不会解析signed cookie

options:

    decode: 用于解码cookie值的方法,默认为decodeURIComponent

what is session ?

    session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。

session 可以存放在 1)内存、2)cookie本身、3)redis 或 memcached 等缓存中,或者4)数据库中。

Express Session

github 地址

Installation

$ npm install express-session

var session = require('express-session')

session(option)

  • cookie: cookie的相关属性设置,默认为:{ path: '/', httpOnly: true, secure: false, maxAge: null }
  • genid: 用于生成新的session id的方法
  • name: cookie中session Id的名字,默认为:connect.sid
  • proxy: 当设置安全cookie时,信任反向代理
  • resave: 即使request.session没有变化,也强制将session再保存到store中
  • rolling: 强制在每个响应中设置session id cookie
  • saveUninitialized: 强制将未初始化的session保存到store中
  • secret(required option)用于session id cookie签名
  • store(default:MemoryStore,通常使用redis)
  • unset

将session存储在redis中,可实现不同服务间的session共享

connect-redis

redis store : github地址(又见TJ...)

node-redis

redis client : github地址

我的相关代码:

const cookieParser = require('cookie-parser'); 
const session = require('express-session');
const redisStore = require('connect-redis')(session);
const redis = require('redis'); 

//connect redis   
const redisClient = redis.createClient(); 
redisClient.on('error',function(err){ 
  logger.error(err);  
  process.exit(1);
});
app.use(cookieParser());  
app.use(session({ 
  store: new redisStore({ 
client:redisClient
  }), 
  secret: 'ACDataServer', 
  cookie: {   
maxAge: 12*60*60*1000 
  },  
  proxy: true,
  saveUninitialized: true,
  resave: false   
}));                    

cookie被禁用处理方法

由于cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。

  • 经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面,附加方式也有两种:

        一种是作为URL路径的附加信息,表现形式为http://...../xxx;jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764

        另一种是作为查询字符串附加在URL后面,表现形式为http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764

    这两种方式对于用户来说是没有区别的,只是服务器在解析的时候处理的方式不同,采用第一种方式也有利于把session id的信息和正常程序参数区分开来。

    为了在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。

  • 另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。比如下面的表单

      <form name="testform" action="/xxx">
      	<input type="text">
      </form>
    
  • 在被传递给客户端之前将被改写成

      <form name="testform" action="/xxx">
      <input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
      <input type="text">
      </form>
    

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.