漫漫技术路

  • 首页

  • 标签

  • 分类

  • 归档

  • 搜索

WebRTC1-原理探究

发表于 2016-09-28 | 更新于 2018-11-30 | 分类于 前端技术

1.抛砖引玉

WebRTC (Web Real-Time Communications) 是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流或/和音频流或者其他任意数据的传输
实时查看WebRTC在浏览器中的支持情况: http://caniuse.com/#search=webRTC
FirFox 45+,Chrome 29+,Oprea 36+,Edge 14+,Android Brower 50+支持,其余支持情况有问题。

备注:有的时候会使用adapter.js,这个js文件是为了提高兼容性,可以直接使用API 不用加前缀
使用:

  • 下载
  • 引用 <script src="adapter.js">

    https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/adapter.js

几个概念

  • SDP(Session Description Protocol)
    SDP是一种会话描述协议,用来描述双方的IP地址和端口号,通信所使用的带宽,会话的名称、标识符、激活时间,双方所要传输的媒体类型(视频、音频、文本)、媒体格式等等。该协议仅包含所要传递的媒体的描述信息,而不直接传递媒体内容。
  • ICE(Interactive Connectivity Establishment)
    ICE是一种以UDP为基础用于实现穿越NAT网管或者防火墙的协议。
  • TURN&&STUN
    两种协议都是用来明确自己的外网地址的,差别是如果要服务器辅助进行数据交换则设置TURN服务器,不需要则设置STUN服务。

核心API

  • Navigator.getUserMedia
    用来获取视频和音频,在浏览器装有摄像头和麦克风的情况下使用.navigator.getUserMedia(constraints, successCallback, errorCallback);constraints是用来控制视频和音频是否获取,一般设为{video:true,audio: true},即视频和音频都获取。
  • RTCPeerConnection
    RTCPeerConnection是一个表示两个浏览器端的连接的对象,其含有关于这个连接的所有信息和相关方法,是WebRTC的核心API,负责制作建立连接的SDP、ICE等报文,管理连接状态等等。
  • RTCDataChannel
    RTCDataChannel由RTCPeerConnection创建,需要传递视频、音频以外的数据时使用,它代表浏览器两端间的一个数据通道,和这个数据通道有关的属性和方法都记录在这个对象里。

2.按图索骥


过程:

  1. 首先双方都建立一个RTCPeerConnection的实例,其中一方(称为offer方)用RTCPeerConnection.createOffer()创建一个会话描述sessionDescription,该会话描述包含SDP报文信息和该sessionDescription的类型(type)
  2. 接下来调用RTCPeerConnection.setLocalDescription()方法将本地的localDescription设置为刚才创建的sessionDescription。之后将创建的sessionDescription发送给对方(称为answer方),发送方式没有规定,可以通过服务器中转,可以通过IM软件发送(这里使用WebSocket信令服务器)。
  3. answer端接收到sessionDescription后调用RTCPeerConnection. setRemoteDescription方法设置,然后调用RTCPeerConnection. createAnswer方法产生自己的sessionDescription。
  4. 再将创建的sessionDescription发送给offfer方,同样发送方式没有规定。offer方接收到sessionDescrip后调用RTCPeerConnection. setRemoteDescription方法设置,这样双方的SDP信息就交换完成了。
  5. 在完成SDP的交换后双方还要交换ICE candidate信息。双方首先设置RTCPeerConnection.onicecandidate回调函数,当candidate可用时,双方中的一方将所有icecandidate发送给对方,发送方式同样没有规定,接收方调用RTCPeerConnection.addIceCandidate方法接收candidate信息。经过这些步骤后双方连接就建立完成了。

3.纸上可谈兵

3.1.由简入繁

html文件

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

<head>
<meta charset="UTF-8">
<meta name="keywords" content="JavaScript, WebRTC" />
<meta name="description" content="WebRTC" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1">
<title>获取本地视频</title>
<link rel="stylesheet" href="simpleVideo.css">
</head>

<body>
<!-- <video /> -->
<video></video>
<p><br></p>
<script src='../lib/adapter.js'></script>
<script type="text/javascript" src="simpleVideo.js"></script>
</body>
</html>

js文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var constraints = {
video: true,
audio:true
};

function successCallback(stream) {
window.stream = stream; // stream available to console
console.log(stream);
console.log(stream.getVideoTracks());
console.log(stream.getAudioTracks());

var video = document.querySelector("video");
video.src = window.URL.createObjectURL(stream);
video.play();
}

function errorCallback(error) {
console.log("getUserMedia error: ", error);
}

getUserMedia(constraints, successCallback, errorCallback);

3.2.顺藤摸瓜

这是个完整的例子,参照2.按图索骥部分理解

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
<!DOCTYPE html>
<html>

<head>
<meta name="keywords" content="JavaScript, WebRTC" />
<meta name="description" content="WebRTC codelab" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1">
<title>WebRTC codelab: step 2</title>
<link rel="stylesheet" href="css/index.css">
<!-- css可以使用一些滤镜效果 -->
<style>

</style>
<script src='js/lib/adapter.js'></script>
</head>

<body>
<video id="localVideo" autoplay muted></video>
<video id="remoteVideo" autoplay muted></video>
<div>
<button id="startButton">Start</button>
<button id="callButton">Call</button>
<button id="hangupButton">Hang Up</button>
</div>
<script src="index.js">
</body>

</html>

index.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
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
<script>
var localStream, localPeerConnection, remotePeerConnection;

var localVideo = document.getElementById("localVideo");
var remoteVideo = document.getElementById("remoteVideo");

var startButton = document.getElementById("startButton");
var callButton = document.getElementById("callButton");
var hangupButton = document.getElementById("hangupButton");
startButton.disabled = false;
callButton.disabled = true;
hangupButton.disabled = true;
startButton.onclick = start;
callButton.onclick = call;
hangupButton.onclick = hangup;

function trace(text) {
console.log((performance.now() / 1000).toFixed(3) + ": " + text);
}

function gotStream(stream) {
trace("Received local stream");
localVideo.src = URL.createObjectURL(stream);
localStream = stream;
callButton.disabled = false;
}

function start() {
trace("Requesting local stream");
startButton.disabled = true;
getUserMedia({
audio: true,
video: true
}, gotStream,
function(error) {
trace("getUserMedia error: ", error);
});
}

function call() {
callButton.disabled = true;
hangupButton.disabled = false;
trace("Starting call");

if (localStream.getVideoTracks().length > 0) {
trace('Using video device: ' + localStream.getVideoTracks()[0].label);
}
if (localStream.getAudioTracks().length > 0) {
trace('Using audio device: ' + localStream.getAudioTracks()[0].label);
}

var servers = null;//本机测试不用其他服务器

localPeerConnection = new RTCPeerConnection(servers);//offer方
trace("Created local peer connection object localPeerConnection");
localPeerConnection.onicecandidate = gotLocalIceCandidate;//offer方发送ICE
remotePeerConnection = new RTCPeerConnection(servers);//answe方
trace("Created remote peer connection object remotePeerConnection");
remotePeerConnection.onicecandidate = gotRemoteIceCandidate;//answer方发送ICE
remotePeerConnection.onaddstream = gotRemoteStream;//设置视频流

localPeerConnection.addStream(localStream);
trace("Added localStream to localPeerConnection");
localPeerConnection.createOffer(gotLocalDescription, handleError);//作为offer,产生自己的SessionDescription【SD】信息
}

function gotLocalDescription(description) {//description是offer方的SD
localPeerConnection.setLocalDescription(description);
trace("Offer from localPeerConnection: \n" + description.sdp);
remotePeerConnection.setRemoteDescription(description);//answer方接收offer的SD
remotePeerConnection.createAnswer(gotRemoteDescription, handleError);//answer方发送自己的SD
}

function gotRemoteDescription(description) {
remotePeerConnection.setLocalDescription(description);//anwer方设置本身自己的SD
trace("Answer from remotePeerConnection: \n" + description.sdp);
localPeerConnection.setRemoteDescription(description);//offer接收answer方的SD
}

function hangup() {
trace("Ending call");
localPeerConnection.close();
remotePeerConnection.close();
localPeerConnection = null;
remotePeerConnection = null;
hangupButton.disabled = true;
callButton.disabled = false;
}

function gotRemoteStream(event) {
remoteVideo.src = URL.createObjectURL(event.stream);
trace("Received remote stream");
}

function gotLocalIceCandidate(event) {
if (event.candidate) {
remotePeerConnection.addIceCandidate(new RTCIceCandidate(event.candidate));//answer方接收ICE
trace("Local ICE candidate: \n" + event.candidate.candidate);
}
}

function gotRemoteIceCandidate(event) {
if (event.candidate) {
localPeerConnection.addIceCandidate(new RTCIceCandidate(event.candidate));//offer方接收ICE
trace("Remote ICE candidate: \n " + event.candidate.candidate);
}
}

function handleError() {}
</script>

另外加一些CSS可以很容易实现滤镜效果

1
2
3
4
5
	video {
filter: hue-rotate(180deg) saturate(200%);
-moz-filter: hue-rotate(180deg) saturate(200%);
-webkit-filter: hue-rotate(180deg) saturate(200%);
}

运行这两段代码,就可以调出来本地视频窗口

参考资料

强烈推荐的WebRTC入门教程
google webRTC
WebRTC官网
WebRTC-W3School
MDN-WebRTC

WebRTC实践教程
使用WebRTC搭建前端视频聊天室——信令篇
可以用WebRTC来做视频直播吗?-知乎
实时猫–WebRTC服务商

OpenLayers 3实践与原理探究4.1-ol3源码分析-底层基础

发表于 2016-09-28 | 更新于 2018-11-30 | 分类于 WebGIS

因为下面的内容会分模块介绍源码,所以这里为了方便,首先介绍源码的目录结构
在OpenLayers 3官网的下载页面下载我们在开发工程中需要的文件(如:v3.17.1.zip),注意如果需要编译源代码,需要下载包含编译功能的文件包:https://github.com/openlayers/ol3/releases 下载指定release版本的源码,注意是Source code (zip)或者Source code (tar.gz)。
ol3源码目录结构.png

  • apidoc是ol3的api文档,打开ol.html就可以在浏览器中离线使用,当然也可以在官网中查看api;
  • build是ol3编译过的文件,工程开发中可以直接使用,下部分的案例是基于离线的源码的;
  • closure-library是google的closure库文件夹;
  • css里面只有ol.css一个文件,是定义ol3的全局样式,项目开发中需要引入;
  • doc提供给我们一些的案例,打开quickstart.html即可看到快速开始的案例;
  • examples是比较丰富的例子,和官网中的examples一样;
  • ol就是我们要分析的源码文件夹;
  • ol.ext是ol3所要使用的js库。

ol/ol文件夹下是我们分析的源码,分析基本思路:文件夹下的文件是公用的部分(A部分),文件夹是分部分写的(B部分)。

0.底层基础

0.1 ol.js

第一行就可以看出,ol.js提供全局的第一命名空间ol

1
goog.provide('ol');

唯一的一个方法是:继承

1
2
3
4
ol.inherits = function(childCtor, parentCtor) {
childCtor.prototype = Object.create(parentCtor.prototype);
childCtor.prototype.constructor = childCtor;
};

0.2 object.js

1
2
3
4
5
6
7
8
goog.provide('ol.Object');
goog.provide('ol.ObjectEvent');
goog.provide('ol.ObjectEventType');

goog.require('ol.Observable');
goog.require('ol.events');
goog.require('ol.events.Event');
goog.require('ol.object');

ol命名空间下所有的基本对象,比如map对象,feature矢量地图对象,都应该建立在ol.Object基础上。如:


map.js

1
2
ol.Object.call(this);
ol.inherits(ol.Map, ol.Object);

feature.js

1
2
ol.Object.call(this);
ol.inherits(ol.Feature, ol.Object);

通过这行代码:

1
ol.inherits(ol.Object, ol.Observable);

我们发现ol.Object继承ol.Observable
Observable.js

1
ol.inherits(ol.Observable, ol.events.EventTarget);

我们发现ol.Observable继承ol.EventTarget;
这样,我们可以知道继承ol.Object后也就继承了基础事件ol.events。

0.3 events.js

ol3的基础事件

1
2
3
4
5
goog.provide('ol.events');
goog.provide('ol.events.EventType');
goog.provide('ol.events.KeyCode');

goog.require('ol.object');

提供的所有基础事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ol.events.EventType = {
CHANGE: 'change',
CLICK: 'click',
DBLCLICK: 'dblclick',
DRAGENTER: 'dragenter',
DRAGOVER: 'dragover',
DROP: 'drop',
ERROR: 'error',
KEYDOWN: 'keydown',
KEYPRESS: 'keypress',
LOAD: 'load',
MOUSEDOWN: 'mousedown',
MOUSEMOVE: 'mousemove',
MOUSEOUT: 'mouseout',
MOUSEUP: 'mouseup',
MOUSEWHEEL: 'mousewheel',
MSPOINTERDOWN: 'mspointerdown',
RESIZE: 'resize',
TOUCHSTART: 'touchstart',
TOUCHMOVE: 'touchmove',
TOUCHEND: 'touchend',
WHEEL: 'wheel'
};

我们分析一下提供的几个方法

1
2
3
4
ol.events.bindListener_ = function(listenerObj) {};
ol.events.listen = function(target, type, listener, opt_this, opt_once) {};
ol.events.unlisten = function(target, type, listener, opt_this) {};
ol.events.unlistenAll = function(target) {};

其中方法名末尾带有”_”为私有方法,不带的为提供出去的共有方法。

0.4 math.js

提供基础的数学运算方法,角度转化弧度函数如:

1
2
3
ol.math.toRadians = function(angleInDegrees) {
return angleInDegrees * Math.PI / 180;
};

0.5 animation.js

提供bounce、pan、rotate、zoom四种方法

0.6 collection.js

对ol命名空间下的对象集合的操作。

1
2
3
4
5
6
goog.provide('ol.Collection');
goog.provide('ol.CollectionEvent');
goog.provide('ol.CollectionEventType');

goog.require('ol.events.Event');
goog.require('ol.Object');
1
2
3
4
ol.CollectionEventType = {
ADD: 'add'
REMOVE: 'remove'
};

继承

1
2
ol.inherits(ol.CollectionEvent, ol.events.Event);
ol.inherits(ol.Collection, ol.Object);

方法举例:

1
2
3
4
5
6
7
8
9
10
ol.Collection.prototype.remove = function(elem) {
var arr = this.array_;
var i, ii;
for (i = 0, ii = arr.length; i < ii; ++i) {
if (arr[i] === elem) {
return this.removeAt(i);
}
}
return undefined;
};

0.7 uri.js

通过url加载地图,其中params包含请求地图的宽、高、分辨率、地图范围

1
2
3
4
5
6
7
8
9
10
ol.uri.appendParams = function(uri, params) {
var qs = Object.keys(params).map(function(k) {
return k + '=' + encodeURIComponent(params[k]);
}).join('&');
// remove any trailing ? or &
uri = uri.replace(/[?&]$/, '');
// append ? or & depending on whether uri has existing parameters
uri = uri.indexOf('?') === -1 ? uri + '?' : uri + '&';
return uri + qs;
};

OpenLayers 3实践与原理探究4.4-ol3源码分析-render

发表于 2016-09-28 | 更新于 2018-11-30 | 分类于 WebGIS

前面几节的内容介绍了Map,View,Source,Layer,这些其实我们都是要么在对象属性中设置 ,要么是通过方法设置,实质上是通过共享的全局变量设置地图包含的图层,地图的显示效果,但是如果真正上绘制在浏览器上,需要渲染在canvas(ol3常用的渲染方式).

由于源码代码量比较大,这里只是从大部分介绍流程。
网上有人(OpenLayers 3源码那些事)总结一张图的不错,这里拿来用一下
ol3渲染流程

0.从map.js开始

  • 1) render/renderSync
    具体流程用注释的形式标出
1
2
3
4
5
6
7
//renderSync是异步的,同样道理
ol.Map.prototype.render = function() {
if (this.animationDelayKey_ === undefined) {
this.animationDelayKey_ = ol.global.requestAnimationFrame(
this.animationDelay_);
}
};
  • 2) animationDelay_
1
2
3
4
this.animationDelay_ = function() {
this.animationDelayKey_ = undefined;
this.renderFrame_.call(this, Date.now());
}.bind(this);
  • 3) renderFrame_
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
ol.Map.prototype.renderFrame_ = function(time) {

var i, ii, viewState;

var size = this.getSize();
var view = this.getView();
var extent = ol.extent.createEmpty();
/** @type {?olx.FrameState} */
var frameState = null;
if (size !== undefined && ol.size.hasArea(size) && view && view.isDef()) {
var viewHints = view.getHints(this.frameState_ ? this.frameState_.viewHints : undefined);
var layerStatesArray = this.getLayerGroup().getLayerStatesArray();
var layerStates = {};
for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
layerStates[goog.getUid(layerStatesArray[i].layer)] = layerStatesArray[i];
}
viewState = view.getState();
frameState = /** @type {olx.FrameState} */ ({ //1.准备frameState
animate: false,
attributions: {},
coordinateToPixelMatrix: this.coordinateToPixelMatrix_,
extent: extent,
focus: !this.focus_ ? viewState.center : this.focus_,
index: this.frameIndex_++,
layerStates: layerStates,//layer的常量属性,通过共享作为全局变量
layerStatesArray: layerStatesArray,
logos: ol.object.assign({}, this.logos_),
pixelRatio: this.pixelRatio_,
pixelToCoordinateMatrix: this.pixelToCoordinateMatrix_,
postRenderFunctions: [],
size: size,
skippedFeatureUids: this.skippedFeatureUids_,
tileQueue: this.tileQueue_,
time: time,
usedTiles: {},
viewState: viewState,//view的常量属性,通过共享作为全局变量
viewHints: viewHints,
wantedTiles: {}
});
}

if (frameState) {
var preRenderFunctions = this.preRenderFunctions_;//2.渲染前
var n = 0, preRenderFunction;
for (i = 0, ii = preRenderFunctions.length; i < ii; ++i) {
preRenderFunction = preRenderFunctions[i];
if (preRenderFunction(this, frameState)) {
preRenderFunctions[n++] = preRenderFunction;
}
}
preRenderFunctions.length = n;

frameState.extent = ol.extent.getForViewAndSize(viewState.center,
viewState.resolution, viewState.rotation, frameState.size, extent);
}

this.frameState_ = frameState;
this.renderer_.renderFrame(frameState); //3.渲染

if (frameState) {
if (frameState.animate) {
this.render();
}
Array.prototype.push.apply(
this.postRenderFunctions_, frameState.postRenderFunctions);//4.渲染后

var idle = this.preRenderFunctions_.length === 0 &&
!frameState.viewHints[ol.ViewHint.ANIMATING] &&
!frameState.viewHints[ol.ViewHint.INTERACTING] &&
!ol.extent.equals(frameState.extent, this.previousExtent_);

if (idle) {
this.dispatchEvent(
new ol.MapEvent(ol.MapEventType.MOVEEND, this, frameState));
ol.extent.clone(frameState.extent, this.previousExtent_);
}
}

this.dispatchEvent(
new ol.MapEvent(ol.MapEventType.POSTRENDER, this, frameState));

goog.async.nextTick(this.handlePostRender, this);

};

下面讲诉的渲染的第3步 renderFrame(frameState)
有关渲染的源代码在ol/ol/render,ol/ol/renderer下。
ol/ol/render文件夹下是渲染的基本属性和方法,利用设计模式中的工厂模式,用来构造ol/ol/renderer。

1.渲染Map

ol/ol/renderer/maprenderer.js

1
ol.renderer.Map = function(container, map) {}

这只是个父类,具体实现类位置在:
ol/ol/renderer/canvas/canvasmaprenderer.js–ol.render.canvas.Map(默认)
ol/ol/renderer/canvas/webglmaprenderer.js–ol.render.webgl.Map
ol/ol/renderer/canvas/dommaprenderer.js–ol.render.dom.Map

主要介绍第一种ol/ol/renderer/canvas/canvasmaprenderer.js

1
2
3
4
5
6
7
8
9
10
11
ol.renderer.canvas.Map = function(container, map) {
ol.renderer.Map.call(this, container, map);
this.context_ = ol.dom.createCanvasContext2D();

this.canvas_ = this.context_.canvas;

this.canvas_.style.width = '100%';
this.canvas_.style.height = '100%';
this.canvas_.className = ol.css.CLASS_UNSELECTABLE;
container.insertBefore(this.canvas_, container.childNodes[0] || null);
}

渲染逻辑

1
ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {}

2.渲染Layer

ol/ol/renderer/layerrenderer.js

1
2
3
4
5
6
ol.renderer.Layer = function(layer) {

ol.Observable.call(this);

this.layer_ = layer;
};

这只是个父类,具体实现类位置在:
ol/ol/renderer/canvas/canvaslayerrenderer.js–ol.render.canvas.Layer(默认)
ol/ol/renderer/canvas/webgllayerrenderer.js–ol.render.webgl.Layer
ol/ol/renderer/canvas/domlayerrenderer.js–ol.render.dom.Layer

主要介绍第一种ol.render.canvas.Layer(默认):
这个类又分为三种类型:

  • ol.render.canvas.TileLayer
  • ol.render.canvas.VectorLayer
  • ol.render.canvas.VectorTileLayer

ol.render.canvas.TileLayer渲染逻辑

1
2
3
4
5
6
7
8
9
10
11
ol.renderer.canvas.TileLayer.prototype.prepareFrame = function(
frameState, layerState) {} //第一步
ol.renderer.canvas.TileLayer.prototype.composeFrame = function(//第二步
frameState, layerState, context) {
var transform = this.getTransform(frameState, 0);
this.dispatchPreComposeEvent(context, frameState, transform);
this.renderTileImages(context, frameState, layerState);
this.dispatchPostComposeEvent(context, frameState, transform);
};

ol.renderer.canvas.TileLayer.prototype.renderTileImages = function(context, frameState, layerState) {}

参考文献:

  • OpenLayers 3源码解析视频
  • OpenLayers 3源码那些事(上)
  • OpenLayers 3源码那些事(下)

webpack基础实践1

发表于 2016-09-28 | 更新于 2018-11-30 | 分类于 前端技术

这是个webpack的入门教程,看到网上blog大多是配置好了再解释,这样来的不太直观。本文从第一步开始慢慢做起,一步一步走下来,最后再总结,这样直观看到每个配置行代表什么含义。
webpack的作用是什么?现在说太多可能对于入门的同学来说也不好理解,索性这里就记住一句话,一张图得了
一张图

一句话
webpack是能把各种资源,例如JS(JSX),coffee,样式(CSS/SASS/LESS),图片作为模块来进行打包和处理。

1.安装

1.1 安装全局webpack

前提是在本地先安装了node.js.

1
$ npm install webpack -g

1.2 将依赖写入package.json

新建的话:

1
npm init

一路回车就可以,其实这些都是项目的描述信息和git地址等信息,这些信息我们可以后面再文件中直接修改
如果是clone的项目,已经有package.json文件了,就运行命令(忽略步骤1.3,2.3引入css加载器部分)

1
npm install

1.3 安装局部webpack

1
npm install webpack --save-dev

2.开始使用

2.1 起步

entry.js作为我们的入口文件,其中会包含其他模块(js)或者是CSS

1
document.write("入口entry.js");

index.html

1
2
3
4
5
6
7
8
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript" src="bundle.js" charset="utf-8"></script>
</body>
</html>

运行webpack命令

1
$ webpack ./entry.js bundle.js

然后index.html就可以work了

1
入口entry.js

2.2 引入其他模块

content.js

1
module.exports = "模块content.js";

entry.js

1
document.write(require("./content.js"));

编译命令同上
运行index.html结果

1
模块content.js

2.3 引入CSS

引入css加载器

1
2
npm install css-loader --save-dev
npm install style-loader --save-dev

style.css

1
2
3
body {
background: yellow;
}

entry.js

1
2
require("!style!css!./style.css");
document.write(require("./content.js"));

最后编译,就是这么简单随意完成了,如果我们想这样require("./style.css");引入css,岂不是更加完美
使用编译命令

1
webpack ./entry.js bundle.js --module-bind 'css=style!css'

2.4 引入SASS文件

引入sass加载器

1
2
npm install node-sass --save-dev
npm install sass-loader --save-dev

index.scss

1
2
3
body{
color:white;
}

同css一样
entry.js

1
require("!style!css!sass!./index.scss");

当然我们还不是很满意。简单点,编译命令的方式简单点。所以我们来到了配置文件

3.配置文件

新建文件webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
entry: "./entry.js",
output: {
path: __dirname,
filename: "bundle.js"
},
module: {
loaders: [
{ test: /\.css$/, loader: "style!css" },//css加载器
{ test: /\.scss$/, loader: "style!css!sass" }//sass加载器
]
}
};

编译命令就剩下这样的

1
webpack

5.图片的打包

图片是用url-loader加载的。css中的url属性,其实就是一种封装过的require操作。

1
2
npm install url-loader --save-dev
npm install file-loader --save-dev

webpack.config.js

1
{test: /\.(jpg|png)$/, loader: "url?limit=8192"}

在js中 entry.js

1
2
3
var img = document.createElement("img"); 
img.src = require("./img/webpack.png");
document.body.appendChild(img);

或者直接在css中写

1
2
3
4
5
div.img{
width: 300px;
height: 300px;
background: url("./img/font-icon.png");//小于8kb的图片会打包处理成Base64的图片
}

6.常用webpack编译命令

1
2
3
4
5
webpack //基本命令
webpack --progress --colors //显示打包过程
webpack -w //实时进行打包更新,文件改变时候,自动打包
webpack -p // 对打包后的文件进行压缩,提供production
webpack -d // 提供source map,方便调试。

关于对图片打包 AMD/CommonJS/ES6的使用在下一篇博客中webpack基础实践2
参考文章

  • webpack官网-getting-start
  • webpack前端模块加载工具

EChart 2升级EChart 3注意事项

发表于 2016-09-27 | 更新于 2018-11-30 | 分类于 前端技术

本文是根据自己的实践进行总结过来的,是不完全的所有升级注意事项。
如果想直接看结果,请移步到第4部分内容

1.背景

EChart 3是在2015年12月发布的新版本,相比较EChart 2,主要的变化总结如下:

  • 1) 支持了直角坐标系(catesian,同 grid)、极坐标系(polar)、地理坐标系(geo)
  • 2) 移动端的优化,说明白就是将源码体积减小
  • 3) 新增更多图表类型,增加了一些动态效果
  • 4) 更丰富的交互模式
  • 5) EChart 2推荐使用模块化单文件引入,EChart 3可以选择独立文件或者在webpack中使用模块化(在第2部分说明)
  • 6) 异步数据加载与更新(在第3部分说明)。

2 模块/非模块

2.1 EChart 2模块化引入

EChart 2自带有模块化机制,不用使用其它AMD/CMD库就可以require进来echarts提供的模块
EChart 2 引入进来的目录结构:

示例代码:

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
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>ECharts</title>
</head>
<body>
<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="height:400px"></div>
<!-- ECharts单文件引入 -->
<script src="http://echarts.baidu.com/build/dist/echarts.js"></script>
<script type="text/javascript">
// 路径配置
require.config({
paths: {
echarts: 'http://echarts.baidu.com/build/dist'
}
});

// 使用
require(
[
'echarts',
'echarts/chart/bar' // 使用柱状图就加载bar模块,按需加载
],
function (ec) {
// 基于准备好的dom,初始化echarts图表
var myChart = ec.init(document.getElementById('main'));

var option = {
tooltip: {
show: true
},
legend: {
data:['销量']
},
xAxis : [
{
type : 'category',
data : ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
}
],
yAxis : [
{
type : 'value'
}
],
series : [
{
"name":"销量",
"type":"bar",
"data":[5, 20, 40, 10, 10, 20]
}
]
};

// 为echarts对象加载数据
myChart.setOption(option);
}
);
</script>
</body>

2.2 EChart 2非模块化引入

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
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>ECharts</title>
</head>
<body>
<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="height:400px"></div>
<!-- ECharts单文件引入 -->
<script src="http://echarts.baidu.com/build/dist/echarts-all.js"></script>
<script type="text/javascript">
// 基于准备好的dom,初始化echarts图表
var myChart = echarts.init(document.getElementById('main'));

var option = {
tooltip: {
show: true
},
legend: {
data:['销量']
},
xAxis : [
{
type : 'category',
data : ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
}
],
yAxis : [
{
type : 'value'
}
],
series : [
{
"name":"销量",
"type":"bar",
"data":[5, 20, 40, 10, 10, 20]
}
]
};

// 为echarts对象加载数据
myChart.setOption(option);
</script>
</body>

2.3 EChart 3模块化引入

通过npm命令安装

1
npm install echarts --save

按需引入 ECharts 图表和组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 引入 ECharts 主模块
var echarts = require('echarts/lib/echarts');
// 引入柱状图
require('echarts/lib/chart/bar');
// 引入提示框和标题组件
require('echarts/lib/component/tooltip');
require('echarts/lib/component/title');

// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('main'));
// 绘制图表
myChart.setOption({
title: { text: 'ECharts 入门示例' },
tooltip: {},
xAxis: {
data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
});

2.4 EChart 3非模块化引入

同2.2类似

1
<script src="echarts.min.js"></script>

3.异步数据加载与更新

由于地图模块分辨率变高,为了不增大源码的体积,地图模块采用按照需要下载引入
在地图下载页面,下载需要的世界地图/中国地图/中国分省地图,下载后的格式有js和json两种。
对应js格式的,引入的方式是通过script标签

1
2
3
4
5
6
7
8
9
10
11
<script src="echarts.js"></script>
<script src="map/js/china.js"></script>
<script>
var chart = echarts.init(document.getElementById('main'));
chart.setOption({
series: [{
type: 'map',
map: 'china'
}]
});
</script>

通过JSON格式引入就可以实现异步数据加载与更新

1
2
3
4
5
6
7
8
9
10
$.get('map/json/china.json', function (chinaJson) {
echarts.registerMap('china', chinaJson);
var chart = echarts.init(document.getElementById('main'));
chart.setOption({
series: [{
type: 'map',
map: 'china'
}]
});
});

4.升级总结

4.1 配置变化举例

EChart 2 EChart 3
option.series.mapLocation 删去,使用left,top,bottom,right定义位置
option.series.textFixed 删去地区的名称文本位置修正
dataRange颜色标识属性(示例4.1-1) visualMap
单个echarts 实例中最多只能存在一个 grid 组件 ECharts 3 中可以存在任意个 grid 组件

一个网格中(示例4.1-2)|
| addData , setSeries 方法设置配置项|统一使用setOption(示例4.1-3)|
|级联this.myChart = ec.init(dom).showLoading({effect:’bubble’}).hideLoading();|不支持|
| myChart.component.tooltip.showTip 这种形式调用相应的接口触发图表行为|dispatchAction(示例4.1-4)|
示例4.1-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
     dataRange: {
realtime: false,
itemHeight: 80,
splitNumber:6,
borderWidth:1,
textStyle: { color: '#333333' },
text: ['高', '低'],
calculable: true
},
//=======================================================
visualMap: {
min: 0,
max: 1000000,
text: ['High', 'Low'],
realtime: false,
calculable: true,
inRange: {
color: ['lightskyblue', 'yellow', 'orangered']
}
},

示例4.1-2
图略,官网例子:http://echarts.baidu.com/gallery/editor.html?c=scatter-anscombe-quartet
部分配置属性变化,需要修改

示例4.1-3

1
2
3
4
5
6
7
myChart.setOption({
visualMap: {
inRange: {
color: ...
}
}
})//注意最后一定要再次setOption(option);option是配置对象

示例4.1-4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
myChart.on('brushselected', renderBrushed);
setTimeout(function() {
self.myChart.dispatchAction({
type: 'brush',
areas: [{
geoIndex: 0,
brushType: 'polygon',
coordRange: [
[119.72, 34.85],
[117.05, 34.06],
[117.49, 33.75],
[123.16, 29.92],
[121.64, 34.08]
]
}]
});
}, 0);


function renderBrushed(params) { //... ...
}

4.2 第2部分的模块与非模块更改时候需要注意

4.3 地图模块

EChart 2:

1
2
3
4
5
6
chart.setOption({
series: [{
type: 'map',
map: 'china'//'world'
}]
});

EChart 3,需要下载地图,使用方式见第3部分。

CSS3--font-face使用

发表于 2016-09-27 | 更新于 2018-11-30 | 分类于 前端技术

1.介绍

  • @font-face是CSS3中的一个模块,他主要是把自己定义的Web字体嵌入到你的网页中,不用担心兼容性,@font-face在IE4中都支持。
  • 如果是用字体做logo,英文的话字体和图片占用大小差不多,但是中文的字体包一般比较大,最好还是使用图片的形式。

2.快速实践

  • 下载字体需要格式为.tff格式的字体文件
  • 搜索Webfont Generator,或者直接使用该网站提供的服务。这很简单,进入网站后选择.tff字体文件上传,勾选同意的复选框,点击Generate web font,点击Download Package下载,解压缩文件。
  • 使用
    新建index.css
1
2
3
4
5
6
7
8
9
10
11
12
@font-face {
font-family: 'Happy-Camper-Regular';
src: url('../fonts2/Happy-Camper-Regular.eot');
src: url('../fonts2/Happy-Camper-Regular.eot?#iefix') format('embedded-opentype'), url('../fonts2/Happy-Camper-Regular.woff') format('woff'), url('../fonts2/Happy-Camper-Regular.ttf') format('truetype'), url('../fonts2/Happy-Camper-Regular.svg#SingleMaltaRegular') format('svg');
font-weight: normal;
font-style: normal;
}

h2.demo {
font-size: 100px;
font-family: 'Happy-Camper-Regular'
}
1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>字体</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<h2 class="demo">hello world!You are my Destiny</h2>
</body>
</html>

3.字体icon

使用某些字体,如:WebSymbols-Regular百度云下载地址,Guifx字体,包括现在开源的比较流行的Font Awesome,使用方法同上。在html文件中如下示例:

1
2
3
4
5
<span>A</span>
<span>B</span>
<span>C</span>
<span>D</span>
<span>F</span>

每一行显示的是其对应的图标

参考文献:

  • 下载字体的地方
  • CSS3 @font-face
  • @font-face制作Web Icon

javascript面向对象和面向委托

发表于 2016-09-27 | 更新于 2018-11-30 | 分类于 前端技术

昨天看了一本书《你不知道的javascript(上)》关于这方面的内容,体会颇深,其中书中讲到的把javascript当作是面向委托的语言比面向对象的解释更加贴切,下面我就简单结合自己的理解,书写阐述一下,也可以作为一种笔记记录。

1. 提取精华——几个重要的方法

1.1 原型链关联

  • Bar.prototype = Foo.prototype;
  • Bar.prototype = new Foo();
  • Bar.prototype = Object.create(Foo.prototype);
    第一种方式,没有创建Bar.prototype的新对象Bar.prototype直接引用了Foo.prototype,修改Bar.prototype会影响Foo.prototype
    第二种方式,创建了一个关联Bar.prototype的新对象,new其实是调用Foo的“构造函数”,有些东西会影响到Bar()的后代。
    第三种方式,Object.create() 方法创建一个拥有指定原型和若干个指定属性的对象。
    语法:Object.create(proto, [ propertiesObject ])
    参数:proto 一个对象,作为新创建对象的原型。
    propertiesObject 可选。该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符(这些属性描述符的结构与Object.defineProperties()的第二个参数一样)

    MDN

ES5之前Object.create Poyfill代码:

1
2
3
4
5
6
7
if(!Object.create){
Object.create = function(o){
function F(){};
F.prototype = o;
return new F(); //new的作用参见上述 第二种方式
}
}

ES5:Object.setPrototypeOf(Bar.prototype,Foo.prototype)更加标准可靠

1.2 ES6 class

内部也是通过原型链实现的,只是一种语法糖。

2.针尖麦芒——面向对象(OO) VS 面向委托(对象关联 OLOO)

  • OO:类的继承是复制行为,简单说关系是父子关系
    OLOO: 只是对象的关联(基于原型/原型链),简单说关系是兄弟关系,互相关联。

  • 代码
    OO风格:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function Foo(who){
    this.name = who;
    }
    Foo.prototype.identity = function(){
    return "I am "+this.name;
    };

    function Bar(who){
    Foo.call(this,who);
    }
    Bar.prototype = Object.create(Foo.prototype);

    Bar.prototype.speak = function(){
    alert("hello,"+this.identity()+" .");
    };

    var b1 = new Bar('b1');
    var b2 = new Bar('b2');
    b1.speak();
    b2.speak();

OLOO风格:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Foo = {
init: function(who) {
this.name = who;
},
identity: function() {
return "I am " + this.name;
}
};

Bar = Object.create(Foo);
Bar.speak = function() {
alert("hello," + this.identity() + " .");
};

var b1 = Object.create(Bar);
b1.init('b1');
var b2 = Object.create(Bar);
b2.init('b2');
b1.speak();
b2.speak();

3.问题探究

内省:我们想看Foo和Bar之间的关系
OO:对比的是Bar.prototype与Foo的关系,并不是Bar和Foo的关系

1
2
3
console.log(Bar.prototype instanceof Foo);  //true
console.log(Object.getPrototypeOf(Bar.prototype) === Foo.prototype);//true
console.log(Foo.prototype.isPrototypeOf(Bar.prototype));//true

OLOO:是Bar和Foo的关系

1
2
console.log(Object.getPrototypeOf(Bar) === Foo);
console.log(Foo.isPrototypeOf(Bar));

HTTP协议实践篇--使用Fiddler与后台php交互

发表于 2016-09-27 | 更新于 2018-11-30 | 分类于 HTTP/TCP/IP

工具:

  • PHP、Apache服务器,端口号这里设置为8000,如果本机没有安装php环境,可以选择wamp或者xampp集成的php环境开发器
  • fiddler是比较好用的抓包工具,它是免费的,具体使用我们不单独介绍。

1.模拟form表单提交数据

html表单写法

1
2
3
4
<form action="http://127.0.0.1:8000/test.php" method="post" >
<input type="text" name="name">
<input type="submit" value="提交">
</form>

method改为post,get去体会它们的区别

  • 最大的区别是get方式,提交的话会将提交的数据放在url中,如http://127.0.0.1:8000/test.php?name=hello
    post请求提交的name=hello会放在header请求体内,具体位置在【报文主体】
  • get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB
  • 其实,两种方式都可以向服务器传送数据,向服务器上获取数据。
    test.php
1
2
3
4
5
6
<?php 
$name1 = $_POST['name'];
$name2 = $_GET['name'];
echo "$name1";
echo "$name2";
?>

完整的请求头是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST http://127.0.0.1:8000/test.php HTTP/1.1
Host: 127.0.0.1:8000
Connection: keep-alive
Content-Length: 10
Cache-Control: max-age=0
Origin: null
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8

name=hello

最后的结果,分别两种方式提交,总会有一个会显示没有定义,一个显示出请求的数据

下面就用Fiddler模拟

  • 使用post方式

    Content-Type不能忽略
    1
    2
    3
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 10
    Host: 127.0.0.1:8000

点击右上角【Execute】,就能模拟一个form表单提交数据了。
右边会显示一条我们刚刚的HTTP请求。

  • 使用get方式

    【Execute】执行

2.模拟文件操作

2.1 上传文件

post方式提交请求,很少会用get方式去请求文件
html

1
2
3
4
5
<form action="http://127.0.0.1:8000/test/upload_file.php" method="post" enctype="multipart/form-data">
<input type="text" name="name"><br>
<input type="file" name="file" id="file"><br>
<input type="submit" value="提交">
</form>

php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php 
$name = $_POST['name'];

echo "$name";
echo "<br>";
echo $_FILES["file"]["name"];//$_FILES["file"]通过 HTTP POST 方式上传到当前脚本的项目的数组。
echo "<br>";

if (file_exists("upload/" . $_FILES["file"]["name"])) {
echo $_FILES["file"]["name"] . " already exists. ";
}else{
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload/" . $_FILES["file"]["name"]);
echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
}
?>

请求头文件

  • 在chrome中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST http://127.0.0.1:8000/test/upload_file.php HTTP/1.1
Host: 127.0.0.1:8000
Connection: keep-alive
Content-Length: 291
Cache-Control: max-age=0
Origin: null
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryC1Pk1uMWXzAMqRMF
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8

------WebKitFormBoundaryC1Pk1uMWXzAMqRMF
Content-Disposition: form-data; name="name"

wenjian
------WebKitFormBoundaryC1Pk1uMWXzAMqRMF
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/plain

This is a txt.
------WebKitFormBoundaryC1Pk1uMWXzAMqRMF--
  • 在Firfox中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST http://127.0.0.1:8000/test/upload_file.php HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Type: multipart/form-data; boundary=---------------------------31340552315478
Content-Length: 302

-----------------------------31340552315478
Content-Disposition: form-data; name="name"

txt文件
-----------------------------31340552315478
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/plain

This is a txt.
-----------------------------31340552315478--

其中最大的差异也就是boundary分界线
分界线里的是上传文件的信息,如果是个图片,我们会看到

1
2
3
4
Content-Disposition: form-data; name="file"; filename="CCGIS2.png"
Content-Type: image/png

//下面这些是图片的信息

响应头信息差别并不大

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
Date: Tue, 20 Sep 2016 06:48:58 GMT
Server: Apache/2.4.16 (Win64) PHP/5.6.13
X-Powered-By: PHP/5.6.13
Content-Length: 43
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

wenjian<br>1.txt<br>Stored in: upload/1.txt

用Fiddler模拟

这里的头信息完全是照抄在chrome请求的头信息,其中最重要的是Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryC1Pk1uMWXzAMqRMF,我们用这一行就可以去执行,当然Fiddler会自动加入

1
2
Host: 127.0.0.1:8000
Content-Length: 295

有意思的是我们在Request Body中可以修改filename就可以修改上传后的文件名,我们也可以添加些内容,上传到服务器端。

1
2
3
4
5
6
7
8
9
10
------WebKitFormBoundaryC1Pk1uMWXzAMqRMF
Content-Disposition: form-data; name="name"

wenjian
------WebKitFormBoundaryC1Pk1uMWXzAMqRMF
Content-Disposition: form-data; name="file"; filename="3.txt"
Content-Type: text/plain

This is a txt. 这里是加入的内容 hahahha 哈哈哈
------WebKitFormBoundaryC1Pk1uMWXzAMqRMF--

我们甚至可以修改boundary:Content-Type: multipart/form-data; boundary=----123456
那么Request Body中

1
2
3
4
5
6
7
8
9
10
------123456
Content-Disposition: form-data; name="name"

wenjian
------123456
Content-Disposition: form-data; name="file"; filename="4.txt"
Content-Type: text/plain

This is a txt.hahahha 哈哈哈
------123456

值得一提的是,Ruqest Body右边的Upload File可以将选择的文件放在请求体中。注意修改name属性,与php文件的获取字段相同。

2.2 请求文件

请求文件其实很简单
POST/GET http://127.0.0.1:8000/test/upload/1.txt
【Excute】执行即可

既然我们是来实践HTTP协议的,那么很重要的文件缓存这方面的我们也可以实践,这些内容我打算单独写一篇博客

———————–华丽的分界线—————————-
感觉这些技术很基础,但是也很黑客,我们完全可以使用这些技术去“黑”些网站,哈哈,当然要遵纪守法,当然有这些技术还是不够的。

File Input多次添加文件,动态删除文件,用来实现上传等操作

发表于 2016-09-27 | 更新于 2018-11-30 | 分类于 前端技术

1.需求图示

实现

2.按图索骥

  • 添加 实际上,添加附件就是<input type="file" id="myFile">的控件,var fileList = getElementById(myFile).files就可以得到选择的文件的FileList对象,这个对象是类数组的对象(含义有点像函数参数arguments)。记住这一点很重要。

  • 显示 下面的显示文件名的面板根据上传的文件名name显示

3.刨根问底

  • FileList类数组对象
    console.log(fileList)打印出来的结果显示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    	FileList  
    0:File
    lastModified:1446204650848
    lastModifiedDate:Fri Oct 30 2015 19:30:50 GMT+0800 (中国标准时间)
    name:"CCGIS.png"
    size:809542
    type:"image/png"
    webkitRelativePath:""
    __proto__:File
    length:1
    __proto__:FileList

    思考:我们只需要能动态修改fileList即可,第一想法是将它转化为数组进行操作。
    files = Array.prototype.slice.call(files);

4.付诸行动

动手编程吧:
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
var fileInput = document.getElementById('myFile');
var files = fileInput.files; //filelist

$('#myFile').on('change', function(event) {

files = fileInput.files; //应该重新获取
console.log(files);

files = Array.prototype.slice.call(files); //全部转化为数组
fileLists = fileLists.concat(files);
//显示文件名面板
if (files.length !== 0) {
var html = '';
for (var i = 0; i < files.length; i++) {
html += "<p>" + files[i].name + "&nbsp&nbsp<img class='icon-remove'></p>";
}
$('.upfile-list-mes').append(html);
}
});

/*点击叉号可以删除要上传的文件*/
$('.upfile-list-mes').on('click', '.icon-remove', function(event) {
var ind = $(this).parent().index();
$(this).parent().css('display', 'none');
fileLists.splice(ind, 1);//修改fileLists
console.log(fileLists);
});

HTTP协议实践篇--浏览器缓存总结、利用Fiddler和apache模拟

发表于 2016-09-27 | 更新于 2018-11-30 | 分类于 HTTP/TCP/IP

1.浏览器缓存

废话少说,我们先了解浏览器缓存的知识。

其中优先级是:Cache-Control>Expires>协商缓存
浏览器访问缓存的顺序是:

2.浏览器刷新的几种状态

  • 普通模式 我们下面的叙述在没有特殊说明的情况下就是这个模式
  • 普通页面跳转(点击页面链接跳转,window.open,在地址栏敲回车,刷新页面)
    • 无缓存情况下,请求会返回所有资源结果
    • 设置Expires并且未过期时,浏览器将不会发出http请求
    • 如果Expires过期,则会发送相应请求,并附带上Last-Modifed等信息,供服务器校验
  • 页面刷新(F5)
    这种情况一下,一般会看到很多304的请求,就是说即便资源设置了Expires且未过期,浏览器也会发送相应请求,命中协商缓存。
  • 强制刷新(Ctrl+F5)
    效果和无缓存时候一致,返回200的结果

3.强缓存

返回的http状态为200,在chrome的开发者工具的network里面size会显示为from cache

3.1 Cache-Control

请求指令

响应指令

这里需要注意no-cache对客户端和服务器含义是不同的,见下图

3.2 Expires

资源失效的日期

1
Expires:  Wed, 21 Sep 2016 12:06:44 GMT


实践:

实践3-1 html文件中

在html文件中

1
2
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="Wed, 25 Oct 2016 13:19:55 GMT">

这些方法不常用,而且测试不能通过

实践3-2 PHP中设置

可以使用php设置,当然也可以在apche服务器中进行设置

1
2
3
4
5
header("Cache-Control: public");
header("Pragma: cache");
$offset = 30*60*60*24; // cache 1 month
$ExpStr = "Expires: ".gmdate("D, d M Y H:i:s", time() + $offset)." GMT";
header($ExpStr);

chrome network工具栏显示

打开浏览器新窗口的方式测试,而不是F5刷新
chrome network工具栏显示

实践3-3 不需要PHP

可以像浅谈浏览器http的缓存机制文中所使用的方法

  • 在fildder右下角黑色区域–命令行,输入如:bpu localhost:8000 阻断来自localhost:8000的本地http请求
  • 点击被拦截的请求,可以在右栏直接修改报文内容(上半区域是请求报文,下半区域是响应报文),点击黄色的“Break on Response”按钮可以执行下一步(把请求发给服务器),点击绿色的按钮“Run to Completion”可以直接完成整个请求过程

4.协商缓存

当浏览器对某个资源的请求没有命中强缓存,就会发一个请求到服务器,验证协商缓存是否命中,如果协商缓存命中,请求响应返回的http状态为304并且会显示一个Not Modified的字符串

4.1 Last-Modified,If-Modified-Since

这对名词通常是成对出现的
last-modified:服务端设置的文档的最后的更新日期
if-modified-since用于指定这个时间以后的服务器资源,GMT格式

4.2 ETag、If-None-Match/if-match

这对名词通常也是成对出现的
ETag用于服务器向客户端传送的代表实体内容特征的标记信息
If-None-Match/if-match服务器给客户机传送网页的时候,可以传递代表实体内容特征的头字段(ETag),这种头字段被叫做实体标签。当客户机再次向服务端发请求的时候,会使用if-match携带实体标签信息


实践:
php

1
header("Last-Modified:".gmdate("D, d M Y H:i:s") . " GMT" );

使用fiddler请求

1
2
3
如:
If-Modified-Since: Wed, 04 Oct 2016 13:32:30 GMT
Last-Modified: Wed, 05 Oct 2016 13:32:30 GMT


测试了几遍,并没有返回 304,原因不明


参考阅读:

  • php header()函数设置页面Cache缓存
  • 在php编程中使用header()函数发送文件头,设置浏览器缓存,加快站点的访问速度
  • 浅谈浏览器http的缓存机制
  • HTML meta标签总结与属性的使用介绍
1…67

Ruyi Zhao

70 日志
5 分类
52 标签
© 2018 Ruyi Zhao
由 Hexo 强力驱动 v3.8.0
|
主题 – NexT.Pisces v6.5.0