之前因为赶制功能,所以整个APP 的实现都是比较粗糙的,只求把功能完善了就行了,但是最近有了时间,就做了些性能上面的优化,整个APP 的功能一般功能还能够正常使用的。之前在写RN 客户端的时候做了一些调查,发现有些库对Markdown 的支持不是不是很好,有些代码会显示混乱,所以就果断放弃这些库。还是采用webView 的形式来进行展示。在最近的提交中,最大的变化莫过于优化了FlatList组件,之前把FlatList
的onEndReachedThreshold
的值设置得太大。导致每次进入APP的时候都会很卡,但是过一段时间数据请求完毕之后就会好很多。所以在这里把值改小了。还有就是initialNumToRender
属性改小了,能够刚刚展示充满一屏的数据。所以现在使用起来也没有以前那么卡了。接下来的话会继续研究一下react-native-camera这个组件。因为最近在跑demo的时候都会出问题的。因为这个APP 还是会支持扫描二维码登录。所以有必要研究一下。作者是小白,有什么错误的地方请指正。如果你对这个项目也有兴趣,请帮忙star一下。谢谢。传送门
React Native CNode 性能优化篇
基于VUE的后台引擎
基于VUE的后台引擎
预览
特性
- 基于Vue2.0
- 基于Bulma 0.3
- 基于platojs框架
- IE10+
- 1024*768及以上分辨率
- ES6+语法
- 拥有功能完善的表格组件
- 拥有丰富的表单组件
依赖
- Node >= v7
- NPM >= v3
- Webpack v3
Demos与文档
项目地址
使用
npm install
// 本地运行
npm run dev
// 打包
npm run build
其他
Promise简单使用
ES6原生提供了Promise对象。所谓Promise,就是Node中的一个对象,用来传递异步操作的消息。
Promise的特点
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending、Resolved和Rejected。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,他的英语意思就是【承诺】,表示其他手段无法改变。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变,会一直保持这个结果。就算改变已经发生了,你在对Promise对象添加回调函数,也会立即得到这个结果,这与实践(Event)完全不同,事件的特点是:如果你错过了它,再去监听,是得不到结果的。
Promise简单例子
创建一个Promise
var promise = new Promise(function (resolve, reject) {
setTimeout(function () {
console.log('执行完成');
resolve('随便什么数据');
}, 1500)
})
Promise的构造函数接收一个参数,就是一个函数,并传入两个参数:resolve、reject,分别表示异步操作执行成功后的回调函数和异步执行失败后的回调函数。其实这里用“成功”和“失败”来描述并不准确,按照标准来讲,resolve是将Promise的状态置为fullfiled,reject是将Promise的状态置为rejected。不过在我们开始阶段可以先这么理解,后面再细究概念。
上边的例子中,我们只是创建了一个Promise的对象,并没有调用它,然后我们来看下边的这个完整的例子:
function runAsync () {
var promise = new Promise(function (resolve, reject) {
setTimeout(function () {
console.log('执行完成');
resolve('随便什么数据');
}, 2000);
});
return promise;
}
runAsync();
这个时候你应该有两个疑问:1. 包装这么一个函数有什么用?2. resolve(‘随便什么数据’);这个是干什么的?
接下来我们继续讲。在我们包装好的函数最后,会return出Promise对象,也就是说,执行这个函数我们得到了一个Promise对象。还记得Promise对象上有then、catch方法吧。这就是强大之处了,看下边的代码:
runAsync().then(function (data) {
console.log(data);
// 后边可以用传过来的数据来做一些其他操作
// ......
});
在runAsync()的返回上直接调用then方法,then方法接收一个参数,是函数,并且会拿到我们在runAsync中调用resolve时传的参数。运行这段代码,会在两秒后输出“执行完成”,“随便什么数据”。
以上的例子中还不能很好的表现出Promise的强大之处,下面我们开始讲解链式操作
链式操作
首先看一个例子
function runAsync1(){
var promise = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务1执行完成');
resolve('随便什么数据1');
}, 1000);
});
return promise;
}
function runAsync2(){
var promise = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务2执行完成');
resolve('随便什么数据2');
}, 2000);
});
return promise;
}
function runAsync3(){
var promise = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务3执行完成');
resolve('随便什么数据3');
}, 2000);
});
return promise;
}
runAsync1()
.then(function (data) {
console.log(data);
return runAsync2();
})
.then(function (data) {
console.log(data);
return runAsync3();
})
.then(function (data) {
console.log(data);
});
这段代码运行的结果是:
异步任务1执行完成
随便什么数据1
异步任务2执行完成
随便什么数据2
异步任务3执行完成
随便什么数据3
如果我们来把上班执行的代码改为这样:
runAsync1()
.then(function (data) {
console.log(data);
return runAsync2();
})
.then(function (data) {
console.log(data);
return '直接返回数据';
})
.then(function (data) {
console.log(data);
done();
});
这样执行完的结果就会变成:
异步任务1执行完成
随便什么数据1
异步任务2执行完成
随便什么数据2
直接返回数据
这里的原因自己慢慢体会吧!!!
reject的用法
到这里,你应该对Promise有了最基本的了解了。在ES6中除了resolve,还有reject这个功能。事实上,我们前面的例子中都是只有“执行成功”的回调,还没有“失败”的情况,reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调,看下面的代码:
function getNumber() {
var promise = new Promise(function (resolve, reject) {
setTimeout(function () {
var num = Math.ceil(Math.random() * 10);
if (num < 5) {
resolve(num);
} else {
reject('数字太大了');
}
}, 1000);
});
return promise;
}
getNumber().then(
function (data) {
console.log('resolved');
console.log(data);
},
function (reason, data) {
console.log('rejected');
console.log(reason);
});
getNumber函数用来获取一个数字,1秒后执行完成,如果数字小于5,我们认为是“成功”了,调用resolve修改Promise的状态。否则我们就认为是“失败”了,调用reject并传递一个参数,作为失败的原因。
运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据,多次运行这段代码,你会随机到下面两种结果:
resolved
3
或者
rejected
数字太大了
catch的用法
我们知道Promise对象除了then方法,还有一个catch方法,他是做什么用的呢?其实它和then的第二个参数一样,用来指定reject的回调,用法是这样的:
getNumber()
.then(function (data) {
console.log('resolved');
console.log(data);
})
.catch(function (reason) {
console.log('rejected');
console.log(reason);
})
效果和写在then的第二个参数里边是一样的。不过它还有另一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常(代码出错),那么并不会报错卡死js,而是进到这个catch方法中。请看下面的代码:
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
console.log(somedata); //此处的somedata未定义
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
});
在resolve的回调中,我们console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是这里,我们会得到这样的结果:
resolved
4
rejected
[ReferenceError: somedata is not defined]
###all的用法 Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。我们仍旧使用上面定义好的runAsync1、runAsync2、runAsync3这三个函数,看下面的例子:
Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function (results) {
console.log(results);
});
会打印:
异步任务1执行完成
异步任务2执行完成
异步任务3执行完成
[ '随便什么数据1', '随便什么数据2', '随便什么数据3' ]
有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有返回数据,是不是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源,比如图片、flash以及各种静态文件。所有的都加载完后,我们进行页面的初始化。
race的用法
all方法的效果实际上是【谁跑得慢,以谁为准执行回调】,那么相对的就有另一个方法【谁跑的快,以谁为准执行回调】,这就是race方法,这个词本来就是赛跑的意思。race的用法与all一样,我们把上面runAsync()的延时改为1秒来看一下:
Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function (results) {
console.log(results);
});
这个异步操作同样是并行执行的。1秒后runAsync已经执行完了,此时then里面的就执行了。结果是这样的:
异步任务1执行完成
随便什么数据1
异步任务2执行完成
异步任务3执行完成
这个时候runAsync2()和runAsync3()并没有停止,仍旧再执行。于是再过1秒后,输出了他们结束的标志。
关于 Egg 3.x的疑问
- Egg 3.x 会结合 pandora 来做进程管理?那 egg-cluster 和之前 egg 的 agent 机制会大改吗?如果不大改能说明下大概的方案吗?如果大改,有相关迁移指南吗?
- Egg 3.x 会兼容koa 中间件,那之前Egg的中间件的顺序原则会被破坏吗?
Can't set headers after they are sent.
在egg里面怎么用command加鼠标左键点到 具体方法里面,可以设置吗
新项目用的蛋蛋,突然发现command加鼠标左键没法点到其他模块的方法里面,这个可以设置吗,点习惯了,突然点不过去,好难受。。。
[FullStack IM] Vue+Vue-Router+Vuex With Express
Site : http://zfcheng.top GITHUB: http://github.com/DoubleCG/seanet Welcome!
基于 Vue.js 的支持本地化储存记事本 SPA
VUEMEMO
基于 Vue.js 的简单记事本 SPA ,Mint-UI、Vue、VueRouter、Vuex,使用localStorage作为数据本地持久化,并支持使用Markdown格式笔记,主要功能有增查改删笔记、按条件过滤和排序笔记、并支持文字和图片等形式的笔记
DEMO
点击这里看演示:DEMO
源代码:github
BUILD SETUP
# 安装依赖
npm install
# 开发模式localhost:8080
npm run dev
# 打包构建
npm run build
主要功能
- v1
- ✔️响应适配
- ✔️创建、修改笔记
- ✔️删除部分或全部笔记
- ✔️查看笔记详细内容
- ✔️标记笔记是否完成
- ✔️切换笔记显示模式
- v2
- ✔️按是否完成进行过滤
- ✔️按创建时间排序
- ✔️按完成情况进行过滤
- ✔️按类别进行过滤
- ✔️收藏、取消收藏和显示收藏笔记
- ✔️通过localStorage对象的数据本地持久化
- v3
- 支持Markdown格式
- 实时保存笔记
- 通过base64支持保存图片
- 通过Canvas支持绘图
MESSAGE ME
发帖测试发帖测试
发帖测试发帖测试哈哈哈恩恩
vue强调是一个mvvm的框架,请问,m,v,vm分别代表什么意思?
vue强调是一个mvvm的框架,请问,m,v,vm分别代表什么意思?在代码中,分别的提现在哪?求指点
来自酷炫的 CNodeMD
我想问下mobx是这样用的吗?我直接越过react组件的props和state了
前段时间用的redux,发现用起来非常麻烦,自己会用了也很难教会别人,昨天看了下mobx,用法如下 先定义一个类似于store的东西
import { observable } from 'mobx'
import { topicListApi } from '../config/api';
class topicStore {
@observable
topic = {
data: [],
page: 1,
tab: ''
}
constructor () {
this.getData()
}
getData = () => {
const { tab, page } = this.topic
topicListApi(page, tab).then((data: any) => {
if (data.success) {
this.topic = {
...this.topic,
data: data.data
}
}
})
}
filter = (tab: string) => {
this.topic.tab = tab
this.getData()
}
flip = (page: number) => {
this.topic.page = page
this.getData()
}
}
export default new topicStore();
然后在react组件内部
import {observer} from 'mobx-react';
import topicStore from '../../mobx/topic'
@observer
class TopicIndex extends React.Component<{}, any> {
render() {
...
{topicStore.topic.data.map((topic: topicObject, index: number) => {
return this.topicTemplate(topic, index)
})}
}
...
我就直接拿来用了,不像redux要绑定props,这样当store里的值发生变化,组件会自动刷,直接绕过react组件的state和props, 我想问下,这是正确用法吗?
node.js父子进程间通讯
最近在学习node.js
的child_process的文档,通过fork
方法创建子进程的时,有个silent
的配置选项
const fork = require('child_process').fork
const cp = fork('./sub.js', [], { silent: true })
如果将silent
配置为了true
,那么子进程的stdin/stdout/stderr
将会pipe
到父进程。
因为通过fork
的方法去创建子进程的话,父子进程是可以通过ipc通道
进行通讯的。
那么这个将stdin/stdout/stderr
都pipe到父进程我想也是为了父子进程间的通讯吧?不过这样做实际上有什么用途以及实际场景呢?
gcoord: 转换WGS84、GCJ02、BD09坐标,解决百度地图高德地图坐标系不统一的问题
做过地图相关开发的同学肯定会遇到这样一个问题:同样的经纬度坐标,在百度地图和高德地图上位置不一样。
关于坐标系
我们通常用经纬度来表示一个地理位置,但是由于一些原因,我们从不同渠道得到的经纬度信息可能并不是在同一个坐标系下。
- 高德地图、腾讯地图以及谷歌中国区地图使用的是GCJ-02坐标系
- 百度地图使用的是BD-09坐标系
- 底层接口(HTML5 Geolocation或ios、安卓API)通过GPS设备获取的坐标使用的是WGS-84坐标系
不同的坐标系之间可能有几十到几百米的偏移,所以在开发基于地图的产品,或者做地理数据可视化时,我们需要修正不同坐标系之间的偏差。
WGS-84 - 世界大地测量系统
WGS-84(World Geodetic System, WGS)是使用最广泛的坐标系,也是世界通用的坐标系,GPS设备得到的经纬度就是在WGS84坐标系下的经纬度。通常通过底层接口得到的定位信息都是WGS84坐标系。
GCJ-02 - 国测局坐标
GCJ-02(G-Guojia国家,C-Cehui测绘,J-Ju局),又被称为火星坐标系,是一种基于WGS-84制定的大地测量系统,由中国国测局制定。此坐标系所采用的混淆算法会在经纬度中加入随机的偏移。
国家规定,中国大陆所有公开地理数据都需要至少用GCJ-02进行加密,也就是说我们从国内公司的产品中得到的数据,一定是经过了加密的。绝大部分国内互联网地图提供商都是使用GCJ-02坐标系,包括高德地图,谷歌地图中国区等。
导航电子地图在公开出版、销售、传播、展示和使用前,必须进行空间位置技术处理。— GB 20263―2006《导航电子地图安全处理技术基本要求》,4.1
BD-09 - 百度坐标系
BD-09(Baidu, BD)是百度地图使用的地理坐标系,其在GCJ-02上多增加了一次变换,用来保护用户隐私。从百度产品中得到的坐标都是BD-09坐标系。
解决方案
百度地图以及高德地图都提供了一些方法来转换不同坐标系下的坐标,但是它们都需要进行网络请求,性能很差。 在春节假期时,我做了一个库gcoord来做这些事。
gcoord
gcoord主要解决了两个问题
- 能将坐标在不同坐标系下相互转换
- 能够处理GeoJSON
GeoJSON是地理行业一种通用的数据格式,它本质上就是JSON,不过对字段有一些约定。
gcoord使用起来非常简单 例如从手机的GPS得到一个经纬度坐标,需要将其展示在百度地图上,则可以通过gcoord将当前坐标从WGS-84坐标系转换为BD-09坐标系
var result = gcoord.transform(
[ 116.403988, 39.914266 ], // 经纬度坐标
gcoord.WGS84, // 当前坐标系
gcoord.BD09 // 目标坐标系
);
console.log( result ); // [ 116.41661560068297, 39.92196580126834 ]
详细的使用方式请查看gcoord的文档
欢迎大家star
[北京海淀] 搜狐招聘 游戏服务器端高级工程师 (20K~50K) 5名
##招聘类型:社招
工作地点:北京海淀区科学院南路2号院3号楼搜狐媒体大厦
工资范围:20K~50K
##岗位概况:
搜狐视频-互娱创新事业部,棋牌类游戏开发,上升期,机会多。
##岗位要求:
1、负责基于Node.js的游戏服务端程序设计和开发、项目维护;
2、分析项目需求,给出良好的解决方案并予以实施;
3、进行系统功能模块的开发与优化;
4、完成其他服务器端相关工作;
5、工作稳定性较高,不曾频繁更换工作;
##希望寻找这样的你:
1、热爱游戏行业,3年以上游戏服务端开发经验。
2、精通node.js/JS网络编程,熟悉高并发、高吞吐量、分布式系统开发;
3、熟悉pomelo的开发框架,了解pomelo内部的实现原理;
4、熟悉tcp/ip, tcp, https, websocket, mqtt网络协议;
5、熟悉Linux系统的常用命令与软件;
6、熟悉Mysql,redis等常用数据库;
7、具备良好的分析解决问题能力和Bug解决能力,能独立承担开发工作;
8、良好的团队协作精神,认真负责,有良好的抗压能力;
9、本科以上学历,计算机相关专业;
##福利:
可提供有竞争力的薪酬
弹性工作时间,不强制加班,结果导向,带薪年假
饭补、加班餐、免费饮料咖啡、节假日福利等应有尽有,各种兴趣群及活动俱乐部
##联系方式:
接收简历邮箱:guoqingzhou@sohu-inc.com
标题请注明:xxx应聘游戏服务器端高级工程师
【北京】北京盛安德科技发展有限公司诚聘Node.JS全栈开发良将(薪资:20K-30K)
我们是谁,我们做什么北京盛安德科技发展有限公司(以下简称“盛安德”)成立于2001年,是国际领先的软件开发服务提供商,专注于为客户开发“快速解决问题、创造真正价值“的软件。 我们的愿景和目标是“成为最适合程序员工作和发展的公司”。成立16年以来,我们一直致力于企业管理理念的创新,以提供自由宽松、开放合作的环境让程序员有机会去成长和改变。 “自由创新、开放合作”的企业文化由此培养了越来越多的程序员成长为独立自主,有创造力的敏捷开发工程师。他们能够站在客户的立场上,立足于客户的业务模式和信息化目标,与客户共同面对问题,协同创新,开发出能快速解决客户问题,给客户带来真正价值的软件。这也是盛安德的核心优势, 在全球1000+的客户中赢得了良好的口碑。 盛安德秉承“为客户创造真正价值”的服务宗旨,根据客户(企业)要解决的问题以及差异化的业务模型,针对不同客户量身提供由2~7个敏捷开发工程师组成的敏捷小团队提供的软件研发+服务,帮助中小企业以最小的信息化投入,获得最为直接的收益。而在这个过程中,程序员在帮助客户解决问题、创造价值的同时,自身的价值也在不断提升,盛安德也因此离自己的愿景和目标越来越近。
我们拥有 1000+建立合作的客户 50+正在合作超过两年的客户 30+客户分布的国家,包括中国、美国、英国、澳大利亚、瑞士、德国、芬兰、荷兰、比利时等 400+员工在全球21个办公地点为客户提供服务 40+员工从一线工程师成长为负责交付和管理的角色 0从未和客户产生过知识产权相关的纠纷
我们的价值观和传统• 诚实,不说谎 – 这是每一个盛安德员工坚守的第一准则。 • 不做自有知识产权的软件产品 – 做纯粹的技术服务公司, 从根本上保护客户的知识产权。 • 坚持公平的商业规则 – 我们一直坚持不提供回扣等有违商业公平原则的行为, 并且时刻保持审视自身,给客户创造真正的价值。 • 敏捷 – 从2006年我们开始在整个公司倡导并实践敏捷理念,借助敏捷拉近工程师和市场的距离。 • 精简运营团队规模 – 只有这样才能把最好的资源提供给一线员工。 • 投资于员工– 为员工提供多样化的培训和发展平台。 这些价值观和传统始终指导着我们的业务、机制、品牌、员工发展等每一个业务环节。
我们的网站:英文网站:http://www.shinetechchina.com; 中文网站:http://www.shinetechchina.com.cn
** 我们的福利:** 我们是开放的管理文化,不介意您之前的工作经历和学历背景,只要您爱生活,爱交流,就可以加入我们; 加入我们,您不必担心迟到,我们实行弹性办公,无需打卡,上下班悠闲自得; 加入我们,休假宽松,第一年5天年假,第二年10天年假,比国家规定多出好多呦; 加入我们,您可享用营养的健康水果,休息室里有不限时不限量的茶点及饮料,还有香浓的咖啡; 加入我们,五险一金齐全,并增加午餐补助、笔记本补助; 加入我们,可享受每年一次的健康体检,并组织其他形式丰富的运动或者活动; 加入我们,您可在图书室借阅各类热门图书,在工作之余让身心得到放松; 加入我们,您可参加公司定期举行的敏捷或者技术方面的培训,时刻给自己的技术充电;
岗位职责及任职要求:岗位职责: 1,负责项目前端、后端的开发,完成整体业务模块的研发工作,具备快速解决问题的能力; 2,参与系统需求分析与设计,负责完成核心代码编写和单元测试( Jest、Mocha) ,并撰写相关技术文档; 3,熟悉 Html5、CSS3,能够根据 Zeplin 设计编写结构良好的响应式布局 Html、CSS 及动效; 4,扎实的 ES6 功底 ,能够快速设计、研发具备可扩展性的 RESTful 接口; 5,参与性能效率优化,所开发的应用需能满足千万级的访问量;
任职要求: 1,具备持续学习能力,对各种新技术、编程语言及框架有学习热情,开发过开源框架者优先; 2,熟悉主流前端框架及其生态链(Vue/React/Angular 以及对应的路由和状态管理方案) ,熟练使用各种工具包 ; 3,丰富的 Node.js 后端开发经验,良好的算法/数据结构基础,精通 Express/Koa/PM2/Bluebird/Mongoose 等框架及工具; 4,熟悉各种常用数据库及缓存产品中的一种或多种,如: MongoDB 、PostgreSQL、 Mysql 、Redis 、Memcache 等; 5,对 CSS/Javascript 性能优化、解决多浏览器兼容性问题具备一定的经验; 6,熟练掌握 Logstash、Elasticsearch、Kibana 者优先; 7,良好的编程习惯,追求极致的代码质量, 能按时、独立、高质量地完成工作 ; 8,英文读写熟练、口语熟练者优先;
薪资标准: 20K-30K(根据个人能力确定)
工作地址:北京市西城区新德街甲20号中影器材大厦705
联系方式:邮箱:wangy@shinetechsoftware.com 电话:010-62370842 联系人:王女士
不要混淆nodejs和浏览器中的event loop
跟大家分享一篇心得,如有错漏欢迎指正,不胜感激。
不同的event loop
event loop是一个执行模型,在不同的地方有不同的实现。浏览器和nodejs基于不同的技术实现了各自的event loop。网上关于它的介绍多如牛毛,但大多数是基于浏览器的,真正讲nodejs的event loop的并没有多少,甚至很多将浏览器和nodejs的event loop等同起来的。 我觉得讨论event loop要做到以下两点:
- 首先要确定好上下文,nodejs和浏览器的event loop是两个有明确区分的事物,不能混为一谈。
- 其次,讨论一些js异步代码的执行顺序时候,要基于node的源码而不是自己的臆想。
简单来讲,
- nodejs的event是基于libuv,而浏览器的event loop则在html5的规范中明确定义。
- libuv已经对event loop作出了实现,而html5规范中只是定义了浏览器中event loop的模型,具体实现留给了浏览器厂商。
nodejs中的event loop
关于nodejs中的event loop有两个地方可以参考,一个是nodejs官方的文档;另一个是libuv的官方的文档,前者已经对nodejs有一个比较完整的描述,而后者则有更多细节的描述。nodejs正在快速发展,源码变化很大,以下的讨论都是基于nodejs9.5.0。
(然而nodejs的event loop似乎比预料更加复杂,在查看nodejs源码的过程中我惊奇发现原来nodejs的event loop的某些阶段,还会将v8的micro task queue中的任务取出来运行,看来nodejs的浏览器的event loop还是存在一些关联,这些细节我们往后再讨论,目前先关注重点内容。)
event loop的6个阶段(phase)
nodejs的event loop分为6个阶段,每个阶段的作用如下(process.nextTick()
在6个阶段结束的时候都会执行,文章后半部分会详细分析process.nextTick()
的回调是怎么引进event loop,仅仅从uv_run()
是找不到process.nextTick()
是如何牵涉进来):
- timers:执行
setTimeout()
和setInterval()
中到期的callback。 - I/O callbacks:上一轮循环中有少数的I/Ocallback会被延迟到这一轮的这一阶段执行
- idle, prepare:仅内部使用
- poll:最为重要的阶段,执行I/O callback,在适当的条件下会阻塞在这个阶段
- check:执行setImmediate的callback
- close callbacks:执行close事件的callback,例如
socket.on("close",func)
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
event loop的每一次循环都需要依次经过上述的阶段。 每个阶段都有自己的callback队列,每当进入某个阶段,都会从所属的队列中取出callback来执行,当队列为空或者被执行callback的数量达到系统的最大数量时,进入下一阶段。这六个阶段都执行完毕称为一轮循环。
event loop的核心代码在deps/uv/src/unix/core.c
int uv_run(uv_loop_t* loop, uv_run_mode mode) {
int timeout;
int r;
int ran_pending;
/*
从uv__loop_alive中我们知道event loop继续的条件是以下三者之一:
1,有活跃的handles(libuv定义handle就是一些long-lived objects,例如tcp server这样)
2,有活跃的request
3,loop中的closing_handles
*/
r = uv__loop_alive(loop);
if (!r)
uv__update_time(loop);
while (r != 0 && loop->stop_flag == 0) {
uv__update_time(loop);//更新时间变量,这个变量在uv__run_timers中会用到
uv__run_timers(loop);//timers阶段
ran_pending = uv__run_pending(loop);//从libuv的文档中可知,这个其实就是I/O callback阶段,ran_pending指示队列是否为空
uv__run_idle(loop);//idle阶段
uv__run_prepare(loop);//prepare阶段
timeout = 0;
/**
设置poll阶段的超时时间,以下几种情况下超时会被设为0,这意味着此时poll阶段不会被阻塞,在下面的poll阶段我们还会详细讨论这个
1,stop_flag不为0
2,没有活跃的handles和request
3,idle、I/O callback、close阶段的handle队列不为空
否则,设为timer阶段的callback队列中,距离当前时间最近的那个
**/
if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
timeout = uv_backend_timeout(loop);
uv__io_poll(loop, timeout);//poll阶段
uv__run_check(loop);//check阶段
uv__run_closing_handles(loop);//close阶段
//如果mode == UV_RUN_ONCE(意味着流程继续向前)时,在所有阶段结束后还会检查一次timers,这个的逻辑的原因不太明确
if (mode == UV_RUN_ONCE) {
uv__update_time(loop);
uv__run_timers(loop);
}
r = uv__loop_alive(loop);
if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
break;
}
if (loop->stop_flag != 0)
loop->stop_flag = 0;
return r;
}
我对重要部分加上注释,从上述代码可以看到event loop的六个阶段是依次执行的。值得注意的是,在UV_RUN_ONCE模式下,timers阶段在当前循环结束前还会得到一次的执行机会。
timers阶段
timer阶段的代码在deps/uv/src/unix/timer.c的uv__run_timers()
中
void uv__run_timers(uv_loop_t* loop) {
struct heap_node* heap_node;
uv_timer_t* handle;
for (;;) {
heap_node = heap_min((struct heap*) &loop->timer_heap);//取出timer堆上超时时间最小的元素
if (heap_node == NULL)
break;
//根据上面的元素,计算出handle的地址,head_node结构体和container_of的结合非常巧妙,值得学习
handle = container_of(heap_node, uv_timer_t, heap_node);
if (handle->timeout > loop->time)//如果最小的超时时间比循环运行的时间还要小,则表示没有到期的callback需要执行,此时退出timer阶段
break;
uv_timer_stop(handle);//将这个handle移除
uv_timer_again(handle);//如果handle是repeat类型的,重新插入堆里
handle->timer_cb(handle);//执行handle上的callback
}
}
从上面的逻辑可知,在timer阶段其实使用一个最小堆而不是队列来保存所有元素(其实也可以理解,因为timeout的callback是按照超时时间的顺序来调用的,并不是先进先出的队列逻辑),然后循环取出所有到期的callback执行。
I/O callbacks阶段
I/O callbacks阶段的代码在deps/uv/src/unix/core.c的int uv__run_pending()
中
static int uv__run_pending(uv_loop_t* loop) {
QUEUE* q;
QUEUE pq;
uv__io_t* w;
if (QUEUE_EMPTY(&loop->pending_queue))//如果队列为空则退出
return 0;
QUEUE_MOVE(&loop->pending_queue, &pq);//移动该队列
while (!QUEUE_EMPTY(&pq)) {
q = QUEUE_HEAD(&pq);//取出队列的头结点
QUEUE_REMOVE(q);//将其移出队列
QUEUE_INIT(q);//不再引用原来队列的元素
w = QUEUE_DATA(q, uv__io_t, pending_queue);
w->cb(loop, w, POLLOUT);//执行callbak直到队列为空
}
return 1;
}
根据libuv的文档,一些应该在上轮循环poll阶段执行的callback,因为某些原因不能执行,就会被延迟到这一轮的循环的I/O callbacks阶段执行。换句话说这个阶段执行的callbacks是上轮残留的。
idle和prepare阶段
uv__run_idle()
、uv__run_prepare()
、uv__run_check()
定义在文件deps/uv/src/unix/loop-watcher.c中,它们的逻辑非常相似,其中的实现利用了大量的宏(说实在我个人非常烦宏,它的可读性真的很差,为了那点点的性能而使用宏真是值得商榷)。
void uv__run_##name(uv_loop_t* loop) { \
uv_##name##_t* h; \
QUEUE queue; \
QUEUE* q; \
QUEUE_MOVE(&loop->name##_handles, &queue);//用新的头节点取代旧的头节点,相当于将原队列移动到新队列 \
while (!QUEUE_EMPTY(&queue)) {//当新队列不为空 \
q = QUEUE_HEAD(&queue);//取出新队列首元素 \
h = QUEUE_DATA(q, uv_##name##_t, queue);//获取首元素中指向的handle \
QUEUE_REMOVE(q);//将这个元素移出新队列 \
QUEUE_INSERT_TAIL(&loop->name##_handles, q);//然后再插入旧队列尾部 \
h->name##_cb(h);//执行对应的callback \
} \
}
poll阶段
poll阶段的代码+注释高达200行不好逐行分析,我们挑选部分重要代码
void uv__io_poll(uv_loop_t* loop, int timeout) {
//...
//处理观察者队列
while (!QUEUE_EMPTY(&loop->watcher_queue)) {
//...
if (w->events == 0)
op = UV__EPOLL_CTL_ADD;//新增监听这个事件
else
op = UV__EPOLL_CTL_MOD;//修改这个事件
}
//...
//阻塞直到监听的事件来临,前面已经算好timeout以防uv_loop一直阻塞下去
if (no_epoll_wait != 0 || (sigmask != 0 && no_epoll_pwait == 0)) {
nfds = uv__epoll_pwait(loop->backend_fd,
events,
ARRAY_SIZE(events),
timeout,
sigmask);
if (nfds == -1 && errno == ENOSYS)
no_epoll_pwait = 1;
} else {
nfds = uv__epoll_wait(loop->backend_fd,
events,
ARRAY_SIZE(events),
timeout);
if (nfds == -1 && errno == ENOSYS)
no_epoll_wait = 1;
}
//...
for (i = 0; i < nfds; i++) {
if (w == &loop->signal_io_watcher)
have_signals = 1;
else
w->cb(loop, w, pe->events);//执行callback
}
//...
}
可见poll阶段的任务就是阻塞等待监听的事件来临,然后执行对应的callback,其中阻塞是带有超时时间的,以下几种情况都会使得超时时间为0
- uv_run处于UV_RUN_NOWAIT模式下
uv_stop()
被调用- 没有活跃的handles和request
- 有活跃的idle handles
- 有等待关闭的handles 如果上述都不符合,则超时时间为距离现在最近的timer;如果没有timer则poll阶段会一直阻塞下去
check阶段
见上面的 idle和prepare阶段
close阶段
static void uv__run_closing_handles(uv_loop_t* loop) {
uv_handle_t* p;
uv_handle_t* q;
p = loop->closing_handles;
loop->closing_handles = NULL;
while (p) {
q = p->next_closing;
uv__finish_close(p);
p = q;
}
}
这段代码非常浅显,就是循环关闭所有的closing handles,无需多言。其中的callback调用在uv__finish_close()
中
process.nextTick在哪里
文档中提到process.nextTick()
不属于上面的任何一个phase,它在每个phase结束的时候都会运行。但是我们看到uv_run()
中只是依次运行了6个phase的函数,并没有process.nextTick()
影子,那它是怎么被驱动起来的呢?
这个问题要从两个c++和js的源码层面来说明。
process.nextTick在js层面的实现
process.nextTick
的实现在next_tick.js中
function nextTick(callback) {
if (typeof callback !== 'function')
throw new errors.TypeError('ERR_INVALID_CALLBACK');
if (process._exiting)
return;
var args;
switch (arguments.length) {
case 1: break;
case 2: args = [arguments[1]]; break;
case 3: args = [arguments[1], arguments[2]]; break;
case 4: args = [arguments[1], arguments[2], arguments[3]]; break;
default:
args = new Array(arguments.length - 1);
for (var i = 1; i < arguments.length; i++)
args[i - 1] = arguments[i];
}
push(new TickObject(callback, args, getDefaultTriggerAsyncId()));//将callback封装为一个对象放入队列中
}
它并没有什么魔法,也没有调用C++提供的函数,只是简单地将所有回调封装为对象并放入队列。而callback的执行是在函数_tickCallback()
function _tickCallback() {
let tock;
do {
while (tock = shift()) {
const asyncId = tock[async_id_symbol];
emitBefore(asyncId, tock[trigger_async_id_symbol]);
if (destroyHooksExist())
emitDestroy(asyncId);
const callback = tock.callback;
if (tock.args === undefined)
callback();//执行调用process.nextTick()时放置进来的callback
else
Reflect.apply(callback, undefined, tock.args);//执行调用process.nextTick()时放置进来的callback
emitAfter(asyncId);
}
runMicrotasks();
} while (head.top !== head.bottom || emitPromiseRejectionWarnings());
tickInfo[kHasPromiseRejections] = 0;
}
可以看到_tickCallback()
会循环执行队列中所有callback, 因此_tickCallback()
的执行就意味着process.nextTick()
的回调的执行。我们继续搜索一下发现_tickCallback()
在好几个地方都有被调用,但是我们只关注跟event loop相关的。
在next_tick.js中发现
const [
tickInfo,
runMicrotasks
] = process._setupNextTick(_tickCallback);
查找了一下发现在node.cc中有
env->SetMethod(process, "_setupNextTick", SetupNextTick);//暴露_setupNextTick给js
_setupNextTick()
是node.cc那边暴露出来的方法,因此猜测这就是连接event loop的桥梁。
c++中执行process.nextTick的回调
在node.cc中找出SetupNextTick()
函数,有这样的代码片段
void SetupNextTick(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsFunction());
//把js中提供的回调函数(即_tickCallback)保存起来,以供调用
env->set_tick_callback_function(args[0].As<Function>());
...
}
_tickCallback
被放置到env里面去了,那它何时被调用?也是在node.cc中我们发现
void InternalCallbackScope::Close() {
//永远先运行microtasks
if (!tick_info->has_scheduled()) {
env_->isolate()->RunMicrotasks();
}
//...
//终于调用在SetupNextTick()中放置进来的函数了
if (env_->tick_callback_function()->Call(process, 0, nullptr).IsEmpty()) {
env_->tick_info()->set_has_thrown(true);
failed_ = true;
}
}
可知InternalCallbackScope::Close()
会调用它,而InternalCallbackScope::Close()
则在文件node.cc的InternalMakeCallback()
中被调用
MaybeLocal<Value> InternalMakeCallback(Environment* env,
Local<Object> recv,
const Local<Function> callback,
int argc,
Local<Value> argv[],
async_context asyncContext) {
CHECK(!recv.IsEmpty());
InternalCallbackScope scope(env, recv, asyncContext);
//...
scope.Close();//Close会调用_tickCall
//...
}
而InternalMakeCallback()
则是在async_wrap.cc的AsyncWrap::MakeCallback()
中被调用
MaybeLocal<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
int argc,
Local<Value>* argv) {
//cb就是js中传递过来的回调函数
MaybeLocal<Value> ret = InternalMakeCallback(env(), object(), cb, argc, argv, context);
}
AsyncWrap类是异步操作的封装,它是一个顶级的类,TimerWrap、TcpWrap等封装异步的类都继承了它,这意味着这些类封装异步操作的时候都会调用MakeCallback()
。至此真相大白了,uv_run()
中的回调都是经过AsyncWrap::MakeCallback()
包装过的,因此回调执行完毕之后都会执行process.nextTick()
的回调了,与文档的描述是相符合的。整理一下_tickCallback()
的转移并最终被调用的流程
在js层面
_tickCallback()//js中执行process.nextTick()的回调函数
↓
process._setupNextTick(_tickCallback) //c++和js的桥梁,将回调交给C++执行
此时_tickCallback()
被转移到在C++层面,它首先被存储到env中
env->set_tick_callback_function()//将_tickCallback存储到env中
↓
env->SetMethod(process, "_setupNextTick", SetupNextTick);//调用上者,js中process._setupNextTick的真身
被存储到env的_tickCallback()
被调用流程如下:
env_->tick_callback_function()//取出_tickCallback执行
↓
InternalCallbackScope::Close()//调用前者
↓
InternalMakeCallback()//调用前者
↓
AsyncWrap::MakeCallback()//调用前者
↓
被多个封装异步操作的类继承并调用
↓
被uv_run()执行,从而实现在每个phase之后调用process.nextTick提供的回调
整个过程分析得比较粗糙,后面其实很多细节没去寻找,不过大家可以从以下的参考资料补全其它细节。例如timer的整个执行流程可以看
《从./lib/timers.js来看timers相关API底层实现》,是对我没提及地方的一个良好补充。
参考资料
由于node发展非常迅猛,很多早期的源码分析已经过时(源码的目录结构或者实现代码已经改变),不过还是很有指导意义。
- Event loop in JavaScrip:启迪我找到答案的最为关键的文章
- 使用 Google V8 引擎开发可定制的应用程序:这篇文章介绍了v8引擎暴露C++对象给js的方法,对读懂node源码非常有帮助
- 深入理解Node.js:核心思想与源码分析:对node源码的一个全面分析,基于node 6.0
- node 源码粗读系列:基于9.0的源码分析,非常详细且跟上了最新变化
- Node.js挖掘系列
- node源码详解系列
echart单轴散点,x轴怎么显示成日期格式?
初次使用echarts,项目中要做一个下图的样子 但是x轴表示日期那块不知道怎么做,请教一下大神们 下图是我做的不成功例子
招聘 | 区块链攻城狮专场,Cypherpunk 自由与互联网的未来
过去十年,互联网让信息交换变得更快; 未来十年,区块链会让价值交换更快。
我们正打算在这个世界从信息往价值交换转变的路上,做一些事情——让大众更容易地,学习和应用区块链。 如果你对这个方向颇有兴趣,想在瞬息万变的时代不被淘汰,欢迎加入我们。
关于 Blocks.tech
Blocks.tech 以区块链为核心,聚焦 FinTech 金融科技,我们聚集最优质的媒体人,对冲基金从业者,区块链产业一线玩家,旨在成为全球领先的区块链数据供应商,提供区块链新闻、项目及公司信息、数据与价格服务。
我们的团队成员来自《彭博商业周刊》、《周末画报》、创业黑马等等,在我们背后是国内最懂区块链和数字货币的资深从业者。
招聘目录
- Node.js 开发经理:区块链金融产品方向,20-35k
- Node.js 开发工程师:区块链/大数据方向,12-25k
- 高级前端开发经理:16-25k
- 前端工程师:10-15k
- 高级后端工程师:Python、Django 方向,15-30k
- 后端⼯程师:工程师 8-15k;实习生 4-8k
工作地点
北京 · 知春里、三里屯
我们希望你:
要足够热爱编程,对技术能够始终保持欲求不满的渴望;
对代码要像对待女(男)朋友一样,百般呵护、体面;
认知区块链及加密,有不同程度加分;
渴望学习,渴望进步,渴望一切可以提升自己的机会。
我们回报你:
富有竞争力的行业薪资,对于有能力者上不封顶;
创业团队,无限的晋升机会;
有机会与全球顶尖前沿区块链团队的创始人面对面,如有能力采访,甚至深度对谈;学习进阶的回报会超你想象;
有机会外派至美国硅谷、英国伦敦等办事处。
Node.js 开发经理:区块链金融产品方向,20-35k
岗位职责:
负责公司金融产品项目开发管理;
对接产品部门和市场部门,进行需求管理和实现,并实施进度控制;
对产品的性能和安全性负责,调动公司资源进行代码质量管理和安全审计。
职位要求:
对区块链行业和技术有浓厚兴趣;
三年以上工作经验,至少一年管理岗位经验;
熟悉 Node.js 技术栈,对主流 web 框架(Express、Koa)有深入理解;
对于并发模型、原子操作、数据锁、数据库调优有深刻理解;
有测试驱动(TDD)开发项目经验;
有金融产品,业务流产品开发经验为加分项。
Node.js 开发工程师:区块链 / 大数据方向,12-25k
岗位职责:
参与数据库模型设计;
参与项目服务端核心开发;
针对业务需求编写高质量的代码;
与其他小伙伴配合完成产品开发工作。
职位要求:
1-3 年以上的 Node.js 后端开发经验;
熟练掌握 Node.js 服务端开发;
熟练掌握 ES6 语法,及模块化机制;
熟练使用 Express、Koa(1,2) 等后端 web 框架;
熟练掌握 MongoDB、Redis 等各种类型数据库;
熟练使用 Git、GitHub 代码管理;
熟悉 Linux 常用 Shell 命令;
深入理解 Node.js 的特性与 Node.js 优劣势;
对 RESTful API 有一定的了解;
逻辑思维能力强,有责任心,良好的沟通能力,团队合作精神。
加分项:
了解其他后端语言特性,明白语言之间差异性;
了解前端,熟悉前后端工作职责,能站在全局角度设计接口;
了解 DevOps,使用过常用的自动化工具;
在 GitHub 上有维护着自己的开源项目。
高级前端开发经理:16-25k
岗位职责:
参与互联网前端产品架构策划;
与产品和市场部门配合,快速实现需求;
建立和维护前端测试体系,敏捷开发体系;
带领初级前端工程师进行开发。
职位要求:
3 年以上工作经验,至少一年管理经验;
熟悉算法和前端技术发展趋势;
强悍的 js 编程能力,最好有全栈开发经验;
熟悉主流开发框架中的一种或几种(Vue、React、Backbone…);
熟悉 Sass, Less 有开发抽象 CSS 模块的能力;
熟悉主流依赖管理流程(Webpack、Bower、Grunt…);
多屏幕尺寸兼容,多浏览器兼容开发经验;
如果有,请提供 GitHub 帐号。
前端工程师:10-15k
岗位职责:
参与研发产品,配合后台开发人员实现软件客户端的界面和功能;
与产品经理配合同实现软件 UI 和交互方面的开发需求,确保产品具有优质的用户体验效果;
根据产品需求和项目设计要求,完成网页设计和页面制作;
创新界面设计及优化交互体验,提出产品进行结构、流程的优化建议。
职位要求:
1 年以上工作经验;
了解 ES5、ES6 语言规范;
了解并使用过 Less、Sass 等预编译语言;或前端工程化工具 Webpack、FIS、gulp等;或者 MVC、MVVM 框架等经验的优先;
有后端语言 Node.js、Python 等开发经验优先;
计算机及相关专业优先;
英文听读写能力强优先;
如果有,请提供 GitHub 帐号。
高级后端工程师,Python/Django 方向,15-30k
岗位职责:
参与互联网产品的后端架构策划;
实现后端关键模块的搭建;
实现内部数据 API 的设计以及外部的数据 API 对接工作;
对于单个产品和关系部门一起确定需求,制定开发计划;
日常代码审计,管理项目质量;
指导初级工程师进行工作。
职位要求:
3 年以上工作经验;
熟悉算法,熟悉互联网技术演进趋势;
精通 Python 技术栈(Django、Flask);
熟悉常用数据架构技术栈(MySQL、Redis、MQ、ELK、Solr、Kafka 等,有搭建维护经验);
熟悉 TDD/BDD 开发流程,遵守 PEP-8 规范;
性格开朗,快乐开发;
如果有,请提供 GitHub 帐号。
后端⼯程师:工程师 8-15k / 实习生 4-8k
岗位职责:
负责产品功能逻辑研发;
负责爬⾍框架研发;
优化业务架构和技术架构,进行效率、性能、可靠性等指标的整体提升,解决项⽬开发过程中遇到的实际问题。
职位要求:
一年以上工作经验(实习生不做要求);
了解基本算法,熟悉 TCP/IP 基本原理理;
熟悉 Python、Golang、Node.js 编程语言中的一种或几种;
熟练掌握 Linux、MySQL、Nginx、Redis、MQ 系统环境;
了解 Django、Flask、Pandas、NLTK 等开发框架,是加分项;
计算机及相关专业优先;
具有良好的开发习惯,了解 TDD、BDD;
如果有,请提供 GitHub 帐号。
有意向者请发送 PDF 格式简历至
hr@blocks.tech
邮件标题请注明「姓名+应聘岗位」
欢迎推荐身边大牛,入职即送伯乐奖金!
使用 ssh2 遇到的问题
问题描述:
- 最近想通过node.js来ssh到远端server, 于是参考了ssh2的官网写法,使用时发现,多次调用后,出现了memory leak…
- 感觉conn.end()并没有被终止,每次调用都带着上一次的结果,比如第10次调用的时候,就会打印10条 “ console.log(‘Client disconnected’); ”
报错内容:
场景描述
- 远程server登录时先输入root密码,所以在stream.on(‘data’)里加了 “if”去匹配 STDIN data 的关键词,成功后输入root 密码
- 输入“who”指令,匹配要的关键字后,执行stream.close()退出
- 实测中,指令都是被执行成功的
代码如下:
"use strict";
var sshClient = require('ssh2').Client;
var conn = new sshClient();
module.exports.captureWhoAmI = function(targetIp) {
var retryMaxTimes = 30;
var connection1 = {
host: "10.99.143.66",
port: 22,
username: 'username',
password: 'password'
};
conn.on('ready', function () {
console.log('Client connected to :' + targetIp);
var num = 0;
conn.shell(function (err, stream) {
if (err) {
console.log(err)
}
stream.on('close', function (code, signal) {
stream.end();
conn.end();
}).on('data', function (data) {
num += 1;
if (num >= retryMaxTimes) {
console.error('Too many times, giving up to capture!!!');
stream.close();
}
var dataString = data.toString();
if (dataString.indexOf("Password:") != -1) {
stream.write("rootPassword\n");
stream.write("who\n");
}
if (dataString.indexOf("user") != -1) {
stream.close();
}
console.log("conn num:" + num + ":" + data);
}).stderr.on('data', function (data) {
console.log('STDERR: ' + data);
});
})
}).on('end', function() {
console.log('Client disconnected');
}).connect(connection1);
}
希望大家帮忙指点一下,多谢。
【深圳有赞】【15k-30k】「前端工程师」and「微信小程序工程师」
关于有赞
创业是个失败概率很大的事情,我们很高兴从2012底到现在还活着, 而且还活的很不错。
目前有赞旗下的产品有:有赞微商城、有赞零售、有赞美业、有赞餐饮、有赞收银、有赞批发、有赞会议、有赞买家版、有赞微小店。
我们认为,相比较业务来说,团队才是公司的核心。有赞没有“员工”只有“小伙伴”,也没有人姓“公”名“司”,我们有一群聪明、有要性、又皮实的伙伴,这才是我们最大的资产。
我们有很浓的工程师氛围,每周都有很多的有意思的分享:有出国旅游的分享、有如何搭讪的分享、有如何装修房子的分享…技术的分享更是数不胜数。
我们的工作不是很轻松,但我们的氛围很轻松,很open,公司里随处可见骑着独轮车来回跑的工程师、懒人沙发等等,每个月都有节日、新人表演、拓展、派对等,对我们来说,天天都可以是节日。我们倡导简单直接的沟通方式,希望做一家通透的公司。这里并没有过多的等级划分,你可以随时提出自己的意见和任何人PK。
福利方面全额缴纳五险一金,每月880的餐补与100的电话费补贴。经常出去吃饭运动,办公室里常备零食、水果,休息区有电视、桌上足球、台球、三国杀。有空会一起王者荣耀、吃鸡,也会一起去郊外骑马,射箭。每天晚下班的打车费报销,每年给你和你的家人提供旅游和体检等。
关于美业前端
深圳有赞前端团队,一支 11人+ 的团队。 我们的前端无需兼容IE,几乎不做活动页; 标配 Macbook Retina + 大显 + 机械键盘,每年至少一次公费参加行业会议的机会; 2018 年开始,我们是 14薪 + 年终奖;
核心业务:赞美业商家版、有赞美买家版、及有赞美业小程序。 日常技术栈: ReactJS、VueJS、NodeJS、微信小程序等。
我们奉承作为「前端工程师」之前,首先是一个优秀的「软件工程师」,你的知识面应「擅」于前端而不「限」于前端。 我们奉承结果为导向,积极主动的态度。
写作是我们的日常习惯: https://segmentfault.com/blog/fedbjhttps://tech.youzan.com/
开源是我们的业余爱好: https://github.com/youzan
关于我们的业务
我们做的是 Sass 产品,如果你对我们前端团队的业务有兴趣。 我们乐意给你们展示一下。
【有赞美业商家版】
【有赞美业买家版(商家示例)】
【有赞美业买家小程序版(商家示例)】
然后,来点生活照把
先来 P 过的。
然后,来点没P过的。
刚搬新家,搞了一波「暖房 Party」
外出团建的时候,会玩一下飞机
万圣节的时候,我们的首席女神 。
以及我们的霸王龙。
我们注重回顾以及反省,这是年终复盘的回忆。
有时候无耻的装一下嫩,这是六一节。
关于职位
前端工程师
【职位描述】
1、核心技术栈:React(PC),微信小程序,Vue (Mobile),Node(中间层)。 2、参与面向商家的Sass系统开发。 3、参与面C端的移动商城开发。 4、参与基于业务和技术场景的架构、性能优化。 5、参与开发流程改进,公共组件和工具建设,用技术手段提高生产力。 6、咱们无需兼容IE,几乎不做活动页,专注把技术发挥到极致。 7、咱们对新技术保持开放态度,每年有公费参加行业会议的机会。 8、咱们标配 Macbook Retina + 大显 + 机械键盘; 9、阅读、分享、写博客是咱们的日常习惯。
【职位要求】
1、本科及以上学历。 2、扎实的计算机基础。 3、优秀的逻辑思维和编程能力。 4、HTML、CSS、JavaScript 基础扎实。 5、至少熟悉一个主流前端框架。 符合以下任一条件者优先考虑 1、苹果重度用户、Linux爱好者 加分。 2、参与开源并贡献过代码 加分。 3、有后端、iOS、Android开发经验者 加分。 4、有大规模前端项目开发经验者 加分。
微信小程序前端工程师
【岗位职责】
- 参与有赞美业微信小程序端的项目开发。
- 推进微信小程序的开发架构和性能优化。
- 推进微信小程序的开发流程、公共组件、工具建设,提高效率与生产力。
- 参与面向商家的 Sass 系统开发。
- 参与面向 C 端的移动商城开发。
- 咱们对新技术保持开放态度,每年有公费参加行业会议的机会。
- 咱们标配 Macbook Retina + 大显 + 机械键盘。
- 阅读、分享、写博客是咱们的日常习惯。
【任职条件】
- 本科以上学历。
- 熟悉微信生态及微信小程序生态;
- 扎实的计算机基础。
- 优秀的逻辑思维和编程能力。
- HTML、CSS、JavaScript 基础扎实。
- 熟悉以及深入理解一个主流的前端框架。
【以下任何一项都可以是亮点】
- 有微信相关或微信小程序项目经验。
- 苹果重度用户、Linux爱好者;
- 重视规范与质量,患有中晚期代码洁癖癌;
- 参与开源项目并贡献过代码 (请在简历里附上Github链接);
- 有研究与思考,输出博客文章的习惯(请在简历附上博客链接);
- 有 iOS 或者 Android 开发经验;
- 有后端开发经验;
- 有大规模前端项目开发经验;
联系方式
简历投递到:huangjerryc@gmail.com 邮件标题格式:【应聘】岗位 + 姓名 + 来源