前言

又要开始总结了,其实这个拖了很久了,大概有半个月了吧。很早就想开始写了,却不知道从何开始写起。那就从回到武汉开始写写吧。回武汉到现在,正式差不多七个多月了。当时满头就是回家,确实回到了,武汉离家近了,过节的什么的都可以回家,没事见见朋友,一起吃吃饭,坐一坐。然后武汉的工作环境确实不是很理想,it行业相对于北上广深差距还是相当大的。回来之前,自己已经有心理准备的,但是过了半年之后,现在算是基本能接受吧。了解了一下武汉的其他行业的情况,真是不容乐观。也许自己看到的还是太片面了。好一点的貌似就是公务员、it、金融行业的了。回来就是解决一件事情的,这件事情没有解决之前,应该不会离开武汉的。最后既然是总结,那还是要写一写半年来的所做的事情,和认识的一些人。

开年计划

在2016年的时候就开始写总结了,那之前总是计划很多事情,但是很多事情都没有完成。花了一个月的时间,每天一小时把网易公开课的http://v.163.com/special/positivepsychology/学刷了一遍,才理解知晓了一些事情,人的精力是有限的。于是给自己的定的是,主要事情不超过三个。于是2017开年就给自己定了三个事情。由于一些原因,好像这两件事情都不能说,另外一件事情就是读书,多读书。

做的事情

到现在为止,一件事情已经完成,但是付出了比一般多的代价。其中其他两件事情都正在进行当中。进行的还是一般般吧,不是特别好,也不算特别的坏。在武汉参加了一些武汉的群体,线下见了几个朋友。好吧,其实线下就只见了一个朋友而已。小不。做运营的小伙伴,很佩服他。从互联网行业,直接跟朋友一起去创业,搞实体生意了。这里其实有点对不住他,因为自己的原因,没有跟他继续积极的搞事情。

读书这方面,基本每个月一本书吧。目前来说主要还是运营方面的书籍,因为中间加入了一个小密圈,意识到现代互联网社会运营的重要性。于是很关注运营方面的东西,同时也关注理财和思考力方面的。

为什么关注运营方面的书籍,最近的一些思考以及别人的一些提示。现在社会从物质匮乏,到物质丰富,再到现在的物质泛滥成灾。要想获得有用的信息,成本是非常高昂的了。如何使自己的产品在一个信息爆炸的时代,吸引用户和获取关注,是非常有难道的了。以前是金子到哪里都会发光,酒香不怕巷子深。但是现在社会的时候,金子太多了,你不努力发光,别人的光芒就把你的遮盖下去了。你不努力传播自己的酒(产品),你获取的用户就是有限的,或者就被别人的墙给堵死了。产品是道,运营是术。成功让大众接受的产品,二者缺一不可。

理财方面的书籍。工作几年之后发现,如果想早点过上自由的生活是需要物质基础保障的,然后仅仅只靠工资,只能满足基本的生活,想要获取更多的财富,就需要进行投资理财,增加被动收入。这个给我触动最大的事情,就是16年整个中国的房价,一年的涨幅让普通工薪阶级多奋斗几年,甚至几十年。所以现在有点愤青的倾向,但是生活在这个时代,就要去学会接受,学习去抓住机会改变现状。一味的埋冤是没有任何意义的,只有找解决方案,解决问题才是正道。遇到问题之前,还要尽可能的预判,预测评估,提前做好准备。在看《运营之光2.0》里面就提到了一个abz计划。 a计划是你正在做的工作或者事情,保证基本生活。b计划就是你业余时间投入的事情,这个事情可以变成你的a计划,需要有价值产出的。哪天a计划,失败了不行了。你可以马上切换到b计划当中,不至于自己处于完全被动的状态。 z计划就是存钱,在你ab计划都没有马上进行的时候,你可以依靠你的z计划,存储的一些钱,可以让你至少度过半年的无任何收入进账的生活。

思考力方面书籍。人的一切行为都是内在思想的映射。要想获得更多的回报,更快的发展,你需要自己的眼光和眼界别别人看的更远。生活的机会特别多,在现代社会,你都可以完全的靠卖自己的知识,自己的文字来赚取生活需要的钱财,这个是无法想象的时代,这是一个充满可能性的时代,只要你敢想,敢做,说不定哪天就能实现。在微信听书中,听完了整本的王阳明心学,有些唯心主义的感觉。王阳明中国历史上的两个圣人,孔子和王阳明。他的心学中的一点还是很认可的,你要敢想,敢做,你才可能成功,才有可能取得不可估量的成功。在他读书的时候,他问了自己的老师,一个问题,人读书是为了做什么。他的老师回答是做官,服务百姓。他不认同,他说人读书是为了做圣贤,做圣人。然而他的老师,他的父亲,都不看好他,都是各种讽刺。最后他依靠自己的努力和才情,龙场悟道成功。成为中国历史上,除了孔子之外的又一圣人。一个人不敢想,不敢做,那是永远无法突破自己的瓶颈,突破平庸的。书是好东西,书中自有黄金屋,书中自有颜如玉。历史的知识,通过书的载体,传递下来。我们需要努力的去吸取知识,提升自己,融会贯通,早日实现自由。现代社会,赚钱的方式很多,然后很多人看不见,摸不着。同样的事情,同样的现象,有的人为什么可以透过现象看到本质,看到未来的商机,抓住机会,早日实现了财富自由。

任何时候以增加自己的能力为准则做事,每天也是如此。回到武汉懒散了许多,最近几次看到自己在知乎上面的一条评价,任何时候以提升自己的能力为第一要务。遇到任何事情,你所能依靠的就是你自己,哪怕是你的亲生父母,也不是所有的事情都可以依靠的,有些事情也是他们无能为力的,所以努力的提升自己的能力。如何提升自己的能力,一个重要的理论,你需要培养两个以上可以融合的技能,当80%与80%叠加之后,你的核心竞争力,会更加的强,在你的团队你的核心综合实力至少是排在前面的。

正在做的事情

在做三件主线事情之外,另外在看书学习的过程中,添加了另外的几件事情。

  1. 英语学习
  2. 投资理财

所有的所想和理论,只有通过实践才能得到检验,才能更好的感受知识,获取经验。在读书这方面应该说是进入了疯狂的看书学习阶段。作为一个以前从来不看书,讨厌看书的人,现在慢慢的培养热爱看书,至少不讨厌看书的状态,还是很值得鼓励称赞的。据我个人经历,所有的老板都是很热爱学习的,超级热爱学习看书的。其实书是表象,更多的是内在的思维思考力的提升。提升内在的思想力,拉伸自己的天花板。

突然想到非常感谢互联网,身在互联网,感谢时代的红利,身边很多一些大学成绩不错,但是由于专业的选择问题,导致现在找工作的不理想,或者工资相对来说没有计算机相关工作的薪资高。所以这段时间,也是经常碰到别人困惑的时候,没事就调侃别人转行来学习计算机。

现在对自己的内在要求就是学习学习再学习,每天进步一点点,会有复利效应。这个是了解投资理财经常看到的一个词复利。人在一个方面每天积累一点点,到某个时间段,会有指数级别的爆发增长。

时间管理,尝试慢慢培养管理自己的时间意识。以前看到的一句话,越长大自己的时间越少。现在已经感受到了,越长大,生活的羁绊越多,花费在生活的零碎时间逐渐扩大,想要安静的时间块来学习,来做些某些事情,已经越来越苦难了,所以要学会管理自己的时间,管理自己的生活。

人生的意义是享受生活。这是最近思考明白的,也许很浅显,对个人来说,找到了一丝丝的方向感,而不是像之前的那种苍白无力,迷迷糊糊的过生活,活在别人的世界和眼里。

下半年计划

下半年的计划,多看书,多学习,多接触了解不同的事物和学习不同的事物。二十岁就是试错的年龄,想做的事情勇敢去做。花有重开时,人无再少年。让你感到快乐的事情,勇敢的去做吧。

  1. 英语学习
  2. 读思考力的书籍
  3. 个人的生活问题
  4. 房子装修问题

房子马上要交房子了,就剩一两个月了,终于有自己的房子了,不用再租房了。伴随而来的,还有房屋装修的问题。这个也是需要花时间和精力的。在工作之余,还需要关心房屋的装修,注定这个下半年不会有太多的时间或精力去做其他的事情。这个是非常重要的事情,需要放在心上。

好吧,差不多就是这些了。还有好多事情想去做,却没有时间,根本的原因是没有足够的金钱基础支撑自己去做想做的事情,努力赚钱。或者想更好的方式去赚钱,提高进度,提高赚钱的效率,改变赚钱的方式。

前言

为什么同样是看书,看小说类的与看技术知识类的书籍,人的反应会不一样?

一个多月没有写博客,直到最近几天突然想明白一个事情,觉得有必要写一下。博主最近经常看书,看看小说和技术类的书籍。看小说的时候,那是聚精会神,神采奕奕。而看技术类的书籍的时候那就是吸毒一样,流眼泪,打哈欠,想睡觉。于是就有了一个开始的问题。

思维改变生活

我们先把书籍简单分类一下。这里的分类是根据《如何阅读一本书》里面初始介绍的

  1. 一类是获取知识,即增长智慧,提高自己理解力的一类书籍;
  2. 一类是获取资讯,即获取一些信息,不需要过多的思维思考;

那我们现在再把之前的小说类与技术类的书籍简单的按照上面的规则分类一下。就很明显的区分出来。小说类、传记类的书籍都是属于获取资讯类的书籍。技术类专业性质比较强的书籍都是属于获取知识,增长智慧类的书籍。讲到这里,我们就可以进一步思考一下。在获取读这两类的书籍的时候,我们的大脑是如何思考的。

获取知识角度

小说类、传记类,获取信息,然后存储一下,是不需要经过处理的,只简单的存储。技术知识类,就完全不一样了,需要在前有的知识基础之上,进行一系列的思考,逻辑推导,才有可能理解。 从不理解到理解的过程。这是一个理解力的提升,需要不断的练习和实践。这里可以根据人工智能获取知识的过程得到一些理解的借鉴,人工智能需要大量的练习来学习掌握知识。理解力不是凭空出现的。 在读小说类、传记类的就是一个获取知识,从不知道到知道这件事情。这个过程理解力消耗非常非常至之少的。从这个角度来看,两类书籍获取“知识”,大脑消耗的理解力是不同的。人的表现自然也就相应的会发生变化。

本身知识内容

小说类、传记类大部分内容是根据现实去构造的,有现实存在的影子,我们可以从我们过去的经验或现实的生活找到参照物,通过想象力形象化、具像化来帮助我们理解,让我们更愉悦的去理解知识。技术类的完全是纯理论的知识,需要完全靠之前的基础,通过自己的理解力,一层一层的递进。跨越层级是非常难以理解的,难以弄清楚知识。在阅读小说类、传记类、史学类书籍,想象力帮助我们丰富场景内容,可以非常好的来促进我们来获取资讯知识。

前言

人为什么而活着?
在很久以前,就在思考这个问题,查过知乎、查过百度、查过谷歌,依然无法找到自己满意的答案。现在有了一些思考和总结,觉得现在可以简单的回答一下这个问题。
人为了快乐而活,为了享受生命而活。

思维改变生活

16年的时候某一天又看到了http://mindhacks.cn/,非常的受启发。思维改变生活,于是再次把他博客全部看了一遍。同时也把他出版的《暗时间》看了一遍。对个人的影响特别大,特别喜欢他的思维方式。同时也特别喜欢博客中所说的,读书要读“经典”。于是之后的日子就常常潜意识的告诉自己要勤思考。

今年过年的时候,经常和朋友打麻将。以前打麻将每年都是输的,逢打必输。今年开始也不例外,一直觉得是技术问题。直到一场之后,本来一开始挺好的,结果就因为一张牌,改变了整个后来的局势,一直输。之后一直就在思考这样一个问题。为什么运势会因为一张牌的错误,导致整个局势的变化,让我不得其解。让我非常非常的困惑。查过知乎、查过谷歌和自己的思考于回忆整个事情的流程。于是找到一点点的蛛丝马迹,事情的结果与个人的情绪强关联。当意识到这个问题的时候,一下子就像泉涌一样,思路完全被打开了。得出一个人在情绪不稳定的情况下,会作出非常多不符合正常状态的判断或选择。这样事情的结果就会出现各种极端,如果放到打麻将上面,当然是必输的了。当找到问题的原因时候,就该寻找怎么解决了。在后面的几次打牌中,当遇到情绪发生变化的时候,积极调整状态,最后几次竟然是连续的赢了。当然也不能作为科学的依据,因为可能有更深层的原因。这里本人提炼出来的是专注力。专注力是可以有效的调整个人的情绪。使之能够做出符合正常反应的动作或行为措施。在16年的时候,花了一个月的时间,在网易看完了http://v.163.com/special/positivepsychology/视频教程。运动可以改变人们的生活和心态。在此基础上,我抽象出来了专注力这个词。当人们专注于某件事情的时候,无法专注于感受自我的情绪,可以快速的平复情绪,让人恢复到正常状态,从而做出符合正常逻辑的选择或判断。

这几天在阅读一本书《故道白云》,一本讲佛陀的书。一个佛陀正道得道的事情。从非常痛苦到拥抱生活,拥抱快乐自由自在的生活。在佛陀得到之后第一个介绍的就是专注,传播大道。虽然只是一个故事,但也传递出来专注的思想。

专注于一小时的生活,活在当下,活出自我,没有痛苦,自然快乐

后结

拥抱生活,拥抱自己

前言

阅读《如何阅读一本书》丰富了自己的学习体系,基本形成自己的方法论。在之前的时候,就一直想形成自我的思维体系,或者说是个人的认知流程。虽然至今没有形成,但是在学习新的知识方面,基本已经有了明确的知识学习流程。遇到新的或者陌生的知识,知道该如何查阅资料,开始学习。

如何阅读一本书

这是一本普及知识的书籍同时也是一本讲解技法的书籍。这本书介绍了阅读的基本概念,同时也介绍了该如何去阅读一本书。这是一本值得阅读的书籍。

阅读的层级

`
本书讲阅读分为了四层:

  1. 基础阅读
  2. 检视阅读
  3. 分析阅读
  4. 主题阅读

基础阅读

  1. 准备阶段
  2. 识字阶段
  3. 快速积累增长阶段
  4. 阅读理解阶段

这阶段主要比较像 学写字 -> 识词 -> 读文章 -> 大量积累阅读。

检视阅读

  1. 快速阅读
  2. 目录
  3. 附录
  4. 结尾

这里的更多是获取一本书的大体结构,识别书籍的主题内容。

分析阅读

  1. 骨架
  2. 血肉
  3. 评论
  4. 自述

这里是用笔者自己的阅读理解所总结的。这部分内容算是全书的重点的重点。

骨架

任何一本书都有自己的纲要脉络,在阅读一本书的时候,读者需要找出作者的骨架脉络。这个跟我们学习新知识一样,我们要从全局掌握新知识的体系结构,至少在大体上要了解正确的方向和体系,了然于胸,不至于在学习的时候迷失于细节当中。
其实归结为两个问题。 这本书讲的是什么? 这本书是如何讲解的?如果你能完全的回答这两个问题,基本可以断定个人已经基本掌握一本书的大体内容。

血肉

基本的骨架搭建以后,就是详细的描述,论述。主旨,然后进行进一步的详细阐述。主要归结为两个问题。主旨是什么?文章是如何论述或叙述主旨的?

评论

评论的基础是完全已经理解或者掌握书籍所说的或陈述的内容。即读者基本完全了解作者所说的内容,是如何安排的。完全的基于读者已经知道书籍的骨架和血肉。然后和作者的思想进行交流碰撞,与作者进行一些思考的陈述与碰撞。

自述

自述是已经完全的掌握,并可以根据自己的理解,基于自己的思想来进行新的组合阐述。是完完全全的掌握了书籍所描述的内容,同时也有自己的理解。知道书本可能有哪些不足的或没有描述到的地方。

主题阅读

知道需要研究的目标或主题。这个是基础。与作者的认知达成共识,至少在部分观点或论据上面达成共识。

前言

活出自我,做一个有独立人格魅力的人
2016年经历了一些事情,对自己的价值观和生活方式影响特别大,这篇博客大概讲述这一年发生的事情。

2016

楼市(选择往往比努力更重要)

2015年10月政府出了二胎政策,当时不知道怎么想的,突然强烈的预感到房价要涨。当天就跟父母说房价要涨,赶紧买房。由于自己的不坚定(主要原因),导致拖延了一段时间。于是在2106年四月去买房时候多花了十多万。同期和几个初中同学一起看房,我做事比较快,三天就把房子看好,首付付好。然而有的同学比较拖延,导致2016年过完了,房子没有买上,同时房价均价飞涨几十万。

在这次的经历中,我得到几个重要的启示

  1. 如果认准某件事情,请全力以赴地去做,不要拖延,否则将来可能付出成本的代价。
  2. 投资理财思想的重视,2016年的房价增长,让一个普通工薪阶层大几年的工作白费了。
  3. 目前中国的经济并不是完全的市场经济,至少楼市依然被政府操控。

在这段经历以后,自己开始更加关注投资理财方面的知识。在之后的这段时间中,自己看中的股票增长了10%以上,这很好的论证了自己的分析和预估能力,作为一个现代社会的人,我觉得每个人都要懂得理财。

身体健康

我是一个本身就比较注意健康的人。但是还是因为一些不良的生活习惯,导致自己去医院了一趟,在医院躺了半个月。

在这次的经历中,我开始尝试的生活规律化

  1. 每天早晚一杯温开水,每日尽量多喝白开水
  2. 早睡早起的睡眠习惯
  3. 一个星期坚持锻炼几天

在16年的总结当中,看到大量的人说到健康问题,让我自己深深的担忧,同时让我更加坚定了要规律的生活,同时要加强自己的身体锻炼。

时间成本

在知乎上面看到一个问题,大概是《在BAT五年,你获得了什么?》
以前我也是认同那种观点,去大公司和小公司无所谓,只要自己努力就行。然而随着自己的经历和时间事实的论证,我的大部分观点都是错的(这里面的都是针对普通的毕业生,特别牛逼的人除外)。

在大量的回答中,我要看到了几个点

  1. 金钱物质回报,这个是大部分小公司无法给予的
  2. 技术技能和人脉关系的提升,这个更是小公司无法给予的
  3. 平台真的很重要

每个人时间基本都是公平的,如何才能让自己的时间利益最大化,那是我们需求去思考和论证的。如果现在让我回答,从成长角度,从刚毕业的角度,从时间成本角度,我一定推荐去大公司或者至少要去大公司经历一下,对自己的人生方向发展和创业都是有重大影响的,自己现在也会更加偏向大公司。

追求事物本质

2016年初的时候定下了要多看书的目标。也确实看了一些书,说一下对自己的影响特别重大的书籍,推荐大家一定要去看看。 《暗时间》《断舍离》(这本书内容后面介绍)

个人觉得《暗时间》里面的内容很精练,需要多次的阅读才可能更好的去领悟作者想要表达的思想。

在第一次阅读中,有几条对自己的思想影响比较大

  1. 知道事物的本质和知道问题所要问的内容
  2. 时间要充分利用
  3. 读书要读经典

书中列举了特别多的例子来论证,我们在看待事物的时候一定要知晓尽可能全面的知识,才可能做出更加正确的结论或者更加友好的解决方案,否则以过往的经验来判断,经常得到错误的结论。自己在学习产品方面知识的同时,也同样得到过类似的结论,一定要知道用户的确切需求,不要是伪需求。这样才可能做出符合用户需求的产品。

同样的时间,同样的事情,每个人做出来的效果是完全不一样的,充分利用时间让自己更快的成长,让自己得到更大的回报。

现代社会信息大爆炸的时代,信息冗余的时代,我们需要学会挑选知识来学习,需要一定的技能来让自己能够挑选自己需要的知识内容。现在社会,网上太多知识垃圾,让我们难以跳出,让我们难以找到自己想要的知识内容。

国考

在准备公务员的国考当中,我学习了申论,申论给了很大的启发或者灵感,让我确认了一个非常重要的结论。生活中的所有事情都可以归纳为:遇到问题和解决问题。在得到这个结论的时候,我心态一下子放松起来了,因为我是一个热爱折腾的人吧,虽然大部分事情并没有获得很好的结果。但是我对生活坦然了,不在害怕生活的种种。当认识到了问题的本质的时候,就会有方向,就可以找对应的解决方案。人们对于事物的恐惧来自于内心对于事情的不了解。当对一个事情了解其本质之后,心中就会有一种踏实感,安全感。

开心快乐最重要

人生匆匆数十载,开心快乐最重要。

我一直在思考生活的本质是什么?无法解答,也许到离开世上的那一天也无法解答。我此时此刻能回答的就是生活开心快乐是我们永远的追求,这也许是本质。
生活中有很多问题,很多需要我们去解决的事情。大体来讲,是为了让自己开心或者自己的亲人开心。这是我们的终极目标吧。
生活中有很多苦难,我们能做的就是在自己生活范围内,让自己尽量的开心去渡过每一天。毕竟人生匆匆数十载而已。
做一个有意思的人,让自己的生活充满乐趣。如果有下辈子,我希望自己能早点明白这个道理。

信命,信自己,坦然面对生活

尽人事听天命
最好的朋友,谈了快十年的感情,真的是才子佳人,本来要结婚的,却因为不能生孩子从此人生分道扬镳。
人生不如意十有八九。
我觉得人生已经没有什么比这更悲哀的事情了,有些事情就像上帝跟你开了一个玩笑,而不是假的玩笑。有时候多么希望这一切都是假的。
生活变化无常,在有限的时间里努力做你喜欢的事情,爱你想爱的人。过程很重要,经历很重要,我们努力去做好我们可以做的事情,结果有时候真的看天了。

立足当下

《断舍离》中有一个观念对我的影响特别大。在时间轴上,我们能做的就是今天的事情和计划明天的事情。我们要做当下此时此刻对自己重要的事情,让自己能够开心的事情,这也许就是我们该有生活态度和生活方式。人的精力是有限的,我们不要高估自己的能力,同时也不要低估自己的坚持,我们要尽量做此刻能够让自己开心或者让周围朋友亲人开心的事情。事情是在自己能力掌控范围内的同时能够让自己开心的。

积极的正能量

以前的我是偏执的,固执的,认为事物非黑即白。在自己的工作经历和身边朋友的影响,我变了,我变的不再那么理想主义了,变得圆滑了,变得更懂得生活了,不再那么偏执了,人生要有经历和乐趣。

人要有自己坚持和放弃,懂得坚持,懂得放弃。

祝愿所有人新年快乐,开心快乐健康地生活

前言

是的,我又换工作了,距离上一次离职不到半年时间,这次是从广州回到武汉。谈谈这次找工作的经历,主要因为之前的两份工作都是没有怎么面试,直接入职的,所以这次来说,本人觉得多少有值得写的地方。国考,找房子,入职等等一些事情耽搁,到现在终于有空闲的时间记录一下这段时间的感受。

目标需求

因为之前的几家公司都是创业型公司,所以这次回武汉是希望能找一个轻松或者自己感兴趣的方向干干,工资不要太低,满足目前自己的基本生活和房贷,每月多少有点剩余差不多就满足了,初期心里目标是这样的。下面简单整理一下。

  1. 双休
  2. 工资(武汉中等水平)
  3. 工作内容(自己喜欢或者自己熟悉的,压力偏小)

工作投简历

拉勾投了四家,光谷论坛放了简历,联系了两家。找到工作,总共面试了5家。还有几家没有去联系,所以面试都没有去面试,因为找的工作比较满足自己内心的目标,直接就入职了。

暴露的问题

自身基础知识点的不牢固,本人不是太注重记录零碎的知识点。心中想法一直都是够用,知道出了问题去哪里找解决方案就好,对知识细节完全不重视。这样的心理是受大学一位教Java的老师的影响,印象特别深刻。当时学Java,查看API文档,老师说的一句话“用的时候,知道怎么查资料,查API文档就好,不需要死记硬背这些API的”。估计是自己理解片面了,所以在这次找工作的过程中,这方面特别突出,感觉很吃力,因为大部分用过,会用,但是叫自己说一说,叙述叙述,脑袋里面就一篇空白了。再加上自己语言表达能力有限,稍微说快一点就让人感觉特别着急了。

最后

在找工作的过程中,求职者是尽量满足市场的需求,把自身的原有技术提升或者学习新的技术去满足各个公司的需求,同时也尽量满足自己的需求。不过基本是市场导向,市场需要什么样子的人才,自己就需要学习某方面的技能,满足市场需求。这里在zealer一篇科技相对论视频里面看到的,讲述三星帝国是如何起家,崛起的。牢牢的抓住市场需求,一步一步的成就自我,取得今天的成绩。作为一个行业的一员,时刻关注行业的发展方向,关注行业的标杆企业,学习新的技能,提升自己的竞争力,也许某一天你就是这个行业的领头羊。

好的产品是有非常非常重要的两点:1,满足用户需求2,良好的用户体验。如果把自己当做一款产品,自己是否满足这两点。自己该如何做到这两点,需要我们自己去摸索和探索的。人人都是自己的产品经理,把自己管理的好,相信自己绝对不会是一个太差产品经理。这里说的产品这块,是希望自己未来可以做出一款自己满意的产品。以产品的角度来做事,让自己每天进步。

回答一下面试中经常被问到的问题,你从过去的几份工作中,哪份工作你觉得学习的东西最多? 真的,每份工作学到的东西都很多,人本身都是不完善的,一直就在不断的进步和完善自我。技术层面感受最深的是专业的debug能力,让我自己有了信心,面对错误不会慌乱阵脚,专心查找资料,解决问题。其次思想层面是眼光战略,战略系统化。而不是用战术上面的勤奋来掩盖战略上面的懒惰,这样常常做不好一件事情,做事往往事倍功半,吃力不怎么讨好。

最近也看了一些申论视频,觉得特别有意思。总结的就是申论答题就是问题加对策。其实生活中的所有事情不都是这样的嘛。遇到问题,解决问题。完完全全的就是生活的根本本质呀。

自身给自己的定义是概念无能,特别无法理解一些新的概念。总觉得有时候非常简单的事情,非要弄的那么高深复杂化,让人摸不着头脑。另外的角度说明自己不理解新生事物的来龙去脉,没有仔细的去了解。如果想了解一些事情,总是有一定的切入点,让你找到命脉,找到熟悉的地方去了解新鲜的事物或者新的知识。

总结关键字:市场需求 产品 思想 本质
实践是检验真理的唯一标准

最后以最近看到的一个话题结尾。如果你不做现在这份工作,你还能做些什么养活自己?

前言

第一次写读后感,其实也就是自己的一些小想法,小感悟。–<<暗时间>>

说起来看这本书的原因,逛diycode.cc发现上面的几位大神的博客,不知道刘未鹏是谁,然后就在好奇心的驱使下,去简单的搜索一下,去谷歌搜索了一下他的相关资料。然后继而发现这本书<<暗时间>>,到亚马逊和知乎上看了一下这本书的评价,看到本书评价挺高的。而且看到里面的说的心理学相关的,正好自己对心理学也比较感兴趣。于是在网上找了一下资源,周末花了一天半把这本书细细的看了一番。里面的东西挺认同的,而且部分正好也是自己处于萌芽的摸索阶段,某些行为和里面的部分内容挺相似的,然后自己有了更系统一点点的认识吧。现在简单的记录一下,也算是对这段时间思考和这本书的个人的认知总结吧。

我想以后自己应该还会来修改此篇博客吧,毕竟是第一次的自我思考的总结。

看清问题本质,多问一个why

人们对于未知是恐惧的亦或者是好奇不知所措的; 对于模糊的问题,是没有完全理解的; 对于已知问题, 是知道如何解决问题,采取何种措施的。

书中从多个角度来陈述**”客观存在的事实”,这里我暂且称为“客观存在的事实”**。不同的解读,不同的结论。

本书目录中的”暗时间”,”设计你自己的进度条”,”如何有效地记忆与学习”,”学习密度和专注力” 等等,无时无刻不在描述客观存在的事实。后面的算法的讲解更是在描述可观存在的事实规律。认识客观存在的事实是一个艰难的过程。人类是感性动物,感性往往战胜理性。经验是把双刃剑,有时候有助于我们,有时候也会害了我们。面对问题,我们需要客观的理清问题,寻根溯源。理清问题,才有助于我们更好的解决问题。书中很多例子,提醒我们需要认识客观条件和事情,不要妄下断言,着急采取措施,导致不必要的错误结论出现。

书中的小熊调试方法,以及我们听到过的小黄鸭调试方法,都在描述一个让我们理清事情的脉络,认识客观存在的事实。

我们需要认清问题,理解问题,更要问一个为什么。驱动我们去理解和解决问题。

战术上面的操作

电脑不用一天是一天,电脑不用也是一天,而人脑是目前来说是精密的电脑,擅用大脑。

遇到问题,勇敢的去解决,去搬走。要不然下次遇到,依然是无解的。虽然前期可能花些时间,但是随着解决的问题越多,自身解决问题的能力越强。以后遇到的问题,类型的问题,会节约大量的时间,让自己去做一些自己更爱做的事情。

小孩学步,你永远不会记得自己跌倒多少次,才学会走路的。现在遇到的问题,你才爬起来几次继续前行。

过一天少一天,把时间花在你认为值得投资的地方,让你开心的地方,提升你幸福感的地方,可能过程是痛苦的,最终的目标是幸福的。

表达能力,目前仅能表达如此

下载地址暗时间

前言

最近需要了解virtual dom, 查了一下网上的资料。发现比较好的文章,翻译也已经有人翻译好了。然后这里就做一个简单的记录。文章链接在最后。

原理概述

virtual dom, 主要的实现原理就是用JavaScript 中的{}对象,来实现一个与树真实的dom树对应的模拟树,然后每次更新找出不同之处,进而来操作真实的dom树。

总结

研究理论和原理发现,最终回归到算法。
算法和数据结构才是重点。

引用

如何实现一个 Virtual DOM 算法

How to write your own Virtual DOM
中文对照翻译

react 设计原理

virtual dom diff

前言

生命不止,折腾不息。一不小心开始入vim坑。作为一个出入vim坑的人,首先就是要解决vim配置问题。

配置

解决vim配置问题,需要明确一下此时此刻作为一个vim新人的需求。本人目前只写写HTML,CSS,JavaScript,ReactJS,Vue,Node.js的人,目前只需要语法检测和代码提示

  • 行数显示
  • 字体自定义
  • 主题自定义
  • 语法检测
  • 代码提示
  • 项目树状结构
  • 文件搜索

插件

在安装插件之前,我们需要先安装一下插件管理工具,在网上浏览了大量的教程,大部分推荐使用vundle 管理插件,中文文档可以看这里。这里的插件自己可以选择自己需要的就好。

  1. 主题 dracula 最近喜欢此主题,电脑上所有的主题都已设置为此主题
  2. 文件操作 nerdtree 非常棒的文件操作插件
  3. 文件查找 ctrlp 文件查找,类似sublime 中的cmd(ctrl) + p操作
  4. CSS语法支持 vim-css3-syntax
  5. JavaScritp语法支持 vim-javascript
  6. 代码补全 YouCompleteMe tern_for_vim 语法提示

注意

在安装YouCompleteMe 的可能有些复杂可以参考这里的教程

参考教程

AlloyTeam
VIM的JavaScript补全
每日一vim

学习教程

youtube视频教程
练习教程

test

前言

接触Reactjs差不多四个多月了,从最初的学习到最终的项目生产(webapp),多多少少有一些经验和积累,不一定是最佳方案,目前对于自己来说用着还是不错的。如果你有更好的实践,欢迎留言讨论,另因为一直没有找到合适的UI库,目前的webapp所需要的组件还不多,没有使用UI库,都是自己写的。
现在正好有时间,于是整理一下,对自己这段时间的一个小小的总结,同时也希望能帮助到一些有需要的人。
这篇大概分两块:
一,学习资料的整理
二,自己在学习和使用过程中遇到的一些问题的整理

学习资料

1.React入门资料
2.其它知识
3.数据管理
4.CSS在React中的使用
5.UI库
6.手机端适配
7.项目实战
8.优化发布上线
9.开发过程中工具(chrome 插件,需要科学上网)
10. vistual dom 的简单实现

两篇文章不是一样

个人开发中遇到的坑

1.字体大小适配

设计稿是750x1334(iphone6 && iphone6s的分辨率来定的)
在使用淘宝适配方案过程中(rem适配),图片大小适配的挺好的,但是字体出现了严重的问题。字体最开始按照教程来的,字体单位使用px,然而字体在安卓机部分机型(分辨率高于等于1920x1080)上,字体显示的异常的大,等不到想要的结果。最后的解决方案,字体依然使用rem单位,才解决此问题。因为此问题已经解决,本人这里暂时没有截图保留。如果大家遇到字体问题,可以试着使用rem作为字体的单位.

2.ES2015兼容性问题

在开发过程中使用ES2015 的新特性是比较爽的,但是因为安卓手机碎片化的问题,以及国内部分手机浏览器的更新问题,很多特性是没有支持的,所以在使用新特性的时候,特别要注意兼容性的问题。这里放一个网址方便大家查看某属性的支持情况。名字特别好记,分can,i,use 。本人在使用过程中遇到过一个坑,之前写过的微信调试过程,就是在遇到坑的情况下写的。

3.Flux 重构 为 Redux

在 Redux 中, state 的定义是比较重要的。与React中的state的概念可以说不完全相同。在最开始重构的时候,完全懵逼,不理解state如何定义。在Redux中,定义state,其实state就是带有数据的全局变量,注意这里的全局变量。React中的state是组件的,是无法在组件之间进行传递的。这里可能后期会写一篇博客介绍,Flux 到 Redux的重构。

4.打包发布

在一次配置PostCSS中,开发模式正常,但是发布总是出现问题,加载不了CSS模块,最后查了好久。原来是Node.js的问题,更新到最新版本就可以正常发布了。

5.单页应用微信支付

我们先定义两个名词:首先把当前页面叫做“Current Page”;当我们从微信别的地方点击链接呼出微信浏览器时所落在的页面、或者点击微信浏览器的刷新按钮时所刷新的页面,我们叫做“Landing Page”。举个例子,我们从任何地方点击链接进入页面A,然后依次浏览到B、C、D,那么Current Page就是D,而Landing Page是A,如果此时我们在D页面点击一下浏览器的刷新按钮,那么Landing Page就变成了D(以上均是在单页应用的环境下,即以hashbang模式通过js更改浏览器路径,直接href跳转的不算)。

问题来了,在ios和安卓下呼出微信支付的时候,微信支付判断当前路径的规则分别是:

IOS:Landing Page
安卓:Current Page

更多详细

6.微信相关

其它人的总结

前言

做完手术之后就一直处于萎靡状态,博客都停了,然后不知不觉2016已经过去了一半多。出来工作之后,总感觉时间过的飞快,一不小心半年过去了,然后一不小心又到年底了。年龄渐长,跟着而来的各种事情(年龄到了,大家都懂的)都来了。不过现在心态还好,事情来了,遇到问题想办法解决就好。这里强烈推荐大家去看一下积极心理学,真的是非常棒的课程。写这篇博客的原因是看到了别人的半年总结,然后又翻了一下自己年初的计划,发现好多事情都跟不上计划,于是想重新调整一下计划,下面就总结一下上半年的事情和下半年事情的调整。

上半年事情概述

  • 4月回武汉买房
  • 4月辞职离开北京白鹭时代
  • 4月回老家做手术
  • 5月在家修养一个月
  • 6月出来做事至今
  1. 买房是因为看到房价飞涨,怕以后连房子都买不起,15年二胎政策出来的时候就预测房价一定会上涨,但是由于自己内心不坚定,导致多花不少钱买房。以后做事,认准了就要坚持去做,免得后悔。
  2. 离开北京是因为北京空气和房价,压力太大了,再加上自己身体,在北京生活不习惯,然后就选择离开了北京。心里还是非常喜欢北京的同事的,喜欢那种工作氛围。
  3. 回家手术就是因为身体原因了。

2016年上半年计划

  1. mvc结构没有整理,到最近熟悉单向数据流的时候,才有了更深刻的认识。
  2. 数据每月买几本,但是看的好像还真不多,准备列一下书单,每读完一本书写写总结。<硅谷传奇> <浪潮之巅> <摄影构图学>
  3. 3d学习了一段时间,现在完全忘记了,也没有做相关的整理记录。“巧秀才跟不得烂笔头”这句话果然是真理。
  4. 由于五月在家休养,直到现在才纯纯的做reactjs开发。才熟悉webpack,reactjs等相关知识。
  5. UI 学习自然是落下了。来到广州,前期一直处于加班状态。然后周末也重新捡起每周一次出去拍摄,技术方面的学习就自然落下了。中间一直在学习摄影和简单的后期,摄影技术和后期有一点点的增长。健身也重新捡起来了,每个一天锻炼一次,做it的还是需要有一个好的身体,其实做任何事情都需要一个好的健康的身体。

现在越来越注重生活,工作并不是生活的全部。每周必然出去走一走晒晒太阳,因为每周上班都是呆在办公室,空调下,完全不怎么见太阳。出去的时候,一般带着相机到广州有名的建筑或者旅游景点逛一逛,还是非常不错的。有时候也参加一下知乎群的活动,认识一下新朋友,毕竟离开了熟悉的圈子,需要建立新的交际圈,认识新的朋友。

经过上半年的计划和所做的事情,发现还是主抓1-2件事情,不要超过三件事情,要不然人会非常的累和疲惫,反而不易于事情做成。建议是计划1到2件主要的事情,坚持的做下去。偶尔中间穿插一下其它的事情。每周至少一天完全的休闲,不做任何工作相关的事情。

2016年下半年计划

  • 英语雅思的考试准备(主)
  • 摄影学习(主)
  • 绘画(副)
  • python(副)
  • 坚持锻炼身体,例行公事(就像每天早上起来刷牙一样的事情)

前言

最近使用reactjs开发单页应用,主要使用reactjs + webpack + redux + es2015(es6),因为使用了es2015 ,由于兼容性的问题,遇到一次比较坑的爬坑经历。情况是这样的,发布单页过后,在苹果手机微信中打开都是正常,在其他浏览器(chrome,safari)也是正常的;在安卓微信中都打不开,但是在chrome,qq浏览器也是正常的。当时就懵逼了,当时心里的状态是这样的,我靠,这是尼玛什么情况。没有办法,只能找办法调试解决了,但是要调试微信中的啊,其它浏览器中都是正常的啊,这时真是尴尬,完全不知道该如何调试手机端的微信(求心里阴影面积)。因为只在微信中出现,chrome中是正常的,那就不能使用chrome 调试方式了。这里是电脑调试手机端chrome方式谷歌官方文档,再贴一个中文的文档教程。最后找到微信web 开发者工具,最终调试成功。最终调试才知道是Object.assign 的兼容性问题,最后使用了object-assign 来解决兼容性的问题。下面来写一下这一次的详细调试过程。

调试工具

步骤

1.前面的可以看官方的文档,按照微信官方文档中的准备工作做好,然后把安卓手机微信中X5 blink 的设置也设置好。然后分别重启手机中的微信和pc的微信web开发者工具。(这里被坑了一下,开始的时候死活不能调试,分别重启了一下手机和web开发者工具就可以调试了)

这里是微信的开始调试之前的准备工作步骤,微信官方文档地址

这里贴一张我这里未开始调试的图

2.开始调试,请确保手机和微信web开发者工具都配置好,然后在web开发者工具中,选择X5 blink 调试, 然后就会弹出一个界面(移动调试,设备列表), 如下图

这里是官方文档的图。

因为我这里没有安卓手机,所以只能先大概的文字描述一下。会有三种调试方式,一个chrome调试方式,一个是X5 blink调试方式,一个是代理的调试方式。这里我们要用的是X5 blink 调试。点击com.tencent.mm 下面的网页inspect,这时候我们就可以调试手机端的微信里面页面了,直接源码调试,可以查看各种源码了,还可以断点调试。

调试到这里基本就结束了,主要是调试的方式,微信web开发者工具还是不错的,可以直接远程调试手机端微信里面的网页了,不像以前只能alert调试,想想就好伤痛。

前言

上周广州和深圳的白鹭开发者聚会,很遗憾没有时间参加,现在补这篇博客吧。这篇也算是继之前gulp的补充。

如果你的目标是打包runtime和使用官方打包成移动app的方式,那么这篇文章可能不是很适合你。当然也欢迎你阅读。

这里是打造个人个性的开发工作流,如果你喜欢折腾欢迎来尝试,毕竟官方的EgretWing 集成度已经是非常高的了。

需求

  1. 发布代码的版本控制
  2. 代码编辑器个人偏好
  3. egret命令有时候gg

工具说明:

​ sublime text 3 代码编辑

     iTerm2  终端工具

​ EgretWing2.5 UI编辑器,集成开发环境

​ ResDepot 资源编辑器

​ chrome 调试和预览工具

​ web pack 代码构建工具

工作流配置

  1. sublime + egret + webpack + chrome + iTerm开发环境配置

  2. EgretWing2.5 + ResDepot UI编辑+资源管理

sublime 插件配置

sublime+iTerm 视窗配置

sublime+iTerm

webpack+egret 改造项目

  1. 创建项目
  2. npm,git初始化
  3. 依赖库安装
  4. webpack+tsconfig配置
  5. 修改原项目

1.创建项目

egret create web pack-egret - -type eui

2. npm init & git init

/webpack-egret npm init

/webpack-egret git init

3. cnpm install

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
"devDependencies": {
"babel-core": "^6.10.4",
"babel-loader": "^6.2.4",
"babel-plugin-transform-es2015-arrow-functions": "^6.8.0",
"babel-plugin-transform-runtime": "^6.9.0",
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.11.1",
"babel-preset-stage-0": "^6.5.0",
"babel-runtime": "^6.9.2",
"awesome-typescript-loader": "^0.15.10",
"http-server": "^0.9.0",
"strip-sourcemap-loader": "0.0.1",
"typescript": "^1.8.7",
"webpack": "^1.12.14",
"webpack-dev-server": "^1.14.1",
"webpack-merge": "^0.14.0",
"webpack-validator": "^2.2.2",
"html-webpack-plugin": "^2.22.0"
}

4.webpack+tsconfig配置

tsconfig.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"compilerOptions": {
"target": "ES5",
"outDir": "bin-debug",
"sourceMap": true
},
"exclude": [
"bin-debug",
"bin-release",
"resource",
"node_modules",
"bower_components"
]
}

webpack.config.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
var webpack = require('webpack');
var path = require('path')
var HtmlWebpackPlugin = require('html-webpack-plugin');
var paths = require('./webpack.paths')
var loaders = require('./webpack.loaders')


module.exports = {
entry: {
main: paths.app
},
resolve: {
root: paths.app,
extensions: ['', '.js', '.ts']
},
output: {
path: paths.build,
filename: 'bundle.js',
publicPath: 'build/'
},
devtool: 'eval-source-map',
devServer: {
historyApiFallback: true,
hot: true,
inline: true,
progress: true
},
module: {
loaders: loaders
},
plugins: [
new webpack.NoErrorsPlugin(),
new webpack.DefinePlugin({
BUILD_MODE: JSON.stringify('development')
})
]
};

这里给出的webpack配置的不全,后面会给出整个项目的github地址

1
2
3
4
5
6
7
8
9
...
<script src="libs/modules/egret/egret.min.js"></script>
<script src="libs/modules/egret/egret.web.min.js"></script>
<script src="libs/modules/game/game.min.js"></script>
<script src="libs/modules/game/game.web.min.js"></script>
<script src="libs/modules/tween/tween.min.js"></script>
<script src="libs/modules/res/res.min.js"></script>
<script src="build/bundle.js"></script>
...

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|----index.html
|----src
| |---- Main.ts
| |---- LoadingUI.ts
| |----Configure.ts
|
|----build
|
|----libs
| |----modules
|
|----node_modules
|
|----resource
| |----assets
| |----config
| |----default.res.json
|
|----tsconfig.json
|----package.json
|----webpack.config.js
|----webpack.loaders.js
|----webpack.paths.js
|----webpack.production.config.js

开发

1.npm run start

这样就不用egret命令啦。这里是热更新的,只需要修改代码,保存一下,然后在浏览器中刷新就可以看到最新的效果。

2. npm run build

发布之后需要上传的目录结构

1
2
3
4
5
|----index.html
|
|----src
|----build
|----libs

参考链接:

sublime+iterm

webpack+egret

sublime+jsFormat

sublime+typescript

webpack-cn

webpack

推荐一下切图工具pxcook

原理讲解

图讲解

server端

server端主要做信息接收和传递以及一些处理。可以理解为一个中转站。

show端

show端就是一个展示端,比如小霸王游戏机中需要的显示器。

control端

control端就好像小霸王游戏中的手柄,来操作游戏。

通信协议websocket

流程讲解

  1. show端。生成一个随机数(xxx),作为自己唯一的ID验证。同时连接server服务器并把自己ID传输过去,标记此WebSocket连接为show端。

  2. server端,记录步骤1中,连接过来的show端,同时记录ID(xxx)和身份标记show端。

  3. 手机扫描show端游戏页面的二维码(二维码包含show端生成的随机数ID),打开control端,连接server服务器端。

  4. server端,对步骤3连接的WebSocket进行判断,通过ID,把show端和control端连接起来。同时标记此WebSocket为control端。

  5. 这两个WebSocket连接就连接起来了,就可以进行通信,信息数据交流了。

说明:为什么要有唯一的ID呢,因为这里的demo是一个show端对应一个control端的需求。可以满足多个人,同时打开show端,然后拿起手机扫描二维码,进行游戏操作。(这里如果想做多人控制一个比如,多人同时摇手机,给加油鼓劲。)

服务端代码

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
var controlArr = new Array();//记录所有control端的连接
var showArr = new Array();//记录所有show端的连接

var ws = require('./node_modules/nodejs-websocket');
var server = ws.createServer(function (connection) {
connection.data = null;
connection.type = null;
console.log("new connetion");
console.log("连接数connection = " + server.connections.length);
//接收数据
connection.on("text", function (str) {
var data = JSON.parse(str);
console.log("userid =",data.userid,"type =",data.type);
if (connection.data === null) {

/**
* 1.判断链接是control端还是show端
* 2.如果是control端,且是第一次发送消息,什么都不做
* 3.如果是control端,不是第一次发送消息,那给所有的show端发送消息请求,游戏开始。
* 4.show端接受消息,对应的唯一show端,做相对应的处理,并返回消息。
* 5.游戏正式开始
*/

if (data.type == "control") {
controlArr.push(connection);
} else if (data.type == "show") {
showArr.push(connection);
}

connection.userid = data.userid;
connection.type = data.type;


//如果是第一次发送消息什么都不做。
if (data.event == "HelloWebSocket") {
return;
}

//如果发送消息的是控制端
if (data.type == "control") {
var msg1 = { userid: connection.userid, type: connection.type, event: data.event, leftOrRight: data.leftOrRight };
var sendMsg1 = JSON.stringify(msg1);
sendMessageToShow(sendMsg1);

}

//如果发送消息的是show端
if(data.type == "show")
{
var msg2 = { userid: connection.userid, type: connection.type, event: data.event, leftOrRight: data.leftOrRight };
var sendMsg2 = JSON.stringify(msg2);
sendMessageToControl(sendMsg2);
}

} else {
broadcast("[" + connection.userid + "] " + connection.userid);
console.log("connection.userid = " + connection.userid);
}

});

connection.on("close", function () {
var data = { userid: connection.userid, type: connection.type, event: "leave", leftOrRight: "null" };
var str = JSON.stringify(data);
broadcast(str);
console.log("userid =",data.userid,"type =",data.type," close");

console.log("连接数connection = " + server.connections.length);
});

connection.on("error", function () {
if (connection.type == "control") {
var indexControl = controlArr.indexOf(connection);
if (indexControl != -1) {
controlArr.splice(indexControl, 1);

}
}
if (connection.type == "show") {
var indexShow = controlArr.indexOf(connection);
if (indexShow != -1) {
controlArr.splice(indexShow, 1);

}
}

});
})
server.listen(8001);
/**
*
* 发送消息到所有连接
*/
function broadcast(str) {
server.connections.forEach(function (connection) {
connection.sendText(str);
})
}
/**
*
* 发送消息到control(控制)端
*/
function sendMessageToControl(str) {
server.connections.forEach(function (connection) {
if (connection.type == "control") {
connection.sendText(str);
}
})
}
/**
*
* 发送消息到show(表现)端
*/
function sendMessageToShow(str) {
server.connections.forEach(function (connection) {
if (connection.type == "show") {
connection.sendText(str);
}
})
}

console.log("服务器启动");

模块介绍

这里使用的是别人封装好的模块nodejs-websocket,点击链接,可以查看详细的API介绍和使用方法的介绍。

这里就只介绍整个操作流程,从创建过程到代码的编写。请先确保电脑已经安装nodejs,如果不会安装请谷歌或者百度,官网链接

安装之后,全局安装nodejs-websocket。在终端执行sudo npm install nodejs-websocket -g。更详细的教程查看这里

我这里使用的是Mac系统,如果不是Mac系统,大家可以对比一下查看。

操作步骤

1.安装依赖模块

打开终端,输入cd,然后拖拽文件目录,回车

输入mkdir crossscreen && cd crossscreen,这里是创建crossscreen文件夹并进入crossscreen文件夹

输入mkdir server && cd server,这里是创建server文件夹并进入server文件夹

输入npm install nodejs-websocket,然后回车,安装成功终端应该是这样的

系统文件目录应该是这样的

这里说一下,有的Mac可能执行的图不是和我这里表现的一样,因为我这里安装了zsh

2.编写server.js代码

因为这里编辑纯js代码,这里使用的编辑器是VS code.

把server文件夹下内容导入VS code,然后创建server.js文件。

接下来,大家就可以把代码拷贝到我们创建的server.js里面。

如果想运行server.js ,直接在终端输入node server.js 中间隔着空格,记住要在server文件夹下,或者直接拖拽到终端也行。
结果应该是这样的。

3.代码讲解

1
var ws = require('./node_modules/nodejs-websocket');

引用依赖库

1
2
3
4
5
var server = ws.createServer(function (connection) {

})

server.listen(8001);

创建server,监听8001端口。

1
2
3
connection.on("text", function (str) {});
connection.on("close", function (str) {});
connection.on("error", function (str) {});

监听传送过来的数据
监听连接关闭
监听连接错误

扩展思路

数据格式

数据格式,这里使用的是使用的JSON来传送数据。如果想在egret中使用protobuf,请查看这里

因为这里比较简单所以就是定义的如下格式,这个可以根据自己的习惯来定。

1
2
3
4
5
6
{
"userid": "",
"type": "",
"event": "",
"data": ""
}

声明类 Message

1
2
3
4
5
6
7
8
9
10
11
12
class Message {
constructor(userid:string,event:string,type:string,data:string) {
this.userid = userid;
this.type = type;
this.event = event;
this.data = null;
}
userid:string = null;
type:string = null;// "show" or "control"
event:string = null;// "gameStart"
data:string = null;//“left”,“right”
}

利用JSON.parse(),JSON.stringify()来转换。

WebSocket

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
class WebSocket {
constructor() {

}

static instance: WebSocket = new WebSocket();
webSocket: egret.WebSocket = new egret.WebSocket();

static getInstance(): MyWebSocket {

return WebSocket.instance;
}
//初始化
init(url:string,port:any): void {
//接收消息
this.webSocket.addEventListener(egret.ProgressEvent.SOCKET_DATA, this.onReceiveMessage, this);
//连接
this.webSocket.addEventListener(egret.Event.CONNECT, this.onSocketOpen, this);

this.webSocket.connect(url, port);
//添加链接关闭侦听,手动关闭或者服务器关闭连接会调用此方法
this.webSocket.addEventListener(egret.Event.CLOSE, this.onSocketClose, this);
//添加异常侦听,出现异常会调用此方法
this.webSocket.addEventListener(egret.IOErrorEvent.IO_ERROR, this.onSocketError, this);
}

/**
* 连接成功
*/
private onSocketOpen(): void {
var cmd = new Message(GlobalData.userid,"HelloWebSocket","show","null");
var msg = JSON.stringify(cmd);
console.log("连接成功,发送数据:" + msg);

this.webSocket.writeUTF(msg);
}
/**
* 传送数据
*/
sendMesssage(str:string)
{
this.webSocket.writeUTF(str);
this.webSocket.flush();
}
/**
* 接收消息
*/
onReceiveMessage(e: egret.Event): void {
var msg = this.webSocket.readUTF();
var event = new game.SceneEvent(game.SceneEvent.ChangeScene);//自定义的事件来传送数据

event.eventData = JSON.parse(msg); //转换字符串为JSON格式

if(GlobalData.userid == event.eventData.userid)// 判断是否是属于同一组连接
game.ViewManager.getInstance().dispatchEvent(event)
}

private onSocketClose(): void {
console.log("WebSocketClose");
}

private onSocketError(): void {
console.log("WebSocketError");
}

}

更多的使用方式查看API;

二维码

二维码使用的库是https://github.com/cxh612/qrCode

具体的使用功能可以查看readme.md

解析URL

API 使用egret.getOption

扩展思路

  1. 手机做遥感,pc做显示器。
  2. 体感游戏,手机做感应器,pc做显示端。
  3. 两个手机,情侣之间,朋友之间的对抗赛,同步屏幕操作。

代码部署

说在之前

因为个人的一些习惯癖好,不喜欢PHP,写起来感觉好难受。再一个Node.js又比较火,又可以使用TypeScript来编写,于是很愉快的决定使用Node.js了。写玩之后,发现找一个支持node.js的服务器好难,在这个上面折腾了好久。游戏1天半差不多就写完了,结果部署折腾了快一个星期的时间,因为不是很懂服务器,没有办法。最后只好找同学来指点一下,然后就自己折腾去了。最后买了阿里云。

服务器

我这里购买的是阿里云,配置的镜像是Ubuntu 14.04 64位。因为个人相比而言,相对于来说偏向Linux服务器,对于Ubuntu 更熟悉一些。这个根据个人的习惯爱好就行。有兴趣的,可以先在本地电脑安装虚拟机,然后安装Ubuntu。如果你不愿意折腾,那就直接购买就好了。如果购买阿里云,可以到网上找一些优惠码,这个阿里云还是固定发送一些的。我的推荐码xy0glk 阿里云购买地址

安装Node.js

因为只当服务器使用,这里说一下安装Node.js,在Ubuntu下,node 这个命令可能被占用,一般使用nodejs 命令.如果不想换,继续使用node命令,那么查看这里具体的操作可以查看这里

sftp上传

这里使用的是Yummy FTP,购买阿里云之后会给你发一份邮件给你。

输入IP,用户名,端口号,然后就可以连接了。

然后上传服务器端代码

然后进入终端连接,启动服务器端代码,更详细的教程

前言

最近了解到electron,于是去简单的研究了一下。网上找个详细的教程,没有找到比较全的,然后就把整个流程简单的记录一下,方便需要的人。

环境搭建

笔者这里使用的Mac,osx 10.11.3 ,在安装electron环境之前,确保本机已经安装Node.js,Git并确保尽可能的是最新的版本。笔者这里的Node版本4.2.2,Git版本2.5.4。

1.切换镜像

按照官方的教程,是无法正常运行的这里,我们需要使用一下淘宝的镜像(这里感谢淘宝)。这里我们在终端执行以下命令:

1
npm install -g cnpm --registry=https://registry.npm.taobao.org

2.安装electron

在终端执行以下命令

1
cnpm install electron-prebuilt -g

3.获取官方实例

在终端执行以下命令或者直接到https://github.com/atom/electron-quick-start下载整个项目

1
git clone https://github.com/atom/electron-quick-start

发布项目

1.安装打包工具

因为我们需要将我们的源代码进行混淆打包,免得别人看到源码。我们在终端执行

1
npm install -g asar

2.打包项目

命令 asar pack|p [options] <dir> <output>

这里在终端进到刚才下载的官方例子文件夹外,执行以下命令:

1
asar p electron-quick-start app

然后修改打包后的文件后缀,修改为app.asar。这里说明的是一定要以app为打包文件名称。打包之后然后到https://github.com/atom/electron/releases。笔者这里是Mac系统,下载的是electron-v0.36.10-darwin-x64.zip。解压,把app.asar拷贝到Electron.app/Contents/Resources/resource下,其它的不要做任何修改,到此算是完成源代码打包。

还有修改应用名称,icon待续。。。

缘由

公司同事内部分享的一篇讲解查找内存泄漏的博客,个人觉得很赞,就决定翻译一下,翻译不重要的地方会略过。英文不太好,翻译不好的地方或者有错误的地方欢迎指正。 原博客链接http://www.alexkras.com/simple-guide-to-finding-a-javascript-memory-leak-in-node-js/

简介

几个月前,有一次出现内存泄漏,不得不调试,查找问题所在。我查找了一些相关的资料和文章,但是认真仔细读过之后,还是有疑惑到底应该怎么去调试查找问题的所在。

我希望这篇文章对查找node中的内存泄漏有一个简单的引导。我将使用一种简单的方式来引导查找内存泄漏的问题。个人认为这种方式可以满足大部分需求。对于某些情况,这种方式可能不行。我将提供一些其他的资料供你参考。

最小理论

JavaScript是一种垃圾回收语言。因此,所有的内存使用都是由一个node进程来管理自动的分配和回收,通过V8 JavaSript引擎。

V8 怎么知道什么时候去回收内存?在程序中,从跟节点开始,V8有数据图始终保存所有变量的。在JavaScript中有四种数据类型。真假值,字符串,数字和对象。前3种都是简单类型,这3种数据类型能够保存数据指向他们。对象和其它所有类型都是对象类型(数组也是对象类型),都能保存引用指向其它的对象。

V8周期性的根据这个内存引用列表,试着查找一些从根节点无法查找到的数据。如果这些数据无法在根节点上查找到,V8 就确认这些数据不在被使用并且释放掉这些内存。这个机制被叫做垃圾回收。

什么时候会发生内存泄漏?

在JavaScript中内存泄漏发生在当数据不需要,在根节点中依然可以查找到时。 V8 会认为这些数据依然在使用而且不会释放掉这些数据对应的内存。为了能够调试内存泄漏问题,我们需要保存该数据通过错误调用,并且确保V8 能够去清理掉

非常重要的是要记住,垃圾回收并不是每时每刻都在清理。一般V8 在认为合适的情况下会执行垃圾回收。例如,V8 会周期性的执行垃圾回收,或者当V8 发现剩余内存变少到一定量的时候也会执行一次垃圾回收。节点有所有变量的内存链连接所有的进程,因此V8能够智能地随时调用。

未完待续。。。

缘由

作为一名伪前端攻城师,还是有必要学习一下前端工具和前端框架的。于是自己就折腾了一下,通过在egret引擎中使用gulp,来学习gulp的使用。这里是别人已经收集整理好的,gulp比较齐全的资料https://github.com/Platform-CUF/use-gulp

gulp简介

Gulp.js 是一个自动化构建工具,开发者可以使用它在项目开发过程中自动执行常见任务。Gulp.js 是基于 Node.js 构建的,利用 Node.js 流的威力,你可以快速构建项目并减少频繁的 IO 操作。Gulp.js 源文件和你用来定义任务的 Gulp 文件都是通过 JavaScript(或者 CoffeeScript )源码来实现的。

在egret中,这里就是希望使用它编译TypeScript文件,虽然egret已经做了一些封装,满足了基本的需求,这里完全就是自己折腾,用前端工具,做一些定制化的操作。然后实现一些定制化的功能,比如压缩资源。比如模块编译。等等。

gulp安装

1.全局安装gulp:

$ npm install gulp -g

如果安装失败,前面可以添加一个sudo

sudo npm install gulp -g

因为可能被国内的某些原因,无法正常安装。大家可以安装淘宝镜像

2.作为项目的开发依赖(devDependencies)安装:

在egret项目根目录下,命令行中输入以下命令:

npm install --save-dev gulp

这里是到项目根目录安装的。即egret项目的根目录。

在egret项目根目录创建gulpfile.js文件。代码

1
2
3
4
5
6
7
var gulp = require('gulp');
gulp.task('default', function() {
// 将你的默认的任务代码放在这

console.log("hello gulp")
});

在egret项目根目录下,命令行中输入:gulp ,
终端输出内容中会有单独的一行,显示内容为 hello gulp;

3.编译TypeScript

在egret中,输入egret build 则egret引擎会构建项目,TypeScript文件生成对应JavaScript文件,文件生成的目录是bin-debug。

在这里使用gulp来构建,我们同样默认还是生成在bin-debug文件目录下。
在egret项目根目录下,命令行中输入以下命令:

npm install --save-dev gulp-typescript

在egret中,构建项目使用的命令是egret build ,这里我们就创建一个build任务,gulpfile.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
var gulp = require('gulp');
var ts = require('gulp-typescript')
gulp.task('default', function() {
// 将你的默认的任务代码放在这
console.log("hello gulp");
});

//创建一个build 任务
gulp.task('build', function() {
// 1. 找到文件
gulp.src('src/**/*.ts')
// 2. 编译TypeScript文件
.pipe(ts({
"compilerOptions": {
"target": "ES6",
"outDir": "bin-debug",
"sourceMap": true
},
"exclude": [
"bin-debug",
"bin-release",
"resource"
]
}))
// 3. 保存编译后的文件
.pipe(gulp.dest('bin-debug'));
})

在egret项目根目录下,命令行中输入: gulp build ,
我们查看对应的bin-debug目录下的JavaScript文件,是完全对应TypeScript文件生成的。
这里我们就实现了egret build 功能。

这里在终端输入gulp build 时候,终端会输出比较多的错误提示,说是没有引用对应的库文件。
接下来我们解决这个报错问题,我们引用新的gulp插件,直接调用egret默认提供的命令。

在egret项目根目录下,命令行中输入以下命令:

npm install --save-dev gulp-shell

我们用shell脚本,调用egret引擎默认提供的命令功能。在gulpfile.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
var gulp = require('gulp');
var ts = require('gulp-typescript');
var shell = require('gulp-shell');

gulp.task('default', function () {
// 将你的默认的任务代码放在这
console.log("hello gulp");
});


gulp.task('build', function () {
// 1. 找到文件
gulp.src('src/**/*.ts')
// 2. 编译TypeScript文件
.pipe(ts({
"compilerOptions": {
"target": "ES6",
"outDir": "bin-debug",
"sourceMap": true
},
"exclude": [
"bin-debug",
"bin-release",
"resource"
]
}))
// 3. 保存编译后的文件
.pipe(gulp.dest('bin-debug'));
})

//调用egret run -a 命令,实现增量编译
gulp.task('run', function(){
gulp.src('')
.pipe(shell([
'egret build -e',
'egret run -a'
]))

});

4.发布Egret项目

这里我们同样使用egret自带的命令。参考步骤3中编译操作。

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
var gulp = require('gulp');
var ts = require('gulp-typescript');
var shell = require('gulp-shell');

gulp.task('default', function () {
// 将你的默认的任务代码放在这
console.log("hello gulp");
});

// gulp build 编译egret项目
gulp.task('build', function () {
// 1. 找到文件
gulp.src('src/**/*.ts')
// 2. 编译TypeScript文件
.pipe(ts({
"compilerOptions": {
"target": "ES6",
"outDir": "bin-debug",
"sourceMap": true
},
"exclude": [
"bin-debug",
"bin-release",
"resource"
]
}))
// 3. 保存编译后的文件
.pipe(gulp.dest('bin-debug'));
})

//调用 gulp run ,利用egret提供的实现增量编译
gulp.task('run', function () {
gulp.src('')
.pipe(shell([
'egret build -e',
'egret run -a'
]))

});


// 发布 gulp publish
gulp.task('publish', function () {
gulp.src('')
.pipe(shell([
'egret build -e',
'egret publish '
]))
})


发布我们这里基本的已经实现了,但是egret官方提供的可以默认的添加版本号,这里我们用另外的插
件yargs来实现

在egret项目根目录下,命令行中输入以下命令:

npm install --save-dev yargs

gulpfile.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
var gulp = require('gulp');
var ts = require('gulp-typescript');
var shell = require('gulp-shell');
var argv = require('yargs').argv;

gulp.task('default', function () {
// 将你的默认的任务代码放在这
console.log("hello gulp");
});

// gulp build 编译egret项目
gulp.task('build', function () {
// 1. 找到文件
gulp.src('src/**/*.ts')
// 2. 编译TypeScript文件
.pipe(ts({
"compilerOptions": {
"target": "ES6",
"outDir": "bin-debug",
"sourceMap": true
},
"exclude": [
"bin-debug",
"bin-release",
"resource"
]
}))
// 3. 保存编译后的文件
.pipe(gulp.dest('bin-debug'));
})

//调用 gulp run ,利用egret提供的实现增量编译
gulp.task('run', function () {
gulp.src('')
.pipe(shell([
'egret build -e',
'egret run -a'
]))

});


// 发布 gulp publish --version 1000
gulp.task('publish', function () {

gulp.src('')
.pipe(shell([
'egret build -e',
'egret publish --version ' +argv.version
]))
})

这里,我们就完成了egret提供的基本相同的功能。剩下的就是我们的自定义功能了。

5.自定义功能

5.1压缩图片资源

安装压缩插件,在egret项目根目录下,命令行中输入以下命令:

npm install --save-dev gulp-imagemin

然后新建一个imagemin的任务,代码:

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
var gulp = require('gulp');
var ts = require('gulp-typescript');
var shell = require('gulp-shell');
var argv = require('yargs').argv;
var image = require('gulp-imagemin');


gulp.task('default', function () {
// 将你的默认的任务代码放在这
console.log("hello gulp");
});

// gulp build 编译egret项目
gulp.task('build', function () {
// 1. 找到文件
gulp.src('src/**/*.ts')
// 2. 编译TypeScript文件
.pipe(ts({
"compilerOptions": {
"target": "ES6",
"outDir": "bin-debug",
"sourceMap": true
},
"exclude": [
"bin-debug",
"bin-release",
"resource"
]
}))
// 3. 保存编译后的文件
.pipe(gulp.dest('bin-debug'));
})

//调用 gulp run ,利用egret提供的实现增量编译
gulp.task('run', function () {
gulp.src('')
.pipe(shell([
'egret build -e',
'egret run -a'
]))

});


// 发布 gulp publish --version 1000
gulp.task('publish', function () {

gulp.src('')
.pipe(shell([
'egret build -e',
'egret publish --version ' + argv.version
]))
})


// 压缩图片功能 gulp imagemin
gulp.task('imagemin', function () {
gulp.src('resource/**/*.{png,jpg,gif}')
.pipe(image({
optimizationLevel: 5, //类型:Number 默认:3 取值范围:0-7(优化等级)
progressive: true, //类型:Boolean 默认:false 无损压缩jpg图片
interlaced: true //类型:Boolean 默认:false 隔行扫描gif进行渲染

}))
.pipe(gulp.dest('bin-release/resource'));
});


配合imagemin-pngquant 来实现深度压缩资源。
安装压缩插件,在egret项目根目录下,命令行中输入以下命令:

npm install imagemin-pngquant --save-dev

然后修改一下资源压缩代码

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
var gulp = require('gulp');
var ts = require('gulp-typescript');
var shell = require('gulp-shell');
var argv = require('yargs').argv;
var image = require('gulp-imagemin');
var pngquant = require('imagemin-pngquant');

gulp.task('default', function () {
// 将你的默认的任务代码放在这
console.log("hello gulp");
});

// gulp build 编译egret项目
gulp.task('build', function () {
// 1. 找到文件
gulp.src('src/**/*.ts')
// 2. 编译TypeScript文件
.pipe(ts({
"compilerOptions": {
"target": "ES6",
"outDir": "bin-debug",
"sourceMap": true
},
"exclude": [
"bin-debug",
"bin-release",
"resource"
]
}))
// 3. 保存编译后的文件
.pipe(gulp.dest('bin-debug'));
})

//调用 gulp run ,利用egret提供的实现增量编译
gulp.task('run', function () {
gulp.src('')
.pipe(shell([
'egret build -e',
'egret run -a'
]))

});


// 发布 gulp publish --version 1000
gulp.task('publish', function () {

gulp.src('')
.pipe(shell([
'egret build -e',
'egret publish --version ' + argv.version
]))
})


// 压缩图片功能 gulp imagemin
gulp.task('imagemin', function () {
gulp.src('resource/**/*.{png,jpg,gif}')
.pipe(image({
optimizationLevel: 5, //类型:Number 默认:3 取值范围:0-7(优化等级)
progressive: true, //类型:Boolean 默认:false 无损压缩jpg图片
use: [pngquant()]//使用pngquant深度压缩png图片的imagemin插件
}))
.pipe(gulp.dest('bin-release/resource'));
});

这里的压缩是全部压缩,如何实现增量压缩资源,这里使用其它的插件来实现。
安装压缩插件,在egret项目根目录下,命令行中输入以下命令:

npm install gulp-cache --save-dev

修改gulpfile.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
var gulp = require('gulp');
var ts = require('gulp-typescript');
var shell = require('gulp-shell');
var argv = require('yargs').argv;
var image = require('gulp-imagemin');
var pngquant = require('imagemin-pngquant');
var cache = require('gulp-cache');

gulp.task('default', function () {
// 将你的默认的任务代码放在这
console.log("hello gulp");
});

// gulp build 编译egret项目
gulp.task('build', function () {
// 1. 找到文件
gulp.src('src/**/*.ts')
// 2. 编译TypeScript文件
.pipe(ts({
"compilerOptions": {
"target": "ES6",
"outDir": "bin-debug",
"sourceMap": true
},
"exclude": [
"bin-debug",
"bin-release",
"resource"
]
}))
// 3. 保存编译后的文件
.pipe(gulp.dest('bin-debug'));
})

//调用 gulp run ,利用egret提供的实现增量编译
gulp.task('run', function () {
gulp.src('')
.pipe(shell([
'egret build -e',
'egret run -a'
]))

});


// 发布 gulp publish --version 1000
gulp.task('publish', function () {
gulp.src('')
.pipe(shell([
'egret build -e',
'egret publish --version ' + argv.version
]))
})


// 压缩图片功能 gulp imagemin
gulp.task('imagemin', function () {
gulp.src('resource/**/*.{png,jpg,gif}')
.pipe(cache(image({
optimizationLevel: 5, //类型:Number 默认:3 取值范围:0-7(优化等级)
progressive: true, //类型:Boolean 默认:false 无损压缩jpg图片
use: [pngquant()]//使用pngquant深度压缩png图片的imagemin插件
})))
.pipe(gulp.dest('bin-release/resource'));//压缩资源后的保存路径,这里可以填写发布后的路径
});

5.2添加版本控制

未完待续…

前言

这里会简单的介绍一下设计模式的存在意义和历史发展,这样会有一个联想记忆,可能理解会更深刻一些,让自己会思索更深层次的内容,而不是简单背诵一些简单的原则或者原理。这里笔者推荐《设计模式之蝉》 这本书来学习设计模式面向对象编程

设计模式意义

在软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。(维基百科)能够高效重复的利用已有的资源而不重复的造轮子。

设计模式历史

建筑师克里斯托佛·亚历山大在1977/79年编制了一本汇集设计模式的书,但是这种设计模式的思想在建筑设计领域里的影响远没有后来在软件开发领域里传播的广泛。

肯特·贝克和沃德·坎宁安在1987年,利用克里斯托佛·亚历山大在建筑设计领域里的思想开发了设计模式并把此思想应用在Smalltalk中的图形用户接口(GUI)的生成中。一年后埃里希·伽玛在他的苏黎世大学博士毕业论文中开始尝试把这种思想改写为适用于软件开发。与此同时James Coplien 在1989年至1991年也在利用相同的思想致力于C++的开发,而后于1991年发表了他的著作Advanced C++ Programming Styles and Idioms。同年Erich Gamma 得到了博士学位,然后去了美国,在那与Richard Helm, Ralph Johnson ,John Vlissides 合作出版了《设计模式:可复用面向对象软件的基础》(Design Patterns - Elements of Reusable Object-Oriented Software) 一书,在此书中共收录了23个设计模式。

这四位作者在软件开发领域里以“四人帮”(英语,Gang of Four,简称GoF)而闻名,并且他们在此书中的协作导致了软件设计模式的突破。有时,GoF也会用于代指《设计模式》这本书。[维基百科]

设计模式六大原则

这里依然是从结构入手,这样大脑有一个大体的认识和印象。

上图结构主要两大块:

1.单一接口,类,方法的设计(单一原则接口分离原则)

2.类与类之间关系的设计(里氏替换,依赖倒置,迪米特)

3.设计之初考虑的原则(开闭原则)

设计模式演变

笔者认真的过了一遍演变的23种设计模式,都是适用于一定的特殊场景。阅读过程中认真的验证了一下这23种模式,都是遵守上述设计模式的六大原则,所以笔者推荐的是认真领悟设计模式的六大原则,在实际生产中,自己怎么使用舒服同时也能满足项目的需求,那就怎么用。因为最终的需求还是满足项目的需求,完成项目。如果非要推荐,个人推荐工厂模式,观察者模式,桥接模式,原型模式

前言

不知不觉2015年过去了,回望2015年,觉得自己过的很惭愧,虽然有一点进步,但是还是不够。于是好好整理计划2016。望2017年回望2016年,不要太多遗憾。勉励自己一句话:人生苦短,及时行乐。

缘由

继上一篇翻译的文章使用TypeScript开发React之后,对翻译文章有点上瘾了。最近也在研究ES2015,找到一篇不错的文章,于是又开始翻译起来了,就有了这篇博客。原文链接。下面正式翻译了,翻译并不是全文对比翻译的,也加了部分自己查找到资料的整理。

ECMAScript6

简介

ECMAScript 6,又名ECMAScript 2015,是ECMAScript 标准的最新版本。ES6是一个重要的更新对于JavaScript,并且这次更新是ES5在2009更新之后的首次更新。这些功能的正在集成在大部分JavaScript引擎中。

查看ES6 标准都在这篇详细的说明书中。

ES6 包含以下新的功能点:

ECMAScript6 新特性

箭头操作符

箭头操作符是函数简写的一种表现行式。它在语法结构上比较像C#,Java8和CoffeeScript里面的功能。它同时支持两种使用方式,表达式方式和声明方式。与function申明方式不同的地方是,箭头操作符号能够在相同的代码编辑模块中保留相同的this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 表达式方式
var odds = evens.map(v => v + 1);
var nums = evens.map((v, i) => v + i);
var pairs = evens.map(v => ({even: v, odd: v + 1}));

// 申明方式
nums.forEach(v => {
if (v % 5 === 0)
fives.push(v);
});

// 词汇 this
var bob = {
_name: "Bob",
_friends: [],
printFriends() {
this._friends.forEach(f =>
console.log(this._name + " knows " + f));
}
}

类的支持

ES6 类是一个语法糖通过对JS原型模式的包装来实现。有一个简单类型申明形式使得类模式更加方便使用。类支持原型为基础的继承,super调用,实例和静态方法和构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class SkinnedMesh extends THREE.Mesh {
constructor(geometry, materials) {
super(geometry, materials);

this.idMatrix = SkinnedMesh.defaultMatrix();
this.bones = [];
this.boneMatrices = [];
//...
}
update(camera) {
//...
super.update();
}
get boneCount() {
return this.bones.length;
}
set matrixType(matrixType) {
this.idMatrix = SkinnedMesh[matrixType]();
}
static defaultMatrix() {
return new THREE.Matrix4();
}
}

增强的对象字面量

对象字面量扩展到支持设置背景原型,简写为Foo Foo作业:定义方法,使super调用,和计算属性名称和表达式。另外,这些也给对象的文字和类声明更紧密的结合在一起,让对象设计受益于一些相同的便利

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
// __proto__
__proto__: theProtoObj,
// Shorthand for ‘handler: handler’
handler,
// Methods
toString() {
// Super calls
return "d " + super.toString();
},
// Computed (dynamic) property names
[ 'prop_' + (() => 42)() ]: 42
};

字符串模板

字符串模板提供构造字符串语法糖,这个类似在Perl,Python和更多的字符串插值功能。可选的,可以添加一个标记,以允许字符串结构来进行定制,避免从字符串内容里注入攻击或构造更高级别的数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 基础的字符串
`In JavaScript '\n' is a line-feed.`

// 多行字符串
`In JavaScript this is
not legal.""`""

// String interpolation
"var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`"

// Construct an HTTP request prefix is used to interpret the replacements and construction
POST`http://foo.org/bar?a=${a}&b=${b}
Content-Type: application/json
X-Credentials: ${credentials}
{ "foo": ${foo},
"bar": ${bar}}`(myOnReadyStateChangeHandler);

解构

解构允许结合使用模式匹配,与匹配的数组和对象的支持。解构是错误弱化,类似于标准的对象查找foo[“bar”] ,使用解构赋值访问对象中未定义的属性,将会得到undifined。更详细的用法点击这里

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
// 数组匹配
var [a, , b] = [1,2,3];

// 对象匹配
var { op: a, lhs: { op: b }, rhs: c }
= getASTNode()

// object matching shorthand
// binds `op`, `lhs` and `rhs` in scope
var {op, lhs, rhs} = getASTNode()

// Can be used in parameter position
function g({name: x}) {
console.log(x);
}
g({name: 5})

// Fail-soft destructuring
var [a] = [];
a === undefined;

// Fail-soft destructuring with defaults
var [a = 1] = [];
a === 1;

参数默认值,剩余参数,拓展参数

被评估的默认参数值。在函数调用中,将数组转换为连续的参数。将…参数绑定到数组。剩余参数省略必要的arguments而且在一般情况更适用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function f(x, y=12) {
// y is 12 if not passed (or passed as undefined)
return x + y;
}
f(3) == 15
function f(x, ...y) {
// y is an Array
return x * y.length;
}
f(3, "hello", true) == 6
function f(x, y, z) {
return x + y + z;
}
// Pass each elem of array as argument
f(...[1,2,3]) == 6

let与const 关键字

可以把let看成var,只是它定义的变量被限定在了特定范围内才能使用,而离开这个范围则无效。const则很直观,用来定义常量,即无法被更改值的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
function f() {
{
let x;
{
// okay, block scoped name
const x = "sneaky";
// error, const
x = "foo";
}
// error, already declared in block
let x = "inner";
}
}

迭代器,for..of

迭代器对象能够像CLR的IEnumerable或Java 的Iterable。一般化的for..in定制基于迭代器的迭代与for..of。不要求实现一个数组,使懒惰的设计模式,如LINQ。ES6中新引入的for of循环功能相似,不同的是每次循环它提供的不是序号而是值。for-of循环不仅仅是为遍历数组而设计的。基本上所有类数组对象都适用,比如DOM NodeListS,也能用在字符串上。更多详细的用法可以点击这里查看

iterator:它是这么一个对象,拥有一个next方法,这个方法返回一个对象{done,value},这个对象包含两个属性,一个布尔类型的done和包含任意值的value
iterable: 这是这么一个对象,拥有一个obj[@@iterator]方法,这个方法返回一个iterator
generator: 它是一种特殊的iterator。反的next方法可以接收一个参数并且返回值取决与它的构造函数(generator function)。generator同时拥有一个throw方法
generator 函数: 即generator的构造函数。此函数内可以使用yield关键字。在yield出现的地方可以通过generator的next或throw方法向外界传递值。generator 函数是通过function*来声明的
yield 关键字:它可以暂停函数的执行,随后可以再进进入函数继续执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let fibonacci = {
[Symbol.iterator]() {
let pre = 0, cur = 1;
return {
next() {
[pre, cur] = [cur, pre + cur];
return { done: false, value: cur }
}
}
}
}

for (var n of fibonacci) {
// truncate the sequence at 1000
if (n > 1000)
break;
console.log(n);
}

迭代是基于这些duck类型接口(使用TypeScript 类型的语法来阐述)

1
2
3
4
5
6
7
8
9
10
interface IteratorResult {
done: boolean;
value: any;
}
interface Iterator {
next(): IteratorResult;
}
interface Iterable {
[Symbol.iterator](): Iterator
}

generator

generator简化重复使用 function*yield。声明为function*函数返回一个generator实例。generator是迭代器的子类图包含一些扩展nextthrow,这些能够使用的值将会返回到generator,因此yield是一个关键字,如果return,返回一个value或者throws。更多的内容
注:也可用于启用“ await’般的异步编程,又见ES7等待提案。
生成器函数最大的特点是可以中断自己,但普通函数不可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var fibonacci = {
[Symbol.iterator]: function*() {
var pre = 0, cur = 1;
for (;;) {
var temp = pre;
pre = cur;
cur += temp;
yield cur;
}
}
}

for (var n of fibonacci) {
// truncate the sequence at 1000
if (n > 1000)
break;
console.log(n);
}

generator接口是 (使用TypeScript 类型的语法来阐述)

1
2
3
4
interface Generator extends Iterator {
next(value?: any): IteratorResult;
throw(exception: any);
}

字符编码标准

非断补充,支持完整的Unicode ,包括串新的Unicode文本形式和新的RegExp ú模式来处理代码点,以及新的API来处理在21位的代码点水平的字符串。这些增加支持JavaScript的建立全球的应用程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// same as ES5.1
"𠮷".length == 2

// new RegExp behaviour, opt-in ‘u’
"𠮷".match(/./u)[0].length == 2

// new form
"\u{20BB7}"=="𠮷"=="\uD842\uDFB7"

// new String ops
"𠮷".codePointAt(0) == 0x20BB7

// for-of iterates code points
for(var c of "𠮷") {
console.log(c);
}

模块

对于模块组件定义语言级的支持。将从流行的JavaScript模块装载机模式(AMD,CommonJS)。将不同功能的代码分别写在不同文件中,各模块只需导出公共接口部分,然后通过模块的导入的方式可以在其他地方使用。

1
2
3
4
5
6
7
8
9
10
11
// lib/math.js
export function sum(x, y) {
return x + y;
}
export var pi = 3.141593;
// app.js
import * as math from "lib/math";
alert("2π = " + math.sum(math.pi, math.pi));
// otherApp.js
import {sum, pi} from "lib/math";
alert("2π = " + sum(pi, pi));

一些附加功能包括export defaultexport *:

1
2
3
4
5
6
7
8
9
// lib/mathplusplus.js
export * from "lib/math";
export var e = 2.71828182846;
export default function(x) {
return Math.log(x);
}
// app.js
import ln, {pi, e} from "lib/mathplusplus";
alert("2π = " + ln(e)*pi*2);

模块装载机模式

模块装载机支持:

  • 动态加载
  • 状态隔离
  • 全局命名空间隔离
  • 编译钩
  • 嵌套虚拟化

可以配置默认模块加载程序,新的加载程序可以构造来评估并且在分离或约束的情况下加载代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Dynamic loading – ‘System’ is default loader
System.import('lib/math').then(function(m) {
alert("2π = " + m.sum(m.pi, m.pi));
});

// Create execution sandboxes – new Loaders
var loader = new Loader({
global: fixup(window) // replace ‘console.log’
});
loader.eval("console.log('hello world!');");

// Directly manipulate module cache
System.get('jquery');
System.set('jquery', Module({$: $})); // WARNING: not yet finalized

Map,Set 和 WeakMap,WeakSet

这些是新加的集合类型,提供了更加方便的获取属性值的方法,不用像以前一样用hasOwnProperty来检查某个属性是属于原型链上的呢还是当前对象的。同时,在进行属性值添加与获取时有专门的get,set 方法。有时候我们会把对象作为一个对象的键用来存放属性值,普通集合类型比如简单对象会阻止垃圾回收器对这些作为属性键存在的对象的回收,有造成内存泄漏的危险。而WeakMap,WeakSet则更加安全些,这些作为属性键的对象如果没有别的变量在引用它们,则会被回收释放掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;

// Maps
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;

// Weak Maps
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined

// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set

Proxies

Proxy可以监听对象身上发生了什么事情,并在这些事情发生后执行一些相应的操作。一下子让我们对一个对象有了很强的追踪能力,同时在数据绑定方面也很有用处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Proxying a normal object
var target = {};
var handler = {
get: function (receiver, name) {
return `Hello, ${name}!`;
}
};

var p = new Proxy(target, handler);
p.world === 'Hello, world!';
// Proxying a function object
var target = function () { return 'I am the target'; };
var handler = {
apply: function (receiver, ...args) {
return 'I am the proxy';
}
};

var p = new Proxy(target, handler);
p() === 'I am the proxy';

有陷阱可用于所有的运行时级元操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var handler =
{
get:...,
set:...,
has:...,
deleteProperty:...,
apply:...,
construct:...,
getOwnPropertyDescriptor:...,
defineProperty:...,
getPrototypeOf:...,
setPrototypeOf:...,
enumerate:...,
ownKeys:...,
preventExtensions:...,
isExtensible:...
}

Symbols类型

符号启用对象状态访问控制。元件使性能受到任何字符串(如ES5 )或符号来键入。符号是一个新的基本类型。用于调试的可选参数的说明 - 而不是身份的一部分。符号是唯一的(像gensym ),但不专用,因为它们是经由像Object.getOwnPropertySymbols反射功能暴露。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var MyClass = (function() {

// module scoped symbol
var key = Symbol("key");

function MyClass(privateData) {
this[key] = privateData;
}

MyClass.prototype = {
doStuff: function() {
... this[key] ...
}
};

return MyClass;
})();

var c = new MyClass("hello")
c["key"] === undefined

子类内置

在ES6中,像Array,Date 和DOM Element元素都可以被继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 数组的伪代码
class Array {
constructor(...args) { /* ... */ }
static [Symbol.create]() {
// Install special [[DefineOwnProperty]]
// to magically update 'length'
}
}

// 用户继承的子类
class MyArray extends Array {
constructor(...args) { super(...args); }
}

// Two-phase 'new':
// 1) Call @@create to allocate object
// 2) Invoke constructor on new instance
var arr = new MyArray();
arr[1] = 12;
arr.length == 2

Promises

Promises 是一个异步编程库。Promises 是可以在未来提供价值的一个类。Promises 是用在许多现有的JavaScript库。

1
2
3
4
5
6
7
8
9
10
11
12
13
function timeout(duration = 0) {
return new Promise((resolve, reject) => {
setTimeout(resolve, duration);
})
}

var p = timeout(1000).then(() => {
return timeout(2000);
}).then(() => {
throw new Error("hmm");
}).catch(err => {
return Promise.all([timeout(100), timeout(200)]);
})

Math,Number,String,Object 的新API

对Math,Number,String还有Object等添加了许多新的API。以下对这些新API进行了简单展示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Number.EPSILON
Number.isInteger(Infinity) // false
Number.isNaN("NaN") // false

Math.acosh(3) // 1.762747174039086
Math.hypot(3, 4) // 5
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2

"abcde".includes("cd") // true
"abc".repeat(3) // "abcabcabc"

Array.from(document.querySelectorAll('*')) // 返回一个数组
Array.of(1, 2, 3) // 类似 new Array(...), 但是没有特别的一个参数
[0, 0, 0].fill(7, 1) // [0,7,7]
[1, 2, 3].find(x => x == 3) // 3
[1, 2, 3].findIndex(x => x == 2) // 1
[1, 2, 3, 4, 5].copyWithin(3, 0) // [1, 2, 3, 1, 2]
["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
["a", "b", "c"].keys() // iterator 0, 1, 2
["a", "b", "c"].values() // iterator "a", "b", "c"

Object.assign(Point, { origin: new Point(0,0) })

二进制和八进制

两个新的数字文字形式添加二进制(B)、八进制(O)。

1
2
0b111110111 === 503 // true
0o767 === 503 // true

反射API

全部反射API暴露运行级别元的操作的对象。这是有效地代理API的反射,并且允许使得对应于相同的元的操作作为代理陷阱呼叫。尤其适用于实现代理。

1
暂时没有demo

递归调用

在递归调用堆栈不保证不增长。确保在无限制的的输入情况下递归算法的安全。

1
2
3
4
5
6
7
8
9
function factorial(n, acc = 1) {
'use strict';
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}

// 在大多数实现中的堆栈溢出
// 但是在ES6中对于任意的输入都是安全的
factorial(100000)

总结

总结就是一句话,前后端差异越来越小了。

缘由

最近在业余学习React,本身接触的egret一年多了,接触TypeScript时间还算有段时间,非常喜欢TypeScript。学习了TypeScript 就再也不想写JavaScript了。看了找了好些教程都是直接用JavaScript来写React,比较痛苦。今天找到了一篇比较好的教程。于是就想翻译一下,这里是原始博客链接,于是就有了这篇博客。

简单的介绍使用TypeScript 和 Atom 来开发 React 应用。

我们准备从 TodoMVC 项目开始,使用React 和 TypeScript 来开发著名的TODO App.

在这篇文章中你将学习到如下内容:

  • 1.搭建开发环境
  • 2.设置项目
  • 3.React 基础组件
  • 4.使用TypeScript 开发 React组件
  • 5.编译应用
  • 6.运行应用

搭建开发环境

我们将开始搭建开发环境

$ npm install -g typescript tsd

说明:如果你使用的是OSX系统,请在命令行前面加上 sudo

  • 为Atom安装 atom-typescript插件

$ apm install atom-typescript

这个插件有一些很酷的功能,如HTML 转换成 TSX:

或依赖视图:

如果你感兴趣,请到project’s page on Github查看该插件更多的功能。

这个插件方便我们调试React应用 可以查看属性值和所选组件的状态。

设置项目

到本教程结束时,该项目的结构类似下面的一个:

让我们从创建应用的根目录开始

1
2
$ mkdir typescript-react
$ cd typescript-react

然后在项目的根目录里面创建 package.json 文件

1
2
3
4
5
6
7
8
{
private: true,
dependencies: {
director: "^1.2.0",
react: "^0.13.3",
todomvc-app-css: "^2.0.0"
}
}

再然后你可以使用npm来安装项目所依赖的文件

1
2
// 在项目的根目录文件
$ npm install

这个命令会在项目的根目录生成一个node_modules文件夹,在node_modules文件夹下会包含有三个文件,分别是director,reacttodomvc-app-css

我们现在开始安装一些TypeScript类型定义文件

类型定义文件通常用于定义公用的第三方库文件API接口,比如React。在一些IDE中使用TypeScript开发应用,IDE可以通过这些接口提供一些代码提示,方便项目的开发。

类型定义文件也用于编译器来确保我们正确地使用第三方库。

我们现在需要React 的类型定义文件,我们可以通过下面的命令来创建他们。

1
2
3
//在应用的根目录
$ tsd init
$ tsd install react --save

以上命令会在应用的根目录创建一个 tsd.json文件和 typings文件夹,在typings文件夹中包含react文件夹

我们也需要下载然后取名为global.d.ts保存到typings/react文件夹下。

我们现在到应用根目录创建index.html文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!doctype html>
<html lang="en" data-framework="typescript">
<head>
<meta charset="utf-8">
<title>React • TodoMVC</title>
<link rel="stylesheet"
href="node_modules/todomvc-common/base.css">
<link rel="stylesheet"
href="node_modules/todomvc-app-css/index.css">
</head>
<body>
<section class="todoapp"></section>
<footer class="info">
<p>Double-click to edit a todo</p>
<p>
Created by
<a href="http://github.com/remojansen/">Remo H. Jansen</a>
</p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script type="text/javascript"
src="node_modules/react/dist/react-with-addons.js">
</script>
<script type="text/javascript"
src="node_modules/director/build/director.js">
</script>
<script type="text/javascript" src="js/constants.js"></script>
<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/todoModel.js"></script>
<script type="text/javascript" src="js/todoItem.js"></script>
<script type="text/javascript" src="js/footer.js"></script>
<script type="text/javascript" src="js/app.js"></script>
</body>
</html>

此时,你的应用目录结构应该是这样的

你可能注意到在index.html文件中有些JavaScript文件是缺少的,我们接下来将解决这个问题。

React 基础组件

组件是React的主要应用程序块,一个组件表示一个自包含的用户界面,组件通常会显示一些数据,并且能够处理一些用户交互。

组件可以包含子组件。我们即将开发的应用程序是非常小的,所以我们只会开发一个顶级组件取名TodoApp。

TodoApp 组件将有多个组件组成,包含一个TodoFooter组件和一些TodoItem组件。

组件区分不同的数据集:属性和状态

属性

属性(短的属性)是一个组件的配置,它的选项,如果你可以。它们是从上面接收到的,并且是不可改变的,因为接收它们的组件是有关的。

组件不能更改其属性,但它负责将其子组件的属性放在一起。

状态

该状态从一个组件安装时开始,并在时间(主要是用户事件产生的情况下)遭受突变。这是一个序列化表示在时间快照一点

组件管理其自己的内部,除了设置初始状态与它的孩子的状态不改。你可以说内部是私有的。

当我们使用TypeScript定义一个新的React组件的时候,我们必须申明接口的属性和状态,如下:

1
2
3
4
class SomeComponent extends React.Component<ISomeComponentProps, ISomeComponentState> {
// ...
}

现在,我们有我们的项目结构的地方,我们知道的基本知识,现在开始开发我们的组件。

使用TypeScript 开发 React组件

我们在应用根目录下创建一个名为js的文件夹,然后我们将创建如下图的目录结构文件

当我们创建完成之后,我们可以随意的修改它们。

interfaces.d.ts

我们将把我们应用中的所有接口都定义在此文件中。我们使用的扩展。d.ts(这也是由类型定义文件使用)代替。TS因为这个文件不会编译成为JavaScript文件。在我们编程过程中,此文件不会被编译,因为TypeScript接口不会编译成JavaScript。

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
// Defines the interface of the structure of a task
interface ITodo {
id: string,
title: string,
completed: boolean
}


// Defines the interface of the properties of the TodoItem component
interface ITodoItemProps {
key : string,
todo : ITodo;
editing? : boolean;
onSave: (val: any) => void;
onDestroy: () => void;
onEdit: () => void;
onCancel: (event : any) => void;
onToggle: () => void;
}


// Defines the interface of the state of the TodoItem component
interface ITodoItemState {
editText : string
}


// Defines the interface of the properties of the Footer component
interface ITodoFooterProps {
completedCount : number;
onClearCompleted : any;
nowShowing : string;
count : number;
}

// Defines the TodoModel interface
interface ITodoModel {
key : any;
todos : Array<ITodo>;
onChanges : Array<any>;
subscribe(onChange);
inform();
addTodo(title : string);
toggleAll(checked);
toggle(todoToToggle);
destroy(todo);
save(todoToSave, text);
clearCompleted();
}

// Defines the interface of the properties of the App component
interface IAppProps {
model : ITodoModel;
}

// Defines the interface of the state of the App component
interface IAppState {
editing? : string;
nowShowing? : string
}

常量

这个文件用于定义一些常量。通常用于保存一些键盘的常量和一些事件。

我们也可以用一些值来区分一些任务的当前状态:

  • COMPLETED_TODOS 用于完成任务时
  • ACTIVE_TODOS 用于没有完成任务时
  • ALL_TODOS 用于显示所有任务时使用
1
2
3
4
5
6
7
namespace app.constants {
export var ALL_TODOS = 'all';
export var ACTIVE_TODOS = 'active';
export var COMPLETED_TODOS = 'completed';
export var ENTER_KEY = 13;
export var ESCAPE_KEY = 27;
}

实用工具

这个文件包含一个叫Utils的类。这个类不仅仅包含是一些静态方法。

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
namespace app.miscelanious {

export class Utils {

// generates a new Universally unique identify (UUID)
// the UUID is used to identify each of the tasks
public static uuid() : string {
/*jshint bitwise:false */
var i, random;
var uuid = '';

for (i = 0; i < 32; i++) {
random = Math.random() * 16 | 0;
if (i === 8 || i === 12 || i === 16 || i === 20) {
uuid += '-';
}
uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random))
.toString(16);
}

return uuid;
}

// adds 's' to the end of a given world when count > 1
public static pluralize(count, word) {
return count === 1 ? word : word + 's';
}

// stores data using the localStorage API
public static store(namespace, data?) {
if (data) {
return localStorage.setItem(namespace, JSON.stringify(data));
}

var store = localStorage.getItem(namespace);
return (store && JSON.parse(store)) || [];
}

// just a helper for inheritance
public static extend(...objs : any[]) : any {
var newObj = {};
for (var i = 0; i < objs.length; i++) {
var obj = objs[i];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
}
return newObj;
}

}
}

模型

TodoModel 是一个通用 “model” 对象。由于这个应用程序是非常小的,它甚至可能不值得把这个逻辑分开,但我们这样做是为了演示一种方式来分离你的应用程序的部分。

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
/// <reference path="../typings/react/react-global.d.ts" />
/// <reference path="./interfaces.d.ts"/>

namespace app.models {

export class TodoModel implements ITodoModel {

public key : string; // key used for local storage
public todos : Array<ITodo>; // a list of tasks
public onChanges : Array<any>; // a list of events

constructor(key) {
this.key = key;
this.todos = app.miscelanious.Utils.store(key);
this.onChanges = [];
}

// the following are some methods
// used to manipulate the list of tasks

public subscribe(onChange) {
this.onChanges.push(onChange);
}

public inform() {
app.miscelanious.Utils.store(this.key, this.todos);
this.onChanges.forEach(function (cb) { cb(); });
}

public addTodo(title : string) {
this.todos = this.todos.concat({
id: app.miscelanious.Utils.uuid(),
title: title,
completed: false
});

this.inform();
}

public toggleAll(checked) {
// Note: it's usually better to use immutable
// data structures since they're easier to
// reason about and React works very
// well with them. That's why we use
// map() and filter() everywhere instead of
// mutating the array or todo items themselves.
this.todos = this.todos.map<ITodo>((todo : ITodo) => {
return app.miscelanious.Utils.extend(
{}, todo, {completed: checked}
);
});

this.inform();
}

public toggle(todoToToggle) {
this.todos = this.todos.map<ITodo>((todo : ITodo) => {
return todo !== todoToToggle ?
todo :
app.miscelanious.Utils.extend(
{}, todo, {completed: !todo.completed}
);
});

this.inform();
}

public destroy(todo) {
this.todos = this.todos.filter(function (candidate) {
return candidate !== todo;
});

this.inform();
}

public save(todoToSave, text) {
this.todos = this.todos.map(function (todo) {
return todo !== todoToSave ? todo : app.miscelanious.Utils.extend({}, todo, {title: text});
});

this.inform();
}

public clearCompleted() {
this.todos = this.todos.filter(function (todo) {
return !todo.completed;
});

this.inform();
}
}

}

页脚

这个文件使用.tsx扩展代替.ts,因为它包含一些TSX 代码。

TSX 是 JSX的一种超集。我们将使用TSX代替客户端HTML模板因为TSX和JSX是用来生成一个在DOM状态存储。当组件的属性或者状态发生变化时,React会从最有效率的方式去更新DOM状态存储,并且同时把这些改变及时应用于DOM的真实展现。这个过程React非常高效的及时更新DOM内容。

说明:我们需要一些额外的编译选项来编译.tsx文件。我们将在这篇文章结尾介绍更多关于这方面的内容

这个页脚组件允许用户去根据状态去删选任务列表和显示一些任务数。这个组件没有状态(注意如何通过将其传递给该接口的状态)但是有一些继承于父亲组件(TodoApp组件)的属性。

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
/// <reference path="../typings/react/react-global.d.ts" />
/// <reference path="./interfaces.d.ts"/>

namespace app.components {

export class TodoFooter extends React.Component<ITodoFooterProps, {}> {

public render() {
var activeTodoWord = app.miscelanious.Utils.pluralize(this.props.count, 'item');
var clearButton = null;

if (this.props.completedCount > 0) {
clearButton = (
<button
className="clear-completed"
onClick={this.props.onClearCompleted}>
Clear completed
</button>
);
}

// React idiom for shortcutting to `classSet` since it'll be used often
var cx = React.addons.classSet;
var nowShowing = this.props.nowShowing;
return (
<footer className="footer">
<span className="todo-count">
<strong>{this.props.count}</strong> {activeTodoWord} left
</span>
<ul className="filters">
<li>
<a
href="#/"
className={cx({selected: nowShowing === app.constants.ALL_TODOS})}>
All
</a>
</li>
{' '}
<li>
<a
href="#/active"
className={cx({selected: nowShowing === app.constants.ACTIVE_TODOS})}>
Active
</a>
</li>
{' '}
<li>
<a
href="#/completed"
className={cx({selected: nowShowing === app.constants.COMPLETED_TODOS})}>
Completed
</a>
</li>
</ul>
{clearButton}
</footer>
);
}
}

}

todoItem

TodoItem组件代表一个任务列表中的一个任务。
这个组件不仅有属性(ITodoItemProps)而且还有状态(ITodoItemState)。

这个组件初始化状态是在构造函数里面设置同时属性是通过父亲组件传递过来的构造函数的参数来设置组件的(TodoApp组件)

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
/// <reference path="../typings/react/react-global.d.ts" />
/// <reference path="./interfaces.d.ts"/>

namespace app.components {

export class TodoItem extends React.Component<ITodoItemProps, ITodoItemState> {

constructor(props : ITodoItemProps){
super(props);
// set initial state
this.state = { editText: this.props.todo.title };
}

public handleSubmit(event) {
var val = this.state.editText.trim();
if (val) {
this.props.onSave(val);
this.setState({editText: val});
} else {
this.props.onDestroy();
}
}

public handleEdit() {
this.props.onEdit();
this.setState({editText: this.props.todo.title});
}

public handleKeyDown(event) {
if (event.which === app.constants.ESCAPE_KEY) {
this.setState({editText: this.props.todo.title});
this.props.onCancel(event);
} else if (event.which === app.constants.ENTER_KEY) {
this.handleSubmit(event);
}
}

public handleChange(event) {
this.setState({editText: event.target.value});
}

// This is a completely optional performance enhancement
// that you can implement on any React component. If you
// were to delete this method the app would still work
// correctly (and still be very performant!), we just use it
// as an example of how little code it takes to get an order
// of magnitude performance improvement.
public shouldComponentUpdate(nextProps, nextState) {
return (
nextProps.todo !== this.props.todo ||
nextProps.editing !== this.props.editing ||
nextState.editText !== this.state.editText
);
}

// Safely manipulate the DOM after updating the state
// when invoking this.props.onEdit() in the handleEdit
// method above.
public componentDidUpdate(prevProps) {
if (!prevProps.editing && this.props.editing) {
var node = React.findDOMNode<HTMLInputElement>(this.refs["editField"]);
node.focus();
node.setSelectionRange(node.value.length, node.value.length);
}
}

public render() {
return (
<li className={React.addons.classSet({
completed: this.props.todo.completed,
editing: this.props.editing
})}>
<div className="view">
<input
className="toggle"
type="checkbox"
checked={this.props.todo.completed}
onChange={this.props.onToggle}
/>
<label onDoubleClick={ e => this.handleEdit() }>
{this.props.todo.title}
</label>
<button className="destroy" onClick={this.props.onDestroy} />
</div>
<input
ref="editField"
className="edit"
value={this.state.editText}
onBlur={ e => this.handleSubmit(e) }
onChange={ e => this.handleChange(e) }
onKeyDown={ e => this.handleKeyDown(e) }
/>
</li>
);
}
}

}

app模块

此文件包含应用程序的入口点,这是该应用程序只有顶层组件的todoapp组件声明。

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
/// <reference path="../typings/react/react-global.d.ts" />
/// <reference path="./interfaces.d.ts"/>

// We should have installed a type declaration file but
// for the director npm package but it is not available
// so we will use this declaration to avoid compilation
// errors for now.
declare var Router : any;

var TodoModel = app.models.TodoModel;
var TodoFooter = app.components.TodoFooter;
var TodoItem = app.components.TodoItem;

namespace app.components {

export class TodoApp extends React.Component<IAppProps, IAppState> {

constructor(props : IAppProps) {
super(props);
this.state = {
nowShowing: app.constants.ALL_TODOS,
editing: null
};
}

public componentDidMount() {
var setState = this.setState;
// we will configure the Router here
// our router is provided by the
// director npm module
// the router observes changes in the URL and
// triggers some component's event accordingly
var router = Router({
'/': setState.bind(this, {nowShowing: app.constants.ALL_TODOS}),
'/active': setState.bind(this, {nowShowing: app.constants.ACTIVE_TODOS}),
'/completed': setState.bind(this, {nowShowing: app.constants.COMPLETED_TODOS})
});
router.init('/');
}

public handleNewTodoKeyDown(event) {
if (event.keyCode !== app.constants.ENTER_KEY) {
return;
}

event.preventDefault();

var val = React.findDOMNode<HTMLInputElement>(this.refs["newField"]).value.trim();

if (val) {
this.props.model.addTodo(val);
React.findDOMNode<HTMLInputElement>(this.refs["newField"]).value = '';
}
}

public toggleAll(event) {
var checked = event.target.checked;
this.props.model.toggleAll(checked);
}

public toggle(todoToToggle) {
this.props.model.toggle(todoToToggle);
}

public destroy(todo) {
this.props.model.destroy(todo);
}

public edit(todo) {
this.setState({editing: todo.id});
}

public save(todoToSave, text) {
this.props.model.save(todoToSave, text);
this.setState({editing: null});
}

public cancel() {
this.setState({editing: null});
}

public clearCompleted() {
this.props.model.clearCompleted();
}

// the JSX syntax is quite intuitive but check out
// https://facebook.github.io/react/docs/jsx-in-depth.html
// if you need additional help
public render() {
var footer;
var main;
var todos = this.props.model.todos;

var shownTodos = todos.filter(function (todo) {
switch (this.state.nowShowing) {
case app.constants.ACTIVE_TODOS:
return !todo.completed;
case app.constants.COMPLETED_TODOS:
return todo.completed;
default:
return true;
}
}, this);

var todoItems = shownTodos.map(function (todo) {
return (
<TodoItem
key={todo.id}
todo={todo}
onToggle={this.toggle.bind(this, todo)}
onDestroy={this.destroy.bind(this, todo)}
onEdit={this.edit.bind(this, todo)}
editing={this.state.editing === todo.id}
onSave={this.save.bind(this, todo)}
onCancel={ e => this.cancel() }
/>
);
}, this);

var activeTodoCount = todos.reduce(function (accum, todo) {
return todo.completed ? accum : accum + 1;
}, 0);

var completedCount = todos.length - activeTodoCount;

if (activeTodoCount || completedCount) {
footer =
<TodoFooter
count={activeTodoCount}
completedCount={completedCount}
nowShowing={this.state.nowShowing}
onClearCompleted={ e=> this.clearCompleted() }
/>;
}

if (todos.length) {
main = (
<section className="main">
<input
className="toggle-all"
type="checkbox"
onChange={ e => this.toggleAll(e) }
checked={activeTodoCount === 0}
/>
<ul className="todo-list">
{todoItems}
</ul>
</section>
);
}

return (
<div>
<header className="header">
<h1>todos</h1>
<input
ref="newField"
className="new-todo"
placeholder="What needs to be done?"
onKeyDown={ e => this.handleNewTodoKeyDown(e) }
autoFocus={true}
/>
</header>
{main}
{footer}
</div>
);
}
}
}

var model = new TodoModel('react-todos');
var TodoApp = app.components.TodoApp;

function render() {
React.render(
<TodoApp model={model}/>,
document.getElementsByClassName('todoapp')[0]
);
}

model.subscribe(render);
render();

确保其中的 this在所有的时候都指向正确的元素。例如,你应用使用箭头函数:
onKeyDown = { e => this.handleNewTodoKeyDown(e) }
代替
onKeyDown = { this.handleNewTodoKeyDown }

确保this指向handleNewTodoKeyDown函数内部的组件。

编译应用程序

为了编译我们的应用程序,我们需要在js文件夹下添加一个tsconfig.json文件。

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
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"isolatedModules": false,
"jsx": "react",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"preserveConstEnums": true,
"suppressImplicitAnyIndexErrors": true
},
"filesGlob": [
"**/*.ts",
"**/*.tsx",
"!node_modules/**"
],
"files": [
"constants.ts",
"interfaces.d.ts",
"todoModel.ts",
"utils.ts",
"app.tsx",
"footer.tsx",
"todoItem.tsx"
],
"exclude": []
}

如果我们认真阅读了TypeScript compiler options我们就能够知道该如何使用tsconfig.json文件:

参数--project或者-p 都能用于编译项目在给定的文件下。这个文件需要含有tsconfig.json文件来直接编译。

我们可以编译我们的应用程序通过以下命令:

1
2
//在应用程序的根目录
$ tsc -p js

执行这个命令之后,应该会在js文件夹下生成以下JavaScript文件:

这些文件会在我们的index.html文件中引用:

1
2
3
4
5
6
<script type="text/javascript" src="js/constants.js"></script>
<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/todoModel.js"></script>
<script type="text/javascript" src="js/todoItem.js"></script>
<script type="text/javascript" src="js/footer.js"></script>
<script type="text/javascript" src="js/app.js"></script>

接下来我们准备运行我们的应用程序。

运行应用程序

为了运行我们的应用程序,我们需要一个web服务器。这里我们通过npm来安装http-server。这里博主推荐使用https://www.npmjs.com/package/anywhere

我们通过以下命令来安装http-server:

1
$ npm install -g http-server

如果你使用的是OSX系统,请使用sudo权限

运用以下命令来运行应用程序:

1
2
// 在应用的根目录
$ http-server

如果你打开浏览器导航到http://127.0.0.1:8080/,你应用可以看到应用程序正在运行:

记得打开谷歌浏览器开发者工具去查看一下React 开发者扩展工具 并且查看一下当你与应用程序交互时,如何改变组件的属性和状态的值。

总结

在这篇博客中我们已经学习了怎么搭建开发环境和怎么使用TypeScript来开发React应用程序。

你可以查看这个项目的源码

如果你想了解更多内容?请查看Type React and Redux

缘由

来egret差不多一年了,还差几天就整整一年了。来之前说要认认真真的看一下引擎源码,结果来了快一年都没有看,年末正在整理这一年的事情,发现还没有写过。然后现在就简单的整理了一下。这中间egret引擎从2.0到2.5是一个大版本的更新,架构也调整了。这里就直接看的是2.5以后的引擎了。

来白鹭之前,是白鹭引擎的忠实用户,那时候公司说要做HTML5游戏,说要用游戏引擎做,那时候第一次知道游戏引擎这个东西,但是一直不了解游戏引擎是什么东西。那时候正好快国庆,在同学学校图书馆借了一本cocos2dx书籍,在租房刷了7天的书,那时候才对游戏引擎有了初步的认识和理解。那时候租房都是没有网络的,学习白鹭引擎,都是把白鹭引擎的官方的教程网页右键保存在本地的。因为不懂,始终都是停留在使用阶段。那时候的教程还是非常少的,现在的完善多了,这里官方地址

今天的初探主要是看核心库,从类结构上来,具体的API内容,大家就查看官方的API文档
其实也就是一张简单的图了

大概的三块:事件,显示对象,其它

这样整体来看,我们大脑就有一个大概的印象,在学习和理解API上,就有一个整体的结构思路或者说是脉络,能够整体的把控引擎。这样对引擎的理解和使用是非常好的。这里推荐一篇博客,讲的是技能掌握程度,还有程序员水平分等级,你属于哪一级?然后初探已经完了,这理主要想说的是大家从结构上来学习东西和理解事情,这样会有一个更全局的认识和理解。好吧,白鹭引擎的初探就到这里了,其实这里更多的是想对自己思索的一些总结。

多一些探索精神和死磕精神

说在最后的话。这两天把科技相对论看了几遍,其中的一句话是非常的认同的找到技术和产品的平衡点。现在想从个人的理解回答一下知乎上的一个问题,说的是egret引擎为什么要从一开始就搭建周边环境,研发十多款工具。我觉得现在有些理论可以回答这个问题,在真正做一个成功的产品的时候,脱离工具流的引擎,即使非常引擎本身非常优秀,那就是像是一把利刃,缺少合适的握柄,无法把引擎的实力发挥到最大,没有高效的周边开发工具,那是无法成长壮大,那顶多是一个游戏框架,无法独立的成活下去,特别是一个技术型的驱动公司,更应该把握产品和技术的平衡点。

最近一直在玩终端,但是每次启动都要先Spotlight -> 输入ter -> enter 打开终端感觉很快,突然有一天想到是否有快捷键可以一键打开终端,结果谷歌了一下还真有,于是就有了这篇简单的教程博客。本人系统是10.11.1版本

  1. 打开自带的Automator(可以用右上角Spotlight搜索找到)

  2. 选择 “新建文稿”,在创建界面选择”服务”

  3. 左侧列表中,找到:操作—资源库—实用工具—开启应用程序,右侧选择”没有输入”,最后双击”开启应用程序”

  4. 在新出现的界面里选择需要的应用程序,例如这里选择了”终端”(实用工具-终端app)

  5. 保存(cmd+s),随便起个你能记得住的名字。这里起名”Open Terminal”

  6. 打开:系统偏好设置-键盘-快捷键-服务-找到刚才起名的”Open Terminal”

一直在使用git,但是一直是可视化工具使用,没有具体的使用命令行,今天就系统的整理学习了一下。以下是贴图

pdf教程

前言

在之前的所有的工作都是准备工作,现在接下来正式开始编写代码。

这里面主要会讲解一些EgretWing 的使用和exml与逻辑代码结合的例子,初学者对这里很是有疑惑。这里要说一下我们之前皮肤编辑中使用的ID,不知道大家有没有印象,我们给很多组件设置了ID。我们就是利用这个ID使皮肤中的组件和逻辑代码关联起来,进而对逻辑组件进行逻辑操作。

在正式编辑皮肤与逻辑之间代码之前,我们先编辑一下其它代码,主要进行界面的管理.Wing 中整理如下布局,这个根据个人习惯。

场景切换

这里不会过多的讲解,更详细的内容请参考这里的教程

这里主要还是利用自定义事件来传递消息,进行场景切换操作。在Wing的中快捷整理代码,现在需求全选代码,然后ctrl/cmd+i,这个快捷键就是格式化代码,马上会提供全局代码格式化快捷方式。自定义一个类,取名SceneEvent,具体内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module game {
/**
*
* @author xsstomy
*
*/
export class SceneEvent extends egret.Event{
public static ChangeScene: string = "changeScene";
public eventType: string;//事件类型
public eventObj: any;//对象


public static GAME_START: string = "gamestart";
public static GAME_PLAYING: string = "gameplaying";
public static GAME_END: string = "gameend";
public constructor(type: string,bubbles: boolean = false,cancelable: boolean = false) {
super(type,bubbles,cancelable);
}
}
}

然后创建一个管理类,取名ViewManager

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
module game {
/**
*
* @author xsstomy
* 场景舞台,这里是我个人设定的为舞台
*/
export class ViewManager extends egret.Sprite {
public constructor() {
super();
}

private static instance: ViewManager;
private gameStart: GameStart;
private gamePlaying: GamePlaying;
private gameOver: GameOver;

//获取单例
public static getInstance(): ViewManager {
if(ViewManager.instance == null) {
ViewManager.instance = new ViewManager();
}
return ViewManager.instance;
}

//开始
public start() {
this.init();

this.initListener();
}

//初始化数据
private init() {
this.gameStart = new GameStart();
this.gameOver = new GameOver();
this.gamePlaying = new GamePlaying();

this.addChild(this.gameStart);
}

//初始化事件监听
private initListener() {

this.addEventListener(SceneEvent.ChangeScene,this.onChangeScene,this);
}
private onChangeScene(e: SceneEvent) {

//移除所有子对象
this.removeChildren();

//判断事件,接下来添加哪个场景在舞台展现
switch(e.eventType) {
case SceneEvent.GAME_START:
this.addChild(this.gameStart);
break;

case SceneEvent.GAME_PLAYING:
this.addChild(this.gamePlaying);
break;

case SceneEvent.GAME_END:
this.addChild(this.gameOver);
break;
default: break;
}
}
}
}

皮肤与逻辑代码

这里以GameStart.ts类讲解。

this.skinName = "gameStartSkin";
我们默认在构造函数中赋值皮肤,更多使用方式查看这里.

public startBtn:eui.Button;
这里定义了一个共有变量,取名为startBtn,类型为eui.Button.注意 这里的名称很重要,可以对比图中的图层,一个Button组件,ID为startBtn. 这两者就同一个同一个ID关联起来了。即面板中的Button组件,游戏开始按钮,在逻辑代码中,我们操作startBtn这个代码中的逻辑,那么显示效果就跟着发生变化。

private sceneEvent: SceneEvent = new SceneEvent(SceneEvent.ChangeScene);
这里定义一个场景切换事件变量。

1
2
3
4
5
6
public childrenCreated() {

this.startBtn.addEventListener(egret.TouchEvent.TOUCH_TAP,this.onTouchTab,this)

this.sceneEvent.eventType = SceneEvent.GAME_PLAYING;
}

这里我们覆写 childrenCreated() 方法。这个方法,在皮肤上所有的子元素创建完成之后才会调用(前提是同步调用)。即相当于,我们需要的操作的元素已经初始化好了,然后我们在这个方法里面添加相对应的逻辑。这里给button添加了一个点击事件。这里说一下,在eui和gui中,所有的组件的touchEnabled 默认的都为 true;非gui和eui,显示对象,默认都是关闭的,即touchEnabled = false

1
2
3
private onTouchTab(e: egret.TouchEvent) {
ViewManager.getInstance().dispatchEvent(this.sceneEvent);
}

监听startBtn点击事件。

页面分析

loading页面,游戏开始页面,游戏进行中页面,游戏结束页面

皮肤

1.loading页面

1.1 在项目Racing下resource/mySkins/文件,鼠标右键创建exml,取名loadingUISkin,基于eui.Component组件.
宽度为480.高度为800.

1.2 拖拽素材到设计区域,在属性面板点击填充,然后设置ID为loadingBg

1.3 拖拽一个group组件到设计区域,然后拖拽素材到group组件内,然后在属性面板点击填充,调整一下group的合适大小,设置groupID为loadingGroup

1.4 同步骤1.3在group组件内添加一个label组件,设置ID为loadingText,设置约束。

1.5 现在来编辑loading页面对应的逻辑类。我们把项目的类修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class LoadingUI extends eui.Component {
public constructor() {
super();
this.skinName = "loadingUISkin";
}

public loadingText: eui.Label;
public loadingGroup: eui.Group;
private loadingLength: number = 235;//车道的像素距离

public setProgress(current,total): void {
this.loadingText&&(this.loadingText.text = "" + Math.floor((current / total) * 100) + "%");
this.loadingGroup&&(this.loadingGroup.x += Math.floor(this.loadingLength / total));
}
}

这里loadingLength要简单的解释一下,如下图红框长度距离就是235,也就是loadingLength.

这里要说一下,因为引擎改版的原因,可能进度条已经无法正常显示了,这里就不同步更新了

在这里修改之后,我们还需要在我们的入口类Main.ts中修改一下。
主要修改了onResourceLoadComplete方法里面的内容,这里更详细的教程可以参考这里的loading.
loading页面到此结束了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private onResourceLoadComplete(event:RES.ResourceEvent):void {
if (event.groupName == "preload") {

this.stage.removeChild(this.loadingView);
RES.removeEventListener(RES.ResourceEvent.GROUP_COMPLETE, this.onResourceLoadComplete, this);
RES.removeEventListener(RES.ResourceEvent.GROUP_LOAD_ERROR, this.onResourceLoadError, this);
RES.removeEventListener(RES.ResourceEvent.GROUP_PROGRESS, this.onResourceProgress, this);
this.isResourceLoadEnd = true;
this.createScene();
}else if(event.groupName =="loadingui")
{
//Config loading process interface
//设置加载进度界面
this.loadingView = new LoadingUI();
this.stage.addChild(this.loadingView);
RES.loadGroup("preload");
}
}

开始界面比较简单,都是图片资源.

2.游戏开始界面

2.1 创建皮肤基于eui.Component组件,宽度480,高度800,取名gameStartSkin

2.2 拖拽素材进行布局,同时对应的素材设置对应的ID

最终效果图

3.游戏进行中界面

3.1创建皮肤基于eui.Component组件,宽度480,高度800,取名gamePlayingSkin

3.2 拖拽准备好的背景图到设计面板,然后在属性栏设置位置x:0,y:0;然后取名为bg

3.3 把在小车,起跑线,车灯按照3.2中的操作,设置位置和ID

3.4 设置distanceBg,参考3.2的操作步骤,设置对应的属性值。

3.5 设置timeBg,参考3.2的操作步骤,设置对应的属性值。

3.6 拷贝一份bg,设置ID为bg1,位置x:0,y:-800。

3.7 拖拽BitmapLabel组件,然后设置坐标,设置ID为distanceNum。

3.8 拖拽BitmapLabel组件,然后设置坐标,设置ID为timeNum。

4.游戏结束界面

4.1创建皮肤基于eui.Component组件,宽度480,高度800,取名gameOverSkin

4.2拖拽Button组件到设计区域,然后用快捷方式创建Button皮肤,然后设置位置和ID

4.3拖拽一个group组件,同时拖拽一个背景素材到group组件内部,然后设置位置和ID.这里没有设置约束,主要是准备在逻辑中做一个
向下滑动的效果。

4.4拖拽结果素材到设计区域,设置位置和ID,这里取名ID为result,在逻辑代码中判断是成功了还是失败了。然后替换资源,这里可以理解为只是
一个零时的替代资源。

页面到这里基本已经结束了。下一节,主要讲解皮肤和逻辑类的使用。

教程的目的和用意

这里通过一个小的EUI项目,主要介绍新的EUI在Egret Wing 中的教程使用,同时会大概讲解我们推荐的egret项目开发流程,以及EgretWing 中的一些细小功能,不会过多的涉及代码的逻辑及代码的解析。希望大家能够通过此系列文章能够熟悉使用EgretWing。

开发之工具

  • TextureMerger 1.5.5版本
  • ResDepot 1.4.2
  • EgretWing 2.1.4prerelease
  • EgretEngine 2.5.5

准备工作

这里大概介绍一下开发的流程。

  1. 准备好项目需要的素材,这里推荐使用碎图.(至于原因,大家可以查看一下ResDepot中发布时候的合图功能)
  2. 创建EUI项目,把资源拷贝到项目/resource/mysource下.(这里文件名可以根据自己喜好取名)
  3. 打开ResDepot,然后用ResDepot打开刚才创建的项目中resource/default.res.json 文件.
  4. 把准备好的资源素材全部拖拽到ResDepot 刚才打开的default.res.json文件中,然后整理操作.(具体使用方法)
  5. 项目开发.
  6. 项目开发完成,发布项目h5版本.
  7. 在ResDepot 中发布资源.

这里说下,我这里主要以一个简单的赛车游戏做讲解。
规则很简单,是否能够跑到10000km。如果跑到10000km,就算是成功,如果在游戏过程中碰撞到其它小车,就算是失败。

这里正式开始开发之前准备工作具体操作

  1. 打开EgretWing 2.1.4 ,创建EUI项目 ,取名Racing,这里引擎是2.5.5版本。

  2. 点击下一步,因为我们要做的赛车游戏是锁定竖屏的,我这里缩放模式选择fixedWidth,旋转设置选择portrait,具体的区别查看缩放模式和旋转说明

  3. 在Racing项目中,resource文件下,创建两个文件夹,一个叫mySkins,一个叫mySources,把准备好的资源拷贝到mySources
    文件夹下,然后删除项目自带的资源和皮肤,即Racing/resource/下的assets和eui_skins文件。然后点击包资源管理器中的刷新按钮。
    这里的拷贝资源不是在EgretWing中拷贝。结果如下图

  4. 在游戏中因为要使用到数字,即使用到eui.BitmapLabel组件,这里我们需要用TextureMerger,处理一下资源具体操作。然后用ResDepot打开Racing项目中resource文件下的default.res.json,删除项目中自带的资源,然后添加在第3步中准备好的资源。具体的操作这里不细述ResDepot教程。最终的结果,可以下载源代码查看配置。链接.

到这里我们的准备工作基本已经完成。这里我们简单分析一下接下来要做的界面。

赛车游戏界面

在这里,主要会做几个界面,loadingUI,游戏开始,游戏进行中,游戏结束等几个界面。一般会有美术提供或者策划提供游戏原型。这里就按照自己的思路来了。

这篇文章就讲到这里。敬请期待后面的文章。

0%