愿你万里归来,仍是少年

今年,是我工作的第十个年头,最近家中有变故,每个月都要回来几次。 有了更多的机会走在年少时熟悉的路上,偶尔能见到儿时熟悉的面孔,也常常想起一些往事。

儿时的很多事情,其实我早已不记得,六岁前能回想起来的东西,其实也只有不多的几个画面:比如幼儿园时的午睡,幼儿园老师给的锅巴;还记得有一次从幼儿园的秋千上摔下来;小的时候总爱赖在大姨家,大表姐二表姐总是一路把我哄回家,然后趁我不注意再离开;还记得家里的故事书还有大姨给我讲故事听;还能想起来大姨带着我坐火车去三姨家,三姨家门口有条河,还记得小时候的健力宝;很多年之后,三姨三姨夫还有三表姐从很远的地方搬来我们的城市,和我们家住在一个房子里。

关于儿时更多的回忆,都来自别人的描述,我大姨总是给我讲,小时候揍过我,因为我爬在桥上伸头往桥下看;还有就是小时候的我话特别多,嘴总是闲不住;家里人总是说,小时候我一哭,整个气象局大院都能听到;而让我印象深刻的,是关于我的另一个故事。

姥姥走了之后大姨家成了家里人的聚集地,孩子们也常去大姨家,大姨父是个脾气很不好的人,所以姐姐们在大姨父面前都会假装很听话,我是个例外,据说有一次我在外面玩,大姨父叫我吃饭,我没搭理他,把他气够呛。大姨说我是小时候很耿直,不懂得”弯弯绕”。

而另一个伴随了我整个童年的故事,来自大院里的一个大叔,小时候我们都管他叫”老郑”。老郑是我爸单位的同事,住在离我家几十米外的一个胡同口,现在还能清晰地想起来他家房子后面破破烂烂的墙。小时候我爸单位的同事都管我叫”黑狗儿”,唯独老郑,每次见到我不只叫我黑狗,还叫我”滚刀肉”,”小兔崽子”,没有任何原因,他只是想给自己找个乐子。而我也一定会回他几句”你才是滚刀肉”, “你是老兔崽子”, 我还很清楚的记得有一年冬天我回了他几句,他抓住了我,把我扛在肩膀上,威胁要把我扔到路边的水沟里,但我仍然倔强的一直在骂他。

这个状态一直持续了很多年, 每年都会有几次激烈的冲突, 直到很多年之后, 有一年寒假我和儿时的玩伴李远鹏在老爸单位门口的院子里玩摔炮(真正的一摔就炸,里面是火药和火石,后来就禁止销售了),老郑正好从单位出来,见到我就又说了些什么,具体内容已经想不起来了,说完他就踩着角落那边半米多高的水泥台子,往大院的北门外去了。

接下来我做了一件到现在都觉得很牛逼得事情,我把手里剩下的几个摔炮全部放在了水泥台子上,那个刚刚他踩过的地方,然后把摔炮的盒子摊开了,盖在上面,然后和李远鹏一起离开。后来发生的事就和我预料的一样,我的记忆里总是有几百米之外看到他又回来踩响了摔炮的画面,然后他很大声地嚷嚷了些什么,但我不确定这个画面是不是真的存在,也或者是我这么些年自己塞到记忆里的。但我能确定的是,几天之后我爸因为这个事情训了我一顿,所以我确定陷阱的确生效了,那是儿时记忆里最后一次和老郑发生冲突,他活得挺好,但是再也没有发生过和之前一样的事情。

后来上了初中,平时不怎么用心读书,但成绩还好,能排在班级十几名,学年一百名开外(一个年级十个班),记得有一次考了个学年七十多名,但我是一个不安分的主儿,不写作业,上课扰乱纪律这种事总是少不了我,还记得有一次班主任因为个什么事情把我叫起来,最后问了我一句”你有没有信心考学年前六十”,我从没有过这种想法,所以我直接说了”没有”,然后。。。全班哄堂大笑,我是第一个回答了没有的人,在所有人看来这种政治不正确是无法接受的。

还有另外一件事,有一天下午自习课,我问了刘学斌一道题,被班主任爬后门逮个正着,处罚是一人罚款五块(也可能是十块),后来我想想自己蛮蠢的,当时老师只抓到了我,问我:”你刚才和谁说话”,我说”我刚问刘学斌题,然后他告诉我了。”,我要是说他没告诉我就不会连累他了,之后很多天,老师把我叫起来,让我交钱,我都和她说:”没有”,因为的确没有这么多零花钱,另外觉得因为这个事情找我妈要钱好像不太合理,所以这次罚款就被我硬生生赖掉了。但是刘学斌老老实实交了罚款,后来有一次和他一起回家路过他妈的理发店门口还被他妈因为这个事情盘问,现在想起来还是觉得对不住我的好朋友。

高中之后,我和刘学斌就再也没有联系过,但在一所高中,常常能见到,从没说过话,好像在高三的时候,我俩刚好在一家店里吃晚饭,我鼓足了勇气去和他寒暄了几句,那时我们最后一次对话,我还很清楚的记得初中的时候晚上放学我总是绕道陪他走到家附近,然后再骑车回家,我还记得他特别喜欢骑我的十八变斯波兹曼,前轮调到最小,后轮调到最大,他会笑着说”没阻力”,我知道他可能看不到我写的文字,但我还能想起来当年和他一起玩游戏机,一起和他去娱乐城玩拳皇97&98,我现在还能清楚地背出来当年送给他的QQ号码,我还能想起来当年我们常常走过的那条路的样子。

再后来,高中、大学、在外工作,无数次因为我的“耿直”把自己陷入尴尬的境地,曾经当着全班同学的面和老师理论对错,不知道多少次在公司会议上数落领导和公司的不是;当然也因为这样,身边有了不少好朋友,得到了不少人的信任。

这些年在外经历了很多,我的人生目标也一次又一次的在进行修正,虽然我一直认为价值观没有发生变化,但是或多或少一定会受到外界环境的影响。在大公司做事说话都要小心翼翼,每天反反复复在和身边的人谈论着车子房子和孩子,已经很少能和一个新认识的朋友谈谈理想和未来。

最后两句话,是抄来的:愿你万里归来,仍是少年;愿你心有所安,仍怀赤子之心。

我的2016

这一年对我来说又是一个丰富多彩的一年,过去很长一段时间因为种种原因没有再静下心来写一些什么,所以这次一定要把这一篇写完,不希望2016年的总结和前几年一样不了了之。

工作篇

#友宝 #裁员 #优酷 #阿里 #996

过去一年对我影响最大的事情可能都与工作相关,15年底友宝裁员,我没选择去深圳总部,只能拿钱走人。和同事几个人一起去了他们成立的新公司,和来北京之前一样,工作的第八个年头没有公司年会,没有双薪,没有年终奖,也没有希望,所以我离开了,重新启程。

没能通过美团的面试,也没有去极客学院,最后机缘巧合入职了优酷会员中心。背景调查用了整整一个月的时间,3月入职,4月优酷土豆被阿里收购,所以摇身一变成了阿里人。

新的工作有一些机会可以接触到一些核心业务,也有可以负责一些比较重要的技术改造,令我感到意外的是,这一年竟然几乎没怎么和产品经理打交道。

下半年优酷经历了大规模的空降和换血,新官上任最重要的几件事是融入阿里的文化,使用阿里的技术,当然也要在双十一、双十二时接受考验。代价是很惨痛的,我们经历了4个月的996,多数人连十一长假都在加班,还有一堆的年假和带薪病假等着过期。

令人欣慰的是,昨天,是996的最后一天,今年会有年会,有双薪,有项目奖,有年终奖。

个人成长篇

#英语 #口琴 #吉他 #C #游泳

除了工作,今年花了最多时间的事情是学英语,背了几千个单词,早晚的交通时间大多都用来学英语,如果不出意外,今天会完成在扇贝单词的第174次打卡。

除了996最忙的一段时间之外,每周都会去游泳,一方面为了锻炼身体,另一方面为了保证长时间写代码颈椎不会很痛,今年还是没能掌握自由泳,但是蛙泳各方面都有进步,明年要在速度上有所提高。

今年很少利用业余时间研究技术,这可能是一件很遗憾的事情,但是在工作时间内还是学到了大量的东西,在工作中处理的数据规模和并发量也达到了一个新的层次。

最近几个月花了一些时间重新学习了C语言,现在已经把相关语法特性学习完,正在学习数据结构和算法,希望能坚持下去,用到实际应用中,希望未来有机会能深入了解现在用到的相关技术栈的实现原理。

大概有半年没有再弹过吉他,谱子已经忘光,但是今年还是有进步的,可以大横按F和弦了。布鲁斯口琴也有进步,虽然不是很熟练,但已经基本上找到了压音的方法。

旅行篇

#香港 #泰国 #澳门 #台湾 #北戴河 #深圳 #珠海

护照已经拿到手快三年,今年终于用到了,换工作的空档借道香港去泰国转了转,玩的蛮好,更重要的是也正是从那个时候开始,让我有了另一个目标,想去更远的地方走一走,工作或者生活,也正是从那个时候开始认认真真的学英语。

体验过泰国之后就对国内旅游不再感兴趣了,今年去了一次北戴河,体验糟透了。

下半年花了一些时间规划了一次台湾之旅,整个过程很曲折,不过在台湾的那段时间是非常开心的,也颠覆了我对很多事情的想法,想去了解相关的政治、历史和文化,因为我从来没想过,这些几十年前被迫离开故土的人,如今在这里生活的如此幸福。

去台湾之前顺便去澳门逛了一下,这里的确是男人的天堂,不过最终我还是没有在澳门赌上两把,在见识过别人十几万一局的豪赌之后,我对赌博这个事情有了新的认识;虽然整个在澳门的过程中我都没见到,但估计澳门的色情产业也是很发达的。

理财篇

#互联网金融 #A股 #美股

离开友宝之后我的账户上第一次达到了6位数,所以也开始思考如何进行理财,目前大部分的钱放在定期的互联网金融产品里,一小部分在活期产品里,另一小部分在A股和美股市场,股票这东西我就不展开了,目前还不具备相关的知识,甚至还不能以正确的心态来看待,未来还要持续的关注和学习。

读书篇

  • 《C Primer Plus》内容很丰富,适合入门学习,这是我第一次把一本如此厚的书认认真真的看了两遍,但是看完之后才发现,这只是万里长征的第一步。

  • 《美国种族简史》内容很有趣,的确引发了不少思考。

  • 《图解HTTP》、《 JavaScript设计模式》、《RabbitMQ实战》这三本,我现在竟然已经回忆不起来里面的任何细节了,看来需要好好反思一下自己的读书方法。

  • 《硅谷之谜》今年不经意的发现,吴军博士也开始写烂书了,很失望。

  • 《人月神话》书里讨论了很多软件工程中的问题,很多事情都很有共鸣,这些问题被大家讨论了几十年,但是为什么现在这么多的团队还是做不好?现在还对书里提到的外科医生团队记忆犹新,希望以后可以有机会尝试。

  • 《深入浅出Rails》为数不多的rails中文书籍,书中的内容比较旧,而且也只展示了rails中最基础的一部分,很遗憾没能走上rails这条路。

  • 《Node与Express开发》、《ECMAScript6入门》前两年JavaScript实在是太火了,所以忍不住也学习了一些相关内容,感叹JavaScript相关技术进步速度如此迅速。

  • 《JavaScript高级程序设计》是本好书,我第一次读到面向对象那部分的时候是十分震撼的,进阶必读。

电影&电视篇

  • 地球脉动
  • 西部世界
  • 怦然心动
  • 奇异博士
  • 大鱼海棠
  • 超人总动员
  • 纳尼亚传奇1
  • 釜山行
  • 魔兽
  • 奇幻森林
  • 华尔街之狼
  • 恐怖直播
  • 心迷宫
  • 唐人街探案
  • 寻龙诀
  • 云中行走
  • 一个勺子
  • 老炮儿

今年看剧较少,希望明年有更多的好电影,有了投影仪之后看电影的体验更好了。

游戏篇

#Ingress #皇室战争 #MineCraft #暗黑3

  • 今年还偶尔会进Ingress里转转,暑假的时候变成了军火商,在淘宝卖了不少游戏道具;
  • 沉迷了几个月皇室战争,玩竞技类游戏始终让我感觉很痛苦,特别是这类付费手游,所以最终还是决定放弃竞技游戏;
  • 之后玩了一段时间Mine Craft,是一款很不错的游戏,感觉可以wan shang;
  • 最近两个月为了找回曾经玩电脑游戏的那种快乐,买了暗黑3,装在新mac pro上,进度比较慢,不过估计未来还会玩一段时间;
  • 今年一直对PS和VR眼镜蠢蠢欲动,估计下次有机会就下手了。

剁手篇

  • HHKB Professlonal 2:去年买了一个白色无刻印放在单位,在家李用电脑感觉各种不习惯,眼看着人民币贬值,越来越贵,最终还是又买了一个回来,这样家里的工作台就完美了。

  • 投影仪:大幅度的提升幸福感,没有买各种国产智能投影仪,感觉两三千块拥有这么功能画面和亮度一定不会特别理想,最后买了一款Sony的低端投影,没有内置系统,没有音箱,没有3D,外接了电视盒子和蓝牙音箱,体验蛮好,缺点是噪音有些大,看节目的时候可以忽略不计。

  • 打印机:总会有很多时候需要去打印社或者公司打印、扫描一些东西,后来想一想还是买了,三四百块钱还是无线的,很方便。

  • Apple TV:在香港顺手买的,体验很好,可以在国内实在没有太多用途,挂闲鱼卖掉了。

  • Iphone SE,Iphone 6:上半年终于买了自己的第一部Iphone,到手没到一周去音乐节被扒走了,郁闷了两周之后又入了低配的Iphone 6,入了苹果的坑就很难换别的产品了,另外,16G真的不够用,不够用,不够用。

  • NAS:一方面换了mac之后磁盘空间十分紧张,另一方面也感觉有一些数据很重要,比如代码、照片、文档之类的,需要有安全的备份机制,所以买了nas,两块硬盘做raid,设置了time machine,以后再也不用担心电子设备丢掉或者磁盘坏掉,还买了个PT账号,里面的小电影已经多的看不过来了。

  • 另外还入了登山鞋一双、皮鞋一双、电脑背包和新的眼镜,终于舍得去买一些自己很喜欢的东西了。

其他的碎碎念

  • 拿到了本科毕业证,在等学位证
  • 淘宝店被给了两个恶意差评,被勒索,客服拒绝受理相关投诉
  • 去年过年老板给发了200块钱红包 #大SB
  • 去年过年没赶上航班
  • 今年坐了好多好多好多次飞机
  • 今年仍然没买到春运的火车票 #票贩子越来越专业
  • 大公司政治水很深
  • 乌龟挺健康,但是背甲角质化很严重,买了各种食物来补充营养
  • 种了几次花花草草都没发芽
  • uber hackthon 被队友坑
  • 自己的一些小项目还是搁置状态
  • 发现自己可能再也买不起北京的房子
  • 想成为老司机但是不想买车
  • 已经9个月没有剪头发
  • 了解了一下开超市相关的事情
  • 最近在了解猎头行业

2017想做些什么

  • 学射箭
  • 好好练习一下单板
  • 清明去新加坡的机票已经买好
  • 去欧美看一看
  • 考摩托驾照
  • 练车,这很重要
  • 修牙
  • 英语口语和单词量
  • 蛙泳要更快,自由泳可以以后再学
  • 这是工作的第十个年头
  • C语言要继续学下去
  • 在技术上要有输出和积累
  • 要考虑结婚生娃,为了人类
  • 口琴要继续练

以上,应该就是今年的总结,有些混乱,因为做过的事情和想做的事情实在太多,希望能尽可能都记录下来,这对我来说很重要。

Screeps新手游戏指南

一款玩着玩着就变成JavaScript大神的游戏。

一款可以增加你赚钱能力的游戏。

在写下这段文字的时候,我已经删除了这个游戏里所有的数据,因为这个游戏消耗了我太多时间,本着纪念这段游戏经历的目地,我写了这篇文章。

什么是Screeps?

The world's first MMO strategy open world game for programmers.

http://screeps.com

官方这样定义这款游戏:第一款针对开发者的大型多人在线开放策略游戏。而对于这款游戏,很多报道里会提到:”最难“,”战术沙盒“等概念。

是的没错,screeps就是这样一款游戏,一款需要编写代码才能玩的在线策略游戏。玩家需要在游戏中编写javascript代码来控制所有的游戏行为。比如:

查找并攻击敌人

1
2
3
4
enemy = creep.pos.findClosestByRange(FIND_HOSTILE_CREEPS); // 找到距离最近的敌人
if (creep.attack(enemy) == ERR_NOT_IN_RANGE) { // 攻击敌人
creep.moveTo(enemy); // 如果距离不够则向敌人移动
}

找到能量矿并采集

1
2
3
4
sources = creep.room.find(FIND_SOURCES); // 找到房间里所有的能量矿
if (creep.harvest(sources[0]) == ERR_NOT_IN_RANGE) { // 采集能量
creep.moveTo(sources[0]); // 如果距离不够则向矿移动
}

你的代码会保存在游戏中,并且一直运行下去。游戏甚至给你提供了缓存,有代码提示的在线代码编辑器,控制台和完善的API文档。

在游戏里都能做什么?

  • 发展基地
  • 生产工人和士兵
  • 采集资源
  • 攻击其他玩家
  • 建造防御设施
  • 和盟友互相支援

目前有哪些游戏元素

  • 两种资源:energy和power
  • 建筑:spawn,power spawn, extension,storage,road,wall,rampart,tower,observer,link
  • 四矿地图和Source Keeper
  • control rank 和 power rank
  • 远程兵种,近战兵种,治疗兵种,混合兵种
  • controller 房间等级,越高能造的建筑越多,上限8
  • GCL 游戏等级,每一个等级可以多控制一个房间

更多内容可以查看官方文档 http://support.screeps.com/

Screeps收费嘛?

游戏中程序执行会消耗游戏中的CPU,每个月你可以免费获得600个CPU,如果优化的好,600个CPU可以很轻松的控制20个游戏单位,但是如果你想发展多基地或者进入排行榜,还是建议你购买CPU进行游戏,CPU的使用上限是每天300个,也就是说,这款游戏每天可能花掉你人民币0-6块钱。

新人必须知道的

  • 你的基地叫做Spawn,你的工人和士兵叫做Creep
  • 你的代码每执行一次是一个tick,一个tick是2秒
  • 绿色的地方是泥地,如果不修路走过去比乌龟还慢,不是泥地也需要修路
  • Creep有1500ticks的生命,可以使用spawn给Creep延长生命,但是并不建议这么做
  • 路每隔一段时间就会坏,需要经常修
  • energy矿每300ticks刷新3000个,计算好你的WORK数量,5-6个就够了
  • 一定要有防御机制,任何人都可能攻击你,并且可以在几分钟内摧毁你的所有建筑,杀死你的所有Creep,而且并不需要有理由

游戏策略

如何提高工人效率?

和现实中一样,明确的分工可以有效的提高工作效率,矿工专门采矿,运输工种专门运输,升级工人只负责升级,但由于CPU有限,你需要在效率和工人数量上进行权衡。

如何节省CPU?

你可以像调试其他项目一样调试你的程序效率,找到那些CPU消耗很高的代码并且优化它们,官方文档上对每个方法的CPU消耗情况有详细的说明,对于新手,CPU最大的消耗在于查找目标和跨房间移动,解决这两个问题可以节省超过一半的CPU。

应该选择什么样的房间进行游戏?

遵循一个原则:附近资源丰富并且容易防守

移动速度是如何计算的?

请参考 http://codepen.io/findoff/full/RPmqOd/

如何高效的修路

  • 合理修路可以节约1/4的creep花费,你只要有一半的MOVE就可以达到最大移动速度。
  • 只要有creep经过的地方都应该修路,有一个creep走的地方修一条,有多个creep走的地方修两条,如果非常多,就修很多条,但是不要到处修。

建筑的摆放有什么要注意的

  • storage:让从所有搬运工的走动距离最短,通常选择房间的正中央,让其他使用能量的建筑环绕在周围。
  • extension:离storage要近,游戏中有各种个样的高效摆放方式。
  • spawn:让所有的单位出去工作的距离最短,通常在中心附近。
  • tower:分散在房间的各个角落,并且保证出口附近所有的位置都在10个距离以内,这样能做到最高效的升级围墙,防御敌人,修复建筑,但是要记住tower的效率没有工人高。
  • wall和rampart:建议建在最外围,这样可以让最少的敌人进入房间,易于防守和维修,一个外围的围墙最多能被3个近距离单位+4个远距离单位攻击,而一个内部的围墙可以被至少3个近距离单位+十几个远距离单位围攻。

Link的使用

  • 用来代替搬运工运输资源
  • 用来当升级工人的扩展存储
  • 用来当小型的storage
  • 将link建造在外围rampart上可以实现在有围墙的情况下和其他玩家交换资源

你可能不知道的

  • 在有observer之前,你只能看到有你的单位的房间,我指的是代码里
  • 你可以帮助其他玩家建造和维修
  • 你可以帮助其他玩家升级Controller
  • 你可以把资源丢进其他玩家的storage
  • rampart可以叠加在任何建筑上,千万别忘了给spawn、storage上rampart,wall上也可以放一个rampart
  • renew一个creep并不划算,但是你可以在creep路过的时候顺便renew一下
  • 你可以用Grunt提交你的本地代码 http://support.screeps.com/hc/en-us/articles/203022512-Committing-local-scripts-using-Grunt

我的全部游戏代码已经发布在Github上 https://github.com/m4ker/screeps

我在Screeps里的最后一分钟

死磕JavaScript面向对象 - 定义对象

前言

近来发现JavaScript能做的事情越来越多,用的人也变多了,有太多的框架和模式都搞不清楚原理(早些年的prototype, yui, jQuery, 到后来的MVC,MVVM),所以想深入的学习一下JavaScript。

经过一段时间的摸索,终于知道了ECMAScript的来龙去脉,而我对JavaScript的认知还停留在ES3上面。但对于ES3,也只知道一些简单的用法,看高手写代码竟然完全看不懂。

最近几天卡在了闭包和对象两个章节上,因为和其他语言差的太多,所以看了很多文章仍然还只是一直半解,对于一个函数作为一等公民的面向对象语言,搞不清楚对象的基本知识实在有些说不过去,所以决定对相关内容进行一次深入的整理。

原始方式 1

1
2
3
4
5
6
7
8
9
// 1.js
var robot = new Object;
// var robot = {};
robot.height = '120cm';
robot.weight = '50kg';
robot.color = 'red';
robot.say = function (string) {
console.log(string);
}

用这种方式是不是感觉有点怪?感觉不像一个整体,所以我们要用下面的方法把对象封装起来:

原始方式 2

1
2
3
4
5
6
7
8
9
// 2.js
var robot = {
height : '120cm',
weight : '50kg',
color : 'blue',
say : function (string) {
console.log(string);
}
}

在JavaScript中,我们通常以以上两种方式创建对象,在实际开发过程中我们会遇到一些问题:

  • 问题1: 没有办法识别对象的类型,因为我们创建的所有对象都是Object
  • 问题2: 创建多个对象很麻烦

工厂模式

工厂模式用来解决代码重用的问题(问题2),可以用一个函数多次创建同一个结构的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 3.js
function createRobot(height, weight, color) {
return {
height : height + 'cm',
weight : weight + 'kg',
color : color,
say : function (string) {
console.log(string);
}
}
}

var robot1 = createRobot(100,50,'red');
var robot2 = createRobot(120,60,'blue');
console.log(robot1.height,robot1.weight,robot1.color); // 100cm 50kg red
console.log(robot2.height,robot2.weight,robot2.color); // 120cm 60kg blue

这段代码看上去已经很好的解决了代码重用的问题,但是又产生了新问题:

  • 问题3:多个同类型对象的方法被重复创建

这里指的就是robot1.say和robot2.say方法,本是相同的函数却被声明了两次,体现在代码里就是:

1
console.log(robot1.say === robot2.say); // false

为了解决这个问题,我们要把say方法拿到工厂外面单独进行定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 4.js
function say(string) {
// var say = function(string) {
console.log(string);
}
function createRobot(height, weight, color) {
return {
height : height + 'cm',
weight : weight + 'kg',
color : color,
say : say
}
}

var robot1 = createRobot(100,50,'red');
var robot2 = createRobot(120,60,'blue');
console.log(robot1.say === robot2.say); // true
console.log(robot1 instanceof Object); // true
console.log(robot1 instanceof createRobot); // false

这样我们就解决了问题3,say方法只被声明了一次,所有robot对象的say方法都指向同一个函数。

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 5.js
function say(string) {
// var say = function(string) {
console.log(string);
}
function Robot(height, weight, color) {
this.height = height + 'cm';
this.weight = weight + 'kg';
this.color = color;
this.say = say;
}
var robot1 = new Robot(100,50,'red');
var robot2 = new Robot(120,60,'blue');
console.log(robot1.height,robot1.weight,robot1.color); // 100cm 50kg red
console.log(robot2.height,robot2.weight,robot2.color); // 120cm 60kg blue
console.log(robot1.say === robot2.say); // true
console.log(robot1 instanceof Object); // true
console.log(robot1 instanceof Robot); // true

构造函数看上去更像其他语言中的面向对象,并且解决了问题1,可以识别对象的类型。

原型模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 6.js
function Robot() {
}
Robot.prototype.height = '120cm';
Robot.prototype.weight = '60kg';
Robot.prototype.color = 'blue';
Robot.prototype.parts = ['body'];
Robot.prototype.say = function(string) {
console.log(string);
}

var robot1 = new Robot();
var robot2 = new Robot();
console.log(robot1.height); // 120cm
console.log(robot2.height); // 120cm
console.log(robot1 instanceof Object); // true
console.log(robot1 instanceof Robot); // true
console.log(robot1.say === robot2.say); // true

这里可以看到,使用原型模式使得robot1和robot2公用了同一套属性值和方法。并且可以正确识别对象类型。

接下来我又尝试了对实例的属性进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log(robot1.hasOwnProperty('height')); // false
console.log(robot2.hasOwnProperty('height')); // false
robot1.height = '100cm';
console.log(robot1.height); // 100cm
console.log(robot2.height); // 120cm
console.log(robot1.hasOwnProperty('height')); // true
console.log(robot2.hasOwnProperty('height')); // false
robot1.say = function(string) {
console.log('Ah...' + string);
};
robot1.say('hello world'); // Ah...hello world
robot2.say('hello world'); // hello world
delete robot1.say;
robot1.say('hello world'); // hello world
robot2.say('hello world'); // hello world

这段代码说明了,实例上定义的属性/方法会屏蔽掉原型上的同名属性/方法,而在delete掉实例上的方法后,原型上的方法在实例上又恢复可用了。但是我遇到了一个例外:

1
2
3
console.log(robot1.parts, robot2.parts); // [ 'body' ] [ 'body' ]
robot1.parts.push('arm');
console.log(robot1.parts, robot2.parts); // [ 'body', 'arm' ] [ 'body', 'arm' ]

对robot1属性的修改同时影响了robot2, 这说明对于数组的修改是发生在原型上的,这并不是我想要得到的效果。

  • 问题4: 对于原型上数组属性的修改影响到了其他实例

《JavaScript 高级程序设计》对原型链进行了详细的解读,当然上面这个问题就是这本书里讲到的,并且还讲了下面一些内容:

1. 创建构造函数的时候发生了什么?

  1. 在创建任何函数的时候,JavaScript都会创建一个原型对象,并且将函数的prototype属性指向该原型对象
  2. 原型对象会有一个constructor属性,指向该函数
  3. 而原型对象的__proto__属性,指向Object.prototype
  4. Object.prototype.constructor 指向 Object 函数
  5. Object.prototype是原型链最顶层,没有__proto__属性
1
2
3
4
5
6
7
8
9
10
11
// 7.js
function myFunc () {
}

console.log(myFunc.prototype); // {}
console.log(typeof myFunc.prototype); // object , 证明1
console.log(myFunc.prototype.constructor); // [Function: myFunc] ,证明2
console.log(myFunc.prototype.__proto__); // {}
console.log(myFunc.prototype.__proto__ === Object.prototype); // true,证明3
console.log(myFunc.prototype.__proto__.constructor); // [Function: Object],证明4
console.log(myFunc.prototype.__proto__.__proto__); // null,证明5

创建构造函数时的原型链

2. 使用new进行实例化之后发生了什么?

  1. 基于构造函数创建了一个实例对象
  2. 该实例对象的__proto__属性指向构造函数的prototype属性
  3. 该实例对象的constructor指向构造函数 // todo: 这部分要放到图片里吗?
  4. 原型链其他部分未发生变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var myObj = new myFunc;

console.log(myObj instanceof myFunc); // true
console.log(myObj instanceof Object); // true
console.log(myObj.constructor); // [Function: myFunc]

console.log(myObj.__proto__ == myFunc.prototype); // true

console.log(myFunc.prototype); // {}
console.log(typeof myFunc.prototype); // object
console.log(myFunc.prototype.constructor); // [Function: myFunc]
console.log(myFunc.prototype.__proto__); // {}
console.log(myFunc.prototype.__proto__.constructor); // [Function: Object]
console.log(myFunc.prototype.__proto__ === Object.prototype); // true
console.log(myFunc.prototype.__proto__.__proto__); // null

实例化对象时的原型链

3. 属性/方法调用的内部流程是怎么样的?

  1. 在实例中找
  2. 如果没找到,去__proto__里找
  3. 如果没找到,去__proto__.__proto__里找
  4. 如果__proto__为null,说明已经到达了原型链的最顶级,也就是Object.prototype,属性/方法在原型链上未定义,返回undefined

4. 重新定义原型链的时候遇到的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
// 8.js
function myFunc() {
}

var myObj = new myFunc();

myFunc.prototype = {
say:function(string){
console.log(string);
}
};

myObj.say('hello world'); // TypeError: undefined is not a function

这看上去很难理解,因为重写构造方法的原型并不会删掉之前的原型,也不会改变实例和原型之间的关系,所以我们又发现了一个问题

  • 问题5:重新定义prototype会切断原型链,并且你会发现constructor也不见了。

重新定义原型前的原型链
重新定义原型后的原型链

所以如果想要完美的重写prototype需要这样:

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
// 9.js
function myFunc() {
}

var myObj = new myFunc;

myFunc.prototype = {
constructor : myFunc,
// __proto__ 会自动创建并指向 Object.prototype
say: function(string) {

console.log(string);
}
};

console.log(myObj instanceof myFunc); // false
console.log(myObj instanceof Object); // true

console.log(myFunc.prototype.__proto__ == myObj.__proto__.__proto__); //true

myObj.__proto__ = myFunc.prototype;

console.log(myObj instanceof myFunc); // true
console.log(myObj instanceof Object); // true

console.log(myFunc.prototype.__proto__ == myObj.__proto__.__proto__); //true

myObj.say('hello world');

除了上面这些问题,其实原型模式还有一个问题:

  • 问题6: 原型模式的构造函数没有参数

混合构造函数&原型模式

为了解决问题4和问题6,就出现和混合构造函数&原型模式

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
// 10.js
function Robot(height, weight, color) { // 问题6解决
this.height = height + 'cm';
this.weight = weight + 'kg';
this.color = color;
this.parts = ['body'];
}

Robot.prototype.say = function(string) {
console.log(string);
}

var robot1 = new Robot(100,50,'red');
var robot2 = new Robot(120,60,'blue');
console.log(robot1);
/*
{ height: '100cm',
weight: '50kg',
color: 'red',
parts: [ 'body' ] }
*/

console.log(robot2);
/*
{ height: '120cm',
weight: '60kg',
color: 'blue',
parts: [ 'body' ] }
*/

robot1.parts.push('arm');
console.log(robot1.parts,robot2.parts); // [ 'body', 'arm' ] [ 'body' ], 问题4解决

动态原型模式

为了让代码看上去更美观一点儿,又有了动态原型模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 11.js
function Robot(height, weight, color) {
this.height = height + 'cm';
this.weight = weight + 'kg';
this.color = color;
this.parts = ['body'];

if (Robot.prototype.say === undefined) {
// if (Robot.say === undefined) { // 这样不行, 会重复执行,why?
// if (this.prototype.say === undefined) { // 这样也不行, TypeError: Cannot read property 'say' of undefined,why?
Robot.prototype.say = function(string) {
console.log(string);
}
}
}

var robot1 = new Robot(100,50,'red');
var robot2 = new Robot(120,60,'blue');

console.log(robot1.say === robot2.say);
robot1.say('hello world');
robot2.say('hello world');

混合工厂模式/寄生构造函数模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 12.js
function createRobot(height, weight, color) {
return {
height : height + 'cm',
weight : weight + 'kg',
color : color,
say : function (string) {
console.log(string);
}
}
}

var robot1 = new createRobot(100,50,'red');
var robot2 = new createRobot(120,60,'blue');
console.log(robot1.height,robot1.weight,robot1.color); // 100cm 50kg red
console.log(robot2.height,robot2.weight,robot2.color); // 120cm 60kg blue

W3School和《JavaScript 高级程序设计》中都提到了这样一种模式,而且还都说明了不推荐这样使用。这里让我很想不明白,从代码上看,和工厂模式没有区别,只是实例化的时候使用了new关键字,但是实例化的时候new并没有生效,个人理解这种方式的工作原理和工厂模式是一样的,所以也和工厂模式有着同样的问题。

(未完待续)

经典问题再解读:不用中间变量交换两个变量的值

大概八年前,写过这样一篇文章:不使用中间变量来交换变量的值,后来面试的时候常常遇到这题,最近翻出来看,发现当时对这个问题的理解不够深刻,所以今天又整理了一下。

1. 一些有限制的方法

字符串版本

1
2
3
4
5
6
<?php
$a = "a";
$b = "b";
$a .= $b; // a=ab, b=b
$b = str_replace($b, "", $a); // a=ab, b=a
$a = str_replace($b, "", $a); // a=b, b=a
1
2
3
4
5
6
<?php
$a = "a";
$b = "b";
$a .= $b; // a=ab, b=b
$b = substr($a, 0, (strlen($a) - strlen($b))); // a=ab, b=a
$a = substr($a, strlen($b)); // a=b, b=a

上面这两个方法使用了字符串替换和截取的方法,有一个限制就是只适用于字符串。

加减法

1
2
3
4
5
a = 1;
b = 2;
a = a + b; // a=3, b=2
b = a - b; // a=3, b=1
a = a - b; // a=2, b=1
1
2
3
4
5
a = 1;
b = 2;
a = b - a; // a=1,b=2
b = b - a; // a=1,b=1
a = b + a; // a=2,b=2

乘除法

1
2
3
a = a * b;
b = a / b;
a = a / b;

用除法来解决这个问题,多了一个限制,b不能等于0

一句话版本

1
a = b + 0 * (b = a);
1
a = (b - a) + (b = a);
1
a = (a + b) - (b = a);
1
a = b + (b = a) * 0;

这些方法利用了表达式的返回值

所有的加减乘除的方法里有两方面限制:

  • 只适用于数字
  • 如果变量是浮点数,会有精度上的损失

看到网上有一些人提出适用+和*的时候会导致结果向上溢出,但其实这并不影响结果,因为最后逆操作会产生一次向下溢出。

eval版

1
eval("a="+b+";b="+a);

eval版本可能有两个问题

  • 安全性
  • 是如果想支持更多的数据类型比较麻烦

异或版本

1
2
3
4
5
6
<?php
$a=10; //$a=1010
$b=12; //$b=1100
$a=$a^$b; //$a=0110,$b=1100
$b=$a^$b; //$a=0110,$b=1010
$a=$a^$b; //$a=1100=12,$b=1010

下面是简化版本

1
2
3
4
<?php
$a ^= $b;
$b ^= $a;
$a ^= $b;

异或适用于整数和字符串

2. 适用于所有的数据类型,并且没有限制的方法

对象版

1
2
3
a = {a : b, b : a};
b = a.b;
a = a.a;

数组版

1
2
3
a = [a,b];
b = a[0];
a = a[1];
1
a = [b,b=a][0];

匿名函数版

1
2
3
4
5
6
7
8
a=(function(){
try {
return b;
}
finally {
b = a;
}
})();

PHP 版本

1
list($var1, $var2) = [$var2, $var1];

Python&Ruby版本

1
a,b = b,a; // python和ruby程序员都笑了:D

ES6 版本

1
[a,b] = [b,a];

3. 总结一下

解决这个问题的基本思想有以下几种:

  • 将两个变量同时放入其中一个变量,再分别取出,例如字符串版本,数组版本,对象版本

  • 将两个变量通过某种计算的结果放入其中一个变量,再用计算结果和另一个已知变量逆向取回结果,例如异或版本和一部分加减乘除的版本

  • 利用语言特性,例如eval版本,匿名函数版本,php版本和python&ruby版本