abbshr.github.io icon indicating copy to clipboard operation
abbshr.github.io copied to clipboard

人们往往接受流行,不是因为想要与众不同,而是因为害怕与众不同

Results 58 abbshr.github.io issues
Sort by recently updated
recently updated
newest added

今年外卖平台火遍大江南北.诸如饿了么,美团外卖,百度外卖,淘点点,超人外卖等等等等层出不穷,校园周边小餐馆的生意也做的是风生水起.各种打折,各种送饮料,各种首单立减,真让整日宅在寝室和实验室的家伙欲罢不能啊~ 不过呢好日子总会有到头哪一天.随着饮料越送越差,折扣也越来越少,我们开始埋怨吃不到物美价廉的美食了. 于是乎,借着某位室友创业的热情,在另一位猥琐室友的怂恿下,走上了一条"革命之路", 因为当时常送果粒橙, 于是我们为这个计划起名为**果粒橙保卫战**. 不扯淡了,其实就是"外卖比价". 我们设计应用架构, 分解任务模块, 规划一系列前进流程, 差不多当晚就开始敲代码. ...当然,代码还要靠我来写-_-,这是既苦逼又令人兴奋的工作.(你能理解为什么) 我把应用核心逻辑模块分为两部分, 一块是爬虫,用于抓取各大外卖平台的网页数据;另一块是数据处理,把爬虫爬来的数据做清洗,格式转换和聚类. 架构分为定时更新任务和web服务. 后台更新任务单独启用一个进程,定时(根据正常人的进食时间统计, 设定约每3个小时更新一次)向一组外卖平台网站请求所需数据,经过清洗和转化,分类存储到数据库中. web服务提供一个面向用户的接口,接收用户查询的地点,返回数据库中整理过的周边餐饮. 爬取数据是个体力活, 你首先要打开控制台看着DOM树一个一个的找父子节点,记id,class,tagname.换句话说写出每家平台通用的CSS path,如果是动态加载的数据,就要找到那个请求地址, 如果数据是JavaScript动态生成的, 你还要模拟执行一次以得出想要的结果. 前台测试通过后,还要用curl之类的工具通过non-browser测试,这是个技术活,因为几乎所有网站都对爬虫做了防范,你要想方设法欺骗web服务器,至于如何做就不谈了(详见之前写的一篇"[crawl前端攻防战](https://github.com/abbshr/abbshr.github.io/issues/27)"), 总之,request模块不适用这个抓取过程, 因此我写了一个简版request专门应付我们要爬的网站server. 得到了原始的html,就可以做数据清洗了,这里使用了Node第三方cheerio模块. 提取必要信息形成一个二维数组,每个元素是一个JSON对象: ``` js [...

开发手记
Project
杂谈
idea
logger
Hack

web标准发展至今,浏览器能干的事越来越多了,各种新鲜技术的出现无疑扩展了客户端app的职能,比如说上一篇po提到的`ArrayBuffer`、`Blob`、`URL`等。这个寒假我写了一個文件拖拽上传的脚本。当时我想毕竟这是一个正经的web应用,就算是前端也一定要做到尽量无可挑剔。因此,新鲜技术终于得以利用。 我们暂且不考虑应用架构和代码风格,怎么做才能无可挑剔呢?答案就是把严格控制工作流程的每一环节:从选择文件到解析文件再到文件上传最后到善后工作。(当然,如果人家禁用JavaScript所有的工作都前功尽弃了。不过为了弥补这一不足,我们在后面会有一个小小的解决方案。) ### 获取文件 文件的获取有两个途径:A.上传按钮选择; B.拖拽选择。这没啥可说的,只是为了方便拖放操作额外写了一个拖放事件库,代码就不贴了。 ### 类型检测 关键是文件解析。或许你要问“上传个文件给服务器,为毛在前端弄那么复杂?”,还是那句话“为了做到应用的完整与无可挑剔”。上传时我们考虑之允许指定格式的文件,如“PDF、DOC、JPEG、CAD”等等能形成人类可读文档的文件,如果文件格式不满足要求则拒绝上传。 要想检测文件类型,至少得把文件头读出来。怎么做?要是以前还真就麻烦了,可是现在web标准给出了`FileReader`,使用它可以将二进制的blob文件进行转化处理。还记得`ArrayBuffer`吗?这里我们就用他们来完成文件头的获取: ``` var typeVerify = function (file) { var filereader; var token = true; // 创建一个FileReader filereader = new FileReader(); filereader.onload...

开发手记
Project
Yinle.me Log
原blog精选系列

1. Head标签中添加Script 2. Body标签尾添加Script 3. 其它? 先来说说为什么会有前两种观点。将Script标签放到哪里并不绝对,两种方式都没有错,要说错就错在乱放上。 最原始的head法是为脚本优先而生的。众所周知,放在head标签里的东西无一例外优先于body加载。 但缺点是太大的文件会增加页面的加载时间(也就是用户等待时问),这多影响人的心情啊! 因此有了在body末尾放脚本一说。对于页面内容优先的网页来说,这的确大大减少了等待时间,不过对于富客户端的Web2.0 app来说,这种选择也是种煎熬:丑陋的框框早早摆在那里,然后等着美化页面和提供交互能力的脚本慢吞吞能加载。特别是对于那些完美主义者们,如果你当真把这种页面给他们看,那就别奢望他们会用你的app了。 那该怎么办?下面就来说说第三类方法: 首先确认你的网站类型:信息资讯?社交网络?游戏?博客、首页?实用工具app、云计算服务平台(网盘、聊天室、在线翻译、云办工、项目管理协作等重量级应用) 不同目地的网站逻辑复杂度亦不同,对交互能力的要求也千差万别。拿云办工为例,它提供在线编辑文档、实时同步备份数据,外加一大堆其它功能。这是一个本地桌面应用的Web版,也就说它对交互能力的要求极高,且基本上90%的交互逻辑都在浏览器中,这就完全依赖脚本了。这种应用级网站的脚本加载异常重要,没了脚本的支持活不了。而像提供资讯那种以信息传播为目的网站却对交互能力要求不高:没了脚本我照样能跑。 所以这里我们只谈以应用为主的网站。为了满足尽快显示该有的元素,先将脚本模块化:负责页面显示的脚本按Level依次穿插到body中,或者某些小脚本必须提前加载那就干脆把它放到head里。这里不免要说网络请求的问题,如果模块太多,每个小脚本都请求一次,这样很不划算,要知道网络I/O是特耗时的。有时在脚本大小很小的时候,可以将这些小脚本合并为一个文件以减少HTTP请求的次数。最后把允许等待的脚本放到body尾。也可以利用ajax异步请求脚本字符串并用eval()执行或者用DOM API 动态创建script标签,随时请求需要的脚本。 好像挺完美。madei!?我们刚才只讨论了脚本的加载,那它们究竟何时执行?lt’s a serious question ! # 跑起脚本 刚才的方法中我们提到“将脚本模块化并依次插入到各元素之间”。不过在老式浏览器中并不会产生多大改变:脚本加载后并不会马上执行,而是继续加载DOM向下完善整个文档树直到最后一个元素加载完毕。 所幸HTML5中为script标签提供了 `async` 和 `defer` 属性。...

前端
脚本加载
原blog精选系列

关于libuv,不必做过多介绍。从Node.js到Luvit,Rust,pyuv等无不体现libuv应用之广泛。 仍然是因“How does it work”的问题促使我去github扒源码。要说现在没有libuv的文档或者参考资料吗?当然有,只不过他们都没有进一步深入介绍,每份参考资料都仅仅停留在API基本使用上,并且还不完整,就像隔着一层窗户纸一样,这难以让人不产生一探究竟的冲动啊。 我想起之前写的那份“阅读Node.js源码”,在写那篇日志前我主要阅读的是多数调用V8相关的源码,而对libuv貌似并没有提及。Node.js的入口源码`node.cc`中有一段展示Node中是如何使用libuv的最最核心的代码,也是事件循环的起点: ``` c++ // src/node.cc // 源码 3626行 { Context::Scope context_scope(env->context()); bool more; do { // 启动event-loop more = uv_run(env->event_loop(), UV_RUN_ONCE); if (more == false)...

Linux
libuv
logger

了解Node.js的运行机理之后就有能力对其进行custom了,我们可以尝试着DIY一个node-v0.10.31.(1) _涉及源码_ - `node.gyp`: Node.js构建编译所用配置文件 - `lib/` 我要custom那些东西呢? 我尚未拜读v8的API,况且C++水平略渣,因此暂且不理会`deps`和`src/*.cc`这两个目录,从最贴近现实处入手: `lib/`和`src/node.js`. 修改`src/node.js`最合适不过了,因为这是官方推荐的自定义Node核心功能的方法.其实还可以扩充Node的JavaScript核心模块组,我这次玩的就是这个. 我向lib目录里添加了`read.i`和`websocket`,这两个是我之前写的Node扩展模块,这次把他们跟Node核心模块整合,哈哈. > read.i用于增强Node.js的接受用户输入的能力, websocket用于扩展Node.js的应用层协议,提供websocket协议支持. #### 添加核心模块 准备read.i的源码`read.i.js`,拷贝到lib目录下. 既然read是提供读取,我们不妨让他使用的便捷一些: ``` js // 这里我修改了src/node.js中初始化全局变量的函数 // 将read.i模块添加到gloabl变量中 startup.globalVariables = function() { global.process...

Node
modules
logger

今天简单的看了看色彩学和颜色理论,终于明白视觉错觉和基色选择的原理,涨了不少姿势。 在玩前端时没少和颜色打交道,大多时间里也都是选选配色。其实我觉得配色不仅仅是看着颜色表一个个试,还应该有混色计算,补色计算等等,例如你想写一个图像处理的软件,其中的反色,混色,颜色渐变等等就需要考虑了。 高中物理讲过,“红(R)、绿(G)、蓝(B)” 是光的三原色,也称三基色。在做网页配色时常见的**RGB值**指的就是红绿蓝三色各自**强度值**以16进制表示的拼接值。 三种色光按不同比例混合就能得到所有颜色(七色)的色光,而同等强度的三基色混合,就会出现**灰色**,灰色代表一个范围(不含色光):[ 纯黑, 纯白 ],其间任何一种颜色都属于灰色。 要说混色理论,首先得了解一些颜色的基础知识。 除了三基色RGB,还有另外一种三基色CMY。按人眼能观察到的颜色来源分类,前者属于**光源三基色**,如灯光、显示器;而后者属于**染料三基色**:青(C)、洋红(M)、黄(Y),如报纸、书等本身不发光的物体。 其实染料三基色也是基于光源三基色而得到的。因为青色吸收红色,洋红吸收绿色,黄色吸收蓝色,这样就再次组成了红绿蓝三基色,通过控制CMY各自的强度,同样达到产生各种色彩。 下面简单介绍RGB三原色的混色计算。 由于不同波长的色光叠加会产生另一种色光,而且这一叠加可能蕴含某种规律。我们将三原色各自的强度以二位16进制数字表示,也就是将强度人为分成256个点。先从最简单的黑色`#000000`,白色`#FFFFFF`考虑。黑色是三色皆尽,白色是三色至上,而`#000000 + #FFFFFF = #FFFFFF`,也就是说“在伸手不见五指的黑夜里手电筒里射出的是白光”,数字的设定恰好符合感官的描述。 对于基本的混色计算,我们可以从单一颜色来分解:比如颜色值`#60b044`,可以拆成颜色1`#600000`、颜色2`#00b000`以及颜色3`#000044`三色光的叠加,当然,其他值的组合也行,只要满足R的加和是60,G的加和时b0,B的加和是44。所以,任意色的普通混色就是对应相加。 CMY的同理,只不过是RGB的补色运算,即: `CMY:(x,y,z) => RGB:(#FF-x,#FF-y,#FF-z)` 这样我们就可以写一个混色函数: ``` js function mix(ca, cb) { r...

杂谈

近期在学习Bash的时候读到了**输入/输出和命令行处理**部分,我本以为讲的不过是I/O重定向、文件读写、输入输出命令之类的东西。事实并非如此,被我忽视的**命令行处理**才是文章的精华所在。虽说我以前写过不少Shell脚本,也感觉那东西很简单好学,不过读完这部分我才发现我一直忽略了一个重要问题:“Shell是如何处理你输入的命令行?”。 现在网上的Shell教程很多,但深入学习的很少,即便有也鲜为人知,在此我将这部分重要的内容加上自己的理解记录下来,方便日后查看。 当你敲入一行代码并交给Shell执行时,命令行处理就开始发挥作用了。 ### 管道行 shell从标准输入或脚本中读取的每行称为一个**管道行**,它包含一个或多个**由0个或多个管道字符`|`分割的**命令。shell会对其读取的每个管道行执行以下操作,也称作**命令行处理**: ### 命令行处理流程 这个过程可分为12个阶段: 1. 将命令分成由固定字符集分割的记号。这个字符集包含:`空格、制表符、换行符、;、(、)、、|、&`。 2. 检测命令的第一个记号,检查是否为不带引号或反斜线的关键字。 - 如果为开放关键字。如`if`等控制结构**起始字符串**、`function`、`{`、`(`。则该命令为复合命令,shell将读取下一条命令,并重复这一过程。 - 如果不是开放关键字。如`else`、`then`、`fi`等或逻辑操作符,则shell给出语法错误。 3. 依据别名列表检查每个命令的第一个关键字,如果找到相应匹配,则替换别名定义,并回退到第一步。 4. 执行大括号扩展。 5. 如果`~`位于单词头,则使用用户的主目录替换`~单词` 6. 对任何以`$`开头的表达式执行变量替换。 7. 对`$(string)`进行命令替换 8. 计算`$((string))`算术表达式 9....

Linux
Bash Shell

前两篇Node学习笔记算是对Linux底层I/O的一个初步认识,但过后发现仍有许多不足和么棱两可之处,借着近日学习Linux系统编程学习的新知识,重新写了这篇日志。 Node最大亮点之一莫过于libuv提供的Async I/O模型了,我也曾在之前介绍过其大概实现原理——多线程模拟,同时谈到了Linux下高效的I/O事件通知机制epoll。epoll就属于这些I/O模型中的一个——I/O多路复用。 不涉及底层细节,这里仅谈Linux中各种I/O技术的差异与特点。 Linux有五大IO模型,分别是**阻塞I/O**、**非阻塞I/O**、**I/O多路复用**、**信号驱动I/O**以及**异步I/O**。 #### 异步I/O 先说我们最关心的异步IO。用户空间的进程的IO函数(如`read()`)执行后会立即返回,不管内核采用如何机制,再怎么折腾,都与应用进程无关了,因此可以继续执行后续代码。当内核进程监听到**IO可用**的事件通知后,会把附加数据从内核空间拷贝到用户空间,然后通知应用进程,应用进程有空时便调用回调函数,收集数据。这是最理想的IO方式,只可惜在Linux内核中原生支持的并不好(aio的性能不行),这也是后来Ryan编写libuv的原因之一。而Windows内核提供了完善的异步IO机制——IOCP模型,这里不作讨论。 借用Unix网络编程中的说法,我们这里将I/O请求到结束这一过程分为两部分: 1. 监视I/O可用事件 2. 从内核空间拷贝数据到用户空间 我们说的阻塞不阻塞全由**用户进程是否等待内核进程完成这两部分**决定! 之所以说异步IO是最理想的IO模型,因为应用进程不理会这两部分,我们的应用进程完全可以做自己想做的事,然后等操作系统的数据可用信号就行了。 #### 信号驱动I/O 这个IO模型相比异步IO要多一步阻塞过程:IO可用事件的监听交由内核,但内核的工作只是到此为止,接下来,将通知应用进程“IO可用”,将底层事件“冒泡”到用户级,然后应用进程再等待内核完成数据的拷贝。这部分是要阻塞进程的。 #### I/O多路复用 你可能不大明白啥叫“IO多路复用”,但类比计算机网络中的多路复用技术:在单一通信线路中传输多种信号…,可能仍然不明白。不过提到select、poll以及epoll我想你会恍然大悟。 是的,select、poll、epoll就是Linux系统的IO多路复用技术。那他们多路复用表现在哪方面呢? 按照manpage上的说法,他们会监听在多个文件描述符上发生的指定I/O相关事件,然后在至少一个事件触发时可处理多个文件描述符。这不同于传统的阻塞IO模型——只能处理一个文件句柄。因此说这是IO多路复用。 IO多路复用属于同步I/O模型。因为他们一直阻塞进程直到得到数据。 那么他们在那里发生的阻塞?其实若要使用这一模型,应用进程的函数调用应该选择**非阻塞调用**,然后才可进入模型的作用域。和前两种模型的最初阶段相同:IO函数的调用都是非阻塞的!调用了马上返回。但接下来情况有所不同:作为**代理人**的IO多路复用会将本次调用相关的文件描述符纳入事件监听范畴内,阻塞应用进程,等待IO可用事件(这里具体是监听fd上注册的IO事件),一旦有感兴趣的事件发生:对于select和poll机制来讲,他们会将所有监听的文件描述符遍历一次(不管是否有没有被监听的事件在那个fd上面发生);对于epoll机制,仅仅遍历那些触发了事件的文件描述符。因此说epoll的性能比前两者要高。但是这仅仅是就海量句柄中有少数活跃句柄的情况而言,如果句柄数量不多,或者说活跃的句柄很多的情况下,epoll的性能并不能体现出来,甚至不如poll。 这个IO模型的两部分都是用户进程等待内核进程完成调用,所以说都是阻塞的,但应用很广泛。libuv在Linux平台下就基于epoll机制和线程池在用户空间模拟了异步IO,并且性能很好。 #### 非阻塞I/O Linux提供的一种可选的I/O方式。用户空间在IO函数调用后立马返回,这的确是非阻塞调用,用户进程也的确可以继续干别的,但为了获取数据,该进程不得不轮询。单单用这种模式的话,不仅性能得不到提升,还白白浪费CPU。 ####...

Node
Linux
libuv
Sys Programming

对于函数来说,ES5并没有多大改动,主要是因为JavaScript里的函数本身就足够强大。不过这里增加了一些简单却又十分实用的内容。 #### arguments实参对象 arguments标示符作为函数的实参列表,在ES5版本下有了改动。 `arguments[i]`往往和实参互为别名,修改了二者中任意一个都会影响到另一个。而ES5移除了这一特性。 并且如果使用了`"use stricts"`语句(严格模式),arguments会变成保留字,也就是说不能将arguments当做变量名使用,不能给其赋值。 #### bind bind方法是一个我认为最棒的新特性之一,因为它大大增强了函数的功能。其用法很简单: ``` js var newfunc = func.bind(obj, arg1, arg2, ...); ``` 意思是将函数`func`作为obj对象的方法(func的`this`绑定至obj),并传入预设参数arg1,arg2...,最后返回一个经过配置后的新函数`newfunc`。 这在函数式编程领域里被称作“Currying”(“柯里化”),也叫“惰性求值”或“部分求值”,是一种应用极广的概念。 该方法的用处很大,在实际应用中我们可用它来做函数的“预配置”、优化层层嵌套的代码结构: ``` js function foo(s) { var c,...

JavaScript核心

如果说ES5中“对象“的新特性让你略有失望,那么”数组“上的特性绝对能让你兴奋不已。 数组的操作应该是新增特性中最多的,总体来讲,给人的感觉有些*\* 函数式 *_味道:大多以函数作为参数,且函数的参数一般为_ 元素,索引,数组本身 *。 他们能够对数组元素进行*\* 遍历、映射、过滤、检测、简化、搜索 **。我暂且引用一下权威指南的内容: #### forEach 该方法接受一个函数作为参数,从头到尾遍历数组同时为每个元素调用这个函数。但这个方法无法在数组遍历完之前停止,方法的返回值永远为undefined。 #### map 简单说就是映射:将每个元素传递给这个函数,并在最后返回一个**新的数组**,函数的返回值作为新数组中的元素。 ``` js [1, 2, , 3].map(function (elem) { return elem + 1; }); // [2,...

JavaScript核心