-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
224 lines (224 loc) · 91.6 KB
/
search.xml
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[轮询机制解决后端任务回调问题]]></title>
<url>%2F2017%2F06%2F07%2F%E8%BD%AE%E8%AF%A2%E6%9C%BA%E5%88%B6%E8%A7%A3%E5%86%B3%E5%90%8E%E7%AB%AF%E4%BB%BB%E5%8A%A1%E5%9B%9E%E8%B0%83%E9%97%AE%E9%A2%98%2F</url>
<content type="text"><![CDATA[某天晚上在上班时间偷偷摸鱼的时候,公司的同事的和我探讨一个问题。 问题同事大概描述了这样一个需求: 现在有一个需求,前端有一个按钮,点击以后会调用后端一个接口,这个接口会根据用户的筛选条件去hadoop上跑任务,将图片的base64转为img然后打包成zip,生成一个下载连接返回给前端,弹出下载框。 hadoop上的这个任务耗时比较久,一般都是10s以上,也就是说如果一直让前端等待,会出现请求超时的问题。 需求现阶段实现以下是现阶段的实现状况: 用户点击下载按钮后,会把以下筛选条件传到后端,如图: 后端在收到请求后,在去hadoop上跑任务前会向数据库插入这么一条数据: id name status url 1 1496980062652 0 name - 以生成任务时的时间戳命名status - 任务状态, 0代表任务正常执行,1代表任务执行完成url - 打包后zip的下载地址, status为0时,url为空,status为1时,url有对应下载地址 同事做到这里就卡住了。 解决方案以生成任务时的时间戳命名真的好吗?以生成任务时的时间戳命名使得生成的任务和任务的内容没有任何联系,举个例子:同样的筛选条件,用户每次去下载的时候都会在hadoop上重新跑一个任务,生成一条新数据。实际上,同样的筛选条件打包出来的数据应该是一样的,没必要每次下载都去重跑一次任务,造成不必要的时间损耗。更直观来讲,也就是说2个用户使用相同的筛选条件,但是他们创建下载任务的时间不同,导致了他们都在hadoop创建了一个任务和数据库插入了一条数据,实际上他们各自插入的数据除了虽然name和url不同,但是url下载下来的文件中的内容是一样的。 我的解决方案是将所有筛选条件拼接在一起,然后encodeURIComponent后作为唯一标志。12let name = [startTime, endTime, sid, client, sign, cuid, number, result];name = encodeURIComponent(name.join('')); 这样做也就是说同样筛选条件下,name的标志具有唯一性,避免任务重跑。如果用户在下载zip的时候之前有人创建过相同条件的任务,那么则无许等待,直接就可以进行下载。 后端接口部分设计再来设计后端接口,根据分析任务一共有三种状态,对应status值如下 未建立任务————- -1 任务运行中————- 0 任务结束,生成url—- 1 接口伪代码如下123456789101112131415161718192021222324252627282930public function download () { $startTime = $this->input->post('startTime'); $endeTime = $this->input->post('endeTime'); $sid = $this->input->post('sid'); $client = $this->input->post('client'); $sign = $this->input->post('sign'); $cuid = $this->input->post('cuid'); $number = $this->input->post('number'); $result = $this->input->post('result'); $name = $this->input->post('name'); $status = getTaskStatus($name); $data = array( 'status' => $status, 'url' => '' ); if ($status == -1) { establishTask($startTime, $endTime, $sid, $cuid, $number, $result); // 去hadoop上跑任务 insertSQL($name, 0); } else if ($status === 0) { } else if ($status === 1) { $data['url'] = getTaskUrl($name); } echo json_encode($data);} getTaskStatus用于从数据库中获取任务状态,当数据库中没有与$name相匹配的数据时则返回-1,有则返回对应的status。 接下来进行判断: 若$status为-1,表明该筛选条件的请求是第一次出现,establishTask创建hadoop任务的同时insertSQL在数据库中插入一条name为$name,status为0的数据。(establishTask在任务结束时会自动修改数据库中的status为1,同时插入url) 若$status为0,表明该筛选条件对应的任务正在hapdoop上运行。 若$status为1,表明该筛选条件对应的任务已经完成,通过getTaskUrl从数据库中取得url。 最后将data返回给前端。 前端轮询机制前端需要做的就是根据后端返回的结果来判定是否需要继续请求,也就是所谓的轮询,根据setInterval来进行实现。 伪代码如下1234567891011121314151617181920212223242526272829303132333435363738394041424344const download = () => { let interval = setInterval(() => { let { startTime, endTime, sid, client, sign, cuid, number, result } = $scope; let name = [startTime, endTime, sid, client, sign, cuid, number, result]; name = encodeURIComponent(name.join('')); let data = { startTime, endTime, sid, client, sign, cuid, number, result, name }; $http({ method: 'POST', url: '/downlaod', data: data, headers:{'Content-Type': 'application/x-www-form-urlencoded'}, transformRequest: function (data) { return $.param(data); } }) .then(res => { if (res.status === 1) { clearInterval(interval); window.location.href = res.url; } }, error => { console.log(error); }) }, 2000);} 时间设置为2s一次轮询,当response中的status为1即获得下载地址,此时通过clearInterval取消interval,关闭轮询。 页面刷新带来的影响如果hadoop 速度极慢,长时间没反应,用户可能会以为页面卡顿了,从而进行页面的刷新。 由于采取了上面同筛选条件下任务标识唯一的方法,即使刷新页面后,用户再点击下载相同条件的任务后也不会再去handoop上重新跑任务以及插入新的数据,如果任务还在running则等待,任务已经结束则直接下载。 如果以最初的时间戳为name,则会导致任务重跑,用户得重新开始等待。 关于为什么不引入socket.io?交流中我曾询问过目前项目中是否还有其余与此相似的功能,但据了解暂时只有这一个需求,所以虽然socket.io相比轮询来说更节约性能,但是没有必要为了一个功能而引入一个库,这样做的感觉是得不尝试。这样做的行为类似于你为了使用underscore中的某个方法而引入整个underscore。 关于轮询机制和websocket的形象对比轮询机制123456789客户端:服务器,你有没有消息要给我啊?服务器:有。客户端:服务器,你有没有消息要给我啊?服务器:没有。——————————无限重复————————————客户端:服务器,你有没有消息要给我啊?服务器:没有。客户端:服务器,你有没有消息要给我啊?服务器:有 websocket12345客户端:服务器,你有我的消息了记得call我。服务器:OK!——————————当有消息的时候————————服务器:有你的消息了,客户端。客户端:收到。]]></content>
<categories>
<category>web</category>
</categories>
<tags>
<tag>javascript</tag>
<tag>php</tag>
</tags>
</entry>
<entry>
<title><![CDATA[关于公司老项目的视图渲染解决方案]]></title>
<url>%2F2017%2F05%2F24%2F%E5%85%B3%E4%BA%8E%E5%85%AC%E5%8F%B8%E8%80%81%E9%A1%B9%E7%9B%AE%E7%9A%84%E8%A7%86%E5%9B%BE%E6%B8%B2%E6%9F%93%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%2F</url>
<content type="text"><![CDATA[最近公司的一个老旧的项目因为业务各方面原因突然活跃起来了,每日大概uv100,pv能达到3500。虽然不知道为什么uv和pv相差能那么大,但是由于使用多了,老项目的一些弊端就出来,需要不停地添加功能和修复BUG,所以我成了这个项目的前端接盘侠。 这项目技术栈大概是jQuery + backbone,项目重构按照目前的情况来看短期是不可能了,所以要做的就是如何去优化。 问题这个项目大概有差不多一年的历史了,在js文件里你随处可以看见下面这种代码,将函数挂载到全局变量上,然后又在另一个js文件里调用。 1234567# one.jswindow.example = funtion () { console.log('test');}# two.jsexample() 刚开始的时候的时候一脸懵比,后来已经习惯了,如果你在当前文件里没有找到该函数的声明,别急,随着html里面js的引用顺序挨个往前面翻就是了,总能找到。 下面才是今天真正的问题,我在某个文件里面看到了大量类似于这样的代码(样式class省略,内容进行了处理)1234var msg = '<div>' + '<div>时间是{{time}} 名字是{{username}</div>' + '<div>内容是{{content}}</div>' + '</div>'; 当数据产生变化的时候,都会出现类似下面的代码,由此产生了大量冗余代码。12345var html = msg.replace(/\{\{time\}\}/, time);html = html.replace(/\{\{username\}\}/, time);html = html.replace(/\{\{content\}\}/, time);$('#target').html(html); 同时jquery的html插入的实际上是清空了#target里的DOM,然后再插入重新生成新的DOM,当然这样会有很大的性能损耗,其次每次这样操作都会产生大量重复的冗余代码。如果代码仅仅只有这么几行或者十几行或许不会有多大影响,但是我扫了一下整个项目,这样的代码粗略估计有至少200行以上了,后续开发中不可避免还会遇到类似的问题,所以写一个小插件解决这问题。 问题解决为了解决上述的问题,我写了一个小插件Shaco,名字来源于LOL中的恶魔小丑萨科的英文名字,寓意为神出鬼没地解决视图渲染的问题。Shaco的总代码量目前102行,其原理是通过数据劫持来监听(Observer)数据的变化,通过编译(Compile)来解析指定元素节点下的变量(Mustach语法),当数据产生变化时通知调用update的updateText刷新文本。 关于ShacoShaco主要由三部分组成 Shaco需要提供的参数为需要解析的元素, 其值建议使用id唯一标志。然后就是需要Shaco需要劫持的数据data,务必为Object。使用例子:123456var shaco = new Shaco({ el: '#app', data: { a: 1 }}); 插件效果: 代码解析部分仅提供部分代码,详情请查看项目Github地址的lib/shaco.js Shaco关于Shaco函数部分, $el为元素节点,$data为需要劫持的数据,$save用来存储模板解析含有变量的节点对象。123456789function Shaco (options) { this.$el = document.querySelector(options.el); // 元素节点 this.$data = options.data; // 需要劫持的数据 this.$save = []; // 模板解析 {node, value, text} // 数据劫持 observe(this.$data, this); // 模板解析 this.$compile = new Compile(this.$el, this);} ObserverObserver模块中通过defineProperty去设置set监听数据,当数据发生变化时,observe新的数据,同时通知update刷新视图。1234567891011set: function (newVal) { if (newVal === val) { return; } var oldData = JSON.parse(JSON.stringify(vm.$data)); val = newVal; observe(newVal, vm); updater.updateText(vm, oldData);} CompileCompile模块中对节点进行解析,当节点是文本节点(即没有子节点)时进行compileText,compileText在处理是会把命中的节点压入$save中。12345678910111213compile: function (node, vm) { var that = this; var childNodes = node.childNodes; [].slice.call(childNodes).forEach(function(node){ if (that.isElement(node)) { that.compile(node, vm); return; } else if (that.isText(node)) { that.compileText(node, vm) } });} updaterupdater在被劫持的数据更新时会被触发,此时会扫描数据,对$save中节点进行遍历,然后更新数据。 性能问题在进行视图渲染更新的时候,可以看到Shaco只更新了对应DOM的nodeValue 而jQuery html()在进行视图渲染更新的时候,重新渲染了整个对应的DOM 对比一下两个操作耗费的时间,发现jQuery html()的大部分时间都集中在Rendering和Paitning上,而Shaco因为只刷新来的nodeValue所以没在这上面耗费多少时间。 题外话Shaco采用ES5的语法编写,对于使用jquery维护的老项目可以毫无顾忌的使用,不用担心任何问题。关于为什么不加入指令解析、watcher等?如果有这需要的话为什么不直接使用vue呢?说到底Shaco只是为一些老项目提供视图渲染方面的一个解决办法,减少冗余代码,而并非说是要徒手撸一个MVVM框架出来。 关于input使用Shaco, 你可以这样做。1234567891011121314<div id="app2"> <p>显示:{{value}}</p> 输入:<input type="text" oninput="input(this)"></div>var shaco2 = new Shaco({ el: '#app2', data: { value: '' }});function input (e) { shaco2.$data.value = e.value;} 嗯,后来问了下项目当初是实习生负责的,所以维护也理所当然落到了我头上! ╮( ̄▽ ̄”)╭”),估计当初也没想到这项目后来居然活了吧~ 贴一张图~ 老夫子写代码就用jquery,复制粘贴就是干.jpg 最后Github项目地址:Shaco在线Demo: https://blog.shanamaid.top/shaco/index.html]]></content>
<categories>
<category>web</category>
</categories>
<tags>
<tag>javascript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[博客小绿锁添加日记与nginx反向代理]]></title>
<url>%2F2017%2F05%2F02%2F%E5%8D%9A%E5%AE%A2%E5%B0%8F%E7%BB%BF%E9%94%81%E6%B7%BB%E5%8A%A0%E6%97%A5%E8%AE%B0%E4%B8%8Enginx%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%2F</url>
<content type="text"><![CDATA[关于https小绿锁的加持小绿锁加持计划拖了有一段时间额,趁着五一假期终于搞定了,中间也是一波三折。 配置dns首先小绿锁的加持使用的是国外的cloudflare(以下简称cf)免费提供的SSL。在cf官网注册一个账号,添加你的域名站点,选择免费计划。 一切完成了以后点击上方一排中的dns图标,你可以看到cf为你提供了两个ns 用这两个ns去替换掉你域名的dns,博主的域名是在新网购买的,替换如下图 在cf的dns页面添加一条DNS Records,如下图 使用https在cf的Crypto页面,将SSL设置为flexible 注意,此时你的博客并不能通过https,此时用https访问会提示无效证书。因为刚设置完的时flexiable候其实没有绿色Active Certificate,官方在左侧已经说明了 It may take up to 24 hours after the site becomes active on Cloudflare for new certificates to issue. 这句话的大概意思就是说需要一段时间去激活证书,大概是24小时。 实际上其实用不了那么久,大概12小时左右博主邮箱就收到了一封激活邮件,告诉博主已经激活了。此时便可以使用https去访问你的网站了。 强制httpscf的Page Rules页面,添加路由规则。通过规则中的Always use https选项,可以将http访问的用户强制跳转到https。设置如下: 这里有个坑的地方就是在Active Certificate未激活前,也就是在你没收到官方的通知邮件前,你会发现你的路由规则中是没Always use https这个选项的。 加持完毕此时ping一下博客域名发现已经成功,同时无论通过http还是https访问你的博客,都带上优雅的小♂绿♀锁,只是初次访问速度上会慢上许多。 ╮( ̄▽ ̄”)╭,反正免费的也就这样吧! 以上大部分参照于我朋友@Choin Tang的在GitHub Pages上使用CloudFlare https CDN cloudflare带来的遗留问题博客加上小绿锁了后一段时间,收到开源项目的使用者的邮件,告知github上开源的三个项目的在线版都无法使用了。看了下,三个开源项目的网址如下 项目 地址 oho阅读 http://www.shanamaid.top:3001/ 163music http://www.shanamaid.top:3000/ webchat http://www.shanamaid.top:7777/ 想了想应该是自己在cf上面忘记添加根域名的A记录了,所以在cf上补上了 过了一段时间,ping了一下www.shanamaid.top,毫无意外,ping通了,感觉没有问题了,然后打开网页带上端口号访问的时候页面死活进不去→_→ 。 翻来覆去查了下,果然是cf的锅,添加的dns记录只能解析到80端口。 正好最近在看《nginx高性能web服务器详解》,为了解决问题那只有上nginx进行反向代理咯~ 先在cf中追加dns records,把域名全部解析到119.29.159.156。 然后在nginx的配置文件中添加下列几行代码,然后./nginx -s reload重新加载一下配置文件。12345678910111213141516171819202122server { listen 80; server_name oho.shanamaid.top; location / { proxy_pass http://119.29.159.156:3001; }}server { listen 80; server_name 163music.shanamaid.top; location / { proxy_pass http://119.29.159.156:3000; }}server { listen 80; server_name webchat.shanamaid.top; location / { proxy_pass http://119.29.159.156:7777; }} 将其分别解析到3001、3000、7777端口,此时就可以通过域名重新访问了。 项目 地址 oho阅读 http://oho.shanamaid.top/ 163music http://163music.shanamaid.top/ webchat http://webchat.shanamaid.top/ 最后通过cf后,开源项目的在线版访问速度变得异常慢,即使开启了gzip还是很难受,过段时间也许会考虑换个域名专门挂开源项目在线版?或者说弃用cf? 暂时就这样吧~~!↖(^ω^)↗ 后记经过cf转发后速度开源项目在线版的读取速度实在惨不忍睹,于是新注册了一个shanalab域名用作挂开源项目的。项目地址改为 项目 地址 oho阅读 http://oho.shanalab.top/ 163music http://163music.shanalab.top/ webchat http://webchat.shanalab.top/ nginx转发配置1234567891011121314151617181920212223```server { listen 80; server_name oho.shanalab.top; location / { proxy_pass http://119.29.159.156:3001; }}server { listen 80; server_name 163music.shanalab.top; location / { proxy_pass http://119.29.159.156:3000; }}server { listen 80; server_name webchat.shanalab.top; location / { proxy_pass http://119.29.159.156:7777; }} ```]]></content>
<categories>
<category>博客开发</category>
</categories>
<tags>
<tag>nginx</tag>
<tag>https</tag>
<tag>反向代理</tag>
</tags>
</entry>
<entry>
<title><![CDATA[《http权威指南》学习笔记]]></title>
<url>%2F2017%2F04%2F19%2F%E3%80%8Ahttp%E6%9D%83%E5%A8%81%E6%8C%87%E5%8D%97%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%2F</url>
<content type="text"><![CDATA[前言关于《http权威指南》,很早以前就想抱着啃了,由于时间段太零碎了,一直没能好好看过。近来公司业务不是很重,同时找暑期实习的时候面试官也聊到了关于http的一些问题,抽空把《http权威指南》前五章看了一下。整理了一些知识点,写成笔记作为备忘的同时也分享给大家。 正文HTTP协议HTTP协议是超文本传输协议(HTTP,HyperText Transfer Protocol)的缩写,简单来讲就是对传输数据的文本格式的一种规范。遵循过HTTP协议的文本称之为报文,按照发送方与接受收方的不同分为两种,客户端发送给服务器端的为请求报文,服务器端发送给客户端的为相应报文。同时HTTP报文是纯文本的形式,不是二进制代码,开发者可以非常方便地进行读写。如下图就是一个GET事务实例中请求报文和响应报文:报文由三部门组成,分别是起始行、首部、主体。 请求报文中的请求起始行: GET /tools.html HTTP/1.0,意思为发起向host主机发起GET请求,请求tools.html文件,使用的是HTTP1.0协议。请求首部意思为,客户端的代理是Mozilla/4.75 [en] (Win98;U),请求的对象(服务端)主机地址为www.joes-hardware.com,同时表明客户端只接受文本格式为html的文本文件,图片格式为git或者jpeg的图片,告诉服务器浏览器支持的语言为en。 响应报文中的请求起始行: HTTP/1.0 200 OK,意思为HTTP/1.0请求响应成功,200是状态码,OK是200状态码对应原因短语。响应首部中,Date对应报文的响应时间,Server是指服务器端的服务器配置,Last-modified代表请求的数据的最后一次的修改日期,Content-length是指响应主体的长度,Content-type代表响应主题的文件格式。响应主体就是客户端所请求的内容。 URLURL是统一资源定位符(Uniform Resource Locator)的缩写,对于URL的认识大多停留在所谓的网址的层面上。实际上URL并不只是应用在http中,ftp://user:password@ftp.prep.ai.edu.cn/pub/gnu也是URL,大多数的URL方案的语法都建立在这个由9部分构成的通用格式上 <scheme>://<user>:<password>@<host>:<port>/path;<params>?<query>#<frag> 但是并非一定会包含上面所有组件。URL中常使用的起始也就三个组件,即方案(scheme)、主机(host)和路径(path),例如http://www.myhost.com/file/test.html,http => 方案,www.myhost.com => 主机,/file/test.html => 路径。当然www.myhost.com并不是我们真正的主机地址,它只是一个域名,通过DNS解析以后会得到主机的真正IP地址。通用组件中各自的对应关系如下表: HTTP、TCP与IP计算机网络协议按照不同分法有不同层数,这里以五层模型举例,HTTP、TCP、IP的关系如下图。 这张图说明了层次关系,但并没有说明他们之间相互的联系,这里我画了一个示意图大致解释一下。 三者关系大致可以通过货物的运送来解释,我需要发送的内容称之为content,可以看作一个物品,在运输货物的时候会对货物进行打包,这层包装我们可以理解为http。物品包装好了肯定需要货车进行运送,而货车可以看作TCP传输协议。货车行驶在高速路上,我们可以把高速路看作IP,而各个收费站我们可以理解为特定的IP地址。 HTTP的方法值得一提的是HTTP的方法并非只有GET与POST,事实上只是因为这两种方法被使用得广泛,导致大家只知道这两种。实际上,HTTP的方法有: GET POST PUT HEAD DELETE TRACE OPTIONS 同时由于HTTP本身是被设计成字段可扩展的,所以新的特性并不会使老的软件失效。而所谓的扩展方法就是指没有在HTTP/1.1规范中定义的方法,而这些方法有助于通过HTTP将web内容发布到web服务器上。 HTTP性能在不考虑的持久连接的情况,我们每次的http请求都会产生一次TCP连接,而每次TCP的连接与关闭都会经历三次握手与四次挥手的过程,当产生大量的http请求的时候,大量的时间会浪费在TCP的连接与关闭上,造成性能损耗,当按最坏的情况考虑的时候甚至会出现端口耗尽的情况。 因为客户端每次连接服务端的时候,都会获得一个新的端口,以实现连接的唯一性,但是由于可用端口有限(一个IP地址的端口可以有65536,其中注册端是口1024到49151,分配给用户进程或应用程序。这些进程主要是用户选择安装的一些应用程序,而不是已经分配好了公认端口的常用程序。这些端口在没有被服务器资源占用的时候,可以用用户端动态选用为源端口)。同时TCP端点在关闭TCP连接的时候,会在内存中维护一个小的控制块,用来记录最近关闭连接的IP地址后端口号,而在这段时间内,该端口号无法被重用。一旦TCP的连接频率超过了某个值,会出现无法连接的问题,也就是TIMEM_WAIT端口耗尽。 当然,使用持久连接,可以减少端口耗尽出现的可能,但是对于持久连接也应该进行合理的维护,当不再使用的时候应该关闭,避免长时间的无意义占用,否则长时间的堆积依然可能会出现端口耗尽的状况。 同时即使没有遇到端口耗尽的问题,也要特别小心有大量连接打开或为处于等待状态的连接分配了大量控制块的情况。因为在有大量打开连接或控制块的情况下,有些操作系统的速度会严重减缓。 《HTTP权威指南下载连接》CSDN免费下载-《HTTP权威指南》]]></content>
<categories>
<category>学习笔记</category>
<category>http</category>
</categories>
<tags>
<tag>http</tag>
<tag>tcp</tag>
</tags>
</entry>
<entry>
<title><![CDATA[react仿追书神器——哦豁阅读器]]></title>
<url>%2F2017%2F04%2F11%2Freact%E4%BB%BF%E8%BF%BD%E4%B9%A6%E7%A5%9E%E5%99%A8%E2%80%94%E2%80%94%E5%93%A6%E8%B1%81%E9%98%85%E8%AF%BB%E5%99%A8%2F</url>
<content type="text"><![CDATA[前言都知道追书神器从某个版本开始就不支持换源了,开始实行收费制度,虽然老版追书神器依然可以使用,但是指不定那天就挂掉了。再加上最近想熟悉一下react,所以本项目哦豁阅读器就诞生了。 Github项目地址:https://github.com/ShanaMaid/oho-reader 欢迎issue,pr,star or follow!我将继续开源更多有趣的项目 推荐一个之前用Vue全家桶写的 网易云音乐PC端 web版本 哦豁阅读器(oho-reader)介绍哦豁阅读器!API源自追书神器,免费使用! 实现追书神器核心功能,做到小说阅读的极简体验,把每一分流量都用到刀刃上! 服务器带宽较小,初次加载比较慢,请谅解!建议clone到本地进行体验! Github项目地址:https://github.com/ShanaMaid/oho-reader 在线地址见Github项目地址的README.md中 Oho阅读器的优势 oho阅读器 追书神器 收费 免费 部分章节免费,其余收费 广告 绿色无广告 定时刷广告 体积 4MB 16.2MB 章节大小 每章5kb左右 掺杂广告,大于5kb oho阅读器初次打开时候加载比较慢,一部分原因是服务器带宽较小,另一部分是因为初次需要下载700kb左右的文件,建议初次下载在wifi下进行。初次下载后oho阅读器会自动进行缓存,以后每次打开页面基本是秒开,消耗流量约在1KB不到。 同时oho器抛弃所有与小说阅读无关的信息,真正做到极简!保证每一分流量都用到小说内容的阅读上,真正做到每章内容加载所用的流量集中在小说章节内容上,视章节字数而定,一般在5kb左右。 oho阅读器目前由于服务器配置、带宽过小原因暂不支持章节内容缓存。 效果Gif图 实现功能 小说搜索 小说详情 小说换源 小说阅读 阅读字体大小变化 阅读背景色变化 阅读设置本地缓存 阅读进度本地缓存 搜索历史本地缓存 使用1234567891011121314151617git clone https://github.com/ShanaMaid/oho-reader.gitcd oho-readernpm install # 开发环境npm run serve访问 http://localhost:8080/# 打包npm run dist# 实际环境cd servernode app.js访问 http://localhost:3001/ 目录结构12345678910111213141516171819202122||—— api 追书神器API说明 |—— cfg webpack配置|—— dist 服务端| |—— app.js 服务端启动入口文件| |—— assets 打包后的资源文件| |—— static 静态资源| |__ index.html 网页入口||——src 资源文件| |—— images 图片资源| |—— components 组件库| |—— method 一些自定义方法,目前是过滤器| |—— filters 自定义过滤器| |—— redux | | |—— action| | |—— reducer| | |__ store| |—— router 路由管理| |—— styles 样式文件| |__ index.jsx 入口|_________________________________________________ 一些注意事项项目中使用追书神器的接口,需要使用http-proxy-middleware进行转发,开发环境下需要在cfg/base.js中的dev中添加下列配置即可123456789101112proxy: { '/api': { target: 'http://api.zhuishushenqi.com/', pathRewrite: {'^/api' : '/'}, changeOrigin: true }, '/chapter': { target: 'http://chapter2.zhuishushenqi.com/', pathRewrite: {'^/chapter' : '/chapter'}, changeOrigin: true }} 实际环境中,服务器端配置123456789101112131415161718192021222324var express = require('express');var proxy = require('http-proxy-middleware');var app = express();app.use('/static', express.static('static'));app.use('/assets', express.static('assets'));app.use('/api', proxy({ target: 'http://api.zhuishushenqi.com/', pathRewrite: {'^/api' : '/'}, changeOrigin: true}));app.use('/chapter', proxy({ target: 'http://chapter2.zhuishushenqi.com/', pathRewrite: {'^/chapter' : '/chapter'}, changeOrigin: true}));app.get('/*', function (req, res) { res.sendFile(__dirname + '/index.html');});app.listen(3001); 支持BUG提交请发送邮箱: uestczeng@gmail.com Github项目地址:https://github.com/ShanaMaid/oho-reader 欢迎issue,pr,star or follow!我将继续开源更多有趣的项目 你的支持将有助于项目维护以及提高用户体验,感谢各位的支持! 后续计划过段时间计划把oho-reader迁移到react-native,具体时间可能要看什么时候有空了。 总结最近粗略使用了一下vue与react,大致感觉就是前者是在html里面写js,后者是在js里面写html,就目前来看两者现在基本上是势均力敌、各有千秋,未来的具体走向如何谁都说不准,当然这是我的个人见解。如果你有什么好的建议或者说值得探讨的话题,可以在下方留言。]]></content>
<categories>
<category>实战开发</category>
<category>React</category>
</categories>
<tags>
<tag>javascript</tag>
<tag>express</tag>
<tag>React</tag>
</tags>
</entry>
<entry>
<title><![CDATA[vue仿163musicPC端]]></title>
<url>%2F2017%2F03%2F21%2Fvue%E4%BB%BF163musicPC%E7%AB%AF%2F</url>
<content type="text"><![CDATA[前言vue2越来越受欢迎,无奈现在在公司做的平台是以ng1.x为主,一直没有机会练手vue2,虽然写过一些小demo,但是与完整的项目相比较中间会少很多东西。于是趁在公司空闲的时候以及周末双休,自己用vue2复写了163musicPC端。相比较之掘金上大大写的很多都是纯静态页面vue2,实际开发中肯定会涉及到接口、数据渲染方面,本项目接口通过http-proxy-middleware, 一个http代理的中间件,进行http请求转发,实现跨域请求,直接复用网易爸爸的接口,在服务端对返回的JSON进行解构即可。 介绍vue-163-music(网易云音乐web版),用vue仿写163音乐客户端版。 原计划仿写完所有页面,碍于网易的接口API有限,实现页面也有限。 不推荐手机端访问。 页面高度为670px,1366 X 768分辨率及其以下按F11全屏浏览效果更佳 Github项目地址:https://github.com/ShanaMaid/vue-163-music 在线地址见Github项目地址的README.md中 欢迎issue,pr,star or follow!我将继续开源更多有趣的项目 使用1234567891011121314151617git clone https://github.com/ShanaMaid/vue-163-music.gitcd vue-163-musicnpm install # 开发环境npm run dev访问 http://localhost:8080/# 打包npm run build# 实际环境cd servernode app.js访问 http://localhost:3000/ 效果截图 工具&技能vue + vuex+ vue-router + vue-resource express http-proxy-middleware 一个http代理的中间件,进行http请求转发,实现跨域请求 store.js 一个非常棒的处理localStorage的轮子,原生localStorage只支持存储字符串类型,轮子进行封装后可以直接存储Array、Object、function、Set等类型 animate.css css动画库 vue-slider-component 滑块组件 postman 接口测试工具 实现功能发现音乐 个性推荐(推荐歌单中除每日歌曲推荐外,其余歌单可点击进入) 播放音乐 上一曲 播放 暂停 下一曲 进度控制 音量控制 音乐搜索输入搜索关键词,回车键搜索,或者点击放大镜图标 单曲(单击或双击歌曲添加至音乐播放列表,部分音乐会存在版权问题无法播放) 歌手 专辑 MV 歌单(左键点击进入歌单列表) 主播电台 (单期节目部分单击或双击歌曲添加至音乐播放列表,目前不存在版权问题) 用户 歌单 播放全部 播放列表 切歌(单击切歌) 删歌(鼠标悬浮在要删除的歌曲上,点击右侧小X) 清空播放列表 本地缓存播放列表 一些问题通过api接口获取的mv播放量基本不准,尚未找到原因,其余类型的播放量准确 目录结构12345678910111213141516171819||—— build |—— config|—— server 服务端| |—— app.js 服务端启动入口文件| |—— static 打包后的资源文件| |__ index.html 网页入口||——src 资源文件| |—— assets 组件静态资源库| |—— components 组件库| |—— deal 163api返回的JSON字符串解构| |—— filters 自定义过滤器| |—— router 路由配置| |—— store vuex状态管理| |—— App.vue 163SPA| |__ main.js SPA入口||__ static 静态资源目录 一些注意事项项目中使用了网易爸爸的接口,需要使用http-proxy-middleware进行转发,开发环境下需要在config/index.js中的dev中添加下列配置即可123456789proxyTable: { '/api': { target: 'http://music.163.com', changeOrigin: true, headers: { Referer: 'http://music.163.com/' } }} 实际环境中,服务器端配置123456789101112131415161718var express = require('express');var proxy = require('http-proxy-middleware');var app = express();app.use('/static', express.static('static'));app.use('/api', proxy({ target: 'http://music.163.com', changeOrigin: true, headers: { Referer: 'http://music.163.com/' }}));app.get('/', function (req, res) { res.sendFile(__dirname + '/index.html');});app.listen(3000); 对返回的数据解构js文件位于src/components/deal/目录下,比如对单曲搜索结果进行解构123456789101112131415161718192021222324single: (data) => { let list = [] let count = data.result.songCount if (count === 0) { return {list, count} } for (let item of data.result.songs) { let singer = '' let { name, mp3Url, duration, id, album: { name: albumName } } = item for (let item of item.artists) { singer += item.name + ' ' } list.push({name, mp3Url, duration, id, albumName, singer}) } return {list, count}} vuex状态管理位于src/components/store目录下 vue-router路由配置管理位于src/components/router目录下 自定义过滤器位于src/components/filters/目录下 网易云音乐接口来源于http://moonlib.com/606.html Github项目地址:https://github.com/ShanaMaid/vue-163-music 总结本项目的实际意义在于熟悉vue生态链以及ES6语法,同时思考如何用vue构建实现一个完整的项目。 最后的感觉vue2在开发中带来的体验确实很棒,vue2能如此之火自然有它的道理。 如果觉得本项目不错的话,别忘记star哦!]]></content>
<categories>
<category>实战开发</category>
<category>vue</category>
</categories>
<tags>
<tag>vue</tag>
<tag>javascript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JavaScript数组的思考]]></title>
<url>%2F2017%2F03%2F13%2FJavaScript%E6%95%B0%E7%BB%84%E7%9A%84%E6%80%9D%E8%80%83%2F</url>
<content type="text"><![CDATA[昨晚睡觉前刷掘金看到一道面试题,由此引发了一系列的拓展与思考。 面试题不使用loop循环,创建一个长度为100的数组,并且每个元素的值等于它的下标 以下是我的一些解决方案 12345678910111213141516171819Array.from(Array(100).keys())[...Array(100).keys()]Object.keys(Array(100))Array.prototype.recursion = function(length) { if (this.length === length) { return this; } this.push(this.length); this.recursion(length);}arr = []arr.recursion(100)Array(100).map(function (val, index) { return index;}) 当然还有一种比较作死的方法1var arr = [0, 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] 用了这种方法你也可能被考官打死!但是确实也没用loop循环。 问题与思考问题将上述方法挨个在控制台上跑了一遍后发现问题了Object.keys(Array(100))的结果为[],即空数组 map方法的结果为[undefined × 100] 思考初步猜测与undefined有关,进行尝试12345678arr = [];arr[10] = 1;arr[20] = 2;Object.keys(arr) //["10", "20"];arr.map(function (val, index) { return index;}); // [undefined × 10, 10, undefined × 9, 20] 可以看到显示的是undefined x 10,这种显示代表的是数组中未初始化的数量,而并非说是10个undefined的值。也就是说下面式子并不等价,下文会提到前者是稀疏数组,后者是密集数组。123Array(5) // [undefined × 5][undefined, undefined, undefined, undefined, undefined] 也就是说利用Array()构造函数创建的数组其实是下面这样的,两者之间是等价的。123Array(5)[,,,,,] 问题很明显了,是因为Object.keys()与map跳过了数组中空的部分,换句话说,也就是数组中没有初始化的部分均会被跳过。发现问题后就很好解决了,只需要利用es6中的fill对数组初始化就可以了。123456Object.keys(Array(100).fill(0))Array(100).fill(0).map(function (val, index) { return index;}) 拓展问题解决后,发现一个问题,以上答案除了递归外均用到了ES6的方法,那么在ES5下怎么解决?查阅相关资料发现了两个词汇稀疏数组与密集数组。简要概括大概意思就是: 稀疏数组:不连续的数组 密集数组:连续的数组 12345sparse = [] //稀疏sparse[1] = 1sparse[10] = 10dense = [1, 2, 3, 4, 5] //密集 创建密集数组的关键在于我们要对数组中的每个index对应的value进行赋值比如这样1Array(5).join().split(',') // ["", "", "", "", ""] 这样创建的数组值为'',而非空,比较推荐这种。 还可以使用apply,比如创建长度为5的密集数组Array.apply(null, Array(5)) 实际等价为Array(undefined,undefined,undefined,undefined,undefined)。 如果考虑ES6的话,我们还可以这样创建密集数组123Array.from({length:5})Array(5).fill(undefined) 虽然数组的值是undefined,但是却是密集数组,换句话来说就是稀疏与密集数组与数组的值没有任何关系,在于数组中的每个值是否被初始化。 12345678910111213sparse = []sparse[1] = 1sparse[10] = 10for (let index in sparse) { console.log('sparse:' + 'index=' + index + ' value=' + sparse[index])}dense = Array.apply(null, Array(5))for (let index in dense) { console.log('dense:' + 'index=' + index + ' value=' + dense[index])}sparse[0] === dense[0] 结果为123456789sparse:index=1 value=1sparse:index=10 value=10dense:index=0 value=undefineddense:index=1 value=undefineddense:index=2 value=undefineddense:index=3 value=undefineddense:index=4 value=undefinedtrue 可以看到dense中的value为undefined,但是并没有被for...in忽略,并且sparse[0] === dense[0]的结果为true说明两者之间的undefined并没有什么区别,唯一的区别是sparse的undefined是代表空(未初始化),dense的undefined是我们赋值的(已初始化)。 换句话来说,虽然我们赋值是undefined,但是由于我们进行了这步赋值操作,js就认为数组已经初始化了,从而不会被for...in跳过。 由此可知js中数组相关的方法对于空位(稀疏数组和密集数组)的处理方式是不同,这里在阮老师的ES6中关于数组的空位中找到了分析。 ES5对空位的处理,已经很不一致了,大多数情况下会忽略空位。 forEach(), filter(), every() 和some()都会跳过空位。 map()会跳过空位,但会保留这个值 join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。 ES6则是明确将空位转为undefined Array.from方法会将数组的空位,转为undefined,也就是说,这个方法不会忽略空位。 扩展运算符(…)也会将空位转为undefined。 copyWithin()会连空位一起拷贝。 fill()会将空位视为正常的数组位置。 for…of循环也会遍历空位。 entries()、keys()、values()、find()和findIndex()会将空位处理成undefined 总之由于对数组的空位的处理规则非常不统一,所以建议避免出现空位。 实际上,typeof Array()我们可以发现结果是object,js中的数组就是一个特殊的对象,换句话来说,js中的数组不是传统意义上的数组。12345678910111213arr = []arr.length // 0arr[0] = 1arr.length // 1arr[100] = 100arr.length // 101arr[100] === arr['100'] // truearr['a'] = 1arr.length // 101 通过上述代码我们可以发现,js的数组中的数字索引其实是字符串形式的数字,同时当我们为数组赋值的时候,如果数值索引的index超过数组原有的长度,数组的长度会自动扩充为index + 1,同时中间的空隙会自动填充undefined,此时数组会变为稀疏数组。当index不为数值的时候,数值依然会被填充进去,但此时length不会发现变化。 数组插值会引起length变化需要满足两个条件 isNaN(parseInt(index,10)) === false index >= length 继续思考,当数组的长度改变的时候,数组中的值是什么情况呢?1234567arr = [1, 2, 3, 4, 5, 6, 7]arr['test'] = 1console.log(arr) // [1, 2, 3, 4, 5, 6, 7, test: 1]arr.length = 1console.log(arr) // [1, test: 1] 可以看到,如果改变后的length如果小于原来的length,凡是满足index(包括字符串形式的数字,在数组索引中两者等价)大于等于length的全部会被删除,即凡是同时满足下列2个条件的都会被删除 isNaN(parseInt(index,10)) === false index >= length 总结面试题不使用loop循环,创建一个长度为100的数组,并且每个元素的值等于它的下标 答案1234567891011121314151617181920212223Array.from(Array(100).keys())[...Array(100).keys()]Object.keys(Array(100).join().split(','))Object.keys(Array(100).fill(undefined))Object.keys(Array.apply(null,{length:100}))Array.prototype.recursion = function(length) { if (this.length === length) { return this; } this.push(this.length); this.recursion(length);}arr = []arr.recursion(100)Array(100).fill(0).map(function (val, index) { return index;}) 后来仔细想了想,答案中依然存在部分问题,使用了Object.keys()的结果数组中的值为字符串形式的数字,map在MDN上Array.prototype.map()的polyfill中的代码来看也是使用了循环。但是出题人的意思应该是指不使用for...in、for...of、for、while循环吧。 总之我认为最稳妥的答案是以下几个1234567891011121314Array.from(Array(100).keys())[...Array(100).keys()]Array.prototype.recursion = function(length) { if (this.length === length) { return this; } this.push(this.length); this.recursion(length);}arr = []arr.recursion(100)]]></content>
<categories>
<category>学习笔记</category>
<category>JavaScript</category>
</categories>
<tags>
<tag>JavaScript</tag>
<tag>学习笔记</tag>
</tags>
</entry>
<entry>
<title><![CDATA[vue仿PC端163music图片滚动组件]]></title>
<url>%2F2017%2F03%2F03%2Fvue%E4%BB%BFPC%E7%AB%AF163music%E5%9B%BE%E7%89%87%E6%BB%9A%E5%8A%A8%E7%BB%84%E4%BB%B6%2F</url>
<content type="text"><![CDATA[介绍这是一款模仿PC端网易云音乐的vue图片滚动插件 Github项目地址-vue-image-scroll 在线文档和demo 欢迎各位dalao指点,star or PR! 安装与使用安装1npm install vue-image-scroll 使用123456789101112131415161718192021 <template> <div> <slider v-bind="setting"> </div> </template><script>import slider from 'vue-image-scroll';export default { components: { slider }, data: function() { return { setting: { image: ['1.jpg', '2.jpg', '3.jpg'] } } }}</script> 本地调试12345git clone https://github.com/ShanaMaid/vue-image-scroll.gitnpm install npm run dev 说明项目使用vue-cli开发,源文件在src/components/Slider.vue中,lib中的index.js为压缩后的文件 开发遇到的问题打包组件的时候遇到,loader error的问题,发现webpack2.0开始loaders中babel以及url等都需要写作babel-loader、url-loader]]></content>
<categories>
<category>实战开发</category>
<category>vue</category>
</categories>
<tags>
<tag>vue</tag>
<tag>javascript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[200行代码实现web在线聊天室]]></title>
<url>%2F2017%2F02%2F14%2F200%E8%A1%8C%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0web%E5%9C%A8%E7%BA%BF%E8%81%8A%E5%A4%A9%E5%AE%A4%2F</url>
<content type="text"><![CDATA[项目介绍基于websocket的一个简单的聊天室技术栈 express+socket.io+animate.css+angular关于websocket不了解的点我,而socket.io是对websocket进行封装,提供通用接口。Github项目地址 安装与使用1234567git clone https://github.com/ShanaMaid/websocket-express-webchat.git #下载项目npm install #安装依赖node app.js #启动服务访问 http://localhost/ #进入聊天室 功能 进入房间通知 离开房间通知 消息接收与发送 在线列表 服务器端信息备份 动态GIF 实现思路利用on绑定事件,emit触发绑定事件,服务器端与客户端进行交互12socket.on(eventName,callBack) #绑定事件,eventName可以自定义socket.emit(eventName,data) #触发事件,发送data 更多关于socket.io的详细信息请移步官方文档,点我。 服务端首先添加我们需要使用的模块123var express = require('express');var socket = require('socket.io');var fs = require('fs'); 然后从配置文件config.json读取一些我们需要的关于聊天室的一些配置信息,关于配置文件1234var history_num = config.history_num ; //服务器缓存的历史消息条数var port = config.sever_port; //端口号var backup = config.backup; //是否开启备份var backup_filename = config.backup_filename; //备份文件名字 定义person与history两个数组,用于存储在线人员的名称与服务端缓存的消息条数(即用户第一次进入聊天室推送的历史消息条数)12var person = [];//记录在线情况var history = [];//需要缓存的消息 监听port端口号,创建一个socket对象io123var app = express();var server = app.listen(port);var io = new socket(server); 编写connection事件,响应客户端的连接请求,返回客户端socket,编写客户端要emit的事件…………………………表示代码省略部分,完整代码点击Github项目地址12345678910111213141516171819202122232425262728io.on('connection', (socket) => { ………………………… socket.emit('history',history); #发送服务器记录的历史消息 io.sockets.emit('updatePerson', person); #进行广播,触发所有客户端的updatePerson事件,更新在线列表人员 ………………………… socket.on('sendMsg', (data) => { #发送消息事件 ………………………… io.sockets.emit('news',obj); #进行广播,触发所有客户端的news事件,更新信息。 }); socket.on('setUserName',(data) => { #设定用户名事件 ………………………… io.sockets.emit('updatePerson',person); #进行广播,触发所有客户端的updatePerson事件,更新在线列表人员 io.sockets.emit('news',{content:user+'进入房间',time:Now(),name:'系统消息'}); #进行广播,触发所有客户端的news事件,通知进入房间 ………………………… }); socket.on('disconnect', (socket) => { #掉线事件 if(user!='') { person.forEach((value,index)=>{ if (value===user) { person.splice(index,1); } }); io.sockets.emit('news', {content: user + '离开房间', time: Now(), name: '系统消息'}); io.sockets.emit('updatePerson', person); } });}); 客户端变量初始化123456$scope.data = []; #接收-消息队列$scope.name = ''; #用户名$scope.content = ''; #发送信息内容$scope.personnum = 0; #在线人数$scope.personlist = []; #在线人员列表$scope.flag = false; #是否取名 创建客户端socket12const socket_url = 'http://localhost';var socket = io(socket_url); 完成服务器端的emit的事件1234567891011socket.on('news', (data) => { ……………………});socket.on('history', (data) => { ……………………});socket.on('updatePerson', (data) => { ……………………}); config.json配置文件123456{ "history_num":20, #服务器缓存的历史信息条数 "sever_port":80, #服务器监听端口号 "backup":true, #是否开启服务端信息备份 "backup_filename":"./backup/example.json" #备份文件名字} 聊天信息备份聊天信息以json格式存储在example.json文件中!1234567891011121314151617function backupMsg(filename,obj) { var backup_file = fs.readFileSync(backup_filename); var msg= backup_file!='' ? JSON.parse(backup_file) : []; msg.push(obj); var str = '[\n' msg.forEach((value,index) =>{ if (index!==0){ str+=',\n'; } str += ' {\n "name":"'+value.name+'",\n "time":"'+value.time+'",\n "content":"'+value.content+'"\n }' } ); str += '\n]'; fs.writeFile(filename, str, (err) => { if(err) console.log("fail write :" + arr + " "+Date() + "\n error:"+err); });} 备份信息示例1234567891011121314151617[ { "name":"测试人员1", "time":"2017-2-13 23:32:17", "content":"一条简单的测试信息" }, { "name":"测试人2", "time":"2017-2-13 23:33:42", "content":"那你很棒哦" }, { "name":"测试人3", "time":"2017-2-13 23:33:54", "content":"肯定很棒哦" }] 总结websocket实现了浏览器与服务器全双工通信。举个栗子,原来是这样的轮询机制:123456789客户端:服务器,你有没有消息要给我啊?服务器:有。客户端:服务器,你有没有消息要给我啊?服务器:没有。——————————无限重复————————————客户端:服务器,你有没有消息要给我啊?服务器:没有。客户端:服务器,你有没有消息要给我啊?服务器:你烦不烦啊~ 而现在的websocket:12345客户端:服务器,你有我的消息了记得call我。服务器:OK!——————————当有消息的时候————————服务器:有你的消息了,客户端。客户端:收到。 ##后记 聊天室在线人员显示错误,多人离线时会出现在线列表混乱。目前已修复 聊天室历史加载记录,存在错误,已修复! Github项目地址,欢迎各位交流学习!]]></content>
<categories>
<category>实战开发</category>
<category>web</category>
</categories>
<tags>
<tag>nodejs</tag>
<tag>express</tag>
<tag>angular</tag>
</tags>
</entry>
<entry>
<title><![CDATA[微信小程序2048开发记录]]></title>
<url>%2F2017%2F02%2F06%2F%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F2048%E5%BC%80%E5%8F%91%E8%AE%B0%E5%BD%95%2F</url>
<content type="text"><![CDATA[前言距离微信小程序的兴起已经过去一段较长时间了,之前一直想尝试着写一个小demo玩玩,但是由于学校的各种事情以及准备面试的事情一直耽误。趁着公司放年假这段时间完成了对2048的复写,主体功能已经基本实现,还有一些细节地方待优化。 对于微信小程序的介绍1 微信小程序结构分析每个页面的基本构成依然是html、css、js,但是格式后缀上有些许变换,html与js不变,但是css=>wxss。同时每个项目都有三个关键的文件, app.js、app.json、app.wxss。 以下是官方对于这三个文件的介绍 app.js是小程序的脚本代码。我们可以在这个文件中监听并处理小程序的生命周期函数、声明全局变量,调用框架提供的丰富的 API。 app.wxss 是整个小程序的公共样式表。我们可以在页面组件的 class 属性上直接使用 app.wxss 中声明的样式规则。 app.json 是对整个小程序的全局配置。我们可以在这个文件中配置小程序是由哪些页面组成,配置小程序的窗口背景色,配置导航条样式,配置默认标题。注意该文件不可添加任何注释。 更多信息请阅读官方文档 同时微信小程序采用数据绑定的方式,刷新视图中的数据使用.setData即可。 2 丰富的组件与API微信小程序开发中,官方提供了很多组件与API,足够应用大多数场景下的开发,比如手机应用中最常见的触点坐标的获取,bindtouchstart、bindtouchend、bindtouchmove等。 2048开发1 项目介绍项目成果图 2 开发过程2048作为一个非常简单的小程序,首先是利用长度为4X4=16的item一维数组来储存积分块的积分,若对应位置不存在积分则置’’。核心主要是对积分块处理的算法,这里我使用的算法比较粗陋,一共是对四种手势的处理,这里以向上的手势作为例子讲解。如下图所示,手势向上的时候,我们的目标是将所有积分块朝上移动,移动在图中黑线处,然后对列上相邻的等值积分块进行合并。我处理方法是先将所有积分块朝上移动,在列上将所有方块紧密挨在一起,这一步成为整理积分块的位置。然后再对等值相邻积分块进行合并,将合并后出现的空缺位进行置空。再对所有积分块进行整理。比如上图中,向上滑动后,先是对积分块进行整理,得到如下图然后积分块合并,合并后出现的置空位如图。再次进行积分块整理就得到了最终的结果。 3 算法讲解向上手势对积分块处理的代码1234567891011121314151617181920212223function up() { //整理积分块位置 for (let i = 4; i < item.length; i++) if (item[i] != '') for (let j = i - 4; j >= 0; j -= 4) item[j] == '' ? (item[j] = item[j + 4]) && (item[j + 4] = '') : -1 //合并积分块 for (let i = 0; i < 4; i++) for (let j = 0; j < 4; j++) { if (item[i + j * 4] == '' || item[i + (j + 1) * 4] == '') break; if (item[i + j * 4] == item[i + (j + 1) * 4]) { item[i + j * 4] += item[i + (j + 1) * 4] item[i + (j + 1) * 4] = '' j++ } } //再次整理积分块位置 for (let i = 4; i < item.length; i++) if (item[i] != '') for (let j = i - 4; j >= 0; j -= 4) item[j] == '' ? (item[j] = item[j + 4]) && (item[j + 4] = '') : -1} 算法的整理思路就是对积分块进行遍历,如果出现不为空的积分块i,那么对i-4(因此遍历是从i=4,即第二行开始),即积分块的上方的积分块进行冒泡,判定上方积分块是否为空,若为空,交换积分块,继续向上冒泡,直到冒泡到第一行。然后继续寻找下一个不为空的积分块,一直到遍历完整个数组。 合并积分块方面,大概思路和整理积分块位置差不多,只不过不是判定积分块是否为空,而是判定相邻积分块是否值相等。 4 关于算法优化 不为空积分块的位置可以用一个数组来存储,避免每次进行整个数组的遍历 整理积分块位置和合并积分块同时进行,减少一次整理积分块的消耗的时间 项目地址github wx-2048欢迎各位看官star!]]></content>
<categories>
<category>实战开发</category>
<category>web</category>
</categories>
<tags>
<tag>微信小程序</tag>
</tags>
</entry>
<entry>
<title><![CDATA[我的web前端面试经历————百度]]></title>
<url>%2F2016%2F12%2F05%2F%E6%88%91%E7%9A%84web%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E7%BB%8F%E5%8E%86%2F</url>
<content type="text"><![CDATA[写在前面的话博主就读于电子科技大学,大三狗一枚!面试是个漫长的过程,从海投到收获电话面试,一面、二面、三面,一个步骤出错那么后面就宣告终结。同时,面试过程中你也可能会遇到一些面试官的刁难,甚至部分面试官会说些比较打击你的话,但是大部分面试官都是很棒的! 面试前的准备基础知识必须有牢固的基础知识,足够丰富的项目经历(就我而言差不多是三个完整项目经历,时间累计差不多接近一年)。至少这上面的面试题你能全答出来,说得足够清楚!web前端面试宝典1web前端面试宝典2 表述能力表述能力,你要能把你的答案给面试官描述清楚,注意专业词汇,这将大大提高面试官对你的印象分! 简历简历尽量一页,不要超过两页。简历内容要直奔主题,姓名、电话、邮箱、学校、项目经历!兴趣爱好之类的大部分面试官会视为垃圾信息直接过滤掉,如果你Github有什么star很多的项目千万记得贴上,这点加分非常高!如果你有自己的博客,博客上有一些含金量较高的文章的话也记得贴上自己的博客。当然最重要的一块肯定是你掌握了哪些技术,但是千万不要用网上现在比较火的进度条去表示你对技术的掌握情况,这是非常愚蠢的行为,到底什么算掌握、熟悉、精通?简历内容,总结一下如下 姓名、电话、邮箱、学校等必要信息 项目经历(注意表明使用的技术栈,自己的工作) Github高星项目 个人博客 个人技术介绍 投递简历 拉勾网 实习僧 企业的校招网站就我自身而言,投递出了差不多40份简历,最后接到了7个电话面试,4个进入二面,3个进入三面,3个拿到offer!基本上进入三面以后都比较稳了,当然不排除竞争比较激烈的时候三面刷人! 关于面试是否通过与等待时间问题首先你需要注意的一点是,电话面试如果没通过的话是肯定不会打电话通知你的。如果你电话面试通过了的话,3天之内是一般是会安排下次电话面试,直接联系你的,注意星期六星期天是不计入时间的。 面试流程一面最凶残,最可怕的一个环节,大部分人在这里直接被刷掉。一面会问很多基础的问题,但往往就是这些基础问题导致很多人直接被刷掉,所以打好基础尤为重要。基础问题详情请参照 web前端面试宝典1 web前端面试宝典2举个例子,以下几个的异同。1234line-height:15px;line-height:150%;line-height:1.5;line-height:1.5em; 面试流程 自我介绍(用最简单的语言表明自己最大的优势) HTML基础 CSS基础 JS基础 你有什么想问的?就我自己面试经历来看,各大公司都特别重视原生JS。同时一面中基本不会涉及到框架的问题。 二面二面问的问题就很深入了,会针对你的项目进行深入剖析,对你简历上的技术进行深入追问,看你是否具有真才实干。 面试流程 自我介绍 完整概述一个你感觉最你自己做过最棒的项目 针对技术进行深入探讨 你有什么想问的? 三面能来到这一步基本上非常稳了,而且这个时候你的面试官基本上是你以后进公司的顶头上司了。同时三面的气氛就比较轻松了,当然也会问你一些技术方面的问题。一般三面过没过自己都能根据最后面试官的口气感觉出来。 面试流程 自我介绍 部分技术问题 一些关于公司的介绍 你有什么想问的? HR发offer一般三面完了,三天内会有HR联系你,询问你的一些情况,比如本科在读还是研究生在读,然后给你说一下待遇,多少钱一天啊,什么餐补,住房补助等等之类的。了解清楚后一般2天内会把offer发到你的邮箱!此刻大功告成,准备进入新公司吧! 我的一次完整面试经历————百度一面主要还是问web的一些基础问题,有准备的话通过还是比较容易的。我整理了一下问题大概是这些: css盒子模型 页面加载如何优化 url->页面加载完成的整个流程 优雅降级与渐进增强 xhtml是什么 ajax的优缺点 js组成部分 解释一下变量声明提升 如何跨域访问 js如何判断一个数组 阐述一下js严格模式还有一些其余的问题记不清了,最后面试官问我有什么问题要提的,我问了下部门的技术栈、技术沙龙之类的。最后,礼貌地说了一句:“感谢面试官百忙之中抽空来面试我,这次面试学到了很多,希望贵公司能给我一个接触前沿技术、锻炼自身的机会,谢谢面试官!” 二面二面的面试官首先还是问了一下技术问题。 负载均衡你了解吗?阐述一下 linux环境你熟悉吗?说一些你用过的指令 webpack了解吗?用过哪些功能 对css预编译器有所了解吗?还有些记不清了,大多数时间是在问项目的问题: 完整概述一个你感觉最你自己做过最棒的项目然后,面试官会根据你的回答针对性地提一下问题,举个例子: 你在这个项目中用到了express,那你能说说express的特点是什么吗? 有考虑过如何对项目进行优化吗?从那方面入手?最后依然是国际惯例,我问了下部门的技术栈、技术沙龙之类的,礼貌说了下感谢的话! 三面三面就比较轻松了,面试官会跟你了一些公司文化之类的东西,见招拆招吧,好好表现,没什么重大问题基本上就过了。 总结面试=技术+运气+礼貌!个人认为 礼貌>技术>运气,一个没有礼貌的codder估计没面试官欢迎吧,毕竟他以后是你的同事,肯定希望是个好相处的人。总之,注重礼节,但是技术也不可缺少哦,最后运气也是有的,也许你当天遇到的面试官心情不好,刁难你也说不一定哦,但是如果能把你刁难到证明自己的技术确实有不足之处,需要加油改进哦! 后话博主最后去了百度某部门,想象这一个月的面试,收获颇丰,面试过程中长了不少姿势,最后感谢在前端路上遇到的每一位dalao,感谢各位的指点与帮助!]]></content>
<categories>
<category>杂谈</category>
</categories>
<tags>
<tag>面试</tag>
</tags>
</entry>
<entry>
<title><![CDATA[web前端面试问题总结]]></title>
<url>%2F2016%2F11%2F25%2Fweb%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93%2F</url>
<content type="text"><![CDATA[面试2016年11月25日,参加2次百度远程电话面试,面试后深感自己web基础不够深,面试中暴漏出许多做项目中遗漏的问题,同时由于自身过于紧张,很多问题都没回答完全甚至有些明明知道答案却不知道考官问的问题指的是什么。特此写下笔记总结,鞭策提醒自己。 HTMLHTML5中新增的语义化元素有哪些? article 定义文章 header 页眉 footer 页脚 nav 定义导航链接 section 定义文档中的节(section、区段)。比如章节、页眉、页脚或文档中的其他部分 XHTML与HTML的区别HTML是一种基于web的网页设计语言,XHTML是一个基于XML的置标语言,类似HTML的XML,是一种过渡技术,对代码的书写要求更为严谨,主要是以下几点 XML要求标签正确嵌套 所有元素必须正确关闭 标签区分大小写 属性值要使用双引号 id的属性值去替代name属性值 特殊字符需要进行处理 必须要有根元素 JavaScript数组的操作方法有哪些?各自什么作用? .join() 数组转字符串,无参数的时候默认以,进行分隔。 .reverse() 数组颠倒排序 .sort() 数组排序,默认字母顺序排,自定排序需传入排序函数 .concat() 数组拼接,可跟多个参数,参数可以数组,也可以是值 .slice() 返回数组片段 .splice() 删除数组片段,第一个参数代表起始下标;第二参数代表删除个数,忽略则代表全删;第三、四…………个代表从删除位置开始要插入的内容 .push() 数组末尾加入指定参数,返回值为数组长度 .pop() 删除数组末尾元素,返回删除数值 .unshift() 数组首部加入指定参数,返回数组长度 .shift() 删除头部元素,返回删除的数值 .toString() 转字符串,以都,隔开,与无参数的.join()相同 .toSource() 只有 Gecko 核心的浏览器(比如 Firefox)支持该方法,说明 ajax的优缺点优点: 无刷新页面,给用户体验好 异步与服务器通信,不打断用户操作,具有更迅速的响应能力 前端和后端负载平衡,AJAX可以把以前一些服务器负担的工作转嫁到客户端,利用客户端闲置的能力来处理,减轻服务器和带宽的负担,节约空间和宽带租用成本。并且减轻服务器的负担,AJAX的原则是“按需取数据”,可以最大程度的减少冗余请求和响应对服务器造成的负担,提升站点性能。 基于标准被广泛支持 界面与应用分离,Ajax使WEB中的界面与应用分离(也可以说是数据与呈现分离),有利于分工合作、减少非技术人员对页面的修改造成的WEB应用程序错误、提高效率、也更加适用于现在的发布系统。 缺点: AJAX干掉了Back和History功能,即对浏览器机制的破坏。 存在安全隐患问题,开发者会暴露一定数据和服务器逻辑,还有Ajax也难以避免一些已知的安全弱点,诸如跨站点脚步攻击、SQL注入攻击和基于Credentials的安全漏洞等等 SEO不友好 违背URL和资源定位的初衷 编写复杂、容易出错 ;冗余代码比较多 ajax工作原理 第一步:创建ajax对象(XMLHttpRequest/ActiveXObject(Microsoft.XMLHttp)) 第二步:判断数据传输方式(GET/POST) 第三步:打开链接 open() 第四步:发送 send()当ajax对象完成第四步(onreadystatechange)数据接收完成,判断http响应状态(status)200-300之间或者304(缓存)执行回调函数注意:检测XMLHttpRequest对象的readyState属性,该属性表示请求/响应过程的当前活动阶段,属性值如下: 0:未初始化。尚未调用open()方法 1:启动。已经调用open()方法,但尚未调用send()方法 2:发送。已经调用send()方法,但尚未接收到响应 3:接收。已经接收到部分响应数据 4: 完成。已经接收到全部响应数据,而且已经可以在客户端使用了(如果写原生的js ajax请求需要等到 readyState==4的时候再做处理)其他的js库已经做好处理了 严格模式的优缺点严格模式————js文件第一行写”use strict”,让js解释器以更严格的方式检查代码优点: 消除一些语法不合理的地方 提高编译效率和运行速度 为新版本做铺垫缺点: 如果js文件一部分用,一部分没用,可能导致无效,浪费字节 IE6、7、8、9不支持阉割模式 DOM操作有哪些?创建 createDocumentFragment() //创建一个DOM片段 createElement() //创建一个具体的元素 createTextNode() //创建一个文本节点 添加、移除、替换、插入 appendChild() removeChild() replaceChild() insertBefore() //在已有的子节点前插入一个新的子节点 查找 getElementsByTagName() //通过标签名称 getElementsByName() //通过元素的Name属性的值(IE容错能力较强,会得到一个数组,其中包括id等于name值的) getElementById() //通过元素Id,唯一性 JS组成 核心(ECMAScript) 描述了该语言的语法和基本对象 文档对象模型(DOM)描述了处理网页内容的方法和接口 浏览器对象模型(BOM)描述了与浏览器进行交互的方法和接口 判断数组 arr instanceof Array Array.isArray() arr.constructor == Array JS值类型和引用类型 数字、字符串、布尔都是值类型,存放在栈中 对象、函数、数组等都是引用类型,存放在堆中Notice: 栈,系统自动分配释放 堆,程序员手动分配释放 变量声明提升javascript的变量声明具有hoisting机制,JavaScript引擎在执行的时候,会把所有变量的声明都提升到当前作用域的最前面。优先级顺序: 1、语言内置:所有的作用域中都有 this 和 arguments 关键字 2、形式参数:函数的参数在函数作用域中都是有效的 3、函数声明:形如function foo() {} 4、变量声明:形如var bar; document load 与 document DOMContentLoaded 区别DOM文档加载步骤: 1、解析HTML结构。 2、加载外部脚本和样式表文件。 3、解析并执行脚本代码。 4、DOM树构建完成。//DOMContentLoaded 5、加载图片等外部文件。 6、页面加载完毕。//load在第4步,会触发DOMContentLoaded事件。在第6步,触发load事件。事件DOMContentLoaded和load的区别 CSS书写高效CSS 多利用padding、margin、font等缩写属性 颜色进制代码可以用缩写 #fffff可以缩写为#fff css选择器解析式从右向左,注意合理利用选择器 其他GET与POST的区别 浅谈HTTP中Get与Post的区别,说的很清楚简要总结大概就是: GET请求可以被缓存,POST不能 GET请求保留在浏览器的历史记录中,POST不能 GET请求有长度限制,POST没有 跨域访问 前端解决跨域问题的8种方案(最新最全) 多域名提供资源 CDN缓存更方便 突破浏览器并发限制,一般浏览器每个域名不超过6个 不携带cookie,节省带宽,尤其是上行带宽一般比下行慢 对各种数据类型进行划分存储,通过子域名进行分流,提高网页加载速度 最后更新时间2016年11月27日]]></content>
<categories>
<category>杂谈</category>
</categories>
<tags>
<tag>JavaScript</tag>
<tag>面试</tag>
<tag>CSS</tag>
<tag>HTML</tag>
</tags>
</entry>
<entry>
<title><![CDATA[独立开发日记-剑网3那些事儿]]></title>
<url>%2F2016%2F11%2F23%2F%E7%8B%AC%E7%AB%8B%E5%BC%80%E5%8F%91%E6%97%A5%E8%AE%B0-%E5%89%91%E7%BD%913%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF%2F</url>
<content type="text"><![CDATA[起因该项目前身其实于JX3Spider,由于贴吧的帖子出现具有不定时性,项目最初设置的8小时推送一次在时间上存在太大的间隔,如果缩短推送时间会导致Github出现很多无意义的commit,于是萌生了将该项目进行扩展,由简单的爬虫收录文件扩展为网站,并且提供在线评论、热门文章推送、今日更新、文章检索等功能,后续功能继续推中进。 前端技术栈Bootstrao + jquery + jqueryUI + H+ 界面沿用的H+ UI框架中的部分页面,主要是Bootstrap和jQuery UI,项目最初采用了requirejs进行模块依赖管理,由于单页面使用script的脚本文件排除jquery后,requirejs.mini大小差不多,考虑到性能问题保留了requrrejs配置文件的同时暂时移除了requirejs的使用。 后端技术栈Nodejs + Express + ejs + php + mysql 初次接触express感觉非常轻量,官方API文档写得非常棒,用着也很方便,在模板引擎方面没有采用express自带的jade,选择的是ejs,与H+ UI搭配更方便。 对JX3Spider的php爬虫文件进行了重构,性能优化,同时将信息记录的目标由json文件改成mysql数据库。 同时由于服务器小水管、低配置的原因,mysql数据库与网站挂载的主机进行了分离,一定程度上提高网站的访问速度(虽然还是比较慢╮(╯▽╰)╭,毕竟廉价学生服务器)。 评论评论方面采用多说提供的接口,免去访问者注册的同时提供带有身份标志的评论,同时提供最热文章、最新评论,提供文章推荐的功能。 爬虫基本设置沿用剑侠情缘3网络版贴吧爬虫开发日记,对爬虫进行了重构,增加边界情况考虑,避免无意义的爬虫! 路由形如1http://www.jx3818.top/article/p/1883146654/ exoress中的映射方式 1234router.get('/article/p/:id',function (req, res, next) { var id = req.params.id; ...code...}) 网站:剑网三那些事儿版本更新维护记录: JX3Spider]]></content>
<categories>
<category>实战开发</category>
<category>web</category>
</categories>
<tags>
<tag>PHP</tag>
<tag>express</tag>
<tag>Nodejs</tag>
<tag>ejs</tag>
<tag>requirejs</tag>
</tags>
</entry>
<entry>
<title><![CDATA[剑侠情缘3网络版贴吧爬虫开发日记]]></title>
<url>%2F2016%2F10%2F31%2F%E5%89%91%E4%BE%A0%E6%83%85%E7%BC%983%E7%BD%91%E7%BB%9C%E7%89%88%E8%B4%B4%E5%90%A7%E7%88%AC%E8%99%AB%E5%BC%80%E5%8F%91%E6%97%A5%E8%AE%B0%2F</url>
<content type="text"><![CDATA[Github上项目地址:JX3Spider 开发原因某段时间,博主沉迷剑3无法自拔,说到剑3就不得不提贴吧的818、树洞等帖子(这些帖子其实上讲的就是玩游戏的人因为这个游戏遇到的一些趣事,比如游戏中的情缘,也就是情侣,现实中奔现遇到的一些值得撕逼的事,又或者游戏中一些帮派的事情。反正这些事情基本上都可以当成小说来看了,看了后根本停不下来。),而这些帖子的魅力实际上比游戏本身还要迷人,以至于到现在博主由于时间紧凑,已经弃坑剑3快4个月了,却依然在追贴吧818、树洞,但是不得不佩服贴吧的各位发帖实在太快了,据博主写的爬虫每天统计差不多,每天都有10个以上的相关类型帖子发布,多的时候甚至50多个都有可能,然而由于还有人发其余类型的帖子(交易、攻略等),导致很难观看到每日的所有818、树洞的帖子,往往看帖子的过程中大部分时间浪费在了找帖子上面。因此萌发了,开发爬虫记录自动抓取每日更新的帖子的原因。 实战开发百度贴吧url123http://tieba.baidu.com/f?kw= cardName &ie=utf-8&pn= page * 50# example : http://tieba.baidu.com/f?kw= 剑网3 &ie=utf-8&pn= 50# 剑网3 帖子第一页 帖子url12http://tieba.baidu.com/p/ cardid ?see_lz=1&pn= page# example : http://tieba.baidu.com/p/4818703098?see_lz=1&pn=1 帖子标题筛选12345$tag = '/a href=".*" title=".*"/'; # 筛选帖子$tag_card_url = '/[0-9]+/';# 提取帖子id$tag_card_title = '/e=".*" t/'; # 帖子名字$tag_get_rel_title = '/[^"]{10,1000}/'; # 帖子的真正名字$tag_choose='/.*(818|树洞).*/'; # 判断包含818 或者 树洞 的帖子 帖子id记录1$id_arr = array(); #用于判断帖子是否已经收录 帖子定时推送Github1234exec("git pull origin master",$out); exec("git add -A",$out); exec("git commit -m \"".date('Y-m-d H:i:s',time())."\"",$out); exec("git push origin master",$out); 项目文件说明: getJX3818.php 抓取帖子 pushControl.php 定时更新推送github devide.php 帖子分类]]></content>
<categories>
<category>实战开发</category>
<category>PHP</category>
</categories>
<tags>
<tag>hack</tag>
<tag>PHP</tag>
</tags>
</entry>
<entry>
<title><![CDATA[详解JavaScript中的__proto__与prototype(上)]]></title>
<url>%2F2016%2F10%2F18%2F%E8%AF%A6%E8%A7%A3JavaScript%E4%B8%AD%E7%9A%84-proto-%E4%B8%8Eprototype(%E4%B8%8A)%2F</url>
<content type="text"><![CDATA[先看一段代码12345Function instanceof Object // true Object instanceof Function // true Function instanceof Function //true Object instanceof Object // true Number instanceof Number //false 再来看一段1234567891011121314151617var obj = { a : 1 }; console.log(obj.__proto__ === Object.prototype); // true var str = new String('123'); console.log(str.__proto__ === String.prototype); // true function Point(){}; var Circle = Object.create(Point); console.log(Circle.__proto__ === Point); // true console.log(Circle.__proto__ === Point.prototype); // false var p = new Point(); console.log(Point.__proto__); // function() console.log(Point.prototype); // Point {} console.log(p.__proto__); // Point {} console.log(p.prototype); // undefined 很明显,__proto__与prototype看起来似乎很相似,但是实际上是不同的。先了解一下各自是什么,再进一步探讨。 prototype 显示原型每一个函数在创建之后都会拥有一个名为prototype的属性,这个属性指向函数的原型对象。(通过Function.prototype.bind方法构造出来的函数是个例外,它没有prototype属性。) __proto__ 隐式原型JavaScript中任意对象都有一个内置属性[[prototype]],在ES5之前没有标准的方法访问这个内置属性,但是大多数浏览器都支持通过proto来访问。ES5中有了对于这个内置属性标准的Get方法Object.getPrototypeOf().Note: Object.prototype 这个对象是个例外,它的__proto__值为null 二者关系隐式原型指向创建这个对象的函数(constructor)的prototype123function test(){};var a = new test();console.log(a.__proto__ === test.prototype) ;// true 梳理JavaScript里万物皆对象,方法(Function)是一种特殊的对象。由上述的定义可知,任意对象都有都有__proto__,而prototype只有函数创建之后自己才有,通过该函数创建的对象是没有的。1234function test(){};var a = new test();console.log(test.prototype); // test{}console.log(a.prototype); // undefined 而Object.prototype这个特殊的对象的__proto__位于原型链金字塔的顶端,为null,如下图这里值得一提的是JavaScript的原型链中原型对象prototype之间是通过__proto__联系起来的同时原型对象prototype中都有个预定义的constructor属性,用来引用它的函数对象。这是一种循环引用 123person.prototype.constructor === person //trueFunction.prototype.constructor === Function //trueObject.prototype.constructor === Object //true 这一点在本文后面的图中也有显示,Click有两点需要注意: (1)注意Object.constructor===Function;//true 本身Object就是Function函数构造出来的(2)如何查找一个对象的constructor,就是在该对象的原型链上寻找碰到的第一个constructor属性所指向的对象 再看上面一截代码中的部分123function Point(){}; var Circle = Object.create(Point); console.log(Circle.__proto__ === Point); // true Object.create(),这是ES5中新增的方法,在这之前这被称为原型式继承,我们可以理解为 new Object(),这样我们就很好理解结果true了,实际上,上述代码可以等价理解为123function Point(){};var Circle = new Point();console.log(Circle.__proto__ === Point); // true 等价的原因在上面二者关系有提到。也可以从constructor来理解12console.log(Circle.constructor); //function Point()console.log(Circle.constructor.prototype === Circle.__prototype) // true instanceofinstanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。 语法1object instanceof constructor 参数object 要检测的对象. constructor 某个构造函数 描述instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。 1234//设 L instanceof R //通过判断 L.__proto__.__proto__ ..... === R.prototype ?//最终返回true or false 也就是沿着L的__proto__一直寻找到原型链末端,直到等于R.prototype为止,如果一直到Object.prototyoe都没找到,则返回false。就此,我们结合下面这幅图再剖析一下文章开头部分的代码段就很容易理解了 参照知乎知乎–js中proto和prototype的区别和关系?整理得出,加入部分个人见解,有错之处望各位大牛斧正]]></content>
<categories>
<category>学习笔记</category>
<category>JavaScript</category>
</categories>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[利用Php爬虫备份自己的CSDN博客]]></title>
<url>%2F2016%2F10%2F18%2F%E5%88%A9%E7%94%A8Php%E7%88%AC%E8%99%AB%E5%A4%87%E4%BB%BD%E8%87%AA%E5%B7%B1%E7%9A%84CSDN%E5%8D%9A%E5%AE%A2%2F</url>
<content type="text"><![CDATA[自己写备份博客的原因最近逛自己博客的时候发现左边经常有个小广告,十分恶心,看着很烦。同时自己最近也准备自己搭一个博客,所以就想着把自己CSDN博客上的文章备份下来。本着偷懒至上的原则⁄(⁄ ⁄•⁄㉨⁄•⁄ ⁄)⁄ ,先去百度了一下有木有现成的博客爬虫软件。发现有个叫豆约翰的软件,当时的心情是很激动的有木有φ(゜▽゜*)♪,然而打开的一瞬间居然要收费,还是按月收费。尼玛,程序员的本性简直不能忍。So,自己写了一个github开源项目CSDNSpider,接下来讲解一下思路吧。 项目地址:CSDNSpider Php爬虫那些事儿爬虫需要了解的函数 fopen,用于打开需要爬虫的url,获得一个流 stream_get_contents,用于将fopen获得的流处理成字符串 preg_match,用于正则表达式匹配 curl,这里用作图片的下载 preg_replace,用于替换符合正则表达式的内容当然还有最最重要的 正则表达式,详情可以点击教程这里函数的作用就只进行简单说明了,各位先更深入了解可以去官网查看文档。 整个爬虫的思路关于爬虫首先我们要理清楚我们要做的是什么,需要构造那些函数?整理后我们可以得到以下函数,当然因人而异,可能你会有其它不同的看法 SpiderGo,爬虫主函数 getWebContent,用于获得网站的主要内容 getPageNumber,用于获得网站文章页面的总页数 getArticleList,用于获文章列表包括文章的链接和题目 getArticleContent,用于获得文章的内容 getImage,获取文章中图片的链接 replaceImgUrl,替换文章中的图片连接为本地连接 downloadImg,下载图片 还有些函数就不多做说明了,其实大致文章爬虫主题上就是这些部分,根据不同个网站的需要进行增删部分辅助性的函数 爬虫的一些基本常识爬虫中所谓的翻页、查看文章详情内容等这些点击操作其实都是通过url实现的,所以爬虫的核心点在我看来就是url+正则表达式,就拿我CSDN博客来举例子打开我的CSDN个人博客页面,url栏是这样显示的:http://blog.csdn.net/shanamaid我们可以看到url的格式为:1“http://blog.csdn.net/”+用户名 那么我们要如何通过url实现翻页呢?我们先翻第二页看看,url栏是这样显示的:1http://blog.csdn.net/ShanaMaid/article/list/2 So,我们得到结论,文章翻页的url格式是这样的:1http://blog.csdn.net/用户名/article/list/页数 再来,我们把这个url打开会得到什么东西呢?1http://blog.csdn.net/ShanaMaid/article/list/1 很显然,1http://blog.csdn.net/ShanaMaid/article/list/1 打开的页面和1http://blog.csdn.net/shanamaid 是一样的,也就是说,这两个链接是等价的,但是为了统一url格式,我们在写爬虫的时候肯定用的是1http://blog.csdn.net/ShanaMaid/article/list/1 打开页面而不是1http://blog.csdn.net/shanamaid。 接下来我们打开文章详情页面看看,1http://blog.csdn.net/shanamaid/article/details/52441330 分析得到文章详情页面得到:1http://blog.csdn.net/用户名/article/details/文章ID编号 我们再分析一下文章列表页面发现 1234<span class="link_title"><a href="/shanamaid/article/details/52441330"> <font color="red">[置顶]</font> ReactJS学习之一篇博客教你入门ReactJS </a></span> 发现我们需要的文章的题目和链接都在class=”link_title”的span里面,所以我们可以用一个正则表达式把它提取出来1<span class="link_title">[\w\W]*?<\/span> 然后再用 1<\/?[^>]+> 可以去掉标签得到题目 [置顶]ReactJS学习之一篇博客教你入门ReactJS用 1[0-9]{5,} 可以提取出文章标号,得到52441330即 123456789101112131415//获取文章列表function getArticleList($page,$username){ $url = 'http://blog.csdn.net/'.$username.'/article/list/'.$page; $content = getWebContent($url); $tag = '/<span class="link_title">[\w\W]*?<\/span>/'; $tag_name = '/<\/?[^>]+>/';//去除html标签 $tag_url = '/[0-9]{5,}/';//提取编号 preg_match_all($tag, $content, $result);//提取出包含有文章题目和编号的内容 for ($i=0; $i < sizeof($result[0]); $i++){ preg_match($tag_url,$result[0][$i],$number);//提出出文章题目编号 $result[0][$i]=preg_replace($tag_name,'',$result[0][$i]);//提取出文章题目 $result[1][$i]=$number[0]; } return $result;} 同理,我们观看文章详情页面发现都在id=”article_content”的div里面,同时这个块后面一行内容为 1<!-- Baidu Button BEGIN --> 所以我们可以这样写正则表达式 1<div id="article_content" class="article_content">[\w\W]*<\/div>[\w\W]*<!-- B 则提取文章内容的函数应该这样写 12345678// 获取文章内容function getArticleContent($number,$username){ $url = "http://blog.csdn.net/".$username."/article/details/".$number; $content = getWebContent($url); $tag = '/<div id="article_content" class="article_content">[\w\W]*<\/div>[\w\W]*<!-- B/'; //匹配正文内容 preg_match($tag,$content,$main);//提出正文内容 return $main[0];} 再看看总页数,观察后可以很容易写出下列函数 123456789//获取文章总页数function getPageNumber($content){ $tag = '/共[0-9]+页/'; $tagNumber = '/[0-9]+/'; preg_match($tag, $content,$result); preg_match($tagNumber, $result[0],$sumPage); return $sumPage[0];} 同样我们继续观察img的格式,可以得出正则表达式 1http:\/\/img.blog.csdn.net\/.*?\/Center 提取图片的函数为 123456//提取-文章内容中的图片function getImage($content){ $tag = '/http:\/\/img.blog.csdn.net\/.*?\/Center/'; preg_match_all($tag, $content, $result);//筛选出图片 return $result[0];//图片链接数组} 然后是对提取出的图片的链接进行下载 12345678910111213141516//下载图片function downloadImg($url,$id,$imgName){ if(!is_dir("Img\\".$id)) { mkdir("Img\\".$id); } for ($i=0; $i <sizeof($url) ; $i++) { $curl = curl_init(); curl_setopt($curl,CURLOPT_URL, $url[$i]); curl_setopt ($curl, CURLOPT_HEADER, false); curl_setopt ($curl, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($curl); curl_close($curl); file_put_contents($imgName[$i],$result); }} 由于下载了图片,为了能够离线观看文章,所以还得把文章中img的src中的链接替换为本地连接,可以用下面函数 1234567891011//替换-文章内容中的图片链接为本地function replaceImgUrl($content,$local_url){ $tag = '/http:\/\/img.blog.csdn.net\/.*?\/Center/'; $tag_array = array(); for ($i=0; $i < sizeof($local_url) ; $i++) { $tag_array[$i] = $tag; } $count = 1; $st = preg_replace($tag_array, $local_url, $content,$count); return $st;} 整个爬虫大概就是这样了,最后再提示一遍,完整的文件在我的github上面CSDNSpider]]></content>
<categories>
<category>实战开发</category>
<category>PHP</category>
</categories>
<tags>
<tag>hack</tag>
<tag>PHP</tag>
<tag>Spider</tag>
</tags>
</entry>
<entry>
<title><![CDATA[PHP实战:利用Curl刷CSDN积分]]></title>
<url>%2F2016%2F10%2F16%2FPHP%E5%AE%9E%E6%88%98%EF%BC%9A%E5%88%A9%E7%94%A8Curl%E5%88%B7CSDN%E7%A7%AF%E5%88%86%2F</url>
<content type="text"><![CDATA[这篇博客的由来偶尔有一次机会点开了CSDN排行第一的博主的博客,看到了他的一篇文章!message当时看到这篇文章第一想法是跪拜的,前辈不愧是前辈,不愧是CSDN排名第一。看到前辈后面说这个漏洞已经被封了,但是我又有点心痒难耐,想了想琢磨了一下发现似乎可以在文章访问量上动手脚,测试发现CSDN对文章访问计数是一个IP地址短时间只能访问一次。于是乎想了想能不能从从http报头上动手脚?所以就有了这篇博客~~~ 声明本博客只是为了证明CSDN确实有这个漏洞,由于博主已经被CSDN客服爸爸发了黄牌警告。代码方面就不会全部公布了,只是提供一个思路,同时也希望CSDN客服爸爸尽快修复这个漏洞。再次强调!博主真不是故意的,测试脚本的时候挂服务器上后就去玩游戏了,忘记关了,后来被CSDN客服爸爸黄牌警告才想起。。。。。好在CSDN客服爸爸宽宏大量,原谅了我。我只是单纯想向前辈致敬,结果玩成这样了。。。。 思路首先利用爬虫把自己每篇文章的url给扫描下来,然后利用Php中的curl往里面填入伪造的IP地址 123456789$ip = $_GET['ip'] ? $_GET['ip'] : '1.1.1.1';$ipArr = explode(".",$ip);$ipArr[3]=rand(1,255);$ipArr[2]=rand(1,255);$ipArr[1]=rand(1,255);$ipArr[0]=rand(1,255);$ip = implode(".", $ipArr);$headers['CLIENT-IP'] = $ip;$headers['X-FORWARDED-FOR'] = $ip; 伪造的用户代理 12$user_agent = 'Safari Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/5';curl_setopt($ch, CURLOPT_USERAGENT, $user_agent); 伪造的来路,百度爸爸躺枪 1curl_setopt($ch, CURLOPT_REFERER, "http://www.baidu.com/"); //构造来路 核心思想主要就是这些,有点底子的人也知道剩下的该怎么做了吧?(呸呸呸!!各位别瞎搞,技术分享而已,让大家知道有这么个东西就行了。)然后把伪造的信息发过去再访问页面就蒙混过关了~ 结束语再次强调!此篇文章为技术分享性质,同时让大家知道有这么个东西即可,切勿以此为恶!同时希望CSDN越办越好,同时尽快修复这个BUG吧!]]></content>
<categories>
<category>实战开发</category>
<category>PHP</category>
</categories>
<tags>
<tag>hack</tag>
<tag>PHP</tag>
</tags>
</entry>
</search>