前端跨域请求的几种解决方法

终于忙完啦,又有时间写博客了=、=,虽然这篇很水,这篇文章将简单介绍跨域问题的起因和几种常用的前端解决跨域的方法。

跨域问题的起源–同源策略

浏览器的同源策略限制了一个源加载的文档或脚本如何与来自另一个源的资源进行交互,是一个用于隔离潜在恶意文件的关键的安全机制。

只有当协议(Protocol),主机(host)和端口(port)相同的时候下,两个资源才是同源的。

比如:http://www.cxyblogbiu.com:80

其中 http 为协议(Protocol)

www.cxyblogbiu.com 为主机(host)

80 为端口

只要一个不同,那么就是不同的源。

当网络操作跨域时,浏览器就诸多限制。

跨域解决方法

修改 document.domain

可以通过修改document.domain,将当前域设置为当前域或当前域的超级域。

比如:将 http://www.cxyblogbiu.com 设置为 http://cxyblogbiu.com,那么就可以通过http://cxyblogbiu.com的同源验证啦~

JSONP

但是与后台交互请求时,跨域就会相当麻烦,JSONP就是依靠同源策略允许跨域资源嵌入,来实现跨域请求。

只需要通过 <script src=""> 的方式来请求接口,然后后台返回一个回调函数,包裹返回的数据,这样返回后,浏览器会自动执行回调函数,传入回调的数据。

但是这个方法缺点很多:

  1. 必须是GET请求
  2. 需要在数据外再包裹一层回调函数
  3. 回调函数需要处于全局环境下

CORS(跨域资源共享)

同源策略带来的跨域请求是浏览器独有的,但是为了提高web应用的可用性,浏览器支持跨域请求是必然的,于是有了CORS(跨域资源共享),这将是解决跨域请求的最优策略

CORS的使用需要后端支持,需要在响应头上加入一些CORS的字段:

  1. Access-Control-Allow-Origin

    这个字段用来表明允许的源,可以是固定的源,也可以使用 * 来表明允许所有的源

  2. Access-Control-Allow-Methods

    这个字段表明允许的请求方式,
    比如:Access-Control-Allow-Methods: POST, GET, OPTIONS

支持后就可以用啦

后记

没错,这就写完了,是不是一脸懵逼0、0,

我也是的,一个demo都没有,一个详细的说明也没有=、=

这篇文章注定会回来填坑的,

跨域可不止是请求有跨域哦-、-,还有页面间的跨域,存储空间的跨域~~

参考资料

MDN_同源策略

MDN_CORS

END

2017-05-05 完成

2016-12-12 立项

JS双等号(==)和三等号(===)详解

JS中的双等号(==)和三等号(===)是有很大的区别的,这是初学JS的人都知道的,但是大家都对其中运算原理不是特别清楚,这篇文章将讲述两个等号内部的运算规则,以及一些简单的总结。

运算规则

首先我们来看双等号(==)和三等号(===)的内部的运算规则。虽然有点长,但是只有详细了解其内部的详细规则,我们才能释然一些奇怪的情况(比如 [] == false

双等号(==)

JS中双等号(==)的判断是很复杂的,这也是最容易弄混淆的地方,所以很多的面试都会考察这一点,同时在开发中也要求尽量少使用双等号(==)。

细则

  1. If Type(x) is the same as Type(y) then,

    Return the result of performing Strict Equality Comparison x === y.

  2. If x is null and y is undefined, return true.

  3. If x is undefined and y is null, return true.

  4. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).

  5. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.

  6. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.

  7. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).

  8. If Type(x) is either String, Number, or Symbol and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).

  9. If Type(x) is Object and Type(y) is either String, Number, or Symbol, return the result of the comparison ToPrimitive(x) == y.

  10. Return false.

精简

在上面我们可以看出,双等号(==)的运算规则简单的就是如下规则:

  1. 判断两边的变量 是否同类型 ,如果同类型就返回三等号(===)的运算的结果。

  2. 判断两边的变量 是否一个为 null 一个为 undefined,如果是就返回 true

    ps:可以简单的记为 null == undefinedtrue

  3. 判断两边的变量 是否一个为 Number类型 一个为 String类型,如果是,则将String类型的变量使用 ToNumber 方法转换为Number类型 再执行比较。

    ps:相当于 '123' == 1 转化为 123 == 1

  4. 判断是否有一边为 Boolean类型, 如果有则对这个Boolean类型变量使用 ToNumber 方法转换为Number类型 再执行比较。

    ps:结合上面 '2' == true 先转换为 '2' == 1, 在转换为 2 == 1

  5. 判断两边的变量 是否一个为 Number、String、symbol类型 一个为 Object类型 ,如果是则对Object类型的变量执行ToPrimitive方法的转换后再进行比较

  6. 如果上面的条件都没有满足,那么直接返回 false

三等号(===)

JS中双等号(==)的判断是很简单,它的规则也同样简单。

细则

  1. If Type(x) is different from Type(y), return false.

  2. If Type(x) is Number, then

    1. If x is NaN, return false.

    2. If y is NaN, return false.

    3. If x is the same Number value as y, return true.

    4. If x is +0 and y is -0, return true.

    5. If x is -0 and y is +0, return true.

    6. Return false.

  3. Return SameValueNonNumber(x, y).

精简

三等号(===)的运算规则就很简单:

  1. 类型不同返回 false

  2. 数值类型 NaN 永远返回 false

  3. 其余的基础类型直接比较值是否相等

    对象比较引用是否相同。

特别的转换函数

上面可以看到,在运算中使用了 ToNumber,ToPrimitive,SameValueNonNumber这3个函数,虽然看名字就能知道它是干啥的,但是其中还是有一些坑

ToNumber

Argument Type Result
Undefined Return NaN.
Null Return +0.
Boolean Return 1 if argument is true. Return +0 if argument is false.
Number Return argument (no conversion).
String See grammar and conversion algorithm below.
Symbol Throw a TypeError exception.
Object Apply the following steps:
Let primValue be ? ToPrimitive(argument, hint Number).
Return ? ToNumber(primValue).

其中 StringNumber 的转换是最复杂的,将单开一片文章进行讲解。

ToPrimitive

这个方法的算法看似复杂,其实就是主要将对象转换为基本数据类型。

详细可参看参考资料中的链接ToPrimitive

只需要记住:

  1. 优先转换为 String 时,依次调用对象的 toStringvalueOf ,获取最先返回不是对象的那个值

  2. 优先转换为 Number 时,依次调用对象的 valueOftoString ,获取最先返回不是对象的那个值Number

  3. 默认转换是默认转换为 Number

SameValueNonNumber

这个方法就是比较不是数字的两个值

  1. Assert: Type(x) is not Number.

  2. Assert: Type(x) is the same as Type(y).

  3. If Type(x) is Undefined, return true.

  4. If Type(x) is Null, return true.

  5. If Type(x) is String, then

    If x and y are exactly the same sequence of code units (same length and same code units at corresponding indices), return true;

    otherwise, return false.

  6. If Type(x) is Boolean, then

    If x and y are both true or both false, return true;

    otherwise, return false.

  7. If Type(x) is Symbol, then

    If x and y are both the same Symbol value, return true;

    otherwise, return false.

  8. Return true if x and y are the same Object value.

    Otherwise, return false.

从上面的算法不难看出其实就是很普通的比较,相信看一看就能懂。

一些感到奇怪的情况

这里将列出一些让人感觉奇怪的等式,并给出转换过程

[] == false 为true

[] == false

[] == 0 //转换boolean

‘’ == 0 //转换对象 调用array的toString

0 == 0 //转换string为number

[0] == false 为true

[0] == false

[0] == 0 //转换boolean

‘0’ == 0 //转换对象 由于Array是调用的Object的valueOf,返回的为对象,将继续调用array的toString来获取非对象类型数值

0 == 0 //转换string为number

‘2’ == true 为false

‘2’ == true

‘2’ == 1 //转换boolean

2 == 1 //转换string

undefined == 0 和 null == 0 为false

undefinednull 必须相互比较或者相同才会为 true

参考资料

双等号运算规则

三等号运算规则

ToNumber

ToPrimitive

SameValueNonNumber

END

2017-04-19 完成

2017-04-12 立项

JS异步编程方法

异步永远是编程的一个重要话题,可以提高对计算机资源的利用,避免阻塞等等。这篇文章将讲述JS编程中异步编程的几种方法。

传统的异步编程 callback

传统的异步编程当然是回调,即使后来的异步编程再便捷,它的核心依然是回调。

回调就是:将一个函数(回调函数)作为参数传递给执行异步操作的函数。当异步操作执行完毕的时候,将异步操作的结果作为回调函数的参数来调用回调函数。这样就可以实现异步编程了。

由于异步执行的空间不再主程序(主线程),所以不会对主线程造成阻塞,这对于Web端的用户体验是尤为重要的。

例子

1
2
3
4
5
6
7
8
9
10
11

function f_callback(x, callback){
setTimeout(() => {//模拟异步程序执行了2s
callback(x * x);
}, 2000)
}

f_callback(1, function(val){
console.log(val);
})

优点

  • 简单易懂

缺点

  • 当异步处理嵌套过多时,容易产生回调地狱,代码可读性差

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    f_callback(?, function(val){
    ...
    callback(val,function(val){
    ...
    callback(val,function(val){
    ...
    callback(val,function(val){
    ...
    });
    });
    });
    })

Promise

为了解决多层回调导致的回调地狱,聪明的程序员们想出了很多解决方法,Promise 便是成功的纳入到ES标准中的一个解决方法。

Promise 通过采用 then() 方法来将回调函数从异步嵌套中抽取出来,使的代码可读性更加直观。

同时可以通过在 then() 方法的中的 onFulfilled()onRejected() 返回数据获得一个新的 Promise 来实现 then() 的链式调用。

如果返回的是一个 Promise 对象,那么接下来的 then() 是连接的返回的 Promise 对象。

在编写异步操作函数时,我们只需要返回一个 Promise 对象即可。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13

function f_promise(x){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(x * x);
}, 2000)
})
}

f_promise(2).then( val => {
console.log(val);
})

优点

  • 避免了回调地狱

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    f_promise(?)
    .then(() => {
    return other_f_promise()
    })
    .then(() => {
    return other_f_promise()
    })
    .then(() => {
    return other_f_promise()
    })
    ...
    .catch(callback)

缺点

  • 需要学习新的一个概念
  • 降低了性能(这也是不可避免的嘛)

参考资料

MDN_Promise

Promise A+规范_英文

Promise A+规范_翻译

Generator

Generator 其实只是一个可迭代的对象,它主要是用来控制 Generator function 的内部流程的,但是经过 著名程序员 TJ Holowaychuk 大佬的 co模块 的改造后,便变成了一个很好用的异步编程的东西了。

这里就不详细讲述 Generator 的使用,简单的介绍一下 co 模块的实现方式。

co 只支持:

  • promises
  • thunks (functions)
  • array (parallel execution)
  • objects (parallel execution)
  • generators (delegation)
  • generator functions (delegation)

这样的 yield 数据。

因为也只是自动帮助你调用 Generatornext 方法,或者使用 thunks 帮你调用回调,

手写的方式就是下面这种方式(可以看到手写还不如直接Promise):

例子

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

function * f_generator(x){

yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve(x * x);
}, 2000)
})

yield function f_trunk(callback){
setTimeout(() => {
callback(2);
}, 2000)
}
console.log(y);
}

let g=f_generator(3);
g.next().value.then( val => {
console.log(val);
g.next().value(val => {
console.log(val);
})
});

优点

  • 结合 co 模块还是很好用的

缺点

  • 这本来就不是干异步处理的!

参考资料

Co 模块

MDN_Generator

async/await

async/await 是ES7中提出的一个异步解决方法,目前只能使用 JS转换器(babel) 或者 nodejs(7.0+)来体验。

async/await 其实就是一个自动化执行 Generator 的语法糖(感觉就是学co出来的),但是其语义性更强。

async 函数会返回一个 Promise 对象,当内部的执行完成时,触发这个 Promise 对象的 onFulfilled,传递函数的返回值。

await 会返回其后面的变量,如果是 Promise 对象,则会将Promise 对象的返回值赋出来。如果有错误则直接触发函数反悔的Promise 对象的 onRejected

例子

这是内部正确的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

async function f_async_await(x){
let y =await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(x * x);
}, 2000)
})
return y;
}

f_async_await(4).then( val => {
console.log(val);//9
}).catch(val => {
console.log(val);
})

这是内部有错的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

async function f_async_await(x){
let y =await new Promise((resolve, reject) => {
setTimeout(() => {
reject(x * x);
}, 2000)
})

let y =await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(x * x);
}, 2000)
})

return y;
}

f_async_await(3).then( val => {
console.log(val);
}).catch(val => {
console.log(val+'123');//9123
})

优点

  • 语义明确:将异步的写法,使用类似同步的方法编写出来

缺点

  • 有学习成本:需要接纳新的知识,且必须知道Promise

参考资料

阮一峰_ECMAScript 6 入门_async


结尾

其实所有的异步都是基于callback的思想,可能还有什么事件机制,监听订阅等等,但是它们都是通过调用预先设定好的方法来实现,其实和callback区别不大。

我们改善的都是编写方式,如何更加简单明了方法来编写出更加复杂的功能,才是我们所需要去开拓的。Promise 解决了回调地狱、async/await 使我们可以使用同步的方式去异步编程。都是为了代码更加可读,以及编码更加快捷,这也许就是程序之美的所在~~

END

2017-04-11 完成

2017-04-11 立项

toString和toLocalString的区别

简单讲述toString和toLocalString的区别

简单的讲解

toString 方法和 toLocalString 方法在web的一些对象上都包含有,但一直都是很模糊的,都是将对象内容按照一定的规则转变为一个字符串。而 toStringtoLocalString 的区别便主要是内部转换方式的区别。

toString 方法采用的是规定好的默认的转换方式,在不同的电脑上得到的结果是相同的,利于进行编程处理。

toLocalString 方法会根据电脑本机的配置进行转换,在不同的电脑上得到的结果是不同的,利于内容的展示。

其中比较典型的对象便是 Date 对象。

Date

DatetoString 方法总是返回的一个标准的时间字符串,

toLocalString 方法则会根据电脑的配置时间改变格式。

1
2
3
4
5

let date = new Date();
console.log(date.toString());//Sun Apr 09 2017 13:27:49 GMT+0800 (中国标准时间)
console.log(date.toLocaleString());//2017-04-09 13:27:49

当我将系统的语言配置切换为英语(美国后)

1
2
3
4
5
6
7
8

let date = new Date();
console.log(date.toString());//Sun Apr 09 2017 13:27:49 GMT+0800 (中国标准时间)
console.log(date.toLocaleString());//2017-04-09 13:27:49

console.log(date.toString());//Sun Apr 09 2017 13:47:11 GMT+0800 (China Standard Time)
console.log(date.toLocaleString());//4/9/2017, 1:47:11 PM

可以看到输出的 toLocaleString() 内容格式就变了,而 toString() 除了后面括号内的描述改变了,其他的格式是统一的。

这样我们就对 toString()toLocaleString() 的区别的理解就更加的直观了。

END

2017-04-09 完成

2017-02-19 立项

Promise链式调用方法的实现

Promise作为ES6中用于处理编写异步代码的一个新的对象,使JS的异步代码可读性更高,并且ES6提供了all以及race函数用于处理一些常见的异步情况。但是函数的调用方法不是连贯的,这篇文章将讲述对Promise链式调用的方法的简单实现。

ES6规定的方法

ES6提供了 allrace 方法来处理一些简单的。

Promise.all()

Promise.all() 函数是将可迭代参数中的 promises 打包在一起,当其中全部完成时才触发 onFulfilled,返回与可迭代参数中的 promises 结构对应的由各个promise的返回值构成的数据,当有一个失败时触发 onRejected,返回错误的promise的返回值。

比如当希望两个异步同时完成时在执行接下来的任务,那么就可以使用 Promise.all()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

let a=new Promise( (resolve,reject) => {
//dosomething
resolve('a')
console.log('end a',new Date().getTime())
})

let b=new Promise( (resolve,reject) => {
//dosomething
setTimeout(()=>{
resolve('b')
console.log('end b',new Date().getTime())
},1000)
})

let testall=Promise.all([a,b]).then( val => {
console.log(val,new Date().getTime());
//dosomething
});

这样就可以在两个异步处理执行完后运行,再继续运行需要的代码。

Promise.race()

Promise.race()Promise.all() 相对应。

Promise.race() 函数是在可迭代参数中的 promises 有一个 promise 完成时便触发 onFulfilled,返回这个第一个完成的 promise 的值,当有一个失败时触发 onRejected,返回错误的promise的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

let a=new Promise( (resolve,reject) => {
//dosomething
resolve('a')
console.log('end a',new Date().getTime())
})

let b=new Promise( (resolve,reject) => {
//dosomething
setTimeout(()=>{
reject('b')
console.log('end b',new Date().getTime())
},1000)
})

let testrace=Promise.race([a,b]).then( val => {
console.log(val,new Date().getTime());
//dosomething
});

可以看到当 a 完成时, testrace 便执行了。并且 b 报错也不会再触发 onRejected


这就是ES6提出的两个方法了(ps: resolvereject 只是简单的两种种状态的封装,并没有对一些特殊的异步处理环境提供太多的帮助)。

链式调用的方法

PS:这些方法的需求完全由个人看法而想出来的,不是一定需要这样, Promise.race()Promise.all() 两种方法就已经可以很好的处理异步请求间的关系了。

Promise.race()Promise.all() 需要混用时代码将变得冗余,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14

let g1=Promise.all([a,b]);

let g2=Promise.all([c,d]);

let g3=Promise.all([e,f]);

let h=Promise.race([g1,g2,g3]);

h.then(val=>{
console.log(val,new Date().getTime());
//dosomething
});

这里为何不能使用链式的方法呢?为何不能写成这样的方式?

1
2
3
4
5
6

a.all(b).race(c,d).race(e,f).then(val=>{
console.log(val,new Date().getTime());
//dosomething
});

all的链式实现 and()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

这个函数会将参数中的 `promise``function` 转化出来的 `promise` 与调用该函数的 `promise` 使用 `all` 合并在一起;

Promise.prototype.and=function(){
let promises=[...arguments];
for(let i=0;i<promises.length;i++){
if(!(promises[i] instanceof Promise)){
if(typeof promises[i] === 'function'){
promises[i]=new Promise(promises[i]);
}else{
console.error('错误的promise')
return void(0)
}
}
}

return Promise.all([this].concat(promises))
}

race的链式实现 or()

这个函数会将参数中的 promise 使用 all 合并为一个promise,与调用的promise调用race。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Promise.prototype.or=function(promise){
let promises=[...arguments];
for(let i=0;i<promises.length;i++){
if(!(promises[i] instanceof Promise)){
if(typeof promises[i] === 'function'){
promises[i]=new Promise(promises[i]);
}else{
console.error('错误的promise')
return void(0)
}
}
}

return Promise.race([new Promise( (res,rej)=>{//增加一层来使两个Promise在同一层级
this.then(val=>{
res(val)
});
}),Promise.all(promises)])
}

案例

利用这两个简单的函数我们就可以优化之前的代码。

1
2
3
4
5
6

a.and(b).or(c,d).or(e,f).then(val=>{
console.log(val,new Date().getTime());
//dosomething
});

让我们来猜一猜下面的代码输出什么吧-。-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

function getTimeRandomPromise(val){
return new Promise( (resolve,reject) => {
//dosomething
setTimeout(()=>{
resolve(val)
console.log('end '+val,new Date().getTime())
},Math.random()*1000)
})
}

let a=getTimeRandomPromise('a'),
b=getTimeRandomPromise('b'),
c=getTimeRandomPromise('c'),
d=getTimeRandomPromise('d'),
e=getTimeRandomPromise('e'),
f=getTimeRandomPromise('f');

a.and(b).or(c,d).or(e,f).then(val=>{
console.log(val,new Date().getTime());
//dosomething
});

后记

当然链式调用只是一种编程方式,这种方式在理解后用起来很爽快,但是当链式调用过长时,代码可读性将会降低,所以在特别复杂的处理情况下还是列出来的好~

参考资料

MDN_Promise

Promise A+规范_英文

Promise A+规范_翻译

END

2017-04-05 完成

2017-04-01 立项

Nodejs之Stream(1)--简单介绍 可读流和可写流

NODEJS中有一个十分重要的数据类型,就是 Stream(流),然而晃眼一看是很难一下理解node中流的使用方法的,这篇文章将依靠node的文件流简单的讲述 stream 在的可写流和可读流,在node中的基本使用方法。

可读流(Readable Streams)

数据处理当然是从它开始来的地方进来,我们这里也从读取开始。

node中一类流叫做可读流,这类流是对数据的源头所做出的抽象,它只能输出数据而不能接受数据。

建立文件可读流以及data事件

在node中我们可以使用 fs.createReadStream 快速获得一个文件的可读流。

同时通过监听 data 事件来获取数据。

1
2
3
4
5
6
7

let fileReadStream=fs.createReadStream('./test.txt')

fileReadStream.on('data',function(data){
console.log(data)
})

我们可以看到输出了 BufferArray 类型的数据,如果我们想要看到数据的字符内容我们可以在创建文档流的时候设置 encoding,在这里我的文档是 utf-8 的编码,所以我使用 utf-8,这个需要根据文件进行调整。

1
2
3
4
5
6
7
8
9

let fileReadStream=fs.createReadStream('./test.txt',{
encoding:'utf-8'
})

fileReadStream.on('data',function(data){
console.log(data)
})

这样我们就可以将对 BufferArray 的操作改为对 String 的了。

在 Nodejs API创建的流对象Stream中只支持对 BufferArrayString 的操作,在一些第三方的流中我们对其它类型进行操作,称为 对象模式(object mode)

end事件和close事件

end 事件将在流中没有数据可供使用时触发。

close 事件将在流或其底层资源(比如一个文件)关闭后触发。close 事件触发后,该流将不会再触发任何事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

let fs=require('fs');

let fileReadStream=fs.createReadStream('./test.txt',{
encoding:'utf-8'
})

fileReadStream.on('data',function(data){
console.log(data.length)
})

fileReadStream.on('end',function(){
console.log('没有更多的数据啦!');
})

fileReadStream.on('close',function(){
console.log('文件读取完毕!');
})

这3个事件是十分基础和常用的,我们先暂时了解到这里,下面我们看看数据的输出流–可写流。

可写流(Writable Streams)

可写流和可读流相对应,它是对数据的终点所做出的抽象,它只能接受数据而不能输出数据。

建立文件可写流以及write方法

在node中我们可以使用 fs.createWriteStream 方法获取一个文件可写流来将数据写入文件当中。

1
2
3
4
5

let fileWriteStream=fs.createWriteStream(path.resolve(__dirname,'./test-out.txt'));

fileWriteStream.write('这是输出数据!');

使用 write 方法可以将数据写入到流当中,将数据交给可写流处理。

可读流的pipe()

pipe() 方法是将可读流的数据接到可写流上,实现流数据的自动传递,这样即使是可读流较快,目标可写流也不会超负荷(overwhelmed),可以减少一些对数据负荷处理的代码编写。

1
2
3
4
5
6
7
8
9
10

let fileReadStream=fs.createReadStream(path.resolve(__dirname,'./test.txt'));
let fileWriteStream=fs.createWriteStream(path.resolve(__dirname,'./test-out.txt'));

fileReadStream.pipe(fileWriteStream,{ end: false });

fileReadStream.on('end',function(){
fileWriteStream.end('这是输出数据!')
});

我们可以在可读流和可写流中加入一些其他的数据处理流来完成一些操作,比如:压缩文件

后记

这篇文章就大概讲述到这里了,以前的文章都是一口气写了很多,以至于每篇都讲述的过于全面而导致内容很片面,从今以后的文章将会慢慢采用分解的方式讲述,由浅入深,这样我也学习的更加深入,所能写出的文章内容也更加深刻。

这篇文章只是简单的讲述了Steam的可读流和可写流,如果想要了解的更加详细,还是建议去API看看,下一篇文章将讲述双向流,即同时实现了可读流和可写流的Stream(Duplex 流与 Transform 流)。

参考资料

NODE Stream 中文官方文档

END

2017-03-24 完成

2017-03-05 立项

WebWorker异步处理

HTML5的时代来到了,由于前端负责的数据处理越来越大,单单靠一个JS的单线程越来越力不从心,webWorker的出现能更好的解决该问题,这篇文章将讲述webWorker的使用。

简单的实例

使用webWorker我们可以将一些大量计算的事情放置在另一个线程进行处理,这时候我们页面不会被卡死,让网页使用流畅。

比如我们进行一个50亿次的加法计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

console.time('启动页面');

function add5billion(){
var x=0;
for(var i=0;i<1000000000;i++){
x++;
x++;
x++;
x++;
x++;
}
return x;
}
add5billion();


console.timeEnd('启动页面');//启动页面: 2884.2ms

在我的电脑上是2秒多,但一般的电脑可能要慢很多,但是作为页面展示如果需要2秒的时间那么用户体验将是相当不好的,因为两秒种的时间用户都无法操作。

这时候一般只能使用 setTimeout 将计算拆除,或者请求服务器处理,但是都很麻烦,在这里我们使用 Worker 就可以了。

script of html

1
2
3
4
5
6
7
8
9
10
11

console.time('启动页面');

var worker=new Worker('worker-5x10^9.js');//50亿加法计算的函数文件

worker.onmessage=function(e){
console.log(e.data);
}

console.timeEnd('启动页面');//启动页面: 0.196ms

worker-5x10^9.js

1
2
3
4
5
6
7
8
9
10
11

var x=0;
for(var i=0;i<1000000000;i++){
x++;
x++;
x++;
x++;
x++;
}
this.postMessage(x);

使用webWorker可以避免大量的计算导致的拥塞,避免页面假死,充分利用客户端的物理资源。

下面我们来详细讲述如何使用 webWorker

Worker简介

兼容性

虽然作为HTM5的新特性,但是各大浏览器对 webWorker 的支持都是很好的,所以我们基本可以大胆的尝试在工程中使用。

构造函数

webWorker 在浏览器中的构造函数为 Worker()

但是必须传入一个字符串参数作为 webWorker 运行的内容,在上面的示例中便是传入了 worker-5x10^9.js 来让其执行。

语法

new Worker(aURL);

参数

参数名 参数类型 参数描述 是否必要
aURL String webWorker运行脚本的地址,它必须遵循同源策略 TRUE

用法

1
2
3

var worker = new Worker(workerURL);

实例API

我们可以看看输出worker实例对象。

我们可以看到它主要包含4个方法:

  1. onerror
  2. onmessage
  3. postMessage
  4. terminate

十分简单

onerror

这个方法是在Worker的error事件触发时执行,需要给onerror赋值一个函数,并且会给该函数传递一个事件对象。

script of html

1
2
3
4
5
6
7

let worker = new Worker('worker-error.js');

worker.onerror=function(e){
console.log('错误对象',e);
}

worker-error.js

1
2
3

a=b;

这样我们就可以获取到内部的错误信息,并加以处理,但是一般对这方面的需求很少,主要获取内部运行的情况。

onmessage

这个方法是在Worker的内部执行 postMessage 事件触发时执行,需要给onmessage赋值一个函数,并且会给该函数传递一个事件对象。传递的值通过事件对象的 data 属性获取。

script of html

1
2
3
4
5
6
7

let worker = new Worker('worker-postMessage.js');

worker.onmessage=function(e){
console.log(e,e.data);
}

worker-postMessage.js

1
2
3

postMessage("I'm Worker");

这样我们就可以获取到内部的发出信息。

postMessage(aMessage, transferList)

这个方法是向Worker的内部发送消息,在这里不止是传递字符串,可以传递对象等,但是不能传递函数包含的对象。

他会对对象进行深度克隆,所以可以用来进行对象的异步的深度克隆。这一部分在下面进行详细阐述。

参数名 参数类型 参数描述 是否必要
aMessage any 需要发送的消息,可以传递对象,会对对象(可以包含js的内置对象类型)进行深度克隆,但是不支持函数的传递 FALSE
transferList array 将对象的上下文环境移交给worker FALSE

script of html

1
2
3
4
5
6
7
8
9
10
11

let worker = new Worker('worker-backData.js');

worker.onmessage=function(e){
console.log(e.data);
}

worker.postMessage('123');
worker.postMessage({x:1});
worker.postMessage({x:1,y:function(){}});

worker-postMessage.js

1
2
3
4
5

onmessage=function(e){
postMessage(JSON.stringify(e.data));
}

由于执行是异步的,所以还是要主线程的JS执行完毕后才能执行对事件的响应,第一个错误是因为对有函数的对象解析导致的,所以我们只能看到前面两个的字付串输出。

terminate()

这个函数是立刻停止worker的运行,不会等待worker的运行完成。

script of html

1
2
3
4
5
6
7
8
9
10
11
12
13
14

let worker = new Worker('worker-backData.js');

worker.onmessage=function(e){
console.log(e.data);
}

worker.postMessage('123');

setTimeout(()=>{
worker.terminate();
worker.postMessage('223');
},1000)

我们可以看到这里只有 123 输出,虽然依然可以向worker发送消息,但是没什么卵用。

webWorker内部环境

worker的执行的环境和浏览器环境的有一些不同,浏览器内全局变量是 window 而 worker内部是DedicatedWorkerGlobalScope

在这个环境中无法使用window来获取全局变量

script of html

1
2
3
4
5
6
7

let worker = new Worker('worker-window.js');

worker.onmessage=function(e){
console.log(e.data);
}

worker-window.js

1
2
3

window.postMessage('123');

我们可以看到window是没有定义的,想要获取我们需要使用 self

worker-self.js

1
2
3

self.postMessage('123');

这样我们就可以获取内部的全局变量了,其实self在浏览器环境下也是指向全局环境的。

全局环境

worker的内部环境当然也和浏览器下的环境差不多,都含有相关的全局方法。

console

worker的内部环境也是支持console.log来方便调试的,并且它的输出会展示在浏览器的控制台当中

worker-self.js

1
2
3
4
5

console.log('console.log')
console.log('console.error')
console.info('console.info')

对AJAX和webSocket的支持

1
2
3
4

console.log(XMLHttpRequest)//function XMLHttpRequest() { [native code] }
console.log(WebSocket)//function WebSocket() { [native code] }

这两个对象都是支持的,所以webWorker,可以负责数据请求的收发。

无法操作DOM

1
2
3

console.log(document)//error

worker内部是无法操作DOM的,主要是为了线程安全,但是我们在采用VDOM的时候,便可以放入worker内部处理VDOM。

postMessage

在worker的postMessage方法,已经worker内部的postMessage都是支持对象传递的,它可以传递满足The structured clone algorithm | 结构化克隆算法的对象。

script of html

1
2
3
4
5
6
7
8
9

let worker = new Worker('worker-transferList.js');

let a={x:1};

let obj={x:a,y:a,z:{x:1}}

worker.postMessage(obj);

worker-transferList.js

1
2
3
4
5
6

onmessage=function(e){
console.log(e.data.x===e.data.y);//true
console.log(e.data.x===e.data.z);//flase
};

同时在深复制的时候还保持了内部的索引结构。

transferList

最难弄清的其实是 postMessagetransferList 参数。

transferList数组类型

transferList数组类型必须为 ArrayBuffer, MessagePort and ImageBitmap

1
2
3
4
5
6
7
8
9
10
11

let worker = new Worker('worker-transferList.js');

let obj={x:1}

worker.onmessage=function(e){
console.log(e.data);
}

worker.postMessage(obj,[obj]);//error: Value at index 0 does not have a transferable type.

我们设置成 ArrayBuffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

let worker = new Worker('worker-transferList.js');

let bufArr=new ArrayBuffer(100);

let obj={x:bufArr}

console.log(bufArr.byteLength);//100

worker.onmessage=function(e){
console.log(e.data);
}

worker.postMessage(obj,[bufArr]);

console.log(bufArr.byteLength);//0

我们可以看到post成功了,但同时我们会发现bufArr.byteLength的长度改变了,因为byteLength的上下文已经搬移到worker内部了。

script of html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

let worker = new Worker('worker-transferList.js');

let bufArr=new ArrayBuffer(100);

let obj={x:bufArr}

console.log(bufArr.byteLength);//100

worker.onmessage=function(e){
console.log(e.data);
}

worker.postMessage(obj,[bufArr]);

console.log(bufArr.byteLength);//0

worker-transferList.js

1
2
3
4
5
6
7

onmessage=function(e){
console.log('worker beforeSend',e.data.x.byteLength);
postMessage(e.data,[e.data.x]);
console.log('worker afterSend',e.data.x.byteLength);
};

Worker 和 SharedWorker

上述讲述的都是页面独享 Worker 也叫 DedicatedWorker ,然而还有一个 SharedWorker

DedicatedWorker 会在页面关闭时随之关闭,而 SharedWorker 会在所有关联的页面都关闭后才关闭。

shared.js

1
2
3
4
5
6
7
8
9
10
11

let worker = new SharedWorker('worker-shared.js');

worker.port.start();

worker.port.postMessage('123');

worker.port.onmessage=function(e){
console.log(e.data);
};

worker-shared.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

var x=0;

function add(){
setTimeout(()=>{
x++;
add();
},100);
}
add();

addEventListener("connect", function(e){

var port = e.ports[0];

port.addEventListener('message', function(e) {
port.postMessage(x);
});

port.start(); //用onmessage绑定,必须要显式启动端口通信

});

当我们只在第一个页面进行刷新时,输出的都是0,但当有两个页面时,刷新一个页面输出的内容就不再只是0。

当只有一个页面时

当有两个页面时

注意

  1. 我也不知道为何,直接写在HTML中的sharedWorker并不会执行。

总结

这里 webWorker 简单的介绍就算完了,使用 webWorker 能减少页面的拥塞,充分利用客户的物理资源处理大量数据同时还可以用于封装接口层,毕竟是支持AJAX和webSocket的~~~。

参考资料

MDN_Worker

caniuse_Worker

END

2017-03-16 完成

2017-02-25 立项

记人生第一次面试

某天我正在默默的写着代码,突然接到了腾讯的电话面试,哈哈哈哈= =,人生的第一次工作面试就这样被拿走了。也许是真正检验一名工程师的正真的水平吧,事先没有任何通知和征兆。这次面试的知识面特别广,这篇文章将讲述我被面试的内容,以及我的答案,以及后来思考出来的答案= =,有的答案可能需要的很广,将后来以额外的文章补充。

HTTP

原理

在这次面试中我被问及的HTTP的原理,当时一下想的太复杂,没有即时回答上= =其实说简单很简单,说难很难。

在这里简单的说一下我的理解:HTTP是基于TCP协议的面向连接的,采用3次握手的方式建立稳定连接的,用于进行数据交互的协议。

后面我会详细的写一篇文章来介绍HTTP协议

状态码

同时我还被问及HTTP状态码,问我重定向的状态码为多少,当时我有点在301和304之间徘徊,因为平时调试看多了304,脑子一下晕了,当然答案是301。

HTTP状态码:HTTP状态码主要有5大类:

  1. 1xx:这类状态码代表请求已接受,但需要继续请求
  2. 2xx:这类状态码代表请求成功了 代表的为200请求成功
  3. 3xx:这类状态码代表请求被转向 代表的为301重定向
  4. 4xx:这类状态码代表请求错误 代表的为404没有找到资源
  5. 5xx:这类状态码代表服务器出现错误 代表的为500没有服务器错误

后面我会详细的写一篇文章来介绍HTTP的状态码的含义。

HTTP请求跨域

在面试中提到了HTTP请求跨域,我当时想到了

  1. JSONP
  2. CORS
  3. ifreame跨域

=、=然后不知到了,但个人觉得已经差不多了,proxy需要服务器当时没有考虑,,但现在想想还是应该说一说。

WebSokect

因为面试官看我最新的一片博客是有关webSokect的所以问我webSokect的send和message有什么安全问题= =我是一脸懵逼的

只能先暂时埋个坑在这里了。

排序算法

面试过程中问到了排序算法,问我 除了冒泡,快排还有什么排序? 当时我就是反映的很多啊- -,然后我叫不出名字了。。。半天才憋出一个插排,其实还有很多分组排序,堆排序等等,唉平时的算法练少了,是时候继续刷OJ了

JS

如何判断是否为空对象

在面试过程中,还问到如何判断是否为空对象,当时想的是用循环加hasOwnProperty看是否有自己的属性,数组就用length。

但是现在想来还有一些奇怪的方法啊。比如用

  1. JSON.stringify()

如何判断字符串是否为回文

1
2
3
4
5

function palindrome(str){
return str===str.split("").reverse().join("");
}

拿走不谢

数组去重

= =这个是我回答的最流畅的一个也是唯一一个了吧,因为

数组去重

看了就再也不怕数组去重

postMessage跨域

ES6

在面试中问道我常用的ES6语法,当时一下只想到了 Promise 和箭头函数 ,于是便问了我 Promise 和箭头函数的问题但是我是真的刚刚开始学习使用 Promise ,一脸蒙蔽的撞枪口了,虽然用了一个原始的方法实现了多并发但还是没有真正的说出面试官想要的特性 allrace

JQ的查询语句

啊=。=虽然现在JQ已经慢慢落寞了,但是作为曾经的主流开发库,依然考到了很多,同样作为没咋学习JQ的我,也答不上来。。我也不准备现在去学,因为要学一个库是很简单的事情~~。

VUE的优点和缺点

VUE的优点和缺点被问到的时候,当时反映是VUE自身的,但是现在想想当时应该问的和其他框架的优缺点,泪崩啊。。

Vue的优缺点官网上有相关表述:

1: 比Angular轻量化、简单,但是功能较少需要插件来弥补
2: 比React渲染要快,

JS模块化

过程中提及JS模块化的问题,但是面试官并没有深入的询问,但这可能就是下一个大坑- -

web安全

在安全方面我的确了解不多,但面试依然问到了SQL注入和XSS相关的问题,在这里就简单的描述下

SQL注入

SQL注入基本就是由于直接将用户提交的命令用于SQL语句查询或者执行相关命令,导致服务器系统信息泄漏或者执行非法的指令,导致的安全问题。

例如绕过登录验证等。

XSS

XSS是用户植入的代码内容再次展示在他人的页面时,自动执行代码内容导致的安全漏洞,

一般的防御方法主要有:

  1. 转义字符
  2. javascript 字符串进行处理

postMessage跨域

END

2017-03-14 完成

2017-03-13 立项

功能实现之浏览器关闭时弹框提醒

在开发web应用时,有些页面在用户关闭时可能需要保存用户的数据,或者提示用户防止误关

beforeunload 事件

在浏览器中我们可以监听beforeunload事件来知道浏览器即将关闭,并且可以通过给Event对象的returnValue赋值来提示弹框

1
2
3
4
5

window.addEventListener("beforeunload", e => {
e.returnValue = true
})

然后我们刷新浏览器或者退出浏览器就可以看到弹框了

用处

通过监听到这个事件,我们就有机会在页面关闭前执行一段JS,来保存应用相关的数据

1
2
3
4
5

window.addEventListener("beforeunload", e => {
//do some things
})

只要是同步的JS就可以完全执行,如果需要异步回调,考虑到用户可能不会留下,所以异步操作不能完全保证

同时JS不能占用太多时间,比如你用一个超大循环在哪卡之类的,浏览器如果检测到JS在一段时间内并没有正确执行,将会直接关闭页面

参考资料

window.onbeforeunload

END

2017-03-10 完成

2017-12-02 立项

WebSocket简介以及简单的应用

WebSocket是HTML5的一个新特性提供了浏览器端和服务器端双向实时通信的能力,用于实现一些实时在线的应用,这篇文章将简单介绍WebSocket的使用,并实现一个简单的在线聊天的功能。

WebSocket简介

WebSocket与HTTP

WebSocket与HTTP都是一种协议,但是HTTP是一应一答的,无论是 HTTP 1.0 还是 HTTP 1.1,它们都需要浏览器端发出请求(request)后才能让服务器返回响应(respones),在需要实时通讯的应用时,比如在线聊天,在线游戏等,过去只能使用轮询的方式来获取最新的消息,这样会消耗大连的网络资源。

但是有了WebSocket之后,我们可以让浏览器端和服务器端建立一个持久的双向通讯连接,在有新数据的时候自动发送并接收,减少请求的数量,节省网络资源。

HTML5的WebSocket

在HTML5中我们可以使用 WebSocket 对象来连接WebSocket服务器

var ws=new WebSocket(URL,protocols);

参数名 参数描述
URL 需要连接的地址
protocols (可选)可以是一个单个的协议名字字符串或者包含多个协议名字字符串的数组。

当构建成功时会返回一个WebSocket对象,失败时返回错误。

WebSocket对象提供了2个方法:

  1. void close(in optional unsigned long code, in optional DOMString reason);

    这个方法用于关闭WebSocket连接。

    参数名 参数描述
    code 关闭连接的状态号,默认为1000(正常关闭)
    reason 一个字符串,用于描述关闭的原因。这个字符串必须是不长于123字节的UTF-8 文本

    code状态码列表

  2. void send(in DOMString data);

    这个方法用于像服务端发送数据。

    参数名 参数描述
    data 发送的数据

这两个方法是有浏览器主动执行,服务器发过来的消息需要通过监听事件来实现。

WebSocket对象有4个事件:

  1. open事件

    当连接成功连接时会触发该事事件,会传入一个Event对象

  2. message事件

    当接收到消息时会触发该事件,会传入一个Event对象,并且可以通过Event对象data属性获得数据

  3. error事件

    当连接出错时会触发该事事件,并传入一个Event对象

  4. close事件

    当连接关闭的时候会触发该事件,并传入一个Event对象

=、=有了API下面肯定就是要撸起袖子搞一波啦~~~

WebSocket的简单应用-在线简易聊天室

服务器的搭建

在这里我使用nodejs搭建服务器,毕竟咋们是学前端嘛=、=

能搭建Websocket服务器的包有很多,这里我们选用 ws ,因为这个包是十分基础的一个,同时API也与W3C的规范差不多,方便理解。

sever.js

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

var path=require('path');
var http=require('http');
var express=require('express');
var ws=require('ws');

var app=express();
app.use(express.static(path.join(__dirname, './public')));

var server=app.listen(3000,()=>{
console.log('服务器启动');
});

var io=new ws.Server({server});
io.on('connection',function(client){

client.on('message',function(data){
console.log(data);
});
client.on('close',function(data){
console.log(data);
});

setTimeout(function() {
if(client.readyState===1){//防止已断开连接
client.send('来之服务器的消息');
}
}, 1000);

});

这段代码我们就简单的搭建了一个websokect服务器,我们可以从代码中看出,服务器在websokect连接上后,会输出 messageclose 请求的信息,并且我们在连接后会向浏览器发送一条消息。

下面是我们浏览器的JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

var ws=new WebSocket('ws://127.0.0.1:3000/');

ws.onopen=function(event){
console.log('连接成功');
ws.onmessage=function(event){
console.log('收到消息:'+event.data);
}
ws.onclose=function(event){
console.log('连接关闭');
}

ws.send('来自浏览器的消息');

};
ws.onerror=function(event){
console.log('连接出错');
};

我们将这段代码加入到服务器public目录的index.html文件中,然后访问 localhost:3000 查看浏览器和服务器控制台

服务器控制台

浏览器控制台

我们可以看到WebSocket服务器已经成功的运行了起来

简易聊天室的实现

我们这里只实现一个简单的聊天室的功能,发送和接受以及展示。

首先我我们把聊天室的页面进行简单的编写,实现简单的样式

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

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>websokect简易聊天室</title>
<style>
.messageBox{
background: lightslategrey;
}

p{
font-size: 16px;
margin: 5px;
}

.messageBox-sys{
text-align: center;
color:yellow
}

.messageBox-other{
text-align: left;
color:black;
}

.messageBox-my{
text-align: right;
color:white;
}

input{
height: 20px;
margin: 3px;
border: 1px;
}

</style>
</head>
<body>
<div style="width:60%;margin:0 auto;background: grey">
<div id="messageBox" class="messageBox">
<p class="messageBox-sys">系统消息</p>
<p class="messageBox-other">别人的消息</p>
<p class="messageBox-my">我的消息</p>
</div>
<div class="control">
<div style="margin: auto;width: 80%;position: relative">
<input id="message" style="width: 70%" type="" name="" placeholder="输入聊天内容" value="">
<input id="btn-send" style="width:20%" type="button" value="发送">
</div>
</div>
</div>
</body>
</html>

我们在这里使用JSON格式的数据进行数据发送,包含文本内容和发送时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

document.getElementById('btn-send').addEventListener('click',sendMessage);
function sendMessage(){
if(ws.readyState!==1){
alert('连接为建立');
return;
}

var msg=document.getElementById('message').value;
ws.send(JSON.stringify({
msg:msg,
date:new Date().getTime()
}));

var p=document.createElement('p');
p.innerHTML="msg";
p.className="messageBox-my";
document.getElementById('messageBox').appendChild(p);
}

然后监听服务器发回的消息

1
2
3
4
5
6
7
8

ws.onmessage=function(event){
var data=event.data;
var type=data.type||0;
var msg=data.msg||'';
var date=data.date||new Date();
}

页面写好了,我们只需要服务器简单处理下就可以了

服务器需要实现

  1. 收到消息转发给别人

  2. 当有人加入/离开时进行提示

我们只需要一个数组存储好加入进来的每个连接,我们就可以进行统一的转发和处理。

当一个新的连接加入时,我们就可以遍历数组向其他连接发送消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

var linkarr=[];

function join(client){

linkarr.forEach((pepole)=>{
pepole.send(JSON.stringify({
type:0,
msg:"有新的成员加入!现在聊天室有 "+(linkarr.length+1)+" 个人",
date:new Date().getTime()
}));
});

client.send(JSON.stringify({
type:0,
msg:"欢迎加入!现在聊天室有 "+(linkarr.length+1)+" 个人",
date:new Date().getTime()
}));

linkarr.push(client);

}

同理离开和发送信息时也一样

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 leave(client){

var index=linkarr.indexOf(client);
linkarr.splice(index,1);
linkarr.forEach((pepole)=>{
pepole.send(JSON.stringify({
type:0,
msg:"有一位成员离开!现在聊天室有 "+(linkarr.length)+" 个人",
date:new Date().getTime()
}));
});
}

function sendData(client,data){

linkarr.forEach((pepole)=>{
if(pepole===client)return;
pepole.send(JSON.stringify({
type:1,
msg:data.msg,
date:data.date
}));
});
}

然后在我们的需要调用函数的地方加上,我们的服务器就完成啦!!!

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

var path=require('path');
var http=require('http');
var express=require('express');
var ws=require('ws');

var app=express();
app.use(express.static(path.join(__dirname, './public')));

var server=app.listen(3000,()=>{
console.log('服务器启动');
});

var io=new ws.Server({server});

var linkarr=[];

io.on('connection',function(client){
join(client);

client.on('message',function(data){
sendData(client,JSON.parse(data));
});
client.on('close',function(data){
leave(client);
});

});

function join(client){

linkarr.forEach((pepole)=>{
pepole.send(JSON.stringify({
type:0,
msg:"有新的成员加入!现在聊天室有 "+(linkarr.length+1)+" 个人",
date:new Date().getTime()
}));
});

client.send(JSON.stringify({
type:0,
msg:"欢迎加入!现在聊天室有 "+(linkarr.length+1)+" 个人",
date:new Date().getTime()
}));

linkarr.push(client);
}

function leave(client){

var index=linkarr.indexOf(client);
linkarr.splice(index,1);

linkarr.forEach((pepole)=>{
pepole.send(JSON.stringify({
type:0,
msg:"有一位成员离开!现在聊天室有 "+(linkarr.length)+" 个人",
date:new Date().getTime()
}));
});
}

function leave(client){

var index=linkarr.indexOf(client);
linkarr.splice(index,1);

linkarr.forEach((pepole)=>{
pepole.send(JSON.stringify({
type:0,
msg:"有一位成员离开!现在聊天室有 "+(linkarr.length)+" 个人",
date:new Date().getTime()
}));
});
}

function sendData(client,data){

linkarr.forEach((pepole)=>{
if(pepole===client)return;
pepole.send(JSON.stringify({
type:1,
msg:data.msg,
date:data.date
}));
});

}

同理我们的页面也能快速的写出来

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>websokect简易聊天室</title>
<style>
.messageBox{
background: lightslategrey;
}

p{
font-size: 16px;
margin: 5px;
}

.messageBox-sys{
text-align: center;
color:yellow
}

.messageBox-other{
text-align: left;
color:black;
}

.messageBox-my{
text-align: right;
color:white;
}

input{
height: 20px;
margin: 3px;
border: 1px;
}

</style>
</head>
<body>
<div style="width:60%;margin:0 auto;background: grey">
<div id="messageBox" class="messageBox">
</div>
<div class="control">
<div style="margin: auto;width: 80%;position: relative">
<input id="message" style="width: 70%" type="" name="" placeholder="输入聊天内容" value="">
<input id="btn-send" style="width:20%" type="button" value="发送">
</div>
</div>
</div>
<script type="text/javascript">
var ws=new WebSocket('ws://127.0.0.1:3000/');
ws.onopen=function(event){
console.log('连接成功');

ws.onmessage=function(event){
var data=JSON.parse(event.data);
var type=data.type||0;
var msg=data.msg||'';
var date=data.date||new Date();

var p=document.createElement('p');
p.innerHTML=msg;

switch (type){
case 0:p.className="messageBox-sys";break;
case 1:p.className="messageBox-other";break;
default:p.className="messageBox-sys";
}

document.getElementById('messageBox').appendChild(p);
}

ws.onclose=function(event){
console.log('连接关闭');
}

};
ws.onerror=function(event){
console.log('连接出错');
};


document.getElementById('btn-send').addEventListener('click',sendMessage);
function sendMessage(){
if(ws.readyState!==1){
alert('连接为建立');
return;
}

var msg=document.getElementById('message').value;
ws.send(JSON.stringify({
msg:msg,
date:new Date().getTime()
}));

var p=document.createElement('p');
p.innerHTML=msg;
p.className="messageBox-my";
document.getElementById('messageBox').appendChild(p);
}


</script>
</body>
</html>


于是很简单的就完成了一个在线的简易聊天室,=、=下面是一些简单的效果结果

详细DEMO可以查看参考资料

后记

这个DEMO的功能做的很简单,离正真的聊天室功能还差很多,但是。。。不然为嘛叫DEMO呢=、=

有兴趣的话我会慢慢完善功能-、-,所不定我会挂在上我的博客~~~~

啦啦啦

参考资料

MDN_WebSocket

DEMO地址

END

2017-03-05 立项并完成