NODEJS搭建HTTPS服务器

HTTPS是加密后的HTTP通道,它被广泛用于万维网上安全敏感的通讯,这里通过学习我总结一下用NODEJS搭建HTTPS服务器的过程。


HTTP和HTTPS简介

HTTP

超文本传输协议(HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。1960年美国人Ted Nelson构思了一种通过计算机处理文本信息的方法,并称之为超文本(hypertext),这成为了HTTP超文本传输协议标准架构的发展根基。

HTTPS

HTTPS(Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。 它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。https:URL表明它使用了HTTP,但HTTPS存在不同于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间)。这个系统的最初研发由网景公司(Netscape)进行,并内置于其浏览器Netscape Navigator中,提供了身份验证与加密通讯方法。现在它被广泛用于万维网上安全敏感的通讯,例如交易支付方面。

区别

https协议需要到ca申请安全证书,一般免费证书很少,大多需要交费。

http传输的信息是明文传输,https 则是具安全性的ssl加密传输协议。

http和https使用的是完全不同的连接方式,用的端口也不一样,前者是默认为80,后者是默认为443。

http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

证书

既然需要搭建HTTPS服务器,那么我们首先就需要CA证书。

获取认证的证书

有一些网站可以申请购买CA证书

比如:

沃通(WoSign)

godady

PositiveSSL

rapidssl 免费

Let’s Encrypt 免费

startssl 免费

腾讯云 用户免费

阿里云 有免费版

自己生成证书

这里我就不去申请证书了,我们可以使用openssl,自己生成一个证书

ー( ̄~ ̄)ξ 只是别人不认罢了。

安装openssl

openssl window安装很麻烦,我们可以选择安装git客户端,git会顺带帮我们安装上openssl

我们只需要去git官网下载git安装包就可以了

安装时在PATH选项中选择第3个选项,不然openssl不能直接使用

安装完成后我们输入以下指令查看是否安装成功

git –version //查看git安装是否成功

openssl version -a //查看openssl安装是否成功

出现上图的效则表示成功了。

生成证书

主要通过一下3条命令来生成需要的证书文件和KEY。

1
2
3
4
5
6
7
8
9
10

//生成私钥key文件
openssl genrsa 1024 > privateKey.pem

//通过私钥文件生成CSR证书签名
openssl req -new -key privateKey.pem -out csr.pem

//通过私钥文件和CSR证书签名生成证书文件
openssl x509 -req -days 365 -in csr.pem -signkey privateKey.pem -out certificate.crt

执行时根据提示输入相应信息即可。

执行完后会生成3个文件:

privateKey.pem //私钥

csr.pem //CSR证书签名

certificate.crt //证书文件

服务器

下面我们直接搭建我们自己的DEMO服务器

创建package.json,安装一些必要的依赖库

1
2
3
4
5
6
7
{
"name":"杂项之NODEJS搭建HTTPS服务器DEMO",
"version":"1.0.0",
"dependencies":{
"express":"4.14.0"
}
}

然后书写我们的服务器代码 server.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
var express=require('express'),
fs=require('fs'),
http=require('http'),
https=require('https');

var privateKey = fs.readFileSync(__dirname+'/ssl/privateKey.pem', 'utf8');
var certificate = fs.readFileSync(__dirname+'/ssl/certificate.crt', 'utf8');
var credentials = {key: privateKey, cert: certificate};

var app=express();

app.get('/',function(req,res,next){
if(req.protocol === 'https') {
res.status(200).send('HTTPS!');
}
else {
res.status(200).send('HTTP!');
}
});

var httpServer = http.createServer(app);
var httpsServer = https.createServer(credentials, app);

httpServer.listen(3000,function(){
console.log(' - listening on http://*:'+3000);
});

httpsServer.listen(3001,function(){
console.log(' - listening on http://*:'+3001);
});

再将生成的3个文件放入DEMO目录的ssl文件夹中。

目录结构

  • demo/
    • node_modules/
    • ssl/
      • privateKey.pem
      • csr.pem
      • certificate.crt
    • package.json
    • server.js

代码运行

在项目目录下执行

node sever.js

访问HTTP

访问 127.0.0.1:3000

访问HTTPS

访问 127.0.0.1:3001

注意!这是因为你没有加https的协议头,默认为http,所以服务器获取不了请求

访问 https://127.0.0.1:3001

一般浏览器是会对非认证了的私密连接提醒警告的,这只需要继续就可以

这样我们就搭建了一个HTTPS服务器~~~~!

如果希望自己的网站有HTTPS,那么最好使用第三方提供的认证证书,毕竟自己的HTTPS证书,一般不会被信任,会影响体验。

END

2016-11-05 完成

功能实现之输入框根据内容大小改变

前段时间,项目让我实现输入框大小跟随input的内容大小改变,一时懵逼,网上看了看效果也不好,大多都是根据input的size来设置,但是每个字符大小是不一样的,这样就会有明显的偏差,效果很不好。想了2个小时后,我终于想出了解决办法=、=,然后又历经各种问题才实现了可用性较高的方法,下面我就详细讲述一下我的解决方案。

在完全开始准备写这篇博客时,突然发现了一个更简单的方法,之前的就不讲了,垃圾代码!毁我青春!


原版:我们想让输入框的大小和内容想匹配,那么我们肯定就需要知道内容显示的长度……(此处省略1000字)

新版:

思路

我们为了让输入框的大小和内容匹配,我只需要回去内容显示需要的长度就可以了,如何获取呢?

scrollWidth

= =没错,就用scrollWidth就行了,然后把这个值给输入框就行了。

曾经天真的我,还去生成一个元素来显示文本,获取长宽。

唉,年轻啊!

代码实现

初级实现

1
2
3
4
5
6
7
<input type="text">
<script>
document.querySelector("input").addEventListener("input",function(){
this.style.width="0px";
this.style.width=this.scrollWidth+"px";
});
</script>

这样就可以让大小根据输入的内容改变大小了。

代码中

1
this.style.width="0px";

是为了让 scrollWidth 获取最小值,才能达到回缩的效果。也是最快捷的方法。

但是我们一般都有最大和最小的宽度的限制,所以需要简单的改进

中级实现

1
2
3
4
5
6
7
<input type="text">
<script>
document.querySelector("input").addEventListener("input",function(){
this.style.width=minWidth+"px";
this.style.width=((this.scrollWidth<maxWidth)?this.scrollWidth:maxWidth)+"px";
});
</script>

这样通过对 minWidthmaxWidth 设值就可以完成对最大和最小的宽度的限制了,当然也可以进行提前判断来优化,减少对dom的操作。

高级实现

同样的我们可以将函数抽离出来,进行封装。

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
<input type="text">
<script>
document.querySelector("input").addEventListener("input",$0_0({
minWidth:100,
maxWidth:300,
}));

function $0_0(obj){
let {
minWidth = 0,
maxWidth = Infinity,
back = true,
widthChange
} = obj;

return function(){
if(this.scrollWidth<minWidth||this.scrollWidth>maxWidth)return;

if(back)
this.style.width=minWidth+"px";

if(typeof widthChange === 'function')
widthChange.call(this,((this.scrollWidth<maxWidth)?this.scrollWidth:maxWidth))
}
}
</script>

这样我们就完成了一个简单的封装了=、=,如果还有扩展都只需要添加一些判断就行了。

代码

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

function $0_0(obj){
let {
minWidth = 0,
maxWidth = Infinity,
back = true,
widthChange
} = obj;

return function(){
if(this.scrollWidth<minWidth||this.scrollWidth>maxWidth)return;

if(back)
this.style.width=minWidth+"px";

if(typeof widthChange === 'function')
widthChange.call(this,((this.scrollWidth<maxWidth)?this.scrollWidth:maxWidth))
}
}

END

2017-04-25 扩展了widthChange方法,方便框架的调用

2016-10-30 完成

功能实现之JS滚轮滚动事件封装(start,ing,end)

最近在实习中遇到了需要使用原生JS完成滚轮滚动结束事件的监听,基于原生的scroll事件我完成了scrollstart,scrollend的扩展。


源码

ES5- 版

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

function scrollExtend(obj){
var handler=null;
var start=obj.scrollStart;
var scroll=obj.scrollIng;
var end=obj.scrollEnd;
var delay=obj.delay||200;

return function(){
var that = this;
var _argumrnts = arguments;
if(!handler&&start) start.apply(this, arguments);
if(handler) clearTimeout(handler);
if(scroll) scroll.apply(this, arguments);
if(end){
handler = setTimeout(function() {
handler=null;
end.apply(that, _argumrnts);
}, delay);
}
};
}

ES6+ 版

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

function scrollExtend(options){
let handler=null;
let {
scrollStart,
scrollIng,
scrollEnd,
delay = 200
} = options

return function(){
let _arguments = arguments
if(!handler&&scrollStart) scrollStart.apply(this, _arguments)
if(handler) clearTimeout(handler)
if(scrollIng) scrollIng.apply(this, _arguments)
if(scrollEnd){
handler = setTimeout(()=>{
handler = null
scrollEnd.apply(this, _arguments)
},delay)
}
};
}

用法

参数说明

参数 类型 是否必传 说明
scrollStart Funciton 滑动开始的时候的事件
scrollIng Funciton 滑动中的时候的事件
scrollEnd Funciton 滑动结束的时候的事件
delay Number 判断滑动结束的时间延迟
1
2
3
4
5
6
7
8
9
10
11
12
13
14

element.addEventListener('scroll',scrollExtend({
scrollStart:function(){
//这里添加滑动开始的时候的事件
},
scrollIng:function(){
//这里添加滑动的时候的事件
},
scrollEnd:function(){
//这里添加滑动结束的时候的事件
},
delay://这里添加判断滑动结束的时间长 默认200ms
}));

简单案例

结合VUE用法

1
2
3

<div @scroll="scroll($event)"></div>

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

new Vue({
methods:{
scroll:scrollExtend({
scrollStart:function(){
console.log('scrollstart');
},
scrollIng:function(){
console.log('scrolling');
},
scrollEnd:function(){
console.log('scrollend');
},
delay:200
}
}
})

超简单解析

主要就是使用settimeout()和clearTimeout()来延长时间,以及使用匿名函数的思想。

END

2018-2-24 修复错误的MD语法

2017-9-3 允许传递事件参数

2017-4-12

  • 文章改名
  • 增加ES6语法的编写
  • 增加参数说明

2017-3-20 修复直接使用this,无法传递this环境的BUG

2017-2-12 结合VUE,可以直接使用,同时函数内的this环境为vue对象

2016-10-22 完成

设计模式系列之MVC模式实例

前端日益复杂,已经不再是过去简单的展示信息的时代,丰富的交互,丰富的视觉呈现,复杂的数据处理,以及应用级别的网站的出现,前端开发注定不能再如同过去一般。MV*模式也是前端可以借鉴来,解除各个功能间耦合的设计模式,这篇文章只是一个简单的MVC模型的示例和简单阐述,这也是这个系列开始,让我们一起来学习设计模式,让前端开发更加规范,更加轻松,更加清晰!


简介

MVC 是一种设计模式,它主要是解耦数据(Model)和用户界面(View)的关系,由第三组件(Controller)来管理逻辑和用户输入。

Model(模型)

Model主要管理数据,不涉及用户界面。当它的数据发生改变时,它会通知它的观察者(View),让其作出相应的反应。

总之,Model(模型)主要管理数据。

View(视图)

View是Model的表现,用于展示Model的数据。View常常可以提供便利的方式来对Model的数据进行编辑处理。

总之,View(视图)主要展示数据。

Controller(控制器)

Controller是Model和View之间的中介,当用户操作View时(点击、输入等),它会负责更新Model。

总之,Controller(控制器)主要是提供简单的方法,来管理Model和View之间的关系和变化。

简单总结

在MVC里面,就我的理解是这样的

Model(模型):给View提供数据获取接口并通知改变事件,给Controller提供数据设置接口

View(视图):给Controller提供控制接口并通知用户事件

Controller(控制器):控制

简单示例

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
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<input id="test" /><br>
1:<p class="test"></p><br>
2:<p class="test"></p><br>
3:<p class="test"></p><br>
<script>
//模块
var testModle={
data:"",
//提供设置数据的方法
setData:function(data){
this.data=data;
this.dataChange();
},
//提供获取数据的方法
getData:function(data){
return this.data;
},
//数据更改事件
dataChange:function(){
testView.update();
}
}

//视图
var testView={
//初始化
init:function(){
document.getElementById("test").addEventListener('keyup',this.onchange);
document.getElementById("test").addEventListener('keydown',this.onchange);
document.getElementById("test").addEventListener('keyup',function(){
console.log(234);
})
},
//改变事件
onchange:function(){
testControl.putdata();
},
//数据更新
update:function(){
var ps=document.getElementsByClassName('test');
for(var i=0;ps[i];i++){
ps[i].innerHTML=testModle.getData();
}
}
};

//控制器
var testControl={
//初始化
init:function(){
testView.init();
},
//设置数值
putdata:function(){
testModle.setData(document.getElementById("test").value);
}
}
testView.init();
</script>
</body>
</html>

END

2016-10-21 完成

2016-10-21 立项

ubuntu 简单的iptable接口转发 解决没有root权限无法绑定80端口

Linux内核规定在没有root权限时是不能占用1024以下的端口的。

然而为了安全我们一般都不采用root权限来运行服务器,所以我们这里可以用iptable来简单的完成目的。


在没有root权限的情况下,直接监听80端口会报错

我们只能去监听1020以上的端口才行,然后通过端口转发,让别人访问80端口时访问到我们规定的的端口。

转接方法

方法一:命令行

控制台输入

sudo iptables -t nat -A PREROUTING -i eth0 -p tcp –dport 80 -j REDIRECT –to-port 3000

sudo iptables-save

这条命令是将80端口转发到3000,这样就行了。
只需要将我们的服务器监听到3000端口,访问80端口就能访问到我们的服务器了,同时3000端口当然也可以使用。

方法二:编辑iptable规则文件

当想要方便的修改端口时,还是推荐编辑规则文件。

1、导出规则

控制台输入

sudo sh -c “iptables-save > /etc/iptables.rules”

将规则导出到 /etc/iptables.rules 的文件中

2、编辑规则

控制台输入

sudo vi /etc/iptables.rules

我们可以看到其中有我们刚刚添加的规则

-A PREROUTING -i eth0 -p tcp -m tcp –dport 80 -j REDIRECT –to-ports 3000

这里我们可以修改或者添加其他规则。

3、应用规则文件

控制台输入

sudo iptables-restore /etc/iptables.rules

即可将编辑好的规则文件应用。

之后只要将我们的node服务器监听到转接到的端口就行了~~~

END

2016-10-21 完成

ubuntu安装node环境(mysql+nodejs+pm2)

这篇文章主要是用来讲解在ubuntu上配置node服务器的环境的一些步骤(无脑跟着走就能完成)


MySQL环境安装

1、更新软件包列表

终端窗口输入

sudo apt-get update

来同步软件包列表

2、安装MySQL客户端和服务端

终端窗口输入

sudo apt-get install mysql-server mysql-client

安装MySQL客户端和服务端

执行后回提示(Y/N),输入Y继续安装

安装时会提示你输入MySQL初始密码,之后会再次提示输入root密码

等待安装完成

3、确认MySQL是否成功安装

方法一:使用 sudo service mysql restart

终端窗口输入

sudo service mysql restart

如果mysql启动成功,处于运行状态说明mysql安装成功

方法二:登录MySQL

终端窗口输入

mysql -u root -p

有提示输入root密码,那么安装成功

nodejs环境安装

1、安装NVM

nvm可以帮助快速安装、切换、更新 node的版本,所以这里给出安装方法。

首先从github clone nvm到本地

在控制台输入(没有git文件夹可用其他文件夹,也可以用mkdir指令创建)

cd ~/git

git clone https://github.com/cnpm/nvm.git

这时候输入nvm没有任何用 可以通过在控制台运行

source ~/git/nvm/nvm.sh

执行后在终端输入nvm可获取如下所示

但是这样执行每次重新连接ubuntu都要再输入一次很麻烦,所以需要在连接加载文件中把刚才的语句加入到里面去。

这里我推荐加入到**~/.profile**中

这样每次连接后都可以直接使用nvm.

2、安装nodejs

使用nvm安装node就很轻松了

直接在控制台输入

nvm install 版本号

然后你会看到一个进度条,读完后(下载完后)nvm会默认将node版本设置为你下载的版本。

可以看到已经安装成功了。

但是你会发现当你重新连接后,node和npm指令无法使用所以你同样需要在连接加载文件加入一个选择node版本的语句

nvm use 版本号

然后你每次连接就会看到

Now using node v4.4.7 (npm v2.15.8)

这类的字样,好了这样我们的node就安装完成了。

node挂载工具PM2

pm2是npm里的一个工具,能用来挂载一些程序后台运行,不然当你关闭连接后运行的程序就会关闭。

1、安装pm2

控制台执行命令

npm install -g pm2

npm安装东西在国内很慢,但可以通过简单的设置 –registry 参数来使用国内镜像网站(http://registry.npm.taobao.org)来加速下载

npm install -g pm2 –registry=http://registry.npm.taobao.org

这样就下很快了,这样的方法用来下载其他的npm包也是有效的。

2、使用pm2

使用pm2很简单

pm2 start 文件名

即可

这样程序就挂载了起来,不会再链接关掉后中断,而且可以帮你在发生异常停止时帮你重启程序。

我们可以再配置文件中加上

pm2 ls

这样每次连接就可以看到正在运行的程序~~

这篇文章就到此为止~~

END

2016-10-21 完成

功能实现之JS获取元素相对页面坐标

biubiubiu~~ 一直想写这篇博客但是一直很忙0、0,现在终于貌似可能也许应该空了下来了T T,前一段时间写了个拖拽放下到一个地方的实现,拖拽实现后(详细见博文:效果实现之拖拽及其脱离的解决方案),如何确定是否拖到了目标区域内是个问题,这里我想到了使用各个元素相对文档的位置来判断,其中经历的一些狗血的事情(这就不扯了),下面我来简单的阐述下思路和实现。


思路

这里主要是利用DOM元素的offsetLeft和offsetTop。

在HTML中各个元素相对父级(真?)的位置可以利用offsetLeft和offsetTop来确定,这样我们就向需要的相对页面的坐标迈出了第一步!获取的相对父节点的位子。

下面就很好想了啊-、-,获取父节点的相对它的父节点的坐标再获取父节点的父节点的相对它的父节点的坐标再获取父节点的父节点的父节点的。。。。。。

代码

通过以上我们可以很快的写出如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
function getPageP(e){
var x=e.offsetLeft;
var y=e.offsetTop;
while(e.parentNode.parentNode){
e=e.parentNode;
x+=e.offsetLeft;
y+=e.offsetTop;
}
return {
x:x,
y:y
};
}

这时你肯定认为搞定了~~

才怪诶~~ 略略略~~

DOM的offset是不是相对parentNode的!!!

offset是相对offsetParent的!!!

下面才是真正的代码,满足需求的代码=、=,还对滑动进行的叠加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getPageP(e){
var x=e.offsetLeft;
var y=e.offsetTop;
while(e.offsetParent.offsetParent){
e=e.offsetParent;
x+=e.offsetLeft;
x-=e.scrollLeft;
y+=e.offsetTop;
y-=e.scrollTop;
}
return {
x:x,
y:y
};
}

END

2016-10-20 完成

功能实现之拖拽及其脱离的解决方案

开始学习前端,最先就是想着实现拖动的效果,也是实现了,但一直存在这在快速拖动的情况下,鼠标会一次move超出拖动对象的范围,导致对象脱离,然后鼠标返回对象就会默认一直再拖。今天突然想到了办法,在这里写下来便于理解、记忆。


拖拽实现的方法

拖拽可分为三步:选取,拖动,放下。

这就对应了鼠标的三个事件:鼠标按下(mousedown),鼠标移动(mousemove),鼠标放起(mouseup)。

鼠标按下(mousedown)

我们使用一个变量来标记是否按下,当鼠标按下后,使这个标记变为一个值标记为已按下的状态。同时记录下点击时鼠标相对文本的坐标以及移动前的坐标。

1
2
3
4
5
6
7
8
9
10
11
var startX;
var startY;
var DivstartX;
var DivstartY;
div.addEventListener("mousedown",function(e){
draging=true;
startX=e.clientX;
startY=e.clientY;
DivstartX=div.style.left;
DivstartY=div.style.top;
});

鼠标放起(mouseup)

在鼠标放起后,将这个标记重置为按下的状态。

1
2
3
div.addEventListener("mouseup",function(e){
draging=false;
});

鼠标移动(mousemove)

拖拽的核心功能就是移动,如果所以我们需要移动,那么我们可通过设在对象的style的top和left来实现位置的改变。同时检测鼠标的坐标与之前的坐标对比,可以根据差别获取新的的坐标数据。

1
2
3
4
5
6
div.addEventListener("mousemove",function(e){
if(draging){
div.style.left=DivstartX+e.clientX-startX+'px';
div.style.top=DivstartY+e.clientY-startY+'px';
}
});

我们就可以快速的根据这三个函数实现一个简单的拖拽功能。如下是一个简单的DEMO的HTML代码

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
<!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>Document</title>
<style media="screen">
div{
width: 100px;
height: 100px;
position: absolute;
background-color: red;
}
</style>
</head>
<body>
<div id="dragEx" style="left:100px;top:100px"></div>
<script type="text/javascript">
var div=document.getElementById("dragEx");
var draging=false;
var startX;
var startY;
var DivstartX;
var DivstartY;
div.addEventListener("mousedown",function(e){
draging=true;
startX=e.clientX;
startY=e.clientY;
DivstartX=div.offsetLeft;
DivstartY=div.offsetTop;
});
div.addEventListener("mouseup",function(){
draging=false;
});
div.addEventListener("mousemove",function(e){
if(draging){
div.style.left=DivstartX+e.clientX-startX+'px';
div.style.top=DivstartY+e.clientY-startY+'px';
}
});
</script>
</body>
</html>

脱离的解决方法以及优化

上面的代码虽然实现了拖拽功能,但是当你快速拖动时,鼠标一次性移出拖拽对象的范围就会出现bug,下面讲解,我想到的解决方法。以后有新的想法或解决方案也会在这里列出。

方法一:将move的监听放到document

鼠标一次性移出拖拽对象的范围出现bug,这是原因,那么我们将拖拽监听的对象,不设为本身,将监听的时间放到document全局,那么就不存在脱离了,除非你的鼠标跑出了浏览器显示的地方。

但是如果我们直接将事件永远绑定到document里面将会一直占用资源,特别是鼠标移动这个事件在document是最常触发的。所以我们可以在点击后才添加监听事件,收起后取消监听。这样我们还可以少一个标记是否在拖拽的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var startX;
var startY;
var DivstartX;
var DivstartY;
div.addEventListener("mousedown",function(e){
startX=e.clientX;
startY=e.clientY;
DivstartX=div.offsetLeft;
DivstartY=div.offsetTop;
document.addEventListener("mousemove",move);
});
div.addEventListener("mouseup",function(){
document.removeEventListener("mousemove",move);
});
function move(e){
div.style.left=DivstartX+e.clientX-startX+'px';
div.style.top=DivstartY+e.clientY-startY+'px';
}

改进二:将移动,收起等时间添加到点击事件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var startX;
var startY;
var DivstartX;
var DivstartY;
div.addEventListener("mousedown",function(e){
startX=e.clientX;
startY=e.clientY;
DivstartX=div.offsetLeft;
DivstartY=div.offsetTop;
document.addEventListener("mouseup",up);
document.addEventListener("mousemove",move);
function move(e){
div.style.left=DivstartX+e.clientX-startX+'px';
div.style.top=DivstartY+e.clientY-startY+'px';
}
function up(){
document.removeEventListener("mousemove",move);
document.removeEventListener("mouseup",up);
}
});

这样后就可以肆意的拖拽啦~~!

END

2016-11-17 添加改进二

2016-10-01 创建

功能实现之AJAX简单封装

自己在写一些小项目时,总是经常会使用AJAX请求,每次用原生的很蛋疼,又不想引入JQ库,于是这里我根据JQuery的函数的传参和效果,利用原生JS仿写一套简单AJXA插件。


原生JS发送AJAX请求

简介

AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
AJAX可以允许网页异步与服务器进行数据交换,是一个使用频率很高的技术,所以我们需要将他封装成一个简单的方法,方便调用。

而且现在主流的浏览器都支持:

使用

建立AJAX对象

使用AJAX需要先建立一个AJAX请求对象:XMLHttpRequest

(现在浏览器主要都是内建的这个对象,IE5,6为ActiveXObject,基本已被淘汰,不考虑这个)

1
2
3

var req=new XMLHttpRequest();

设置响应事件

使用AJAX的时候我们可能需要监听一些事件,比如AJAX请求完成等。

我们使用设置onreadystatechange这个方法来实现,这个方法会在AJAX的状态变化时发生改变。它的状态是XMLHttpRequest对象的 readyState 属性保存的。

数值对应的状态

0: 请求未初始化

1: 服务器连接已建立

2: 请求已接收

3: 请求处理中

4: 请求已完成,且响应已就绪

例如设置以下代码便可以监听AJAX完成事件

1
2
3
4
5
6
7
8

req.onreadystatechange = function(){
if(req.readyState === 4)
{
//填写AJAX完成后的事件
}
};

设置请求信息

在XMLHttpRequest中我们可以使用 open 方法来设置主要信息

1
2
3
4
req.open(reqMethod,reqURL,reqAsync);
//reqMethod:为请求方式 如:POST,GET等
//reqURL:为请求的路径,可以是相对的也可以是绝对的
//reqAsync:请求为异步还是同步,默认为TURE:同步

同时我们也可以使用XMLHttpRequest中得 setRequestHeader 方法来设置头信息

1
2
3
req.setRequestHeader(header,value);
//header:请求头的头名
//value:相对于头名的值

发生请求和数据

当所有必要的信息设置完以后我们可以使用 send 方法来发送请求

1
2
req.send(reqData);
//reqData:发生的数据,为string类型 或者 formData对象

获取服务器返回的状态和数据

AJAX一般是用来获取后台的数据,然后反馈给用户,所以我们需要获取数据。

在AJAX执行完了后,数据和状态直接存放在XMLHttpRequest对象的 statusresponseText 里。

status:服务器返回的状态,一般成功为200 失败为404

responseText:为返回的数据,如果为JSON,依然需要转化为JS对象

封装的的插件

ajax(obj)

这个是基础方法,后面会基于这个方法扩展多个方法。

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
/*
obj选项:
必选: reqURL:请求地址
可选: reqMethod:请求方法 默认:GET
reqAsync:请求异同步 默认:异步
reqData:请求数据 默认:空
reqHeader:请求头 默认为空 传入JS对象即可
reqUserName:请求用户名 默认为空
reqUserPassWord:请求密码 默认为空
reqSuccess(data):请求成功时间函数 传入返回的数据
reqError(data):请求失败时的响应函数
reqBefore:发送请求前的函数
reqProgress:请求获取的进度
reqUploadProgress:请求发出的进度
*/
function ajax(obj){
var reqURL = obj.reqURL;
var reqMethod = obj.reqMethod||"get";
var reqAsync = (obj.reqAsync===undefined?true:obj.reqAsync);
var reqData = obj.reqData||"";//直接传数据不解析
var reqHeader = obj.reqHeader;
var reqUserName = obj.reqUserName||"";
var reqUserPassWord = obj.reqUserPassWord||"";

var reqSuccess = obj.reqSuccess;
var reqError = obj.reqError;
var reqBefore = obj.reqBefore;
var reqProgress = obj.reqProgress;
var reqUploadProgress = obj.reqUploadProgress;

var req = new XMLHttpRequest();
req.onreadystatechange = function(){
if(req.readyState === 4)
{
if(parseInt(req.status / 100) <= 2){//200请求系列都是成功
if(reqSuccess && typeof reqSuccess === 'function')reqSuccess(req.responseText);
}else{
if(reqError && typeof reqError === 'function')reqError(req);
}
}
};
req.onerror = function() {
if(reqError && typeof reqError === 'function')reqError(req);
}
req.onprogress=reqProgress;
req.upload.onprogress=reqUploadProgress;

req.open(reqMethod,reqURL,reqAsync,reqUserName,reqUserPassWord);
if(reqHeader){
for(var head in reqHeader){
req.setRequestHeader(head,reqHeader[head]);
}
}
if(reqBefore&&typeof reqBefore === 'function')reqBefore(req);
req.send(reqData);
}

get(url,data,success,error)

基于ajax方法的扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
function get(url,data,success,error){
var arr=[];
for(var name in data){
arr.push(name+"="+data[name]);
}
if(arr.length>0)url=url+'?'+arr.join('&');

ajax({
reqURL:url,
reqSuccess:success,
reqError:error
});
}

getJSON(url,data,success,error)

基于get方法的扩展,转化json格式

1
2
3
4
5
function getJSON(url,data,success,error){
get(url,data,function(data){
success(JSON.parse(data));
},error);
}

post(url,data,success,error)

基于ajax方法的扩展

1
2
3
4
5
6
7
8
9
function post(url,data,success,error){
ajax({
reqURL:url,
reqMethod:'post',
reqData:data,
reqSuccess:success,
reqError:error
});
}

版本

还在修改中,在学习和开发过程中遇到问题会及时修正和更新~~

2017-07-18 修复无法捕捉网络错误的问题,将100和200系列响应认为默认为正确

2017-02-08 修复GET函数多个?的缺陷,增加请求进度的控制

2017-01-13 修复BUG,post请求请求名错误

2016-12-22 修复BUG,异步为false时依然为true

2016-12-19 修复BUG,异步为true时依然为false

2016-11-21 修改、添加post

2016-09-27 添加用户名和密码,添加开始reqBefore前设置XMLHttpRequest对象 添加get方法

2016-09-16 建立