面试总结

前言这次面试发现自己的问题还是很多很多,最基础的js数组的问题都没有熟悉到位,面试官问js数组有哪些方法,我都回答不上来,还有就是很多知识都很零散,以前明明看过,但是就是容易忘,现在对今天面试的题目总结一下。数组去重有哪些方法?排序后遍历去重 let array = [1,22,44,44,44,33,33,33,33,44,22,1] function deleteArray(){ let newArray = []; array = array.sort() array.map((index,item)=>{ if(index != array[item+1]){ newArray.push(index) } }) console.log(newArray) } deleteArray();//[1, 22, 33, 44]说明:sort()方法比较特殊,会将[1,12,3,4,24]排序成[1,12,24,3,4],你会发现如果该数字是两位数的话,sort只会根据第一个数字排序,这样显然是错的,所以我们在进行排序的时候要在sort中传入一个比较函数,这也牵扯出了一道很容易考的题:怎样实现一个随机数组(也称随机洗牌算法)? arr = arr.sort(function(a,b){return Math.random() - 0.5;})//最好是针对数值型数组分析:是的,你没有看错,一行代码就可以解决这个问题,只要在sort中放一个函数就可以,而且是不需要被调用的,根据函数返回值的正负,来判断怎么排序,具体参考这里。indexof去重 let array = [1,22,44,44,44,33,33,33,33,44,22,1] function deleteArray(){ let newArray = []; for(let i = 0;i<array.length;i++){ if(newArray.indexOf(array[i]) == -1){ newArray.push(array[i]) } } console.log(newArray) } deleteArray();set方法去重 let array = [1,22,44,44,44,33,33,33,33,44,22,1] function deleteArray(){ let newArray = [] //使用set方法进行去重过后得到的是一个对象,所以我们还要想办法把它变成一个数组 newArray = new Set(array) //Array.from可以直接将对象转换成数组,也可以使用…newArray的方法将可遍历对象转换成数组 newArray = Array.from(newArray)//或newArray = new Array(…newArray) console.log(newArray) } deleteArray();//[1, 22, 33, 44]使用reduce方法去重参考这里数组的方法有哪些?这些方法中不改变原数组的有哪些?数组的方法有:concat:连接两个数组,不改变原数组join:将数组中的内容放入一个字符串中,括号中的参数就是分隔符,不改变原数组slice:获取数组指定位置区间的元素,不改变原数组不传参数时,相当于不操作,返回整个数组传入一个参数时,会返回从该参数到数组末尾的所有元素传两个参数时,会返回从第一个参数到第二个参数之间的所有元素,但是不包含最后一个元素当传入的参数为负数时,会加上数组的长度再取元素,比如说slice(-2,-1)=>slice(3,4)(假设数组长度为5)当传入两个参数,并且第一个参数大于第二个参数时,会返回空数组pop:删除并返回数组的最后一个元素,会改变原数组shift:从数组的头部删除元素,返回的是该元素,会改变原数组unshift:向数组的头部添加元素,返回的是数组长度,会改变原数组push:向数组的末尾添加一个元素,返回的是数组长度,会改变原数组reverse:将数组进行反转,返回的是反转后的数组,会改变原数组sort:对数组进行排序,原理是先将数组中的元素转换成字符串,然后比较字符串,如果要正确的排序,需要在方法中传入一个比较函数,返回的是排序后的数组,会改变原数组splice:主要用途是在数组的中部插入或删除元素,返回的是从该数组中删除的元素,如果没有删除,则返回空数组,会改变原数组删除任意数量的项,需要传入两个参数,第一参数指定删除的开始位置,第二个参数指定删除多少项插入任意数量的项,需要传入3个参数,分别为起始位置,要删除的项数(一般取0),插入的项(可以为多项)替换指定数目的项,参数和上面完全相同,只是第二个参数不再取0indexOf:查找指定元素的位置,返回该元素第一次出现的位置,不会改变原数组如果只传入一个参数,表明是查找该元素如果传入两个参数,则第一个表示从哪个位置开始往后找,第二个参数表示要查找的元素lastIndexOf:从后往前查找指定元素的位置,只是和indexOf的查找方向不同,其他部分均相同reduce:这个方法很强大,也比较复杂,我专门总结了一篇文章,这个方法也不会改变原数组总结: 改变原数组的方法有:shift,push,pop,unshift,sort,reserve,splice 不改变原数组的有:concat,join,slice,reduce,indexOf,lastIndexOfhttp常见状态码有哪些?服务器怎么判断出是否应该返回304状态码?答:常见的状态码就先不说了,这篇文章中写了,重点看服务器怎么判断应该返回304状态码,具体实现为:浏览器第一次请求资源,响应成功,会返回200,然后浏览器会将这个资源的副本缓存在本地;(这个资源的响应头部会带有一个ETag字段,用来标志这个资源)下次再发送请求时,在请求头中会有一个If Modified Since字段,这个字段是告诉服务器如果在这个时间之后的资源没有发生更改,自己就可以直接从缓存中拿了,如果有更改,就需要发送新的资源了;(服务器就是通过ETag字段来判断资源是否更改)服务器通过匹配ETag字段发现没有更改时,会返回一个304。js中的事件冒泡和事件捕获答:事件冒泡是IE的事件流,事件开始是由最具体的元素接收,然后逐步向上级传播为较不具体的节点;而事件捕获是网景推出的事件流,和事件冒泡刚好相反,事件捕获是从外到内,从最不具体的流向具体的节点,也就是最具体的节点是最后接收到事件的。(补充:DOM2级事件的流的三个阶段跟别为事件捕获阶段,处于目标阶段,事件冒泡阶段)js中基本数据类型有哪些?引用类型有哪些?答:基本数据类型包括:null,undefined,string,number,boolean,symbool(es6中新增);引用类型只有Object;其中基本数据类型是存放在栈中,引用数据类型是存放在堆中。null和undefined的区别?答:null通常表示指向空指针的对象,而undefined一般是表示只进行声明但是没有初始化的变量。将null和undefined转换成number类型返回的是什么?答:Number(null)==0 Number(undefined)==NaN Number("")==0将哪些转换成布尔类型会返回false?答:false,NaN,"",null,undefined,0js事件循环机制?答:事件循环机制就是任务分为主任务和异步任务,只有在主任务都执行完毕后才会去异步队列中取异步任务执行,这样就会造成异步的假象。具体解释,可以参考这里。js中的微任务有哪些?答:微任务有process.nextTick和promise.thencall()和apply()以及bind()的区别?答:首先三者都可以绑定this,从而改变this的指向,区别在于call()和apply()只是绑定this指向,没有返回值;而使用bind()时,会返回一个函数,绑定了this指向后的这个函数在任何地方进行调用this都不会发生改变。而call和apply之间的区别在于前者传参时是逐个传入,而后者是传入一整个数组。数组进行遍历的方法有哪些?它们的区别在哪儿?答:遍历数组的方法有:for – 需要知道数组的长度forEach – 不需要数组的长度,没有返回值map – 返回每次函数调用的结果组成的数组,不改变原数组some – 只要函数调用结果有一个返回true,结果就是trueevery – 函数调用的结果全部返回true时,返回的就是truefilter – 根据条件对数组元素进行筛选,返回的是满足条件的元素组成的数组,不会改变原数组reduce – 为数组中的每个元素执行一个回调函数,上面也有提到。for-of – 用来遍历可迭代的数据结构,比如说数组,字符串,集合,类数组对象等。 遍历对象的方法有:for-in – 返回可枚举属性(包括自身和继承的),也可以遍历数组,不过返回的是数组的下标Object.getOwnPropertyNames – 返回该对象自身的所有属性(包括可枚举的和不可枚举的)组成的数组Object.keys – 返回该对象的自身的可枚举属性组成的数组 ...

December 23, 2018 · 1 min · jiezi

每天一个设计模式之享元模式

作者按:《每天一个设计模式》旨在初步领会设计模式的精髓,目前采用javascript和python两种语言实现。诚然,每种设计模式都有多种实现方式,但此小册只记录最直截了当的实现方式 :)个人技术博客-godbmw.com 欢迎来玩! 每周至少 1 篇原创技术分享,还有开源教程(webpack、设计模式)、面试刷题(偏前端)、知识整理(每周零碎),欢迎长期关注!本篇博客地址是:《每天一个设计模式之享元模式》。如果您也想进行知识整理 + 搭建功能完善/设计简约/快速启动的个人博客,请直接戳theme-bmw0. 项目地址享元模式·代码《每天一个设计模式》地址1. 什么是“享元模式”?享元模式:运用共享技术来减少创建对象的数量,从而减少内存占用、提高性能。享元模式提醒我们将一个对象的属性划分为内部和外部状态。内部状态:可以被对象集合共享,通常不会改变外部状态:根据应用场景经常改变享元模式是利用时间换取空间的优化模式。2. 应用场景享元模式虽然名字听起来比较高深,但是实际使用非常容易:只要是需要大量创建重复的类的代码块,均可以使用享元模式抽离内部/外部状态,减少重复类的创建。为了显示它的强大,下面的代码是简单地实现了大家耳熟能详的“对象池”,以彰显这种设计模式的魅力。3. 代码实现这里利用python和javascript实现了一个“通用对象池”类–ObjectPool。这个类管理一个装载空闲对象的数组,如果外部需要一个对象,直接从对象池中获取,而不是通过new操作。对象池可以大量减少重复创建相同的对象,从而节省了系统内存,提高运行效率。为了形象说明“享元模式”在“对象池”实现和应用,特别准备了模拟了File类,并且模拟了“文件下载”操作。通过阅读下方代码可以发现:对于File类,内部状态是pool属性和download方法;外部状态是name和src(文件名和文件链接)。借助对象池,实现了File类的复用。注:为了方便演示,Javascript实现的是并发操作,Python实现的是串行操作。输出结果略有不同。3.1 Python3 实现from time import sleepclass ObjectPool: # 通用对象池 def init(self): self.__pool = [] # 创建对象 def create(self, Obj): # 对象池中没有空闲对象,则创建一个新的对象 # 对象池中有空闲对象,直接取出,无需再次创建 return self.__pool.pop() if len(self.__pool) > 0 else Obj(self) # 对象回收 def recover(self, obj): return self.__pool.append(obj) # 对象池大小 def size(self): return len(self.__pool)class File: # 模拟文件对象 def init(self, pool): self.__pool = pool def download(self): # 模拟下载操作 print(’+ 从’, self.src, ‘开始下载’, self.name) sleep(0.1) print(’-’, self.name, ‘下载完成’) # 下载完毕后,将对象重新放入对象池 self.__pool.recover(self)if name == ‘main’: obj_pool = ObjectPool() file1 = obj_pool.create(File) file1.name = ‘文件1’ file1.src = ‘https://download1.com’ file1.download() file2 = obj_pool.create(File) file2.name = ‘文件2’ file2.src = ‘https://download2.com’ file2.download() file3 = obj_pool.create(File) file3.name = ‘文件3’ file3.src = ‘https://download3.com’ file3.download() print(’*’ * 20) print(‘下载了3个文件, 但其实只创建了’, obj_pool.size(), ‘个对象’)输出结果(这里为了方便演示直接使用了sleep方法,没有再用多线程模拟):+ 从 https://download1.com 开始下载 文件1- 文件1 下载完成+ 从 https://download2.com 开始下载 文件2- 文件2 下载完成+ 从 https://download3.com 开始下载 文件3- 文件3 下载完成下载了3个文件, 但其实只创建了 1 个对象3.2 ES6 实现// 对象池class ObjectPool { constructor() { this._pool = []; // } // 创建对象 create(Obj) { return this._pool.length === 0 ? new Obj(this) // 对象池中没有空闲对象,则创建一个新的对象 : this._pool.shift(); // 对象池中有空闲对象,直接取出,无需再次创建 } // 对象回收 recover(obj) { return this._pool.push(obj); } // 对象池大小 size() { return this._pool.length; }}// 模拟文件对象class File { constructor(pool) { this.pool = pool; } // 模拟下载操作 download() { console.log(+ 从 ${this.src} 开始下载 ${this.name}); setTimeout(() => { console.log(- ${this.name} 下载完毕); // 下载完毕后, 将对象重新放入对象池 this.pool.recover(this); }, 100); }}/ 以下是测试函数 **********************/let objPool = new ObjectPool();let file1 = objPool.create(File);file1.name = “文件1”;file1.src = “https://download1.com”;file1.download();let file2 = objPool.create(File);file2.name = “文件2”;file2.src = “https://download2.com”;file2.download();setTimeout(() => { let file3 = objPool.create(File); file3.name = “文件3”; file3.src = “https://download3.com”; file3.download();}, 200);setTimeout( () => console.log( ${"*".repeat(50)}\n下载了3个文件,但其实只创建了${objPool.size()}个对象 ), 1000);输出结果如下:+ 从 https://download1.com 开始下载 文件1+ 从 https://download2.com 开始下载 文件2- 文件1 下载完毕- 文件2 下载完毕+ 从 https://download3.com 开始下载 文件3- 文件3 下载完毕************************************************下载了3个文件,但其实只创建了2个对象4. 参考《JavaScript 设计模式和开发实践》 ...

December 20, 2018 · 2 min · jiezi

我出面试题

我一直想如果我是面试官,我会问什么问题来考察应聘者的计算机基本功。写这篇文章主要是做个记录。汇编的栈模型在CSAPP(Computer Systems: A programmer’s perspective,中文叫:深入理解计算机系统)中,有对汇编的栈工作原理的详细描述,这个底层的实现原理对程序员来说非常重要,算是一个基本功。swap为什么是无效的比如swap为什么是不能直接交换两个变量的,就能依靠这个基本功轻松解释。在高级语言中的形参和实参,在汇编中究竟是如何实现的。在这里通通是有答案的。过程调用void swap(int a, int b){ int temp = a; a = b; b = temp;}通过%ebp+8和%ebp+12拿到swap的两个实参(只有实参是实实在在存在内存中的,形参只不过是寄存器中实参的一个拷贝),在三个寄存器里进行交换,却并不写回到实参。所以这个swap是无效的。深入理解指针值、引用、值传递、引用传递、浅拷贝、深拷贝这些都是指针方面的知识,可以看看我写的这部分:programming paradigms面向对象程序设计Override、Overwrite、OverloadOverride是我们常说的面向对象的重载,是真正的重载,子类重载父类的函数。这三个名词中,实际上只存在两种技术一个是多态,一个是函数签名。

December 19, 2018 · 1 min · jiezi

Kafka消息系统基础知识索引

我们在《360度测试:KAFKA会丢数据么?其高可用是否满足需求?》这篇文章中,详细说明了KAFKA是否适合用在业务系统中。但有些朋友,还不知道KAFKA为何物,以及它为何存在。这在工作和面试中是比较吃亏的,因为不知道什么时候起,KAFKA似乎成了一种工程师的必备技能。一些观念的修正从 0.9 版本开始,Kafka 的标语已经从“一个高吞吐量,分布式的消息系统”改为"一个分布式流平台"。Kafka不仅仅是一个队列,而且是一个存储,有超强的堆积能力。Kafka不仅用在吞吐量高的大数据场景,也可以用在有事务要求的业务系统上,但性能较低。Kafka不是Topic越多越好,由于其设计原理,在数量达到阈值后,其性能和Topic数量成反比。引入了消息队列,就等于引入了异步,不管你是出于什么目的。这通常意味着业务流程的改变,甚至产品体验的变更。消息系统是什么典型场景上图是一些小系统的典型架构。考虑订单的业务场景,有大量的请求指向我们的业务系统,如果直接经过复杂的业务逻辑进入业务表,将会有大量请求超时失败。所以我们加入了一张中间缓冲表(或者Redis),用来承接用户的请求。然后,有一个定时任务,不断的从缓冲表中获取数据,进行真正的业务逻辑处理。这种设计有以下几个问题:定时任务的轮询间隔不好控制。业务处理容易延迟。无法横向扩容处理能力,且会引入分布式锁、顺序性保证等问题。当其他业务也需要这些订单数据的时候,业务逻辑就必须要加入到定时任务里。当访问量增加、业务逻辑复杂化的时候,消息队列就呼之欲出了。请求会暂存在消息队列,然后实时通过推(或者拉)的方式进行处理。在此场景下,消息队列充当了削峰和冗余的组件。消息系统的作用削峰 用于承接超出业务系统处理能力的请求,使业务平稳运行。这能够大量节约成本,比如某些秒杀活动,并不是针对峰值设计容量。缓冲 在服务层和缓慢的落地层作为缓冲层存在,作用与削峰类似,但主要用于服务内数据流转。比如批量短信发送。解耦 项目尹始,并不能确定具体需求。消息队列可以作为一个接口层,解耦重要的业务流程。只需要遵守约定,针对数据编程即可获取扩展能力。冗余 消息数据能够采用一对多的方式,供多个毫无关联的业务使用。健壮性 消息队列可以堆积请求,所以消费端业务即使短时间死掉,也不会影响主要业务的正常进行。消息系统要求消息系统即然这么重要,那么除了能够保证高可用,对它本身的特性也有较高需求。大体有下面几点:性能要高 包含消息投递和消息消费,都要快。一般通过增加分片数获取并行处理能力。消息要可靠 在某些场景,不能丢消息。生产、消费、MQ端都不能丢消息。一般通过增加副本,强制刷盘来解决。扩展性要好 能够陪你把项目做大,陪你到天荒地老。增加节点集群增大后,不能降低性能。生态成熟 监控、运维、多语言支持、社区的活跃。KAFKA名词解释基本功能Kafka是一个分布式消息(存储)系统。分布式系统通过分片增加并行度;通过副本增加可靠性,kafka也不例外。我们来看一下它的结构,顺便解释一下其中的术语。你在一台机器上安装了Kafka,那么这台机器就叫Broker,KAFKA集群包含了一个或者多个这样的实例。负责往KAFKA写入数据的组件就叫做Producer,消息的生产者一般写在业务系统里。发送到KAFKA的消息可能有多种,如何区别其分类?就是Topic的概念。一个主题分布式化后,可能会存在多个Broker上。将Topic拆成多个段,增加并行度后,拆成的每个部分叫做Partition,分区一般平均分布在所有机器上。那些消费Kafka中数据的应用程序,就叫做Consumer,我们给某个主题的某个消费业务起一个名字,这么名字就叫做Consumer Group扩展功能Connector 连接器Task,包含Source和Sink两种接口,给用户提供了自定义数据流转的可能。比如从JDBC导入到Kafka,或者将Kafka数据直接落地到DB。Stream 类似于Spark Stream,能够进行流数据处理。但它本身没有集群,只是在KAFKA集群上的抽象。如果你想要实时的流处理,且不需要Hadoop生态的某些东西,那么这个比较适合你。Topic我们的消息就是写在主题里。有了多个Topic,就可以对消息进行归类与隔离。比如登录信息写在user_activity_topic,日志消息写在log_topic中。每一个topic都可以调整其分区数量。假设我们的集群有三个Broker,那么当分区数量为1的时候,消息就仅写在其中一个节点上;当我们的分区为3,消息会根据hash写到三个节点上;当我们的分区为6,那每个节点将会有2个分区信息。增加分区可以增加并行度,但不是越多越好。一般,6-12最佳,最好能够被节点数整除,避免数据倾斜。每个分区都由一系列有序的、不可变的消息组成,这些消息被顺序的追加。分区中的每个消息都有一个连续的序列号叫做offset。Kafka将保留配置时间内的所有消息,所以它也是一个临时存储。在这段时间内,所有的消息都可被消费,并且可以通过改变offset的值进行重复、多次消费。Offset一般由消费者管理,当然也可以通过程序按需要设置。Offset只有commit以后,才会改变,否则,你将一直获取重复的数据。新的kafka已经将这些Offset的放到了一个专有的主题:__consumer_offsets,就是上图的紫色区域。值得一提的是,消费者的个数,不要超过分区的个数。否则,多出来的消费者,将接收不到任何数据。ISR分布式系统保证数据可靠性的一个常用手段就是增加副本个数,ISR就是建立在这个手段上。ISR全称"In-Sync Replicas",是保证HA和一致性的重要机制。副本数对Kafka的吞吐率是有一定的影响,但极大的增强了可用性。一般2-3个为宜。副本有两个要素,一个是数量要够多,一个是不要落在同一个实例上。ISR是针对与Partition的,每个分区都有一个同步列表。N个replicas中,其中一个replica为leader,其他都为follower, leader处理partition的所有读写请求,其他的都是备份。与此同时,follower会被动定期地去复制leader上的数据。如果一个flower比一个leader落后太多,或者超过一定时间未发起数据复制请求,则leader将其重ISR中移除。当ISR中所有Replica都向Leader发送ACK时,leader才commit。Kafka的ISR的管理最终都会反馈到Zookeeper节点上。具体位置为:/brokers/topics/[topic]/partitions/[partition]/state。当Leader节点失效,也会依赖Zk进行新的Leader选举。Offset转移到Kafka内部的Topic以后,KAFKA对ZK的依赖就越来越小了。可靠性消息投递语义At least once可能会丢消息,但不不会重复At most once不不丢消息,但可能重复,所以消费端要做幂等Exactly once消息不不会丢,且保证只投递⼀一次整体的消息投递语义需要Producer端和Consumer端两者来保证。KAFKA默认是At most once,也可以通过配置事务达到Exactly once,但效率很低,不推荐。ACK当生产者向leader发送数据时,可以通过request.required.acks参数来设置数据可靠性的级别:1(默认) 数据发送到Kafka后,经过leader成功接收消息的的确认,就算是发送成功了。在这种情况下,如果leader宕机了,则会丢失数据。0 生产者将数据发送出去就不管了,不去等待任何返回。这种情况下数据传输效率最高,但是数据可靠性确是最低的。-1 producer需要等待ISR中的所有follower都确认接收到数据后才算一次发送完成,可靠性最高。KAFKA为什么快Cache Filesystem Cache PageCache缓存 顺序写 由于现代的操作系统提供了预读和写技术,磁盘的顺序写大多数情况下比随机写内存还要快。Zero-copy 零拷⻉,少了一次内存交换。Batching of Messages 批量量处理。合并小的请求,然后以流的方式进行交互,直顶网络上限。Pull 拉模式 使用拉模式进行消息的获取消费,与消费端处理能力相符。使用场景传递业务消息用户活动日志 • 监控项等日志流处理,比如某些聚合Commit Log,作为某些重要业务的冗余下面是一个日志方面的典型使用场景。压测KAFKA自带压测工具,如下。./kafka-producer-perf-test.sh –topic test001 –num- records 1000000 –record-size 1024 –throughput -1 –producer.config ../config/producer.properties配置管理关注点应⽤用场景 不同的应用场景有不一样的配置策略和不一样的SLA服务水准。需要搞清楚自己的消息是否允许丢失或者重复,然后设定相应的副本数量和ACK模式。Lag 要时刻注意消息的积压。Lag太高意味着处理能力有问题。如果在低峰时候你的消息有积压,那么当大流量到来,必然会出问题。扩容 扩容后会涉及到partition的重新分布,你的网络带宽可能会是瓶颈。磁盘满了 建议设置过期天数,或者设置磁盘最大使用量。log.retention.bytes过期删除 磁盘空间是有限的,建议保留最近的记录,其余自动删除。log.retention.hours log.retention.minutes log.retention.ms 监控管理工具KafkaManager 雅虎出品,可管理多个Kafka集群,是目前功能最全的管理工具。但是注意,当你的Topic太多,监控数据会占用你大量的带宽,造成你的机器负载增高。其监控功能偏弱,不满足需求。KafkaOffsetMonitor 程序一个jar包的形式运行,部署较为方便。只有监控功能,使用起来也较为安全。Kafka Web Console 监控功能较为全面,可以预览消息,监控Offset、Lag等信息,不建议在生产环境中使用。Burrow 是LinkedIn开源的一款专门监控consumer lag的框架。支持报警,只提供HTTP接口,没有webui。Availability Monitor for Kafka 微软开源的Kafka可用性、延迟性的监控框架,提供JMX接口,用的很少。Rebalance消费端Rebalance消费端的上线下线会造成分区与消费者的关系重新分配,造成Rebalance。业务会发生超时、抖动等。服务端reassign服务器扩容、缩容,节点启动、关闭,会造成数据的倾斜,需要对partition进行reassign。在kafka manager后台可以手动触发这个过程,使得分区的分布更加平均。这个过程会造成集群间大量的数据拷贝,当你的集群数据量大,这个过程会持续数个小时或者几天,谨慎操作。linkedin开源了其自动化管理工具cruise-control,有自动化运维需求的不妨一看。结尾本文是KAFKA相关的最基础的知识,基本涵盖了大部分简单的面试题。为了达到Exactly once这个语义,KAFKA做了很多努力,努力的结果就是几乎不可用,吞吐量实在是太低了。如果你真要将“高可靠”挂在嘴上,不如做好“补偿策略”。性能不成,最终的结果可能是整体不可用;而数据丢失,仅是极端情况下的一部分小数据而已。你会如何权衡呢?大流量下的KAFKA是非常吓人的,数据经常将网卡打满。而一旦Broker当机,如果单节点有上T的数据,光启动就需要半个小时,它还要作为Follower去追赶其他Master分区的数据。所以,不要让你的KAFKA集群太大,故障恢复会是一场灾难。启动以后,如果执行reassign,又会是另一番折腾了。 ...

December 18, 2018 · 1 min · jiezi

初级前端开发面试总结

前端面试总结先说背景,本人2018年7月毕业,去年十月校招到今年10月一直在做前端开发工作,年前打算换工作,就重新梳理下面试考点总结包含: JavaScript基础,CSS基础,常见算法和数据结构,React&Vue框架,计算机网络相关知识,可能有的点很细,有的点很大,参考个人情况进行总结,方便对知识进行回忆,有的列举出了参考答案,有的则在文末列举了优秀回答闭包闭包的概念?闭包的作用?使用闭包的注意点?事件机制事件3个阶段 事件捕获阶段,处于目标阶段,事件冒泡阶段默认为事件冒泡ES6let const class 模块化 等等常用ES6知识箭头函数作用,此处可以扩展考察 this指向问题set map 相关原型链4种继承方式new 原理深拷贝&浅拷贝数据类型基本数据类型和引用数据类型跨域同源策略CORSJSONPIframe + document.domain安全CSRF 跨站请求攻击 (原理,保护措施 referer token 验证码,设置cookie的httponly属性,post请求等等XSS (同上HTTP 方法几种方法介绍axios库输入URL到页面成功渲染的过程经典问题 (任何一个步骤都可以引申来考察DNS解析 (具体的解析过程TCP连接http请求 (状态码考察返回数据 浏览器渲染页面 (页面渲染过程上面这些模块下面一一例举DNS解析过程概念浏览器缓存系统缓存路由器缓存域名提供商顶级域名服务器主域名服务器浏览器页面渲染加载html构建页面dom树解析css 构建渲染树渲染树构建完成后,将渲染树绘制到屏幕(回流和重绘JS解析,会阻塞dom树的构建状态码http缓存 304强制缓存 (expires cache-control对比缓存 ( Etag & if-none-match last-modified & if-modified-since301 302 区别(永久性重定向(带缓存和临时性重定向)垃圾回收引用计数标记清除TypeScript使用就不说了,总结下优缺点增加了代码的可读性和可维护性 编译时错误提示包容性 .js 可写为 .ts拥抱ES6, 框架支持学习成本高如:类,泛型,接口函数式编程概念 相同的输入 永远的到相同的输出,且没有任何副作用副作用 比如 ajax请求 改变全局变量优缺点 1.缓存性 2. 可移植性 3. 并行性 不需要共性内存柯里化的概念,作用( 最好不要答装逼 我还没想到更好的答案我列举一个知乎答案: 统一接口,封装的每一层都干干净净,逻辑表现清楚IndexedDB特点区别LocalStorage SessionStorage(1.同步 2.存储大小)WebSocket概念特点readyState排序算法常见的排序算法 复杂度 稳定性快速排序 不稳定 空间复杂度O(logn) 平均时间复杂度O(nlogn) 最差…O(n2)选择排序 不稳定 空间复杂度O(1) …所有都是O(n2)冒泡排序 稳定堆排序 不稳定 空间复杂度O(1) …所有都是O(nlogn)插入排序哈希表数组和链表的特点搜索插入和删除的时间复杂度O(1)二叉树先序遍历中序遍历后序遍历层序表里树高链表翻转链表AVL树 二叉查找树特点O(logn)精度丢失问题0.1 + 0.1 != 0.2 (为什么计算机内部的信息都是由二进制存储的,但是有些浮点数没法用二进制精准的表示出来小数转整数Math.round() 四舍五入Math.ceil() Math.floor()生成随机数引入 Math.random() [0,1) 的随机小数parseInt(Math.random()(m-n)+n) [n, m)Math.round(Math.random()(m-n)+n) [n, m]数组去重var set = new Set([1,1,2,2,3,3,3,3]) var arr = […set]indexOf如何判断数组是数组Array.isArray(arr) ES5的方法arr instanceof Arrayarr.proto.constructor === Array()Object.prototype.toString.call(arr) === ‘[object Array]’ //数据原型和对象原型定义的toString 方法不同Object.prototype.toString.call(obj) === ‘[object Object]’ // 如上Object.prototype.toString.call(null) === ‘[object Null]’ // 推荐此方法数组指定个数去重 findDuplicate(num)es6 Set Map 相关操作反转字符串JS异步异步解决历史(Ajax的进化历程)Promise,Async/Await 用法 Promise 缺点Promise API Promise.all & Promise.raceAjaxReadyState (0, 1, 2, 3, 4) 5中状态js实现ajax优缺点单/双向数据绑定MV** 概念vue/react 数据绑定机制Vue生命周期父子组件通信双向数据绑定vm.$nextTick原理React生命周期Virtual-dom技术以及改良后的Diff算法对比Vue.jsPosition属性几种属性值Box-sizing 属性content-box border-box盒模型 W3c标准盒模型和IE盒模型两栏布局 三栏布局常见自适应布局写法回流和重绘概念举例如何避免 eg: DocumentFragment, Absolute浮动伪元素和伪类清除浮动常见3种方法隐藏元素display nonevisibility hidden 区别垂直居中布局常见的垂直居中布局方法前端性能优化列举常见前端性能优化方法列举一些优秀回答 输入URL你可以看到什么? HTTP缓存 域名解析过程 vue生命周期 React生命周期 CSRF攻击 水平垂直居中布局 ...

December 17, 2018 · 1 min · jiezi

【剑指offer】33.二叉树镜像

题目操作给定的二叉树,将其变换为源二叉树的镜像。二叉树的镜像定义:源二叉树 8 / \ 6 10 / \ / \ 5 7 9 11 镜像二叉树 8 / \ 10 6 / \ / \ 11 9 7 5题解首先先理解题意,镜像通过以下几个步骤可以实现:可以看到首先对根节点的左右进行翻转。再递归的对左子树,以及右子树进行翻转。之前讲过,链表的题目分为四个步骤:连过来、指针走、断后路、调状态。二涉及到树的题目,基本都是递归。一旦涉及到递归,就要搞清楚两个东西:递归的过程。在这里就是,先翻转根节点,再翻转左子树,再翻转右子树。递归结束的条件。 这里就是当树为Null的时候不翻转。public class Solution { public void Mirror(TreeNode root) { // 递归结束条件 if (root == null) return; // 交换左右 TreeNode temp = root.left; root.left = root.right; root.right = temp; // 递归 Mirror(root.left); Mirror(root.right); }}同时一般,我们会有一些边界case去检查一下代码的鲁棒性。比如左右有一个是null 8 / \ 10 Null / \ null null 代码执行到交换没啥问题: 8 / \ Null 10 / \ null null执行到递归,左子树就结束掉了。右子树的话还要递归的执行左右子树,也可以执行正确,但是其实没必要。public class Solution { public void Mirror(TreeNode root) { // 递归结束条件 if (root == null) return; if (roo.left == null && root.left == null) return; // 交换左右 TreeNode temp = root.left; root.left = root.right; root.right = temp; // 递归 Mirror(root.left); Mirror(root.right); }}热门阅读【Leetcode】175. 组合两个表jvm类加载机制学习资料推荐 ...

December 14, 2018 · 1 min · jiezi

每天一个设计模式之命令模式

作者按:《每天一个设计模式》旨在初步领会设计模式的精髓,目前采用javascript和python两种语言实现。诚然,每种设计模式都有多种实现方式,但此小册只记录最直截了当的实现方式 :)原文地址是:《每天一个设计模式之命令模式》欢迎关注个人技术博客:godbmw.com。每周 1 篇原创技术分享!开源教程(webpack、设计模式)、面试刷题(偏前端)、知识整理(每周零碎),欢迎长期关注!如果您也想进行知识整理 + 搭建功能完善/设计简约/快速启动的个人博客,请直接戳theme-bmw0. 示例代码此节全部代码《每天一个设计模式》地址1. 什么是“命令模式”?命令模式是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象。该对象执行命令。在这三步骤中,分别有 3 个不同的主体:发送者、传递者和执行者。在实现过程中,需要特别关注。2. 应用场景有时候需要向某些对象发送请求,但是又不知道请求的接受者是谁,更不知道被请求的操作是什么。此时,命令模式就是以一种松耦合的方式来设计程序。3. 代码实现3.1 python3 实现命令对象将动作的接收者设置在属性中,并且对外暴露了execute接口(按照习惯约定)。在其他类设置命令并且执行命令的时候,只需要按照约定调用Command对象的execute()即可。到底是谁接受命令,并且怎么执行命令,都交给Command对象来处理!author = ‘godbmw.com’# 接受到命令,执行具体操作class Receiver(object): def action(self): print(“按钮按下,执行操作”)# 命令对象class Command: def init(self, receiver): self.receiver = receiver def execute(self): self.receiver.action()# 具体业务类class Button: def init(self): self.command = None # 设置命令对戏那个 def set_command(self, command): self.command = command # 按下按钮,交给命令对象调用相关函数 def down(self): if not self.command: return self.command.execute()if name == “main”: receiver = Receiver() command = Command(receiver) button = Button() button.set_command(command) button.down()3.2 ES6 实现setCommand方法为按钮指定了命令对象,命令对象为调用者(按钮)找到了接收者(MenuBar),并且执行了相关操作。而按钮本身并不需要关心接收者和接受操作。// 接受到命令,执行相关操作const MenuBar = { refresh() { console.log(“刷新菜单页面”); }};// 命令对象,execute方法就是执行相关命令const RefreshMenuBarCommand = receiver => { return { execute() { receiver.refresh(); } };};// 为按钮对象指定对应的 对象const setCommand = (button, command) => { button.onclick = () => { command.execute(); };};let refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);let button = document.querySelector(“button”);setCommand(button, refreshMenuBarCommand);下面是同级目录的 html 代码,在谷歌浏览器中打开创建的index.html,并且打开控制台,即可看到效果。<!DOCTYPE html><html lang=“en”> <head> <meta charset=“UTF-8” /> <meta name=“viewport” content=“width=device-width, initial-scale=1.0” /> <meta http-equiv=“X-UA-Compatible” content=“ie=edge” /> <title>命令模式</title> </head> <body> <button>按钮</button> <script src="./main.js"></script> </body></html>4. 参考《JavaScript 设计模式和开发实践》如何实现命令模式 ...

December 13, 2018 · 1 min · jiezi

从一道简单的“SpringBoot配置文件”相关面试题,我就能知道你的水平

面试要套路,也要技巧。别被背题目的兄弟们给忽悠了。【你来发挥】你比较喜欢什么技术,哪一种最熟?一般自信的面试官都喜欢问这个问题,这次面试的小伙比较年轻,咱也装回B,不然都对不起自己。答: 我比较喜欢Spring,比较有趣。目的: 希望应聘者能够有广度且有深度。如果最感兴趣的是Spring本身,而不是其上的解决方案,那顶多会承担被分解后的编码工作。巧了,咱也熟。【工作经验】SpringBoot相比较SpringMVC,有什么优缺点?答: 有很多方面。觉得最好的就是不用写那么多配置文件了,直接写个注解,通过自动配置,就完成了初始化。目的: 说什么无所谓,主要看有没有总结能力。判断是否用过早期的Spring版本,经历过版本更新更能了解软件开发之痛,接受新知识会考虑兼容和迭代。【实现原理】我想要SpringBoot自动读取我的自定义配置,该做哪些事情?答: 写一个相应的starter目的: 判断是否了解和写过Spring Boot Starter,主要是META-INF目录下的spring.factories文件和AutoConfiguration。了解AOP更佳。【烟幕弹】配置文件是yml格式,这种格式你喜欢么?答: 比较喜欢properties格式,感觉yml格式的配置文件缩进比较难处理。比如当我从网上拷贝一些别人长长的配置文件,可能要花较多时间整理文件格式。目的 此问题没有具体的意图,主要是过渡用。【动手能力】这么喜欢properties方式,能够写一段代码,将yml翻译成properties么? 要是回答相反则反着来。目的 通过简单的伪代码,判断应聘者的动手能力和编码风格。是喜欢问题抽象化还是喜欢立刻动手去写。我希望回答能够有条理,而且能够考虑各种异常情况,比如把自己判断不了的配置交给用户处理;比如空格和<TAB>的处理。【提示】提示一下,你用什么数据结构进行存储?目的 假如应聘者在一段时间内不能有任何产出,会给出简单的提示。找准了存储结构,你就基本完成了工作,此问题还判断了应聘者的培养成本和价值。【基础算法】哦,是树结构,遍历方式是怎样的?前序,后序,中序?目的 判断是否有基础的算法知识。做工程先不要求会什么动态规划或者贪心算法,但起码的数据结构是要了解的。【基础知识】你用到了Map?Java中如何做可排序的Map?目的 是否对java的基础集合类熟悉,期望回答TreeMap,如果回答上来,可能会追问它是什么数据结构(红黑树)。【知识广度】你还接触过哪些配置方式?比较喜欢那种?目的 了解应聘者的知识广度,说不出来也无所谓,了解的多会加分。比如ini、cfg、json、toml、序列化等。【项目规模】我想要把我的配置放在云端,比如数据库密码等,改怎么做?目的 是否了解SpringBoot的组件SpringConfig,或者了解一些其他的开源组件如携程的apollo等。【知识广度】我想要配置文件改动的时候,所有机器自动更新,该怎么办?目的 了解是否知晓常用的同步方式。有两种:一种是定时去轮询更新;一种是使用zk或者etcd这种主动通知的组件。【实现细节】Spring是如何进行配置文件刷新的?目的 这个可真是没写过就真不知道了,主要是org.springframework.cloud.context.scope.refresh.RefreshScope这个类【架构能力】现在我想要将配置文件分发到一部分机器,也就是带有版本的灰度概念,你该如何处理?目的 如果能够从网关、微服务约定,后台操作原型方面去多方位描述一下,更佳。这样筛选的小伙伴,都很棒!能力多少,心中有数。

December 11, 2018 · 1 min · jiezi

每天一个设计模式之订阅-发布模式

博主按:《每天一个设计模式》旨在初步领会设计模式的精髓,目前采用javascript(靠这吃饭)和python(纯粹喜欢)两种语言实现。诚然,每种设计模式都有多种实现方式,但此小册只记录最直截了当的实现方式 :)0. 项目地址每天一个设计模式之订阅-发布模式·原文地址本节课代码《每天一个设计模式》地址1. 什么是“订阅-发布模式”?订阅-发布模式定义了对象之间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都可以得到通知。了解过事件机制或者函数式编程的朋友,应该会体会到“订阅-发布模式”所带来的“时间解耦”和“空间解耦”的优点。借助函数式编程中闭包和回调的概念,可以很优雅地实现这种设计模式。2. “订阅-发布模式” vs 观察者模式订阅-发布模式和观察者模式概念相似,但在订阅-发布模式中,订阅者和发布者之间多了一层中间件:一个被抽象出来的信息调度中心。但其实没有必要太深究 2 者区别,因为《Head First 设计模式》这本经典书都写了:发布+订阅=观察者模式。其核心思想是状态改变和发布通知。在此基础上,根据语言特性,进行实现即可。3. 代码实现3.1 python3 实现python 中我们定义一个事件类Event, 并且为它提供 事件监听函数、(事件完成后)触发函数,以及事件移除函数。任何类都可以通过继承这个通用事件类,来实现“订阅-发布”功能。class Event: def init(self): self.client_list = {} def listen(self, key, fn): if key not in self.client_list: self.client_list[key] = [] self.client_list[key].append(fn) def trigger(self, *args, **kwargs): fns = self.client_list[args[0]] length = len(fns) if not fns or length == 0: return False for fn in fns: fn(*args[1:], **kwargs) return False def remove(self, key, fn): if key not in self.client_list or not fn: return False fns = self.client_list[key] length = len(fns) for _fn in fns: if _fn == fn: fns.remove(_fn) return True# 借助继承为对象安装 发布-订阅 功能class SalesOffice(Event): def init(self): super().init()# 根据自己需求定义一个函数:供事件处理完后调用def handle_event(event_name): def _handle_event(*args, **kwargs): print(“Price is”, *args, “at”, event_name) return _handle_eventif name == “main”: # 创建2个回调函数 fn1 = handle_event(“event01”) fn2 = handle_event(“event02”) sales_office = SalesOffice() # 订阅event01 和 event02 这2个事件,并且绑定相关的 完成后的函数 sales_office.listen(“event01”, fn1) sales_office.listen(“event02”, fn2) # 当两个事件完成时候,触发前几行绑定的相关函数 sales_office.trigger(“event01”, 1000) sales_office.trigger(“event02”, 2000) sales_office.remove(“event01”, fn1) # 打印:False print(sales_office.trigger(“event01”, 1000))3.2 ES6 实现JS 中一般用事件模型来代替传统的发布-订阅模式。任何一个对象的原型链被指向Event的时候,这个对象便可以绑定自定义事件和对应的回调函数。const Event = { clientList: {}, // 绑定事件监听 listen(key, fn) { if (!this.clientList[key]) { this.clientList[key] = []; } this.clientList[key].push(fn); return true; }, // 触发对应事件 trigger() { const key = Array.prototype.shift.apply(arguments), fns = this.clientList[key]; if (!fns || fns.length === 0) { return false; } for (let fn of fns) { fn.apply(null, arguments); } return true; }, // 移除相关事件 remove(key, fn) { let fns = this.clientList[key]; // 如果之前没有绑定事件 // 或者没有指明要移除的事件 // 直接返回 if (!fns || !fn) { return false; } // 反向遍历移除置指定事件函数 for (let l = fns.length - 1; l >= 0; l–) { let _fn = fns[l]; if (_fn === fn) { fns.splice(l, 1); } } return true; }};// 为对象动态安装 发布-订阅 功能const installEvent = obj => { for (let key in Event) { obj[key] = Event[key]; }};let salesOffices = {};installEvent(salesOffices);// 绑定自定义事件和回调函数salesOffices.listen( “event01”, (fn1 = price => { console.log(“Price is”, price, “at event01”); }));salesOffices.listen( “event02”, (fn2 = price => { console.log(“Price is”, price, “at event02”); }));salesOffices.trigger(“event01”, 1000);salesOffices.trigger(“event02”, 2000);salesOffices.remove(“event01”, fn1);// 输出: false// 说明删除成功console.log(salesOffices.trigger(“event01”, 1000));4. 参考维基百科·订阅-发布模式观察者模式和订阅-发布模式的不同《JavaScript 设计模式和开发实践》 ...

December 10, 2018 · 2 min · jiezi

读书笔记(05) - 事件 - JavaScript高级程序设计

HTML依托于JavaScript来实现用户与WEB网页之间的动态交互,接收用户操作并做出相应的反馈,而事件在此间则充当桥梁的重要角色。日常开发中,经常会为某个元素绑定一个事件,编写相应的业务逻辑,在元素被点击时执行,并反馈到用户操作界面。这个过程中,事件就像一个侦听器,当点击动作发生时,才会执行对应的程序。这种模式可称之为观察员模式。接下来就讲讲DOM事件相关知识。何为事件事件就是用户或浏览器自身执行的某种动作常用的DOM事件有click/mouseover/mouseout/keyup/keydown等。事件流事件流描述的是从页面中接收事件的顺序HTML描述的是一个DOM文档结构,而事件流所描述的是DOM文档节点接收事件顺序。而事件流有两种事件模式,捕获/冒泡,两者所描述的事件传递顺序对立相反。事件模式:捕获与冒泡冒泡事件冒泡:事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)规范要求事件冒泡到document对象,而浏览器则会将事件一直冒泡到window对象。所有浏览器都支持事件冒泡(包括IE9以下)。捕获事件捕获:(与事件冒泡相反)事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件与冒泡一样,虽然规定事件应该从document对象开始传播,但浏览器普遍都是从window对象开始捕获。IE9以下不支持事件捕获DOM 事件流"DOM2级事件"规定事件流包括三个阶段,顺序进行事件捕获阶段处于目标阶段事件冒泡阶段TIPS: 实际的目标元素在捕获阶段不会接收到事件,在处于目标阶段时接收事件发生处理,并被看成是冒泡阶段的一部分。尽管"DOM2级事件"规范明确要求捕获阶段不会涉及事件目标,但浏览器会在捕获阶段触发事件对象上的事件。 事件处理程序响应某个事件的函数方法,我们称之为事件处理程序(或事件侦听器)window.onclick = function() { //…}// 这里的function(){}就是事件处理程序HTML事件处理程序HTML中元素支持的事件,可以使用一个同名的HTML特性来指定,而这个特性的值就是js能执行的代码或表达式。写法上可以看出类似HTML中id/type/class等属性的写法,都是on+’…‘缺点:HTML是结构层(显示层),而JavaScript是行为层(业务层)。在显示层上去编写业务逻辑代码处理,会使得HTML与JavaScript代码耦合过于紧密,不好维护。DOM级别一共可以分为四个级别:DOM0级、DOM1级、DOM2级和DOM3级。而DOM事件分为3个级别:DOM 0级事件处理程序,DOM 2级事件处理程序和DOM 3级事件处理程序。DOM 1级中没有规范事件的相关内容,所以没有DOM 1级事件处理。DOM0 级事件处理程序每个元素(HTML元素)都有自己的事件处理程序属性,属性名通常以on开头,例如onclick/onmouseover。为这个属性的值设置一个函数,就可以指定事件处理程序。而将其属性值赋值为null,则完成解绑。(同个元素无法绑定多个同名事件)var myBtn = document.getElementById(‘myBtn’);// 为myBtn绑定事件处理程序, 只能绑定一个myBtn.onclick = function() { alert(‘Hello world!’);}// 解绑myBtn.onclick = null;DOM2 级事件处理程序"DOM2级事件"定义了两个方法,addEventListener()/removeEventListener(),用于为元素绑定和解绑事件。(可绑定多个事件,区别于DOM0级/HTML仅能绑定一个)。el.addEventListener(eventName, callBack, useCapture)eventName: 事件名称callBack: 回调函数,当事件触发时,函数会传入一个参数event,为当前的事件对象useCapture: 默认是false,代表事件句柄在冒泡阶段执行, true则代表在捕获阶段执行var myBtn = document.getElementById(‘myBtn’);var handleClick = function() { alert(‘Hello world!’);}// 绑定事件处理程序myBtn.addEventListener(‘click’, handleClick, false);// 解绑myBtn.removeEventListener(‘click’, handleClick);TIPS:DOM2级事件处理程序,解绑时function必须与传入addEventListener相同// 绑定myBtn.addEventListener(‘click’, function() { // 匿名函数});// 解绑myBtn.removeEventListener(‘click’,function() { // 匿名函数});// add/remove 分别绑定了两个匿名函数(函数为引用类型),所以两个函数并不相同,所以无法成功解绑TIPS:绑定多个事件处理程序时,执行顺序按绑定顺序执行myBtn.addEventListener(‘click’, function() { // step1…})myBtn.addEventListener(‘click’, function() { // step2…})// 执行顺序:step1 -> step2浏览器支持情况:IE9以下不支持DOM2级事件处理程序IE 事件处理程序IE9以下不支持DOM2级事件,但IE提供了与DOM2级事件类似的两个方法,attachEvent()/detachEvent,IE9以下不支持事件捕获,所以attachEvent仅支持冒泡阶段触发,只接收两个参数(eventName, function)。// 绑定myBtn.attachEvent(‘onclick’, handleClick);// 解绑myBtn.detachEvent(‘onclick’, handleClick);TIPS:解绑时function必须与传入attachEvent相同,这点与DOM2级事件相同与DOM0级的区别,DOM0级事件处理在元素的作用域运行,而attachEvent事件处理在全局,this指向window绑定多个事件处理程序时,执行顺序按绑定顺序逆反执行(与DOM2级相反)myBtn.attachEvent(‘click’, function() { // step1…})myBtn.attachEvent(‘click’, function() { // step2…})// 执行顺序:step2 -> step1Event 事件对象常见应用event.preventDefault()阻止默认事件event.stopPropagation()阻止事件流发生传递(冒泡/捕获)event.stopImmediatePropagation()阻止剩余事件处理函数的执行,并阻止当前事件在事件流上传递event.currentTarget当前绑定事件的元素event.target当前触发事件的元素event.stopPropagation()与.stopImmediatePropagation()的区别同个元素绑定多个同名事件时,stopImmediatePropagation不仅阻止了冒泡,而且会阻止后续事件的执行,可以理解为加强版的stopPropagationmyBtn.addEventListener(‘click’, function(event) { // step1; event.stopImmediatePropagation();})myBtn.addEventListener(‘click’, function(event) { // step2; // 我被stopImmediatePropagation阻止掉了!!!})currantTarget与target的区别事件处理程序内部,this等于currentTarget(当前绑定事件的元素),而target(当前触发事件的元素)// currentTarget == targetmyBtn.addEventListener(‘click’, function(event) { event.target == event.currentTarget; // true -> myBtn})// currentTarget != target 捕获/冒泡document.body.addEventListener(‘click’, function(event){ event.target == event.currentTarget; // false // event.target -> myBtn // event.currentTarget -> body})内存与性能WEB网页是运行在浏览器客户端的,而计算机分配给浏览器的内存及CPU占用是有限制的。虽说浏览器引擎不断地发展优化,但是内存占用多了, 性能不免会损耗。内存为元素指定事件绑定程序,事实上是赋值了一个函数方法,而函数在javaScript中是一种引用类型的数据格式,既然是数据那就需要用到内存储存。函数创建多了,消耗掉内存。性能为元素指定事件绑定程序,首先需要对DOM进行查询,找出要绑定事件的元素。而这也会造成DOM元素的访问次数增加。DOM的操作一直是网页性能的一个优化点。了解完事件绑定带来内存跟性能的原理,我们来看一个例子,例如我们有一个ul>li的列表,要监听每一个li的点击事件,并触发事件处理程序。单独绑定的话,10个li就要对DOM元素查询10次,创建的匿名函数就有10个(当然可以共同创建同个函数引用),如果还有20个,30个,100个,那么这种为每个li元素单独绑定事件的方法,绝对不是最优解。这就引出下面的优化方案:“事件委托”。事件委托(事件代理)对"事件处理程序绑定过多"的问题,最好的解决方案就是"事件委托"。它的原理是利用了事件流的"冒泡"机制,事件目标元素会把事件向上层传递,直到document(浏览器会传到window),所以父级节点是可以接收子节点的事件传递。以刚刚ul>li的例子,li有很多个, 但它们有一个共同的父节点ul。li的点击事件会冒泡到ul,因此我们可以在ul上绑定一个事件处理程序,处理所有li的点击事件,然后通过event.target可以确定触发事件的元素。var ulParent = document.getElementById(‘parent’);ulParent.addEventListener(‘click’, function(event) { var taget = event.target; })通过"事件委托"减少了DOM元素的查询,以及多个函数的内存占用,而且还有一个好处,当我们的li是动态的,增加和移除时,都无需再做绑定和解绑事件操作,因为它都会冒泡到父级节点。移除多余的事件绑定文档中移除了绑定了事件的DOM元素,如innerHTML/removeChild()/replaceChild()等可以对DOM进行替换,而移除的DOM元素原先所绑定的事件处理程序,并不能有效被浏览器垃圾回收,所以占用一直存在。所以建议在移除某个DOM元素时,如果其绑定了事件处理程序,需手动解除绑定,释放内存。自定义事件除了为元素绑定支持的事件以外,我们还可以通过Event/CustomEvent来创建开发者自定义事件。两者不同的是CustomEvent可传递一个Object对象来传输数据。// Eventvar eve = new Event(‘custome’);// CustomeEvent 可传参数var eve = new CustomeEvent(‘custome’, { name: ‘KenTsang’, age: 28});// 为DOM元素添加事件监听ele.addEventListener(‘custome’, function() { console.log(‘custome’);})// 触发ele绑定的自定义事件ele.dispatch(eve);事件这块还剩下一部分知识点,后续文章会再就模拟事件这块知识点进行拆分详解。天冷了,更文不易,望大家多多点赞。《JavaScript高级程序设计》作者:以乐之名本文原创,有不当的地方欢迎指出。转载请指明出处。 ...

December 10, 2018 · 1 min · jiezi

这30道Web前端面试题,一个小时内你能否答得出来?

Web前端高薪的诱惑,学习门槛低,无论是学生党还是工作者,都渴望着凭借着自己的能力,空余的时间,想学一下前端。或许是爱好,或许是想从事这份工作。但是真的那么容易吗?如今初级前端的工作,已经接近饱和,并且供过于求的状态,企业需要的更多的是基础牢固,至少会一门后端技能的前端开发工程师。已经不再是以前所说的切图仔,美工。前端开发岗位在很多大企业属于独立的,需要专业技能的岗位。你想知道自己的技术水平与高薪匹不匹配吗?下面分享了一些Web前端的面试题,限时一小时,你看看自己能够答出多少道!这些面试题都是一些非常基础的知识,如果你能一个小时答出来,说明你的基础还是挺不错的。你试一下自己究竟能够答出个什么水平!有没有真本领?答案尽在这些面试题里!一、HTML常见题目01、Doctype作用?严格模式与混杂模式如何区分?它们有何意义?02、HTML5为什么只需要写?03、行内元素有哪些?块级元素有哪些?空(void)元素有那些?04、页面导入样式时,使用link和@import有什么区别?05、介绍一下你对浏览器内核的理解?06、常见的浏览器内核有哪些?07、html5有哪些新特性、移除了那些元素?如何处理HTML5新标签的浏览器兼容问题?08、如何区分HTML和HTML5?09、简述一下你对HTML语义化的理解?10、HTML5的离线储存怎么使用,工作原理能不能解释一下?二、CSS类的题目01、介绍一下标准的CSS的盒子模型?与低版本IE的盒子模型有什么不同的?02、CSS选择符有哪些?哪些属性可以继承?03、CSS优先级算法如何计算?04、CSS3新增伪类有那些?05、如何居中div?如何居中一个浮动元素?如何让绝对定位的div居中?06、display有哪些值?说明他们的作用。07、position的值relative和absolute定位原点是?08、CSS3有哪些新特性?09、请解释一下CSS3的Flexbox(弹性盒布局模型),以及适用场景?10、用纯CSS创建一个三角形的原理是什么?三、JavaScript类的题目01、JavaScript 中 this 是如何工作的02、请解释原型继承 的原理。03、什么是闭包 (closure),如何使用它,为什么要使用它?04、.call 和 .apply 的区别是什么?05、请指出 JavaScript 宿主对象 (host objects) 和原生对象 (native objects) 的区别?06、请指出以下代码的区别:function Person(){}、var person = Person()、var person = new Person()?07、请解释变量声明提升 (hoisting)。08、什么是 “use strict”; ? 使用它的好处和坏处分别是什么?09、什么是事件循环 (event loop)? 10、请解释同步 (synchronous) 和异步 (asynchronous) 函数的区别。如果你答出了绝大多数的或者是全部的题,并且答案也正确了,那么恭喜你……你这时心里是不是有点小窃喜,认为自己有能力拿高薪了?虽然也很想这么告诉你,但事实上这只能表明你的基础扎实,毕竟这只是一些基础的面试题。如果你只答出了小部分或者答出了大部分题但答案不正确,那么我只想说:“骚年,你的水平还差的远呢。”连这么基础的题你都打不出来,还想拿高薪?回去再练一段时间吧!扎实的基础是你拿高薪的重要武器,如果你连基础都不扎实,那么想要攻克“高薪”这个厚实的堡垒,那只是痴人说梦罢了。如果你依然在编程的世界里迷茫,不知道自己的未来规划,可以加入前端学习交流q-u-n-:731771211 里面可以与大神一起交流并走出迷茫。新手、进阶。可进群免费领取学习资料,看看前辈们是如何在编程的世界里傲然前行。

December 6, 2018 · 1 min · jiezi

2019怎么样打造自己的“前端品牌”

这个年底相信对于很多程序猿来说都不算太好过,是的,资本的寒冬已经到来。无论是传言某厂停止社招还是某商城末尾淘汰,亦或者某知名论坛因为“现金流”问题大裁员。这个年底,已经听到了很多知名公司裁员的消息了。关于裁员的思考。首先,资本总是逐利的,当然这是句废话,公司又不是散财童子或者观世音菩萨,公司招人肯定是要赚钱的。再者是互联网行业经历了一个巨大的风口,站在风口上,母猪也能上天。are you ok?互联网行业经历了一段热钱岁月,也催生了一堆泡沫。裁员呢,从公司的角度来看,为了利益的最大化,这两类人是比较危险的。高 p 人员。他们是公司的技术骨干,拿着丰厚的薪水以及股份。对于有些公司而言,高 p 人员消耗了大量的资源。能力靠后的人。这里的能力不只是说技术能力,包括各方面的能力。对于公司而言,这类人的单位产出比太低,甚至可能是负产出。总结就是公司裁员会留下那些要钱少,能做事的人。(老板不包括里面啊)朋友面试的困惑在微信群里经常有人问我,没有项目经验要怎么办,没有出彩的简历该怎么弄?等你看完这篇文章,如果能给你一些帮助的话,那将是我的荣幸。为什么要打造自己的“前端品牌”从面试官的角度来看,面试官只能从你的简历中找出你的亮点,来初步判断你是否适合这个岗位。那么作为前端的面试官,候选者的哪些品质会吸引到面试官呢?开源项目经历。(一般指 github)对自己的项目比较了解有那么一两个很熟悉的技术,比如 canvas,node 都是加分项对常用的框架源码有一定的了解有自己的博客有一定的自驱力漂亮的程序媛有开源项目参与经历至少说明候选者是一个乐于分享,熟悉基本的 git 流程,如果是一些知名项目的话还能体现出候选者的能力水平。对自己项目了解在面试中也是非常重要的一环,因为面试官会根据你写的项目经历去详细问你看你对技术的掌控程度。加分项就不用说了,可以体现出候选者好学。框架源码是大厂必考的一个点之一,为什么呢?因为大厂一般都会使用自己开发的框架,现代框架特性是基本相似的,虚拟 dom,diff,状态管理,路由等,面试官希望候选者能够了解框架的底层原理,而不是 api 的搬运工。自驱力一般指的是候选者的态度,比如学习的毅力,推动团队迭代,分享等。这篇文章着重讲的是有自己的博客。怎么样打造自己的 “前端品牌”积极参与开源项目github 上有各种各样的开源项目,有些项目非常有趣。通过参与 github 上面的开源项目,你可以认识特别多有趣的人,同时也能给你的简历增加权重。比如你给 vue.js 提供过几个 pr 并且成为贡献者之一,或者给 antd 修复了几个 bug,或者参与了某某翻译计划。如果你热爱开源(或者为了面试),你甚至可以花式提 pr。为了开源社区更好的发展,给出下列花式混 pr 的方式开源项目之初会有很多的问题,比如文档不完善,翻译错误,代码 bug 等。正是混 pr 的好时候关注知名开源项目,往往 issues 里面会存在很多的问题,如果你可以帮忙修复一些问题并提交 pull request,是不是既帮助了别人也成就了自己了呢。上面的建议虽然不入流,但是也从侧面推动了开源社区的发展,你也可以混得知名项目的贡献者荣誉。且不问你是怎么获得的,至少你付出了,就比别人更进一步,这就是你的优势。积累日常学习的东西,形成文档。俗语道:好记性不如烂笔头。虽然是陈词滥调,对于程序猿(媛)而言也一般不用“笔”,但确实是一件有意义的事情。我的几任上司都鼓励我多进行分享,多写文档去沉淀自己的技术。正是由于他们分享的这种精神,让我深受感染,也将会一直影响我未来的道路。技术的分享确实是一件非常令人兴奋的事情,一方面来说,分享的时候,你可以听到不同的声音,一个人的力量是有限的,这将帮助你去拓宽你的视野。分享的时候,很多人也会对细节进行详细的询问,你在讲解回答的过程中可以进一步加深自己的理解。另一方面,分享能够提高一个人的专注度,你总是渴望去把最好的东西展示出来,无形之中,你就学到了很多的东西。有一个很现实的问题是,如果你在开发的过程中遇到了一个很困难的问题,你通过搜索,询问各种方式去解决了。但是你没有文档,很久之后,你又遇到了相同的问题,结果你忘了,是不是又要重复去做这些无聊的动作。作为一个管理者而言,假如你的一个下属遇到了一个问题解决了,后面这个人离职了,又来了另外一个人,又是同样的问题,不知所措。这个时候你是不是很崩溃?所以大厂一定会有自己的文档库,(一定会有)。面试官从简历里面能够了解到的,除了你的工作经历,最主要的还是看你的博客,开源项目。写博客是需要坚持的一件事情,毅力很重要。也许刚开始你的文章并不出色,随着时间的推移,技术能力的上升。会越来越精彩,从而打造出你自己的“博客品牌”。我从准备做“前端指南”公众号开始,已经连续 2 个半月早上 6 点准时更新了。只要你能坚持 21 天,后续的就会保持习惯。给初入门写博客的同学们一些建议。前端领域的知识点文章其实都差不多,大家来来回回看的都是差不多的。比如原型链,es6,源码解析,某某开源项目实践。虽然很俗,对于个人而言,如果自己能写一些,对于基础的掌控还是非常有帮助的。借木易杨的计划来看看有哪些文章是可以写的【进阶 1 期】 调用堆栈【进阶 2 期】 作用域闭包【进阶 3 期】 this 全面解析【进阶 4 期】 深浅拷贝原理【进阶 5 期】 原型 Prototype【进阶 6 期】 高阶函数【进阶 7 期】 事件机制【进阶 8 期】 Event Loop 原理【进阶 9 期】 Promise 原理【进阶 10 期】Async/Await 原理【进阶 11 期】防抖/节流原理【进阶 12 期】模块化详解【进阶 13 期】ES6 重难点【进阶 14 期】计算机网络概述【进阶 15 期】浏览器渲染原理【进阶 16 期】webpack 配置【进阶 17 期】webpack 原理【进阶 18 期】前端监控【进阶 19 期】跨域和安全【进阶 20 期】性能优化【进阶 21 期】VirtualDom 原理【进阶 22 期】Diff 算法【进阶 23 期】MVVM 双向绑定【进阶 24 期】Vuex 原理【进阶 25 期】Redux 原理【进阶 26 期】路由原理【进阶 27 期】VueRouter 源码解析【进阶 28 期】ReactRouter 源码解析这些基本上也是各个公司面试会考的一些内容,各位可以从这上面入手。也可以参考 冴羽 的博客进行写作。等到达到一定的水平后,可以去写一些前沿的文章,这对面试而言是很有用的。写在最后希望能够帮助到大家。帮助他人成长,让大家成长到和我一样的水平,这对于我而言也是一种帮助 –我老大 ...

December 6, 2018 · 1 min · jiezi

面试官问:能否模拟实现JS的call和apply方法

之前写过两篇《面试官问:能否模拟实现JS的new操作符》和《面试官问:能否模拟实现JS的bind方法》其中模拟bind方法时是使用的call和apply修改this指向。但面试官可能问:能否不用call和apply来实现呢。意思也就是需要模拟实现call和apply的了。附上之前写文章写过的一段话:已经有很多模拟实现call和apply的文章,为什么自己还要写一遍呢。学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景。你不一定能看到别人看到的风景,体会到别人的心情。只有自己去登山,才能看到不一样的风景,体会才更加深刻。先通过MDN认识下call和applyMDN 文档:Function.prototype.call()语法fun.call(thisArg, arg1, arg2, …)thisArg在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。arg1, arg2, …指定的参数列表返回值返回值是你调用的方法的返回值,若该方法没有返回值,则返回undefined。MDN 文档:Function.prototype.apply()func.apply(thisArg, [argsArray])thisArg可选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。argsArray可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。返回值调用有指定this值和参数的函数的结果。直接先看例子1call 和 apply 的异同相同点:1、call和apply的第一个参数thisArg,都是func运行时指定的this。而且,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。2、都可以只传递一个参数。不同点:apply只接收两个参数,第二个参数可以是数组也可以是类数组,其实也可以是对象,后续的参数忽略不计。call接收第二个及以后一系列的参数。看两个简单例子1和2**:// 例子1:浏览器环境 非严格模式下var doSth = function(a, b){ console.log(this); console.log([a, b]);}doSth.apply(null, [1, 2]); // this是window // [1, 2]doSth.apply(0, [1, 2]); // this 是 Number(0) // [1, 2]doSth.apply(true); // this 是 Boolean(true) // [undefined, undefined]doSth.call(undefined, 1, 2); // this 是 window // [1, 2]doSth.call(‘0’, 1, {a: 1}); // this 是 String(‘0’) // [1, {a: 1}]// 例子2:浏览器环境 严格模式下’use strict’;var doSth2 = function(a, b){ console.log(this); console.log([a, b]);}doSth2.call(0, 1, 2); // this 是 0 // [1, 2]doSth2.apply(‘1’); // this 是 ‘1’ // [undefined, undefined]doSth2.apply(null, [1, 2]); // this 是 null // [1, 2]typeof 有7种类型(undefined number string boolean symbol object function),笔者都验证了一遍:更加验证了相同点第一点,严格模式下,函数的this值就是call和apply的第一个参数thisArg,非严格模式下,thisArg值被指定为 null 或 undefined 时this值会自动替换为指向全局对象,原始值则会被自动包装,也就是new Object()。重新认识了call和apply会发现:它们作用都是一样的,改变函数里的this指向为第一个参数thisArg,如果明确有多少参数,那可以用call,不明确则可以使用apply。也就是说完全可以不使用call,而使用apply代替。也就是说,我们只需要模拟实现apply,call可以根据参数个数都放在一个数组中,给到apply即可。模拟实现 apply既然准备模拟实现apply,那先得看看ES5规范。ES5规范 英文版,ES5规范 中文版。apply的规范下一个就是call的规范,可以点击打开新标签页去查看,这里摘抄一部分。Function.prototype.apply (thisArg, argArray) 当以 thisArg 和 argArray 为参数在一个 func 对象上调用 apply 方法,采用如下步骤:1.如果 IsCallable(func) 是 false, 则抛出一个 TypeError 异常。2.如果 argArray 是 null 或 undefined, 则返回提供 thisArg 作为 this 值并以空参数列表调用 func 的 [[Call]] 内部方法的结果。3.返回提供 thisArg 作为 this 值并以空参数列表调用 func 的 [[Call]] 内部方法的结果。4.如果 Type(argArray) 不是 Object, 则抛出一个 TypeError 异常。5~8 略9.提供 thisArg 作为 this 值并以 argList 作为参数列表,调用 func 的 [[Call]] 内部方法,返回结果。apply 方法的 length 属性是 2。在外面传入的 thisArg 值会修改并成为 this 值。thisArg 是 undefined 或 null 时它会被替换成全局对象,所有其他值会被应用 ToObject 并将结果作为 this 值,这是第三版引入的更改。结合上文和规范,如何将函数里的this指向第一个参数thisArg呢,这是一个问题。这时候请出例子3:// 浏览器环境 非严格模式下var doSth = function(a, b){ console.log(this); console.log(this.name); console.log([a, b]);}var student = { name: ‘轩辕Rowboat’, doSth: doSth,};student.doSth(1, 2); // this === student // true // ‘轩辕Rowboat’ // [1, 2]doSth.apply(student, [1, 2]); // this === student // true // ‘轩辕Rowboat’ // [1, 2]可以得出结论1:在对象student上加一个函数doSth,再执行这个函数,这个函数里的this就指向了这个对象。那也就是可以在thisArg上新增调用函数,执行后删除这个函数即可。知道这些后,我们试着容易实现第一版本:// 浏览器环境 非严格模式function getGlobalObject(){ return this;}Function.prototype.applyFn = function apply(thisArg, argsArray){ // apply 方法的 length 属性是 2。 // 1.如果 IsCallable(func) 是 false, 则抛出一个 TypeError 异常。 if(typeof this !== ‘function’){ throw new TypeError(this + ’ is not a function’); } // 2.如果 argArray 是 null 或 undefined, 则 // 返回提供 thisArg 作为 this 值并以空参数列表调用 func 的 [[Call]] 内部方法的结果。 if(typeof argsArray === ‘undefined’ || argsArray === null){ argsArray = []; } // 3.如果 Type(argArray) 不是 Object, 则抛出一个 TypeError 异常 . if(argsArray !== new Object(argsArray)){ throw new TypeError(‘CreateListFromArrayLike called on non-object’); } if(typeof thisArg === ‘undefined’ || thisArg === null){ // 在外面传入的 thisArg 值会修改并成为 this 值。 // ES3: thisArg 是 undefined 或 null 时它会被替换成全局对象 浏览器里是window thisArg = getGlobalObject(); } // ES3: 所有其他值会被应用 ToObject 并将结果作为 this 值,这是第三版引入的更改。 thisArg = new Object(thisArg); var __fn = ‘__fn’; thisArg[__fn] = this; // 9.提供 thisArg 作为 this 值并以 argList 作为参数列表,调用 func 的 [[Call]] 内部方法,返回结果 var result = thisArg__fn; delete thisArg[__fn]; return result;};实现第一版后,很容易找出两个问题:[ ] 1.fn 同名覆盖问题,thisArg对象上有__fn,那就被覆盖了然后被删除了。针对问题1解决方案一:采用ES6 Sybmol() 独一无二的。可以本来就是模拟ES3的方法。如果面试官不允许用呢。解决方案二:自己用Math.random()模拟实现独一无二的key。面试时可以直接用生成时间戳即可。// 生成UUID 通用唯一识别码// 大概生成 这样一串 ‘18efca2d-6e25-42bf-a636-30b8f9f2de09’function generateUUID(){ 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;}// 简单实现// ‘’ + new Date().getTime();如果这个key万一这对象中还是有,为了保险起见,可以做一次缓存操作。比如如下代码:var student = { name: ‘轩辕Rowboat’, doSth: ‘doSth’,};var originalVal = student.doSth;var hasOriginalVal = student.hasOwnProperty(‘doSth’);student.doSth = function(){};delete student.doSth;// 如果没有,originalVal则为undefined,直接赋值新增了一个undefined,这是不对的,所以需判断一下。if(hasOriginalVal){ student.doSth = originalVal;}console.log(‘student:’, student); // { name: ‘轩辕Rowboat’, doSth: ‘doSth’ }[ ] 2.使用了ES6扩展符…解决方案一:采用eval来执行函数。eval把字符串解析成代码执行。MDN 文档:eval语法eval(string)参数string表示JavaScript表达式,语句或一系列语句的字符串。表达式可以包含变量以及已存在对象的属性。返回值执行指定代码之后的返回值。如果返回值为空,返回undefined解决方案二:但万一面试官不允许用eval呢,毕竟eval是魔鬼。可以采用new Function()来生成执行函数。MDN 文档:Function语法new Function ([arg1[, arg2[, …argN]],] functionBody)参数arg1, arg2, … argN被函数使用的参数的名称必须是合法命名的。参数名称是一个有效的JavaScript标识符的字符串,或者一个用逗号分隔的有效字符串的列表;例如“×”,“theValue”,或“A,B”。functionBody一个含有包括函数定义的JavaScript语句的字符串。接下来看两个例子:简单例子:var sum = new Function(‘a’, ‘b’, ‘return a + b’);console.log(sum(2, 6));// 稍微复杂点的例子:var student = { name: ‘轩辕Rowboat’, doSth: function(argsArray){ console.log(argsArray); console.log(this.name); }};// var result = student.doSth([‘Rowboat’, 18]);// 用new Function()生成函数并执行返回结果var result = new Function(‘return arguments[0][arguments[1]](arguments[2][0], arguments[2][1])’)(student, ‘doSth’, [‘Rowboat’, 18]);// 个数不定// 所以可以写一个函数生成函数代码:function generateFunctionCode(argsArrayLength){ var code = ‘return arguments[0][arguments[1]](’; for(var i = 0; i < argsLength; i++){ if(i > 0){ code += ‘,’; } code += ‘arguments[2][’ + i + ‘]’; } code += ‘)’; // return arguments[0][arguments[1]](arg1, arg2, arg3…) return code;}你可能不知道在ES3、ES5中 undefined 是能修改的可能大部分人不知道。ES5中虽然在全局作用域下不能修改,但在局部作用域中也是能修改的,不信可以复制以下测试代码在控制台执行下。虽然一般情况下是不会的去修改它。function test(){ var undefined = 3; console.log(undefined); // chrome下也是 3}test();所以判断一个变量a是不是undefined,更严谨的方案是typeof a === ‘undefined’或者a === void 0;这里面用的是void,void的作用是计算表达式,始终返回undefined,也可以这样写void(0)。更多可以查看韩子迟的这篇文章:为什么用「void 0」代替「undefined」解决了这几个问题,比较容易实现如下代码。使用 new Function() 模拟实现的apply// 浏览器环境 非严格模式function getGlobalObject(){ return this;}function generateFunctionCode(argsArrayLength){ var code = ‘return arguments[0][arguments[1]](’; for(var i = 0; i < argsArrayLength; i++){ if(i > 0){ code += ‘,’; } code += ‘arguments[2][’ + i + ‘]’; } code += ‘)’; // return arguments[0][arguments[1]](arg1, arg2, arg3…) return code;}Function.prototype.applyFn = function apply(thisArg, argsArray){ // apply 方法的 length 属性是 2。 // 1.如果 IsCallable(func) 是 false, 则抛出一个 TypeError 异常。 if(typeof this !== ‘function’){ throw new TypeError(this + ’ is not a function’); } // 2.如果 argArray 是 null 或 undefined, 则 // 返回提供 thisArg 作为 this 值并以空参数列表调用 func 的 [[Call]] 内部方法的结果。 if(typeof argsArray === ‘undefined’ || argsArray === null){ argsArray = []; } // 3.如果 Type(argArray) 不是 Object, 则抛出一个 TypeError 异常 . if(argsArray !== new Object(argsArray)){ throw new TypeError(‘CreateListFromArrayLike called on non-object’); } if(typeof thisArg === ‘undefined’ || thisArg === null){ // 在外面传入的 thisArg 值会修改并成为 this 值。 // ES3: thisArg 是 undefined 或 null 时它会被替换成全局对象 浏览器里是window thisArg = getGlobalObject(); } // ES3: 所有其他值会被应用 ToObject 并将结果作为 this 值,这是第三版引入的更改。 thisArg = new Object(thisArg); var fn = ‘’ + new Date().getTime(); // 万一还是有 先存储一份,删除后,再恢复该值 var originalVal = thisArg[__fn]; // 是否有原始值 var hasOriginalVal = thisArg.hasOwnProperty(__fn); thisArg[__fn] = this; // 9.提供 thisArg 作为 this 值并以 argList 作为参数列表,调用 func 的 [[Call]] 内部方法,返回结果。 // ES6版 // var result = thisArg__fn; var code = generateFunctionCode(argsArray.length); var result = (new Function(code))(thisArg, __fn, argsArray); delete thisArg[__fn]; if(hasOriginalVal){ thisArg[__fn] = originalVal; } return result;};利用模拟实现的apply模拟实现callFunction.prototype.callFn = function call(thisArg){ var argsArray = []; var argumentsLength = arguments.length; for(var i = 0; i < argumentsLength - 1; i++){ // argsArray.push(arguments[i + 1]); argsArray[i] = arguments[i + 1]; } console.log(‘argsArray:’, argsArray); return this.applyFn(thisArg, argsArray);}// 测试例子var doSth = function (name, age){ var type = Object.prototype.toString.call(this); console.log(typeof doSth); console.log(this === firstArg); console.log(’type:’, type); console.log(’this:’, this); console.log(‘args:’, [name, age], arguments); return ’this–’;};var name = ‘window’;var student = { name: ‘轩辕Rowboat’, age: 18, doSth: ‘doSth’, __fn: ‘doSth’,};var firstArg = student;var result = doSth.applyFn(firstArg, [1, {name: ‘Rowboat’}]);var result2 = doSth.callFn(firstArg, 1, {name: ‘Rowboat’});console.log(‘result:’, result);console.log(‘result2:’, result2);细心的你会发现注释了这一句argsArray.push(arguments[i + 1]);,事实上push方法,内部也有一层循环。所以理论上不使用push性能会更好些。面试官也可能根据这点来问时间复杂度和空间复杂度的问题。// 看看V8引擎中的具体实现:function ArrayPush() { var n = TO_UINT32( this.length ); // 被push的对象的length var m = %_ArgumentsLength(); // push的参数个数 for (var i = 0; i < m; i++) { this[ i + n ] = %_Arguments( i ); // 复制元素 (1) } this.length = n + m; // 修正length属性的值 (2) return this.length;};行文至此,就基本结束了,你可能还发现就是写的非严格模式下,thisArg原始值会包装成对象,添加函数并执行,再删除。而严格模式下还是原始值这个没有实现,而且万一这个对象是冻结对象呢,Object.freeze({}),是无法在这个对象上添加属性的。所以这个方法只能算是非严格模式下的简版实现。最后来总结一下。总结通过MDN认识call和apply,阅读ES5规范,到模拟实现apply,再实现call。就是使用在对象上添加调用apply的函数执行,这时的调用函数的this就指向了这个thisArg,再返回结果。引出了ES6 Symbol,ES6的扩展符…、eval、new Function(),严格模式等。事实上,现实业务场景不需要去模拟实现call和apply,毕竟是ES3就提供的方法。但面试官可以通过这个面试题考察候选人很多基础知识。如:call、apply的使用。ES6 Symbol,ES6的扩展符…,eval,new Function(),严格模式,甚至时间复杂度和空间复杂度等。读者发现有不妥或可改善之处,欢迎指出。另外觉得写得不错,可以点个赞,也是对笔者的一种支持。// 最终版版 删除注释版,详细注释看文章// 浏览器环境 非严格模式function getGlobalObject(){ return this;}function generateFunctionCode(argsArrayLength){ var code = ‘return arguments[0][arguments[1]](’; for(var i = 0; i < argsArrayLength; i++){ if(i > 0){ code += ‘,’; } code += ‘arguments[2][’ + i + ‘]’; } code += ‘)’; return code;}Function.prototype.applyFn = function apply(thisArg, argsArray){ if(typeof this !== ‘function’){ throw new TypeError(this + ’ is not a function’); } if(typeof argsArray === ‘undefined’ || argsArray === null){ argsArray = []; } if(argsArray !== new Object(argsArray)){ throw new TypeError(‘CreateListFromArrayLike called on non-object’); } if(typeof thisArg === ‘undefined’ || thisArg === null){ thisArg = getGlobalObject(); } thisArg = new Object(thisArg); var fn = ‘’ + new Date().getTime(); var originalVal = thisArg[__fn]; var hasOriginalVal = thisArg.hasOwnProperty(__fn); thisArg[__fn] = this; var code = generateFunctionCode(argsArray.length); var result = (new Function(code))(thisArg, __fn, argsArray); delete thisArg[__fn]; if(hasOriginalVal){ thisArg[__fn] = originalVal; } return result;};Function.prototype.callFn = function call(thisArg){ var argsArray = []; var argumentsLength = arguments.length; for(var i = 0; i < argumentsLength - 1; i++){ argsArray[i] = arguments[i + 1]; } return this.applyFn(thisArg, argsArray);}扩展阅读《JavaScript设计模式与开发实践》- 第二章 第 2 章 this、call和applyJS魔法堂:再次认识Function.prototype.call不用call和apply方法模拟实现ES5的bind方法JavaScript深入之call和apply的模拟实现关于作者:常以轩辕Rowboat为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。个人博客segmentfault个人主页掘金个人主页知乎github ...

November 30, 2018 · 6 min · jiezi

刷《一年半经验,百度、有赞、阿里面试总结》·手记

在掘金上看到了一位大佬发了一篇很详细的面试记录文章-《一年半经验,百度、有赞、阿里面试总结》,为了查漏补缺,抽空就详细做了下。(估计只有我这么无聊了哈哈哈)有给出的或者有些不完善的答案,也尽力给出/完善了(可能有错,大家自行辨别)。有些很困难的题目(例如实现Promise),附带相关链接(懒癌患者福利)。总的来说,将这些题目分成了“Javascript”、“CSS”、“浏览器/协议”、“算法”和“Web工程化”5个部分进行回答和代码实现。最后,欢迎来我的博客和我扯犊子:godbmw.com。直接戳本篇原文的地址:刷《一年半经验,百度、有赞、阿里面试总结》·手记1. Javascript相关1.1 回文字符串题目:实现一个函数,判断是不是回文字符串原文的思路是将字符串转化成数组=>反转数组=>拼接成字符串。这种做法充分利用了js的BIF,但性能有所损耗:function run(input) { if (typeof input !== ‘string’) return false; return input.split(’’).reverse().join(’’) === input;}其实正常思路也很简单就能实现,性能更高,但是没有利用js的特性:// 回文字符串const palindrome = (str) => { // 类型判断 if(typeof str !== ‘string’) { return false; } let len = str.length; for(let i = 0; i < len / 2; ++i){ if(str[i] !== str[len - i - 1]){ return false; } } return true;}1.2 实现Storage题目:实现Storage,使得该对象为单例,并对localStorage进行封装设置值setItem(key,value)和getItem(key)题目重点是单例模式,需要注意的是借助localStorage,不是让自己手动实现!const Storage = () => {}Storage.prototype.getInstance = (() => { let instance = null return () => { if(!instance){ instance = new Storage() } return instance }})()Storage.prototype.setItem = (key, value) => { return localStorage.setItem(key, value)}Storage.prototype.getItem = (key) => { return localStorage.getItem(key)}1.3 JS事件流题目:说说事件流吧事件流分为冒泡和捕获。事件冒泡:子元素的触发事件会一直向父节点传递,一直到根结点停止。此过程中,可以在每个节点捕捉到相关事件。可以通过stopPropagation方法终止冒泡。事件捕获:和“事件冒泡”相反,从根节点开始执行,一直向子节点传递,直到目标节点。印象中只有少数浏览器的老旧版本才是这种事件流,可以忽略。1.4 实现函数继承题目:现在有一个函数A和函数B,请你实现B继承A。并且说明他们优缺点。方法一:绑定构造函数优点:可以实现多继承缺点:不能继承父类原型方法/属性function Animal(){ this.species = “动物”;}function Cat(){ Animal.apply(this, arguments); // 父对象的构造函数绑定到子节点上}var cat = new Cat()console.log(cat.species) // 输出:动物方法二:原型链继承优点:能够继承父类原型和实例方法/属性,并且可以捕获父类的原型链改动缺点:无法实现多继承,会浪费一些内存(Cat.prototype.constructor = Cat)。除此之外,需要注意应该将Cat.prototype.constructor重新指向本身。js中交换原型链,均需要修复prototype.constructor指向问题。function Animal(){ this.species = “动物”;}Animal.prototype.func = function(){ console.log(“heel”)}function Cat(){}Cat.prototype = new Animal()Cat.prototype.constructor = Catvar cat = new Cat()console.log(cat.func, cat.species)方法3:结合上面2种方法function Animal(){ this.species = “动物”;}Animal.prototype.func = function(){ console.log(“heel”)}function Cat(){ Animal.apply(this, arguments)}Cat.prototype = new Animal()Cat.prototype.constructor = Cat;var cat = new Cat()console.log(cat.func, cat.species)1.5 ES5对象 vs ES6对象题目:es6 class 的new实例和es5的new实例有什么区别?在ES6中(和ES5相比),class的new实例有以下特点:class的构造参数必须是new来调用,不可以将其作为普通函数执行es6 的class不存在变量提升最重要的是:es6内部方法不可以枚举。es5的prototype上的方法可以枚举。为此我做了以下测试代码进行验证:console.log(ES5Class()) // es5:可以直接作为函数运行// console.log(new ES6Class()) // 会报错:不存在变量提升function ES5Class(){ console.log(“hello”)}ES5Class.prototype.func = function(){ console.log(“Hello world”) }class ES6Class{ constructor(){} func(){ console.log(“Hello world”) }}let es5 = new ES5Class()let es6 = new ES6Class()console.log(“ES5 :")for(let _ in es5){ console.log()}// es6:不可枚举console.log(“ES6 :")for(let _ in es6){ console.log()}这篇《JavaScript创建对象—从es5到es6》对这个问题的深入解释很好,推荐观看!1.6 实现MVVM题目:请简单实现双向数据绑定mvvmvuejs是利用Object.defineProperty来实现的MVVM,采用的是订阅发布模式。每个data中都有set和get属性,这种点对点的效率,比Angular实现MVVM的方式的效率更高。<body> <input type=“text”> <script> const input = document.querySelector(‘input’) const obj = {} Object.defineProperty(obj, ‘data’, { enumerable: false, // 不可枚举 configurable: false, // 不可删除 set(value){ input.value = value _value = value // console.log(input.value) }, get(){ return _value } }) obj.data = ‘123’ input.onchange = e => { obj.data = e.target.value } </script></body>1.7 实现Promise这是一位大佬实现的Promise版本:过了Promie/A+标准的测试!!!网上能搜到的基本都是从这篇文章变形而来或者直接照搬!!!原文地址,直接戳:剖析Promise内部结构,一步一步实现一个完整的、能通过所有Test case的Promise类下面附上一种近乎完美的实现:可能无法和其他Promise库的实现无缝对接。但是,上面的原文实现了全部的,欢迎Mark!function MyPromise(executor){ var that = this this.status = ‘pending’ // 当前状态 this.data = undefined this.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面 this.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面 // 更改状态 => 绑定数据 => 执行回调函数集 function resolve(value){ if(that.status === ‘pending’){ that.status = ‘resolved’ that.data = value for(var i = 0; i < that.onResolvedCallback.length; ++i){ that.onResolvedCallbacki } } } function reject(reason){ if(that.status === ‘pending’){ that.status = ‘rejected’ that.data = reason for(var i = 0; i < that.onResolvedCallback.length; ++i){ that.onRejectedCallbacki } } } try{ executor(resolve, reject) // resolve, reject两个函数可以在外部传入的函数(executor)中调用 } catch(e) { // 考虑到执行过程可能有错 reject(e) }}// 标准是没有catch方法的,实现了then,就实现了catch// then/catch 均要返回一个新的Promise实例MyPromise.prototype.then = function(onResolved, onRejected){ var that = this var promise2 // 值穿透 onResolved = typeof onResolved === ‘function’ ? onResolved : function(v){ return v } onRejected = typeof onRejected === ‘function’ ? onRejected : function(r){ return r } if(that.status === ‘resolved’){ return promise2 = new MyPromise(function(resolve, reject){ try{ var x = onResolved(that.data) if(x instanceof MyPromise){ // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果 x.then(resolve, reject) } resolve(x) // 否则,以它的返回值做为promise2的结果 } catch(e) { reject(e) // 如果出错,以捕获到的错误做为promise2的结果 } }) } if(that.status === ‘rejected’){ return promise2 = new MyPromise(function(resolve, reject){ try{ var x = onRejected(that.data) if(x instanceof MyPromise){ x.then(resolve, reject) } } catch(e) { reject(e) } }) } if(that.status === ‘pending’){ return promise2 = new MyPromise(function(resolve, reject){ self.onResolvedCallback.push(function(reason){ try{ var x = onResolved(that.data) if(x instanceof MyPromise){ x.then(resolve, reject) } } catch(e) { reject(e) } }) self.onRejectedCallback.push(function(value){ try{ var x = onRejected(that.data) if(x instanceof MyPromise){ x.then(resolve, reject) } } catch(e) { reject(e) } }) }) }}MyPromise.prototype.catch = function(onRejected){ return this.then(null, onRejected)}// 以下是简单的测试样例:new MyPromise(resolve => resolve(8)).then(value => { console.log(value)})1.8 Event Loop题目:说一下JS的EventLoop其实阮一峰老师这篇《JavaScript 运行机制详解:再谈Event Loop》已经讲的很清晰了(手动赞)!这里简单总结下:JS是单线程的,其上面的所有任务都是在两个地方执行:执行栈和任务队列。前者是存放同步任务;后者是异步任务有结果后,就在其中放入一个事件。当执行栈的任务都执行完了(栈空),js会读取任务队列,并将可以执行的任务从任务队列丢到执行栈中执行。这个过程是循环进行,所以称作Loop。2. CSS相关2.1 水平垂直居中题目: 两种以上方式实现已知或者未知宽度的垂直水平居中第一种方法就是利用CSS3的translate进行偏移定位,注意:两个参数的百分比都是针对元素本身计算的。.wrap { position: relative; width: 100vw; height: 100vh;}.box { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);}第二种方法是利用CSS3的flex布局,父元素diplay属性设置为flex,并且定义元素在两条轴线的布局方式均为center.wrap { display: flex; justify-content: center; align-items: center; width: 100vw; height: 100vh;}.wrap .box { width: 100px; height: 100px;}第三种方法是利用margin负值来进行元素偏移,优点是浏览器兼容好,缺点是不够灵活(要自行计算margin的值):.wrap { position: relative; width: 100vw; height: 100vh;}.box { position: absolute; top: 50%; left: 50%; width: 100px; height: 100px; margin: -50px 0 0 -50px;}2.2 “点击”改变样式题目:实现效果,点击容器内的图标,图标边框变成border 1px solid red,点击空白处重置。利用event.target可以判断是否是指定元素本身(判断“空白处”),除此之外,注意禁止冒泡(题目指明了“容器内”)。<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0”> <meta http-equiv=“X-UA-Compatible” content=“ie=edge”> <title>Document</title> <style> #app { min-width: 100vw; min-height: 100vh; } #app .icon{ display: inline-block; cursor: pointer; } </style></head><body> <div id=“app”> <span class=“icon”>123456</span> </div> <script> const app = document.querySelector("#app”) const icon = document.querySelector(".icon”) app.addEventListener(“click”, e => { if(e.target === icon){ return; } // 非空白处才去除 border icon.style.border = “none”; }) icon.addEventListener(“click”, e => { // 禁止冒泡 e.stopPropagation() // 更改样式 icon.style.border = “1px solid red”; }) </script></body></html>3. 浏览器/协议相关3.1 缓存机制题目:说一下浏览器的缓存机制。浏览器缓存分为强缓存和协商缓存。缓存的作用是提高客户端速度、节省网络流量、降低服务器压力。强缓存:浏览器请求资源,如果header中的Cache-Control和Expires没有过期,直接从缓存(本地)读取资源,不需要再向服务器请求资源。协商缓存:浏览器请求的资源如果是过期的,那么会向服务器发送请求,header中带有Etag字段。服务器再进行判断,如果ETag匹配,则返回给客户端300系列状态码,客户端继续使用本地缓存;否则,客户端会重新获取数据资源。关于过程中详细的字段,可以参考这篇《http协商缓存VS强缓存》3.2 从URL到页面生成题目:输入URL到看到页面发生的全过程,越详细越好DNS解析建立TCP连接(3次握手)发送HTTP请求,从服务器下载相关内容浏览器构建DOM树和CSS树,然后生成渲染树。这个一个渐进式过程,引擎会力求最快将内容呈现给用户。在第四步的过程中,<script>的位置和加载方式会影响响应速度。搞定了,关闭TCP连接(4次握手)3.3 TCP握手题目:解释TCP建立的时候的3次握手和关闭时候的4次握手看这题的时候,我也是突然懵(手动捂脸)。推荐翻一下计算机网络的相关书籍,对于FIN、ACK等字段的讲解很赞!3.4 CSS和JS位置题目:CSS和JS的位置会影响页面效率,为什么?先说CSS。CSS的位置不会影响加载速度,但是CSS一般放在<head>标签中。前面有说DOM树和CSS树共同生成渲染树,CSS位置太靠后的话,在CSS加载之前,可能会出现闪屏、样式混乱、白屏等情况。再说JS。JS是阻塞加载,默认的<script>标签会加载并且立即执行脚本,如果脚本很复杂或者网络不好,会出现很久的白屏。所以,JS标签一般放到<body>标签最后。现在,也可以为<script>标签设置async或者defer属性。前者是js脚本的加载和执行将与后续文档的加载和渲染同步执行。后者是js脚本的加载将与后续文档的加载和渲染同步执行,当所有元素解析完,再执行js脚本。4. 算法相关4.1 数组全排列题目:现在有一个数组[1,2,3,4],请实现算法,得到这个数组的全排列的数组,如[2,1,3,4],[2,1,4,3]。。。。你这个算法的时间复杂度是多少实现思路:从“开始元素”起,每个元素都和开始元素进行交换;不断缩小范围,最后输出这种排列。暴力法的时间复杂度是 $O(N_N)$,递归实现的时间复杂度是 $O(N!)$如何去重?去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。对于有重复元素的数组,例如:[1, 2, 2],应该剔除重复的情况。每次只需要检查arr[start, i)中是不是有和arr[i]相同的元素,有的话,说明之前已经输出过了,不需要考虑。代码实现:const swap = (arr, i, j) => { let tmp = arr[i] arr[i] = arr[j] arr[j] = tmp}const permutation = arr => { const _permutation = (arr, start) => { if(start === arr.length){ console.log(arr) return } for(let i = start; i < arr.length; ++i){ // 全排列:去重操作 if(arr.slice(start, i).indexOf(arr[i]) !== -1){ continue } swap(arr, i, start) // 和开始元素进行交换 _permutation(arr, start + 1) swap(arr, i, start) // 恢复数组 } return } return _permutation(arr, 0)}permutation([1, 2, 2])console.log("**********")permutation([1, 2, 3, 4])4.2 背包问题题目:我现在有一个背包,容量为m,然后有n个货物,重量分别为w1,w2,w3…wn,每个货物的价值是v1,v2,v3…vn,w和v没有任何关系,请求背包能装下的最大价值。这个还在学习中,背包问题博大精深。。。4.3 图的连通分量题目:我现在有一个canvas,上面随机布着一些黑块,请实现方法,计算canvas上有多少个黑块。这一题可以转化成图的联通分量问题。通过getImageData获得像素数组,从头到尾遍历一遍,就可以判断每个像素是否是黑色。同时,准备一个width * height大小的二维数组,这个数组的每个元素是1/0。如果是黑色,二维数组对应元素就置1;否则置0。然后问题就被转换成了图的连通分量问题。可以通过深度优先遍历或者并查集来实现。之前我用C++实现了,这里不再冗赘:图的遍历和应用并查集5. Web工程化5.1 Dialog组件思路题目:现在要你完成一个Dialog组件,说说你设计的思路?它应该有什么功能?可以指定宽度、高度和位置需要一个遮盖层,遮住底层内容由头部、尾部和正文构成需要监听事件和自定义事件,非单向数据流:例如点击组件右上角,修改父组件的visible属性,关闭组件。关于工程化组件封装,可以去试试ElementUI。这个是ElementUI的Dialog组件:Element-Dialog5.2 React的Diff算法和虚拟DOM题目: react 的虚拟dom是怎么实现的原答案写的挺好滴,这里直接贴了。首先说说为什么要使用Virturl DOM,因为操作真实DOM的耗费的性能代价太高,所以react内部使用js实现了一套dom结构。在每次操作在和真实dom之前,使用实现好的diff算法,对虚拟dom进行比较,递归找出有变化的dom节点,然后对其进行更新操作。为了实现虚拟DOM,我们需要把每一种节点类型抽象成对象,每一种节点类型有自己的属性,也就是prop,每次进行diff的时候,react会先比较该节点类型:假如节点类型不一样,那么react会直接删除该节点,然后直接创建新的节点插入到其中;假如节点类型一样,那么会比较prop是否有更新,假如有prop不一样,那么react会判定该节点有更新,那么重渲染该节点,然后在对其子节点进行比较,一层一层往下,直到没有子节点。参考链接:React源码之Diff算法最后,欢迎来我的博客和我扯犊子:godbmw.com。直接戳本篇原文的地址:刷《一年半经验,百度、有赞、阿里面试总结》·手记 ...

November 30, 2018 · 4 min · jiezi

高级前端面试题大汇总(只有试题,没有答案)

面试题来源于网络,看一下高级前端的面试题,可以知道自己和高级前端的差距。有些面试题会重复。使用过的koa2中间件koa-body原理介绍自己写过的中间件有没有涉及到Cluster介绍pm2master挂了的话pm2怎么处理如何和MySQL进行通信React声明周期及自己的理解如何配置React-Router路由的动态加载模块服务端渲染SSR介绍路由的history介绍Redux数据流的流程Redux如何实现多个组件之间的通信,多个组件使用相同状态如何进行管理多个组件之间如何拆分各自的state,每块小的组件有自己的状态,它们之间还有一些公共的状态需要维护使用过的Redux中间件如何解决跨域的问题常见Http请求头移动端适配1px的问题介绍flex布局其他css方式设置垂直居中居中为什么要使用transform(为什么不使用marginLeft/Top)使用过webpack里面哪些plugin和loaderwebpack里面的插件是怎么实现的dev-server是怎么跑起来项目优化抽取公共文件是怎么配置的项目中如何处理安全问题怎么实现this对象的深拷贝介绍redux,主要解决什么问题文件上传如何做断点续传表单可以跨域吗promise、async有什么区别搜索请求如何处理(防抖)搜索请求中文如何请求介绍观察者模式介绍中介者模式观察者和订阅-发布的区别,各自用在哪里介绍react优化介绍http2.0通过什么做到并发请求http1.1时如何复用tcp连接介绍service worker介绍css3中position:stickyredux请求中间件如何处理并发介绍Promise,异常捕获介绍position属性包括CSS3新增浏览器事件流向介绍事件代理以及优缺点React组件中怎么做事件代理React组件事件代理的原理介绍this各种情况前端怎么控制管理路由使用路由时出现问题如何解决React怎么做数据的检查和变化react-router怎么实现路由切换react-router里的<Link>标签和a标签有什么区别a标签默认事件禁掉之后做了什么才实现了跳转React层面的性能优化整个前端性能提升大致分几类import { Button } from ‘antd’,打包的时候只打包button,分模块加载,是怎么做到的使用import时,webpack对node_modules里的依赖会做什么JS异步解决方案的发展历程以及优缺点Http报文的请求会有几个部分cookie放哪里,cookie能做的事情和存在的价值cookie和token都存放在header里面,为什么只劫持前者cookie和session有哪些方面的区别React中Dom结构发生变化后内部经历了哪些变化React挂载的时候有3个组件,textComponent、composeComponent、domComponent,区别和关系,Dom结构发生变化时怎么区分data的变化,怎么更新,更新怎么调度,如果更新的时候还有其他任务存在怎么处理key主要是解决哪一类的问题,为什么不建议用索引index(重绘)Redux中异步的请求怎么处理Redux中间件是什么东西,接受几个参数(两端的柯里化函数)柯里化函数两端的参数具体是什么东西中间件是怎么拿到store和action,然后怎么处理state是怎么注入到组件的,从reducer到组件经历了什么样的过程koa中response.send、response.rounded、response.json发生了什么事,浏览器为什么能识别到它是一个json结构或是htmlkoa-bodyparser怎么来解析requestwebpack整个生命周期,loader和plugin有什么区别介绍AST(Abstract Syntax Tree)抽象语法树安卓Activity之间数据是怎么传递的安卓4.0到6.0过程中WebView对js兼容性的变化WebView和原生是如何通信跨域怎么解决,有没有使用过Apache等方案对async、await的理解,内部原理介绍下Promise,内部实现清除浮动定位问题(绝对定位、相对定位等)从输入URL到页面加载全过程tcp3次握手tcp属于哪一层(1 物理层 -> 2 数据链路层 -> 3 网络层(ip)-> 4 传输层(tcp) -> 5 应用层(http))redux的设计思想接入redux的过程绑定connect的过程connect原理 webpack介绍== 和 ===的区别,什么情况下用相等==bind、call、apply的区别CSS3动画的了解介绍下原型链(解决的是继承问题吗)对跨域的了解Linux 754 介绍介绍冒泡排序,选择排序,冒泡排序如何优化transform动画和直接使用left、top改变位置有什么优缺点如何判断链表是否有环介绍二叉搜索树的特点介绍暂时性死区ES6中的map和原生的对象有什么区别观察者和发布-订阅的区别react异步渲染的概念,介绍Time Slicing 和 Suspense声明周期的改变中props改变后在哪个生命周期中处理介绍纯函数前端性能优化pureComponent和FunctionComponent区别介绍JSX如何做RN在安卓和IOS端的适配RN为什么能在原生中绘制成原生组件(bundle.js)介绍虚拟DOM如何设计一个localStorage,保证数据的实效性如何设计Promise.all()介绍高阶组件sum(2, 3)实现sum(2)(3)的效果react性能优化两个对象如何比较JS的原型变量作用域链防抖和节流的区别介绍各种异步方案react生命周期介绍Fiber前端性能优化介绍DOM树对比react中的key的作用如何设计状态树介绍css,xsrfhttp缓存控制项目中如何应用数据结构native提供了什么能力给RN如何做工程上的优化shouldComponentUpdate是为了解决什么问题如何解决props层级过深的问题前端怎么做单元测试webpack生命周期webpack打包的整个过程常用的pluginspm2怎么做进程管理,进程挂掉怎么处理不用pm2怎么做进程管理介绍下浏览器跨域怎么去解决跨域问题jsonp方案需要服务端怎么配合Ajax发生跨域要设置什么(前端)加上CORS之后从发起到请求正式成功的过程xsrf跨域攻击的安全性问题怎么防范使用Async会注意哪些东西Async里面有多个await请求,可以怎么优化(请求是否有依赖)Promise和Async处理失败的时候有什么区别Redux在状态管理方面解决了React本身不能解决的问题Redux有没有做过封装react生命周期,常用的生命周期对应的生命周期做什么事遇到性能问题一般在哪个生命周期里解决怎么做性能优化(异步加载组件…)写react有哪些细节可以优化React的事件机制(绑定一个事件到一个组件上)介绍下事件代理,主要解决什么问题前端开发中用到哪些设计模式React/Redux中哪些功能用到了哪些设计模式JS变量类型分为几种,区别是什么JS里垃圾回收机制是什么,常用的是哪种,怎么处理的一般怎么组织CSS(Webpack)小程序里面开页面最多多少React子父组件之间如何传值Emit事件怎么发,需要引入什么介绍下React高阶组件,和普通组件有什么区别一个对象数组,每个子对象包含一个id和name,React如何渲染出全部的name在哪个生命周期里写其中有几个name不存在,通过异步接口获取,如何做渲染的时候key给什么值,可以使用index吗,用id好还是index好webpack如何配sass,需要配哪些loader配css需要哪些loader如何配置把js、css、html单独打包成一个文件div垂直水平居中(flex、绝对定位)两个元素块,一左一右,中间相距10像素上下固定,中间滚动布局如何实现[1, 2, 3, 4, 5]变成[1, 2, 3, a, b, 5]取数组的最大值(ES5、ES6)apply和call的区别ES5和ES6有什么区别some、every、find、filter、map、forEach有什么区别上述数组随机取数,每次返回的值都不一样如何找0-5的随机数,95-99呢页面上有1万个button如何绑定事件如何判断是button页面上生成一万个button,并且绑定事件,如何做(JS原生操作DOM)循环绑定时的index是多少,为什么,怎么解决页面上有一个input,还有一个p标签,改变input后p标签就跟着变化,如何处理监听input的哪个事件,在什么时候触发手写两道算法题对React看法,有没有遇到一些坑对闭包的看法,为什么要用闭包手写数组去重函数手写数组扁平化函数介绍下Promise的用途和性质Promise和Callback有什么区别React生命周期ES6新的特性介绍PromisePromise有几个状态说一下闭包React的生命周期componentWillReceiveProps的触发条件是什么React16.3对生命周期的改变介绍下React的Filber架构画Filber渲染树介绍React高阶组件父子组件之间如何通信Redux怎么实现属性传递,介绍下原理React-Router版本号网站SEO怎么处理介绍下HTTP状态码403、301、302是什么缓存相关的HTTP请求头介绍HTTPSHTTPS怎么建立安全通道前端性能优化(JS原生和React)用户体验做过什么优化对PWA有什么了解对安全有什么了解介绍下数字签名的原理前后端通信使用什么方案RESTful常用的Method介绍下跨域Access-Control-Allow-Origin在服务端哪里配置csrf跨站攻击怎么解决前端和后端怎么联调localStorage和cookie有什么区别CSS选择器有哪些盒子模型,以及标准情况和IE下的区别如何实现高度自适应prototype和——proto——区别_construct是什么new是怎么实现的promise的精髓,以及优缺点如何实现H5手机端的适配rem、flex的区别(root em)em和px的区别React声明周期如何去除url中的#号Redux状态管理器和变量挂载到window中有什么区别webpack和gulp的优缺点如何实现异步加载如何实现分模块打包(多入口)前端性能优化(1js css;2 图片;3 缓存预加载; 4 SSR; 5 多域名加载;6 负载均衡)并发请求资源数上限(6个)base64为什么能提升性能,缺点介绍webp这个图片文件格式介绍koa2Promise如何实现的异步请求,低版本fetch如何低版本适配ajax如何处理跨域CORS如何设置jsonp为什么不支持post方法介绍同源策略React使用过的一些组件介绍Immuable介绍下redux整个流程原理介绍原型链如何继承介绍JS数据类型,基本数据类型和引用数据类型的区别Array是Object类型吗数据类型分别存在哪里var a = {name: “前端开发”}; var b = a; a = null那么b输出什么var a = {b: 1}存放在哪里var a = {b: {c: 1}}存放在哪里栈和堆的区别垃圾回收时栈和堆的区别数组里面有10万个数据,取第一个元素和第10万个元素的时间相差多少栈和堆具体怎么存储介绍闭包以及闭包为什么没清除闭包的使用场景JS怎么实现异步异步整个执行周期Promise的三种状态Async/Await怎么实现Promise和setTimeout执行先后的区别JS为什么要区分微任务和宏任务Promise构造函数是同步还是异步执行,then呢发布-订阅和观察者模式的区别JS执行过程中分为哪些阶段词法作用域和this的区别平常是怎么做继承深拷贝和浅拷贝loadsh深拷贝实现原理ES6中let块作用域是怎么实现的React中setState后发生了什么setState为什么默认是异步setState什么时候是同步的为什么3大框架出现以后就出现很多native(RN)框架(虚拟DOM)虚拟DOM主要做了什么虚拟DOM本身是什么(JS对象)304是什么打包时Hash码是怎么生成的随机值存在一样的情况,如何避免使用webpack构建时有无做一些自定义操作webpack做了什么a,b两个按钮,点击aba,返回顺序可能是baa,如何保证是aba(Promise.then)node接口转发有无做什么优化node起服务如何保证稳定性,平缓降级,重启等RN有没有做热加载RN遇到的兼容性问题RN如何实现一个原生的组件RN混原生和原生混RN有什么不同什么是单页项目遇到的复杂业务场景Promise.all实现原理介绍Promise的特性,优缺点介绍ReduxRN的原理,为什么可以同时在安卓和IOS端运行RN如何调用原生的一些功能介绍RN的缺点介绍排序算法和快排原理堆和栈的区别介绍闭包闭包的核心是什么网络的五层模型HTTP和HTTPS的区别HTTPS的加密过程介绍SSL和TLS介绍DNS解析JS的继承方法介绍垃圾回收cookie的引用为了解决什么问题cookie和localStorage的区别如何解决跨域问题前端性能优化使用canvas绘图时如何组织成通用组件formData和原生的ajax有什么区别介绍下表单提交,和formData有什么关系介绍redux接入流程rudux和全局管理有什么区别(数据可控、数据响应)RN和原生通信介绍MVP怎么组织介绍异步方案promise如何实现then处理koa2中间件原理常用的中间件服务端怎么做统一的状态处理如何对相对路径引用进行优化node文件查找优先级npm2和npm3+有什么区别knex连接数据库响应回调介绍异步方案如何处理异常捕获项目如何管理模块前端性能优化JS继承方案如何判断一个变量是不是数组变量a和b,如何交换事件委托多个标签生成的Dom结构是一个类数组类数组和数组的区别dom的类数组如何转成数组介绍单页面应用和多页面应用redux状态树的管理介绍localstorage的APIhtml语义化的理解<b>和<strong>的区别对闭包的理解工程中闭包使用场景介绍this和原型使用原型最大的好处react设计思路为什么虚拟DOM比真实DOM性能好react常见的通信方式redux整体的工作流程redux和全局对象之间的区别Redux数据回溯设计思路单例、工厂、观察者项目中实际场景项目中树的使用场景以及了解工作收获react生命周期react性能优化添加原生事件不移除为什么会内存泄露还有哪些地方会内存泄露setInterval需要注意的点定时器为什么是不精确的setTimeout(1)和setTimeout(2)之间的区别介绍宏任务和微任务promise里面和then里面执行有什么区别介绍pureComponet介绍Function ComponentReact数据流props和state的区别介绍react context介绍class和ES5的类以及区别介绍箭头函数和普通函数的区别介绍defineProperty方法,什么时候需要用到for..in 和 object.keys的区别介绍闭包,使用场景使用闭包特权函数的使用场景get和post有什么区别React15/16.x的区别重新渲染render会做些什么哪些方法会触发react重新渲染state和props触发更新的生命周期分别有什么区别setState是同步还是异步对无状态组件的理解介绍Redux工作流程介绍ES6的功能let、const以及var的区别浅拷贝和深拷贝的区别介绍箭头函数的this介绍Promise和then介绍快速排序算法:前K个最大的元素对react看法,它的优缺点 使用过程中遇到的问题,如何解决的 react的理念是什么(拿函数式编程来做页面渲染) JS是什么范式语言(面向对象还是函数式编程) koa原理,为什么要用koa(express和koa对比) 使用的koa中间件 ES6使用的语法 Promise 和 async/await 和 callback的区别 Promise有没有解决异步的问题(promise链是真正强大的地方) Promise和setTimeout的区别(Event Loop) 进程和线程的区别(一个node实例就是一个进程,node是单线程,通过事件循环来实现异步) 介绍下DFS深度优先 介绍下观察者模式 观察者模式里面使用的数据结构(不具备顺序 ,是一个list)Vue面试中,经常会被问到的面试题/Vue知识点整理532道前端真实大厂面试题学习ES6笔记──工作中常用到的ES6语法 ...

November 25, 2018 · 1 min · jiezi

记一次前端面试的全过程

引言接上一篇面试总结一年半经验,百度、有赞、阿里面试总结,把这段时间的面试总结束一下吧。本文主要记录一下当天面试的全过程(可能有遗漏,事隔三四天了,我已经尽量回忆了),答案亦为参考答案,仅供借鉴。正文有赞一面结束后过了两天就收到了二面的邀请,我回复面试邀请的短信,说最近可能请假太多,能不能约到晚上面试,对方很很爽快的答应了,就约在了晚上七点半,我回复可以的,然后第二天,收到了确切的面试的时间和地点,时间定在了晚上7点15分。到了面试当天,我提前了五分钟下班,照着百度地图的提示路线(约1小时9分钟),到了公交站等车。。。然后等呀等,等了十五分钟公交还没来,怕自己迟到,就打了个滴滴过去,到了面试地点之后上到了公司的前台,前台没有人,可能是因为到饭点了,前台去吃饭了吧。然后看到前台那层楼好多人在打乒乓球,大家也玩得挺开心,看起来环境也很不错,当时想,诶呀,原来有赞的环境这么好。等了一会儿之后,看了一下短信,发现面试邀请里留有面试官的联系电话,果断打了过去,过了一会儿面试官到前台接我,然后找了一个会议室,开始了当天的面试。面试官:先自我介绍吧我:巴拉巴拉…面试官:先说一下你上一家公司的研发部署流程我:巴拉巴拉…(其实这个是我绝活,每次都可以吹很久)面试官:既然你们是文件覆盖式发布,那你们的缓存是怎么刷新的我:从公司的业务出发,巴拉巴拉…(还没说完)面试官:那我现在就不谈业务,你说一下浏览器的缓存方案吧我:哦,脱离业务呀,首先,浏览器有两种缓存方案,一种是强缓存一种是协商缓存。面试官:嗯,那怎么使用强缓存?我:浏览器在第一次请求资源的时候,服务端响应头里可以设置expires字段,该字段表示该资源的缓存过期时间,第二次请求的时候,如果时间还在该缓存时间之内,则会直接使用缓存,否则重新加载资源,这个expires字段有个缺陷,就是它必须服务端和客户端的时间严格同步才能生效,所以现在很多人不会使用改方案。另外一种方案是第一次请求资源的时候,服务端设置响应头cache-control: max-age,这样设置的意思是告诉浏览器,这个资源什么时候过期,等第二次请求资源的时候,判断是否超出了过期时间,如果没超出,直接使用缓存。面试官:cache-control这个头是服务端设置的还是客户端设置的?我:cache-control服务端设置的面试官:cache-control的其他值,你也说一下吧我:首先是public,客户端和服务端都可以缓存;然后是private,只能客户端缓存;no-store,不使用缓存;no-cache,使用协商缓存。面试官:那你往下说,说一下协商缓存我:协商缓存有两种,一种是Last-Modified,就是第一次请求资源的时候,服务端会在响应头里面设置该字段,表示该资源的最后修改时间,浏览器第二次请求该资源的时候,会在请求头里面加上一个字段If-Modified-Since,值为第一次请求的时候服务端返回的Last-Modified的值,服务端会判断资源当时的最后更改时间与请求头里面的If-Modified-Since字段是否相同,如果相同,则告诉客户端使用缓存,否则重新下载资源。然后另外一种协商缓存时使用ETag,原理与Last-Modified类似,就是第一次请求的时候,服务端会根据资源的内容或者最后修改时间生成一个标识,然后在响应头里面设置为ETag返回给客户端,客户端第二次请求的时候会在请求头里面带上这个ETag,也就是在请求头里面加上If-None-Match字段,服务端接收到了ETag之后判断是否与原来第一次的标识相同,如果相同,则告诉客户端使用缓存。说一下Last-modified/If-Modified-Since和If-None-Match/ETag两种方案的优缺点我:嗯呢,这个我想一想(我并不知道,假装思考一下)……我觉得其实ETag其实也是有的时候是根据资源的最后修改时间生成的,原理和Last-modified好像有点类似,而ETag需要耗费服务端的资源去生成,所以性能较低。。。(虽然不会,也尽量说说,万一面试官也不知道呢。哈哈哈哈)面试官:那说一下性能优化的方案吧我:首先,减少HTTP请求次数,比如说合并CSS和JS文件,但是也不要完全的合并在同一个文件里面,一个域名分散三四个资源,这样方便浏览器去并行下载,当然浏览器对每个域名的并行下载个数有限制,一个域名分配三四个资源虽然增加了HTTP请求数量,但是对比并行下载来说,性价比更高。面试官:为什么浏览器要限制同一域名并行下载资源的个数。我:嗯呢,这个我也想一下(其实我也不知道)……这个我没有深究过,难道是因为浏览器启动了太多下载线程的原因?面试官:下载资源和线程有什么关系?我:除了了每个标签页是一个进程以外,浏览器有一个进程是专门用来管理下载,我觉得大概是每下载一个资源启动一个线程吧(反正我也不知道,也猜猜结果是不是这样)面试官:(沉默了一会儿),进程和线程区别是什么我:进程是分配内存的最小单位,线程是CPU调度的最小单位,进程可以包含多个线程。面试官:nodejs用得多吗?说一下nodejs进程之间是怎么通信的我:nodejs用的比较少,nodejs可以启动子线程,然后用主线程来监听订阅子线程的消息,子线程之间的通信,由主线程来控制。(大概错了吧,应该是进程)面试官:好吧,性能优化继续往下说我:减少HTTP请求数量还可以把图标合并在同一张图片里面,使用的时候用background-position去控制。然后首屏的时候图片使用懒加载的形式,尽量在需要显示的时候在加载它,当然占位符和图片尽量指定宽度和高度,避免图片加载完之后替换图片浏览器会进行回流。面试官:图片懒加载怎么实现我:监听浏览器的滚动事件,结合clientHeight、offsetHeight、scrollTop、scrollHeight等等变量计算当前图片是否在可视区域,如果在,则替换src加载图片,当然这个滚动事件要主要节流。面试官:怎么判断图片是否加载完成我:使用onload事件。面试官:好吧,你继续往下说。我:性能优化的话,还可以合理的利用缓存,尽量把CSS和JS文件使用外链的形式,虽然使用内联的CSS和JS在空缓存的时候更快,因为内联的样式和脚本不需要发送HTTP请求,但是为了尽量发挥浏览器的缓存功能,尽量使用外链形式。我:然后尽量把CSS放在头部,JS放在底部。面试官:假如现在页面里放在head的css文件下载速度很慢,页面会出现什么情况?我:大概页面会等待这个CSS的下载,这个时候页面是白屏状态,然后这个CSS资源会有一个超时时间,假如超过了这个超时时间,这个资源相当于会下载失败,浏览器会直接跳过这个CSS资源,根据已有的CSS资源生成CSS规则树,进而生成Render树,然后渲染页面。面试官:假如我现在在页面动态添加了一个CSS文件,页面一定会回流吗?我:只要假如的CSS影响到了页面的结构,那么浏览器就会回流。面试官:例如页面这个CSS文件中有translate3d呢?我:其实我感觉它不会回流,因为translate3d只是变换了自己的位置,不会影响其他元素的位置,但是实际上是会造成回流的。面试官:那假如我在页面里面加了一个<div style=“position:absolute;width:0;hegiht:0”></div>呢,会回流吗我:不会,因为没有影响页面结构的变化。面试官:好吧,那你继续往下说我:性能优化,尽量使用CDN。面试官:CDN的原理是啥?我:首先,浏览器会先请求CDN域名,CDN域名服务器会给浏览器返回指定域名的CNAME,浏览器在对这些CNAME进行解析,得到CDN缓存服务器的IP地址,浏览器在去请求缓存服务器,CDN缓存服务器根据内部专用的DNS解析得到实际IP,然后缓存服务器会向实际IP地址请求资源,缓存服务器得到资源后进行本地保存和返回给浏览器客户端。面试官:你来实现以下刚刚说的节流函数吧。。。当时有点不记得什么是防抖,什么节流,把函数写成了防抖。(这个时候有一个人走进了会议室,好像是一面小哥)var debounce = function(fn, delay, context) { let timer = null; return function() { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { const arg = Array.prototype.slice.call(arguments); fn.apply(context, arg); }, delay) }}// 测试部分var run = function(text) { console.log(text);}run = debounce(run, 200);run(‘run1’);run(‘run2’);setTimeout(() => { run(‘run3’);}, 201)面试官:我这边没有什么问题了,你还有什么要补充的吗?我:那我把性能优化这个问题说完?面试官:可以。然后我开始描述使用webpack使用进行减少js文件的体积,可以使用babel-plugin-import、babel-plugin-component、webpack.ContextReplacementPlugin、webpack.IgnorePlugin…面试官:这个我知道。你还有什么问题吗?(大概是想结束面试了吧,不想让我往下说了)我:巴拉巴拉。。。问了很多关于有赞公司的问题,比如公司有多少层楼啊、公司主要技术栈啊、公司主要做2B还是2C的啊,公司有多少前端的啊(本人可能还是太啰嗦)最后问了一个问题,问了一下面试官本次便是的评价是啥,面试官只回了一句,还好吧。然后面试到此结束了,全称大概一个多小时。面试结束后,面试官送我到电梯口。。。可以说楼层是真的高,上楼和下楼都需要等很久的电梯。。。到了外面之后,下着大雨,落汤鸡似的又打了个滴滴回家,结束了当天的面试之旅。最后直到昨天,收到了有赞的面试结果回复邮件,告知没有合适的职位(有赞还是挺不错的,没通过面试还通知一下),心里虽然有点不甘,但是想想确实可能是自己不够优秀,或者是自己面得不是很好,或者是自己的能力跟公司的职位不太匹配。在上一篇面试总结中一年半经验,百度、有赞、阿里面试总结,部分同学关心最后面试结果情况,情况是已经有幸的收到了百度的offer,蚂蚁金服的一面面试已经过一周了,不知道是因为流程太长还是一面被挂了。这个时候一想,其实面试还是有很大的运气成分在里面,正好公司需要,正好你又合适,这个时候就很幸运。

November 24, 2018 · 1 min · jiezi

墙裂推荐:搜云库技术团队,整理一年的技术干货

今天整理了一下近大半年以来的一些文章,和我的预期一样,很多文章我都忘记自己曾经写过了,这个记录的过程让我也有了新的理解。希望大家,收藏,点赞,加转发。面试必备面试必备:深入Spring MVC DispatchServlet 源码分析面试必备:一文读懂Spring AOP面向切面编程面试必备:理解JWT鉴权的应用场景及使用建议面试必备:浅谈偏向锁、轻量级锁、重量级锁面试必备:一致性哈希算法的理解与实践面试必备:深入理解为什么要设计幂等性的服务面试必备:面试官最爱的volatile关键字面试必备:Rabbitmq延迟队列实现定时任务面试必备:HashMap源码解析JDK8面试必备:Dubbo服务消费者调用过程面试必备:Dubbo服务提供者发布过程面试必备:Dubbo中暴露服务的过程解析面试必备:Java中transient关键字的作用面试必备:JavaSQL注入危害这么大,该如何来防止呢?面试必备:Java虚拟机运行时数据区面试必备:Redis之数据持久化RDB与AOF面试必备:Redis持久化RDB和AOF优缺点是什么面试必备:Redis集群下的RedLock算法(真分布式锁) 实践面试必备:Redis服务器被攻击后该如何安全加固面试必备:Zookeeper的Leader选举过程面试必备:ZooKeeper和CAP理论及一致性原则面试必备:MySQL从删库到恢复,还用跑路吗?面试必备:MySQL/InnoDB中,乐观锁、悲观锁、共享锁、排它锁、行锁、表锁、死锁概念的理解面试必备:MySQL常用30种SQL查询语句优化方法面试必备:MySQL InnoDB B+树索引和哈希索引的区别? MongoDB 为什么使用B-树?面试必备:常用的分布式事务解决方案介绍有多少种?面试必备:对称加密,非对称加密,公钥和私钥面试必备:如何加密传输和存储用户密码Java并发Java并发:接口限流算法:漏桶算法&令牌桶算法Java并发:深入浅出AQS之共享锁模式源码分析Java并发:深入浅出AQS之独占锁模式源码分析Java并发:了解无锁CAS就从源码分析Java并发:分布式应用限流实践Java并发:Semaphore信号量源码分析Java并发:CAS源码分析面试题面试题:百度、腾讯、阿里、谷歌 面试题视频详解合集面试题:2018最新JAVA算法/数据结构面试题和答案面试题:70道Spring面试题与答案面试题:2018最新Redis面试题与答案面试题:50道多线程面试题与答案(二)面试题:50道多线程面试题与答案(一)面试题:Spring常见的一些面试题与答案面试题:300道Java面试题与答案面试题:40道常见面试题与答案面试题:20道数据库面试题与答案架构架构:应用消息中间件设计可以解决哪些实际问题?架构:搭建大众点评CAT实时应用监控平台架构:如何实现一个TCC分布式事务框架的一点思考架构:Dapper大规模分布式系统的跟踪系统架构:Dubbo 整合 Pinpoint 做分布式服务请求跟踪架构:理解大型分布式架构的演进历史、技术原理、最佳实践架构:解密 Redis 助力双十一背后电商秒杀系统架构:瞬间点击量过万,Redis热点Key的5种解决方案架构:通过案例读懂 RESTful 架构风格架构:一文读懂Apache Flink架构及特性分析架构:大数据推荐系统实时架构和离线架构架构:携程:日处理20亿数据,实时用户行为架构实践架构:什么是微服务架构架构:有赞服务化架构演进架构:微服务架构下静态数据通用缓存机制架构:小型系统如何“微服务”开发架构:Oracle推出开源轻量级 Java 微服务框架 Helidon搭建搭建:网站配置免费的HTTS证书搭建:Apache RocketMQ 单机环境搭建:MongoDB分片(sharding) / 分区 / 集群环境搭建:MongoDB 的安装与详细使用(二)搭建:MongoDB 的安装与详细使用(一)搭建:SolrCloud 集群服务搭建:Solr 单机服务搭建:RabbitMQ 3.6 单机服务搭建:RabbitMQ 3.6 集群服务搭建:离线部署 CDH 5.12.1 及部署Hadoop集群服务搭建:Mycat 读写分离数据库分库分表中间件及简单使用DockerDocker Compose 1.18.0 之服务编排详解Docker 部署 SpringBoot 项目整合 Redis 镜像做访问计数DemoDocker Registry企业级私有镜像仓库Harbor管理WEB UI, 可能是最详细的部署Docker Registry Server 搭建,配置免费HTTPS证书,及拥有权限认证、TLS 的私有仓库Docker Hub 仓库使用,及搭建 Docker RegistryDocker 容器常用操作命令讲解Docker 安装在之初窥 Dockerfile 部署 NginxSpring CloudSpring Cloud(十一)高可用的分布式配置中心 Spring Cloud Bus 消息总线集成(RabbitMQ)Spring Cloud(十)高可用的分布式配置中心 Spring Cloud Config 中使用 RefreshSpring Cloud(九)高可用的分布式配置中心 Spring Cloud Config 集成 Eureka 服务Spring Cloud(八)高可用的分布式配置中心 Spring Cloud ConfigSpring Cloud(七)服务网关 Zuul Filter 使用Spring Cloud(六)服务网关 zuul 快速入门Spring Cloud(五)断路器监控(Hystrix Dashboard)Spring Cloud(四) 服务提供者 Eureka + 服务消费者 FeignSpring Cloud(三) 服务提供者 Eureka + 服务消费者(rest + Ribbon)Spring Cloud(二)Consul 服务治理实现Spring Cloud(一)服务的注册与发现(Eureka)Spring BootSpring Boot 中使用 RabbitMQSpring Boot 中使用 RocketMQSpring Boot 中使用 SolrCloudSpring Boot 中使用 MongoDB 增删改查Spring Boot 中使用 Dubbo 详解Spring Boot 中使用 LogBack 配置Spring Boot 中使用 Docker镜像push到DockerHub上Spring Boot 写的一个java开源图床其他的震惊!面对他人提问某著名程序员居然这样回复爆出惊天内幕!这就是java的优雅停机如何招聘完美的以太坊开发者ElasticSearch优化会员列表搜索软件做异常测试?必知的22个测试点总结!还没用上 JDK 11吧,JDK 12 早期访问构建版使用Mybatis 一级缓存清理无效引起的源码走读程序猿的十二条自我修养,你有吗?Twitter的分布式雪花算法 SnowFlake 每秒自增生成26个万个可排序的ID (Java版)Java 10 新特性解密,引入类型推断机制,2018 年 3 月 20 日发布 ...

November 23, 2018 · 1 min · jiezi

一道面试题引起的思考

今天在认真干(划)活(水)的时候,看到群里有人发了一道头条的面试题,就顺便看了一下,发现挺有意思的,就决定分享给大家,并且给出我的解决方案和思考过程。题目如下:实现一个get函数,使得下面的调用可以输出正确的结果const obj = { selector: { to: { toutiao: “FE Coder”} }, target: [1, 2, { name: ‘byted’}];get(obj, ‘selector.to.toutiao’, ’target[0]’, ’target[2].name’);// [ ‘FE Coder’, 1, ‘byted’]乍眼一看,这不就是实现一个lodash.get方法吗?看上去好像很简单。所以我就开始写了第一个版本。思想其实很简单,遍历传进来的参数,使用split将每一个参数分隔开,然后遍历取值,最终返回结果。function get(data, …args) { return args.map((item) => { const paths = item.split(’.’); let res = data; paths.map(path => res = res[path]); return res; })}一运行,果不其然,报错了。后来仔细看了一下提供的测试代码,发现居然有target[0]这种东西。。居然还带了个数组索引。冷静分析一下,对于后面带了个索引的类型,比如’target[0]’,我们肯定是要特殊对待的。所以,我们首先得先识别到这种特殊的类型,然后再对它进行额外处理。这个时候,很快的就可以想到使用正则表达式来做这个事情。为什么呢?因为像这种带有索引的类型,他们都有一个特色,就是有固定的格式:[num],那么我们只需要能构造出可以匹配这种固定格式的正则,就可以解决这个问题。对于这种格式,不难想到可以用这个正则表达式来做判断:/[[0-9]+]/gi,可是我们还需要将匹配值取出来。这个时候查了下正则表达式的文档(文档点击这里),发现有一个match方法,可以返回匹配成功的结果。那么就让我们来做个测试:const reg = /[[0-9]+]/gi;const str = “target[123123]";const str1 = “target[]“if (reg.test(str)) { console.log(’test success’);}if (!reg.test(str1)) { console.log(’test fail’);}const matchResult = str.match(reg);console.log(matchResult); // ["[123123]"]诶,我们现在已经找到了解决这种问题的方法,那让我们赶紧来继续改进下代码。function get(data, …args) { const reg = /[[0-9]+]/gi; return args.map((item) => { const paths = item.split(’.’); let res = data; paths.map((path) => { if (reg.test(path)) { const match = path.match(reg)[0]; // 将target[0]里的target储存到cmd里 const cmd = path.replace(match, ‘’); // 获取数组索引 const arrIndex = match.replace(/[[]]/gi, ‘’); res = res[cmd][arrIndex]; } else { res = res[path]; } }); return res; });}const obj = { selector: { to: { toutiao: “FE Coder”} }, target: [1, 2, { name: ‘byted’}]};console.log(get(obj, ‘selector.to.toutiao’, ’target[0]’, ’target[2].name’));写完赶紧运行一下,完美,输出了正确的结果了。那么到这里就结束了?改进可是总感觉有点不妥,感觉事情没有那么简单。一般来说,面试题除了考验你解决问题的能力之外,可能还考验着你思考问题的全面性、严谨性。像上面那种写法,如果用户传入了一个不存在的path链或者一些其他特殊情况,就可能导致整个程序crash掉。想下lodash.get调用方式,即使你传入了错误的path,他也可以帮你做处理,并且返回一个undefined。因此,我们还需要完善这个方法。function get(data, …args) { const reg = /[[0-9]+]/gi; return args.map((item) => { const paths = item.split(’.’); let res = data; paths.map(path => { try { if (reg.test(path)) { const match = path.match(reg)[0]; const cmd = path.replace(match, ‘’); const arrIndex = match.replace(/[[]]/gi, ‘’); res = res[cmd][arrIndex]; } else { res = res[path]; } } catch (err) { console.error(err); res = undefined; } }); return res; });}在这里,我们对每一个path的处理进行了try catch处理。若出错了,则返回undefined。哇,这样看起来就比较稳了。那么,有没有别的解决方法呢?群里有一个大佬提出了一种更简单也很取巧的解决方案,就是通过构建一个Function解决这个问题(Function的详细介绍点击这里)。由于代码很简单,我就直接贴出来了:function get(data, …args) { const res = JSON.stringify(data); return args.map((item) => (new Function(try {return ${res}.${item} } catch(e) {}))());}const obj = { selector: { to: { toutiao: “FE Coder”} }, target: [1, 2, { name: ‘byted’}]};console.log(get(obj, ‘selector.to.toutiao’, ’target[0]’, ’target[2].name’, ‘asd’));看完之后,就两个字,牛逼。这种方法我承认一开始我确实没想到,确实是很奇技淫巧。不过仔细思考了下,其实很多框架都用到了这个奇技淫巧。比如说vue里,就使用new Function的方式来动态创建函数,解决执行动态生成的代码的问题。再比如说,Function.prototype.bind方法里(我写了个类似的bind方法:仓库),也使用了Function来解决一些问题(fn.length丢失问题)。说明这个东西还是挺有用的,得学习了解一波,说不定哪天就用到了。总结学习完之后,最重要就是要总结,只有总结下来了,知识才是你自己的。那么我来总结下文章想表达的内容:对于具有固定格式的字符串,可以考虑使用正则表达式来识别和匹配。实现一个功能的时候,不要只考虑正常情况,要多考虑一些非正常情况,比如输入格式不对、用户不按套路来或者因为一些奇奇怪怪的事情报错。并且能对可预见的非正常情况做一个容错处理。有时候还是可以多学习了解一下一些黑科技(比如Function),说不定哪天就可以用它来解决问题。本文地址在->本人博客地址, 欢迎给个 start 或 follow ...

November 23, 2018 · 2 min · jiezi

2018.11.19秋招末第二波前端实习/校招小结

背景个人背景就读于东北某普通二本院校计算机软件工程专业,现大四,北京实习前端方向,自学,vue技术栈时间背景大概是在11月9日准备好简历开始投递秋招差不多已经结束招聘岗位不多,投递对象为大一些的互联网公司事件背景第一个入职的是好未来的前端实习岗,待遇工作环境都不错,与个人技术比较符合没有多少上手成本,工作比较轻松离职原因主要有两个一个是无法转正,二是技术氛围一般,主要是组内前端团队缺少个有前端经验的领导(本来有一个后来调走),实习3个多月,既然拿不到转正offer就打算换个更好的平台去实习本来有准备十月初去重新投一投,由于因为些原因去上海出差一个多月,所以一直耽误到秋招都结束了简历准备简历继续使用第一波面试的简历为原型,主要加上了工作经历,这点很重要,所以对工作经历着重写了写,由于组内项目是第一次使用vue,并且项目才刚刚开始,所以还是有挺多东西可写也参与了很多东西,只要能说明白怎么写都行大概就是这样面试准备面试准备的很匆忙只有大概一周的时间,没时间也懒得去复习一堆一堆的前端知识这段时间主要放在es6和vue框架的准备上,es6主要看了异步相关的promise,async接触的多用的真是很少,vue用的挺久经验也有一些,主要再全面学习下vue的原理和运行机制,关键点包括 MVVM,运行机制,响应式原理,生命周期,虚拟DOM,模板解析,视图更新等等,学习主线主要是下面两篇关于vue原理源码解析的文章剖析vue.js内部运行机制 https://juejin.im/book/5a3666...vue源码全面解析 https://ustbhuangyi.github.io…对,就只准备这些,前端基础还算可以,css或js相关的话觉得临时发挥的大多数也能说个大概,差不多够用,但这取决于面试官的态度,如果面试官真的就是准备一大堆前端基础知识罗列下来问,现场发挥肯定不够用的还得话时间准备下计算机基础知识,大公司一般都重视这些,也取决于面试官,如果正式校招的话应该挺严格,对于算法,网络之类的基础应该占的挺重,这是我最薄弱的点我知道,但这些不是一朝一夕能准备好的,当然临时多背几个算法应该管点用,毕竟有些公司面试并不会特别专业,只是像走流程似的比如让写个排序,临时了解些常用的也能应付应付因为我还是想找个也用vue技术栈的,没多大上手成本,能轻松点面试邀请秋招结束岗位少了很多,在投递简历的一星期内也开始收到一些面试邀请,把面试安排在第二周周一开始,收到面试邀请有滴滴出行,陌陌,一点资讯,新浪微博,人人网面试过程滴滴滴滴的牌子够亮,技术也是贼强,是我很想去的,即使不能转正也是一个很不错的实习平台招的这个岗我理解就是缺人手找点实习生帮忙,面试过程比较简单就是聊天,是个蛮不错的面试官很随和,面试过程能跟随面试者去聊,不会生硬的刻意问知识点问的主要是前端的东西,css,js,es6,vue都有,这个岗也是用vue所以关于vue聊的比较多大概内容如下css:几种布局,inline-block问题(3px margin),flex相关,等js:事件冒泡捕获委托,原型链相关,继承闭包,挺多的记不住了,都是比较简单也很经典的问题es6:关键字,promise和async,多个异步同时处理用promise.all(),他与promisere.race()区别,等等也还好都是简单用法vue:遇到的坑,vue各种通信,vuex,路由/请求拦截器,如何实现响应式,和一些使用经验和技巧,我vue用的比较多经验方面有很多可说的,原理基本也都还熟悉,之前也有写过相关的文章或记的笔记,需要讲的点太多的话我就会在回答的最后把之前的文章或笔记拿出来给他看一下(我电脑放在桌子上这些都有准备随时准备给他看)回答的都还不错除了技术问了些学习经历,聊了聊自学是经历的事也还不错,招的这个实习岗就是他来带,最后他表示觉得不错,我问了他现在开发的项目与使用技术和开发流程之后叫来项目组的领导是个后端,开始挺害怕还以为要问很多算法什么的,不过他说一面面的不错不问技术问题,问了问一些学习,工作的细节整个面试过程大概一个小时,最后表示HR会在一周内联系我,当天下午HR给我发来了offer陌陌来到陌陌后感觉挺办公环境挺漂亮,陌陌技术贼强也是我很想去的,这个面的是个实习岗但表示有机会转正,进门后印象比较深的就是前台姑娘态度真的很蛮横,无论是自己员工还是外来人员我强烈建议现在想面陌陌的读一读这篇文章,因为我的经历跟这个基本差不多,面试题一模一样2017web前端校招面试总结 - 陌陌技术保障组进来后首先是写一个笔试题,面试题不多也很简单,一时忘记手机查下就好描述一下标准模式和怪异模式的区别;实现一个左边div宽度固定,右边div宽度自适应的布局;在Array的原型链上实现array.prototype.inArray的方法;<meta name=“viewport” content=“width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0”>介绍一下各属性是什么含义;display: none 和 visibility: hidden的区别,等等面试体验一般,面试官应该是个初级,看起来不太善于沟通,提问方式属于罗列了一大堆问题,挨个提问那种,做的内容应该是移动端并且偏动画3D效果这些,我上家公司是pc端项目,移动端的东西没那么熟了而且动画写的很少开头问了移动端适配问题,移动端的相关标签,flex布局等移动端相关知识css3主要问一些动画的实现,我说了我基本不怎么写动画效果,主要写功能对css3的标签都知道但写动画不熟,但他还是坚持问了我几个动画的实现,还要让我手写js问很多知识点,不难但很杂而且比较考知识点有点脱离正常开发,比如居中布局的所有方式,继承的多个实现方法,之类的吧记不清了,只记得感觉都是需要刻意去背的东西,我提前没有准备回答一般,没有问框架的东西最后他的结论是我前端基础不太好,我个人认为他问的问题绝大多数很少谈实际经验,也不深入原理,考的更多是各种知识点的说白了就是需要背的东西,只能说明这个面试者有没有好好准备好好背这些,并没有太大的意义整个面试过程比较生硬,不太舒服,但面试内容确实比较简单,我用vue开发比较久对于js原生的东西比如事件之类的用的确实很少了,并且3D动画和相关框架没怎么学过,现在用的vue又没有问,所以结果确实不怎么好二面负责人聊了很多学习工作的事,最后表示说一面表现基础一般,等一周内给答复,整个面试过程大概两个多小时一点资讯一点资讯是校招,也是我很相进的一家,进去一面不是技术面,问了很多学习经历,工作经历,之后让手写一些代码,我刻意说我算法不好,感觉基本上就凉一半了,写个类似两个数组比较取重的东西,说下时间复杂度二面是个女面试官,问题主要在css与js上,没有问框架,promise和async使用,原理都问了很多,css让写个两栏同高的布局,flex相关该问的常问的也都问了,原生js也问了很多,因为我开始就已经不抱什么希望了所以回答的都很随意,也都记不清了,感觉是问的是稍深点,问问题颇有种刨根问底的感觉,最后结束说她等一会等结果,不一会来了个hr表示面试结束,可以走了这家记的不多,印象稍深的就是面试官都不会回答面试者的问题,也不会对回答的问题发表看法,问他应该是什么,我说的对不对,正确应该怎么写之类的问题时就是一笑而过,或者说你回去好好看一下就知道了,对这种态度很诧异小结我比较想去的就是这三家公司,收到滴滴的offer后,新浪微博和人人网都没有去了,新浪微博感觉技术偏老而且工资应该是150一天太低了,人人网感觉比较特殊是很老的公司,并且是校招也应该很难,准备不足看JD感觉不合适并且应该也录取不上所以也就没去两拨实习面试我总结其实都差不多,毕竟实习面试大多都很简单的,主要分两种一是面试官比较有经验不会生硬的只问知识点,会结合实际聊些技术上或者业务上的东西,一些遇到的坑,很容易产生共鸣,技术比较强你说什么都能hold住并跟你聊,入职后一般也都是他来带你,这种感觉比较轻松活跃也不用准备很多或者刷很多面试题。第二种面试过程基本上一问一答,面试官会准备好问题,不太会产生共鸣气氛也比较干,可能面试官也没经验就只是从网上搜的面试题,所以面试前还是最好要做足准备的,毕竟你不能挑选面试官,无论怎么最后吃亏的都是自己。

November 21, 2018 · 1 min · jiezi

【备战春招/秋招系列】美团Java面经总结进阶篇 (附详解答案)

<!– MarkdownTOC –>一 消息队列MQ的套路1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处①.通过异步处理提高系统性能②.降低系统耦合性1.2 那么使用消息队列会带来什么问题?考虑过这个问题吗?1.3 介绍一下你知道哪几种消息队列,该如何选择呢?1.4 关于消息队列其他一些常见的问题展望二 谈谈 InnoDB 和 MyIsam 两者的区别2.1 两者的对比2.2 关于两者的总结三 聊聊 Java 中的集合吧!3.1 Arraylist 与 LinkedList 有什么不同?(注意加上从数据结构分析的内容)3.2 HashMap的底层实现① JDK1.8之前② JDK1.8之后3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解3.4 红黑树这么优秀,为何不直接使用红黑树得了?3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别<!– /MarkdownTOC –>该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb…系列文章:【备战春招/秋招系列1】程序员的简历就该这样写【备战春招/秋招系列2】初出茅庐的程序员该如何准备面试?【备战春招/秋招系列3】Java程序员必备书单【备战春招/秋招系列4】美团面经总结基础篇 (附详解答案)这是我总结的美团面经的进阶篇,后面还有终结篇哦!下面只是我从很多份美团面经中总结的在美团面试中一些常见的问题。不同于个人面经,这份面经具有普适性。每次面试必备的自我介绍、项目介绍这些东西,大家可以自己私下好好思考。我在前面的文章中也提到了应该怎么做自我介绍与项目介绍,详情可以查看这篇文章:【备战春招/秋招系列2】初出茅庐的程序员该如何准备面试?。有人私信我让我对美团面试难度做一个评级,我觉得如果有10级的话,美团面试的难度大概在6级左右吧!部分情况可能因人而异了。消息队列/消息中间件应该是Java程序员必备的一个技能了,如果你之前没接触过消息队列的话,建议先去百度一下某某消息队列入门,然后花2个小时就差不多可以学会任何一种消息队列的使用了。如果说仅仅学会使用是万万不够的,在实际生产环境还要考虑消息丢失等等情况。关于消息队列面试相关的问题,推荐大家也可以看一下视频《Java工程师面试突击第1季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可!一 消息队列MQ的套路面试官一般会先问你这个问题,预热一下,看你知道消息队列不,一般在第一面的时候面试官可能只会问消息队列MQ的应用场景/使用消息队列的好处、使用消息队列会带来什么问题、消息队列的技术选型这几个问题,不会太深究下去,在后面的第二轮/第三轮技术面试中可能会深入问一下。1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。①.通过异步处理提高系统性能如上图,在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。通过以上分析我们可以得出消息队列具有很好的削峰作用的功能——即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示:因为用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败。因此使用消息队列进行异步处理之后,需要适当修改业务流程进行配合,比如用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功,以免交易纠纷。这就类似我们平时手机订火车票和电影票。②.降低系统耦合性我们知道模块分布式部署以后聚合方式通常有两种:1.分布式消息队列和2.分布式服务。先来简单说一下分布式服务:目前使用比较多的用来构建SOA(Service Oriented Architecture面向服务体系结构)的分布式服务框架是阿里巴巴开源的Dubbo.如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章:《高性能优秀的服务框架-dubbo介绍》:https://juejin.im/post/5acadeb1f265da2375072f9c再来谈我们的分布式消息队列:我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。我们最常见的事件驱动架构类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示:消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。 从上图可以看到消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计。消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。 备注: 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的,比如在我们的ActiveMQ消息队列中还有点对点工作模式,具体的会在后面的文章给大家详细介绍,这一篇文章主要还是让大家对消息队列有一个更透彻的了解。这个问题一般会在上一个问题问完之后,紧接着被问到。“使用消息队列会带来什么问题?”这个问题要引起重视,一般我们都会考虑使用消息队列会带来的好处而忽略它带来的问题!1.2 那么使用消息队列会带来什么问题?考虑过这个问题吗?系统可用性降低:系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了!系统复杂性提高: 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!一致性问题: 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!了解下面这个问题是为了我们更好的进行技术选型!该部分摘自:《Java工程师面试突击第1季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可!1.3 介绍一下你知道哪几种消息队列,该如何选择呢?特性ActiveMQRabbitMQRocketMQKafaka单机吞吐量万级,吞吐量比RocketMQ和Kafka要低了一个数量级万级,吞吐量比RocketMQ和Kafka要低了一个数量级10万级,RocketMQ也是可以支撑高吞吐的一种MQ10万级别,这是kafka最大的优点,就是吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景topic数量对吞吐量的影响 topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topictopic从几十个到几百个的时候,吞吐量会大幅度下降。所以在同等机器下,kafka尽量保证topic数量不要过多。如果要支撑大规模topic,需要增加更多的机器资源可用性高,基于主从架构实现高可用性高,基于主从架构实现高可用性非常高,分布式架构非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用消息可靠性有较低的概率丢失数据 经过参数优化配置,可以做到0丢失经过参数优化配置,消息可以做到0丢失时效性ms级微秒级,这是rabbitmq的一大特点,延迟是最低的ms级延迟在ms级以内功能支持MQ领域的功能极其完备基于erlang开发,所以并发能力很强,性能极其好,延时很低MQ功能较为完善,还是分布式的,扩展性好功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准优劣势总结非常成熟,功能强大,在业内大量的公司以及项目中都有应用。偶尔会有较低概率丢失消息,而且现在社区以及国内应用都越来越少,官方社区现在对ActiveMQ 5.x维护越来越少,几个月才发布一个版本而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用erlang语言开发,性能极其好,延时很低;吞吐量到万级,MQ功能比较完备而且开源提供的管理界面非常棒,用起来很好用。社区相对比较活跃,几乎每个月都发布几个版本分在国内一些互联网公司近几年用rabbitmq也比较多一些但是问题也是显而易见的,RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。而且erlang开发,国内有几个公司有实力做erlang源码级别的研究和定制?如果说你没这个实力的话,确实偶尔会有一些问题,你很难去看懂源码,你公司对这个东西的掌控很弱,基本职能依赖于开源社区的快速维护和修复bug。而且rabbitmq集群动态扩展会很麻烦,不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码,很难定制和掌控。接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障。日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是ok的,还可以支撑大规模的topic数量,支持复杂MQ业务场景。而且一个很大的优势在于,阿里出品都是java系的,我们可以自己阅读源码,定制自己公司的MQ,可以掌控。社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ挺好的kafka的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时kafka最好是支撑较少的topic数量即可,保证其超高吞吐量。而且kafka唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。这部分内容,我这里不给出答案,大家可以自行根据自己学习的消息队列查阅相关内容,我可能会在后面的文章中介绍到这部分内容。另外,下面这些问题在视频《Java工程师面试突击第1季-中华石杉老师》中都有提到,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可!1.4 关于消息队列其他一些常见的问题展望引入消息队列之后如何保证高可用性如何保证消息不被重复消费呢?如何保证消息的可靠性传输(如何处理消息丢失的问题)?我该怎么保证从消息队列里拿到的数据按顺序执行?如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?如果让你来开发一个消息队列中间件,你会怎么设计架构?二 谈谈 InnoDB 和 MyIsam 两者的区别2.1 两者的对比1) count运算上的区别: 因为MyISAM缓存有表meta-data(行数等),因此在做COUNT(*)时对于一个结构很好的查询是不需要消耗多少资源的。而对于InnoDB来说,则没有这种缓存。2) 是否支持事务和崩溃后的安全恢复: MyISAM 强调的是性能,每次查询具有原子性,其执行数度比InnoDB类型更快,但是不提供事务支持。但是InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。3)是否支持外键: MyISAM不支持,而InnoDB支持。2.2 关于两者的总结MyISAM更适合读密集的表,而InnoDB更适合写密集的的表。 在数据库做主从分离的情况下,经常选择MyISAM作为主库的存储引擎。一般来说,如果需要事务支持,并且有较高的并发读取频率(MyISAM的表锁的粒度太大,所以当该表写并发量较高时,要等待的查询就会很多了),InnoDB是不错的选择。如果你的数据量很大(MyISAM支持压缩特性可以减少磁盘的空间占用),而且不需要支持事务时,MyISAM是最好的选择。三 聊聊 Java 中的集合吧!3.1 Arraylist 与 LinkedList 有什么不同?(注意加上从数据结构分析的内容)1. 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;2. 底层数据结构: Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构(注意双向链表和双向循环链表的区别:);3. 插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e) 方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element) )时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。4. 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index) 方法)。5. 内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。补充内容:RandomAccess接口public interface RandomAccess {}查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。在binarySearch()方法中,它要判断传入的list 是否RamdomAccess的实例,如果是,调用indexedBinarySearch()方法,如果不是,那么调用iteratorBinarySearch()方法 public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) { if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD) return Collections.indexedBinarySearch(list, key); else return Collections.iteratorBinarySearch(list, key); }ArraysList 实现了 RandomAccess 接口, 而 LinkedList 没有实现。为什么呢?我觉得还是和底层数据结构有关!ArraysList 底层是数组,而 LinkedList 底层是链表。数组天然支持随机访问,时间复杂度为 O(1),所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n),所以不支持快速随机访问。,ArraysList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识,并不是说 ArraysList 实现 RandomAccess 接口才具有快速随机访问功能的!下面再总结一下 list 的遍历方式选择:实现了RadmoAcces接口的list,优先选择普通for循环 ,其次foreach,未实现RadmoAcces接口的ist, 优先选择iterator遍历(foreach遍历底层也是通过iterator实现的),大size的数据,千万不要使用普通for循环Java 中的集合这类问题几乎是面试必问的,问到这类问题的时候,HashMap 又是几乎必问的问题,所以大家一定要引起重视!3.2 HashMap的底层实现① JDK1.8之前JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列。HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的时数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。JDK 1.8 HashMap 的 hash 方法源码:JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。 static final int hash(Object key) { int h; // key.hashCode():返回散列值也就是hashcode // ^ :按位异或 // >>>:无符号右移,忽略符号位,空位都以0补齐 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }对比一下 JDK1.7的 HashMap 的 hash 方法源码.static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4);}相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。所谓 “拉链法” 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。② JDK1.8之后相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。问完 HashMap 的底层原理之后,面试官可能就会紧接着问你 HashMap 底层数据结构相关的问题!3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解红黑树特点:每个节点非红即黑;根节点总是黑色的;每个叶子节点都是黑色的空节点(NIL节点);如果节点是红色的,则它的子节点必须是黑色的(反之不一定);从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)红黑树的应用:TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。为什么要用红黑树简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。3.4 红黑树这么优秀,为何不直接使用红黑树得了?说一下自己对于这个问题的看法:我们知道红黑树属于(自)平衡二叉树,但是为了保持“平衡”是需要付出代价的,红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,这费事啊。你说说我们引入红黑树就是为了查找数据快,如果链表长度很短的话,根本不需要引入红黑树的,你引入之后还要付出代价维持它的平衡。但是链表过长就不一样了。至于为什么选 8 这个值呢?通过概率统计所得,这个值是综合查询成本和新增元素成本得出的最好的一个值。3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别HashMap 和 Hashtable 的区别线程是否安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。初始容量大小和每次扩充容量大小的不同 : ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小(HashMap 中的tableSizeFor()方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。HashSet 和 HashMap 区别如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。)你若盛开,清风自来。 欢迎关注我的微信公众号:“Java面试通关手册”,一个有温度的微信公众号。公众号后台回复关键字“1”,可以免费获取一份我精心准备的小礼物哦! ...

November 14, 2018 · 2 min · jiezi

渣渣前端秋招总结

引言Only the Strong Survive.对于自己来说,近两个半月的秋招终于算是结束了,中间向很多类型的公司投了很多简历,有互联网巨头,有银行三大运营商,也有创业型公司,这其中也只通过部分公司的笔试,进入面试阶段。现在秋招结束,经过不断的跌跌撞撞,也终于找到了一家自己非常满意的公司,正式结束秋招,也不会再去参加后续的春招咯。一、秋招经历对于其他大神来说,我的经历就比较平淡,甚至有点水了,秋招期间只拿到弱弱的三个offer而已,除了酷狗音乐以外都不是那些耳熟能详的公司。以下是我参加了笔试以及面试的公司止步于笔试CVTE多益YY虎牙网易游戏迅雷进入面试阶段腾讯科技(一面 ->二面电话面 挂)顺丰科技(一面 挂)论客科技(一面 挂)4399游戏(一面 挂)中信信用卡中心(一面 -> 与百丽冲突 -> 放弃机会)百丽新零售(一面 ->二面 -> 三面 -> offer)品高软件(一面 ->二面 -> 三面 -> offer)酷狗音乐(一面 ->二面 -> 三面 -> offer)中移互联网(一面 ->二面 -> 三面 -> 没消息,估计挂了)工行(收到面试通知 -> 拿到互联网offer -> 放弃机会)其他一些公司(参加了线下线上的笔试 -> 拿到互联网offer -> 放弃机会)二、秋招准备整个秋招来说,我的准备时间都相对比较短,所以也只能通过不断的笔试面试慢慢累积经验。具体时间如下:9月末才正式从实习公司辞职,开始正式进入秋招大军中第一次笔试由CVTE开始,第一面试由腾讯科技开始最后一次面试由中移互联网收尾(2018-11-06)投递简历阶段可以看出我从9月才正式开始准备秋招,相比于所有人来说都很晚了,也已经错过了很多大公司的网申时间了,在这里我说一下我主要的投递简历的途径。首选当然是在各大公司的官网网申,当时基本上是先把所有在广州深圳的大公司都网申一遍了,当然这样效率也相对比较低下了,只有少部分的公司会发笔试通知。其次就是参加各种宣讲会了,由于在大学城读书,所以参加宣讲会也比较方便,不过说真的参加宣讲会是最累的,基本上需要不断的跑宣讲会,下午在广工参加完之后就需要立刻去华工参加下一场了。参加宣讲会比起网上海投有一个好处就是大部分都有现场笔试,如果现场笔试过了,还是有机会参加面试的。最后就是在各种招聘网站上投递简历了,不过这个我其实投的挺少的,我还是会有针对性的选择公司去进行投递。笔试阶段一开始的笔试基本上都是做炮灰的,虽说在实习期间也有抽时间去刷题准备秋招,可是由于实习期间也有很多事情,终究准备的不够充分。后期笔试做的多了,刷题也达到一定量之后,终于有机会进入面试阶段。说到笔试,大概说一下自己的小心得吧,由于我投递的岗位基本上都是前端开发(除了银行会投系统开发以外),基本上大部分公司的前端笔试题都会只考"前端相关"的题目,不过这个"前端相关"可不只是考JS/CSS/HTML,还会考数据结构,操作系统,计算机网络,Linux等知识,只不过占比会少一点而已。很多人(包括我)会觉得作为一个前端开发,数据结构,操作系统,计算机网络和Linux这些知识以后工作的时候基本上都用不到的,可是作为一个准备进入IT行业人来说,不懂这些是绝对不行的,这是最基本的计算机基础,也是最能体现出你大学四年或者研究生三年的一个知识体量,同时也体现出你对于秋招的准备程度。面试阶段关于简历,自我介绍等我就不多说了,网上有很多类似的文章,这里只要谨记一点就足够了,千万不要虚假或者夸大,这样做不会帮助到你什么,只会挖坑给自己。我曾看过我实验室大神的简历,他们的简历十分简单,实习经验和项目经验都是很简单的一句话带过,后面写上几个项目中用过的关键技术术语就可以了,显得好像什么都没有似的。可是一旦他们被问到相关的知识,却可以全面的解释这个知识点,同时扩展到自己熟悉的方面和领域。在面试过程中,面试官会问很多方面的问题,一般这些问题都是针对你的自我介绍和简历的,所以再次强调一定要对你自己写和说的东西十分熟悉才行,不然这后面一旦被面试官抓住不放,就是无比深渊了(我就是经常这样挖坑给自己……)。下面按照我的自我介绍以及项目经历,总结一下面试官常问的问题:技术问题JavaScript相关事件冒泡、事件捕获以及事件委托对闭包的了解对JS原型链的了解跨域的解决方案对于ES6的了解let、const和var的区别Promise的原理和解决了什么问题localStorage、sessionStorage和cookies的区别HTML和CSS相关HTML5和CSS3的优势对CSS3新增的属性了解em和rem的区别,如何使用多行多列布局(左边固定右边自适应,左右两边固定中间自适应)水平垂直居中如何清除浮动inline,block和inline-block的区别对盒子模型的理解display:none 和 visibility:hidden的区别Vue相关对生命周期的了解组件之间的通讯对Vuex的了解Vue中数据双向绑定的原理对组件化和模块化的了解Vue中methods,watch和compted的区别计算机基础相关对算法的了解红黑树、B树和B+树的区别POST和GET的区别Ajax的状态值与状态码从浏览器输入地址到响应的整个过程非技术问题对前端的了解研究生为什么要选择做前端对未来的规划有没有考虑过做全栈实习期间做了什么工作/学习期间会做什么自我驱动和数据驱动总的来说,校招真的很看重基础。就算你做过很多项目,实习经历丰富,也只会更加追问你基础的东西,所以准备秋招主要还是以基础为主,除了前端知识以外还要多准备计算基础相关的知识。三、总结秋招找工作真的好靠运气,我身边有同学很有实力,找不到理想的工作,可是有一些人能力差一点却又找到挺不错的工作。不过其实运气也是实力的一种,你没有足够的实力就算再有运气也没用,所以好好准备秋招吧,千万不要灰心,把每一场的笔试面试当做一场磨炼,就像我一样,刚开始秋招的时候真的每次都被按在地上摩擦,在经过了那么多场的笔试和面试之后,终于也找到了自己理想的工作,所以我相信大家都肯定没问题的,继续加油吧!!!

November 12, 2018 · 1 min · jiezi

软实力的准备——一篇文章教会你面试中的小套路

面试,一个短时间内对某个人做出判断的测试。很多时候,工程师们只准备了相关的技术点,却没有对非技术的软实力部分做准备。而软实力的考察,不仅贯穿整个面试流程中,更在BOSS面和HR面中尤为关键。鉴于当前业界也没有特别契合的攻略文档,仅有有几篇文章还是HR写的,特有此文。在本文中,我们将从环境与心态,相关问题和表达技巧三个方面阐述如何准备面试,以确保在硬实力的OK的情况下,软实力不掉链子。马上开始~1. 面试中的软实力如下图,面试可以分为硬实力(技术层面)和软实力(非技术层面)的考核。在技术面试中,硬实力是基础,但这不是本文的讨论内容。业界也有很多技术面试文档,大家可以选择自己合适的文档来准备。软实力在我看来和硬实力一样重要。具体来说,硬实力是基础,软实力就是硬实力的Buff。没有基础当然不行,但是一个好的软实力,可以让你得到1+1>2的收益。有些同学技术面试通过的很顺利,等到BOSS面和HR面就被涮了,很大的原因就是软实力没有准备好。既然软实力如此重要,那么我们该从哪几个层面准备呢?2. 软实力的准备2.1 环境与心态准备环境和心态是最容易准备,也是最容易被忽略的部分,但是它们真的很重要。对于环境和心态,我认为主要有以下两条:2.1.1 电话面试:安静无打扰,信号好如果是现场面试,地点往往是面试官订的,按时去就可以了。而如果是电话面试,自己找一个安静,无打扰且电话信号好的地方就特别重要。最好的地点是无人打扰的会议室,事先还要先给朋友家人打个电话试试,不然一旦到时候面试官听不清楚,就会大大影响你的面试质量。面试官就会想,连一个地方都找不好,还能干什么。2.1.2 自信、开朗的心态面试前的半个小时就要开始准备面试了,但是不要看书,而是调整心态。从面试官的角度上来看,一个自信、朝气满满、爱笑的面试者在第一印象上就远胜一个拘束,谨慎,严肃的面试者。而一个良好的心态可以帮助你:在自己没有把握的题目上更有想法更有勇气去尝试解决难题甚至让面试官少问几道题这些都远胜于你在一道题目上的得失。要知道面试是一个综合考察的过程,就好像我们100分的考试,一道题目的得失并不会让你不及格,重要的是考前状态一定要调整好。遇到难题不慌,即使解决不了也尝试说出自己的思路;遇到简单的题也要多考虑一下,不膨胀,不因为马虎大意丢分。多笑,多说,不要惜字如金,这样才能留下更好的印象。2.2 相关问题在拥有了良好的环境和心态后,并不是高枕无忧了,面试中还有一些常见问题需要准备。这些问题往往是准备了就能回答的很好,没有准备就回答的很痛苦的,既然这样,为什么不做好准备呢?下面会是一个题目List,以及面对这些题目时我们的回答方式。2.2.1 你最近在看什么书/有没有研究什么新技术提前找一本技术书或者研究一个新的技术,看完且总结重点,保证说时言之有物即可。2.2.2 你对薪资的要求HR老问这个问题,一般来说第一次问时,不需要直接说出期望的薪资,可以这么回答:我相信公司的规范,可以给我符合我职级的工资。当然,现在前端行业都比较坦诚,HR基本上都会继续问你具体金额,这就需要你在面试前通过你的朋友多多了解新公司的行情,然后结合你自己的情况给出一个数值。其实只要和公司想要给你的差距不是太大,都不是问题。面试是双向选择嘛,可以谈的。但是如果是差距太大的话,HR就会认为你不懂行情,这就有点难受了。2.2.3 说一说你最成功/失败的项目按照STAR法则来说,尝试着去讲一个故事。这个故事要有背景,因为这个背景,导致了什么样的问题,你是如何去尝试解决的,最终取得了什么样的成果。需要注意的是,作为一个工程师,我们还要着重关注以下几个点:数据为王:优化之前的数据,优化之后的数据,提升了XX百分比业务导向:这个项目一定要是你日常核心工作中的横向比较:业界是否有其他的解决方案,你的方案比起其他人有什么样的优势,处在一个什么样的位置归纳提升:一个单独的技术点满分是10分,但是总结整理出来的玩法和套路满分可能是100分2.2.4 你觉得你的优势/劣势是什么优势其实大家都会说,多举例子证明即可。劣势的话,比较稳妥的方式是找一个不太重要的劣势,比如说太过认真,变通较少,但是就是会显得不够真诚。另外一个稍微难点的方法就是仔细考虑自己的优劣势,照实说,但是一定要想好要怎么拉回来。举个例子,我最大的毛病就是钻研的不够深入,比如XXX,我也会去看他们的源码,了解他们的基本原理,但是对于更进一步的优化还欠缺这样的思考,我认为我下一步进步的重点就在这方面,blabla…这样说,就代表了其实不是我不深入,只是我认为自己深入的还不够,能给面试官一个更好的印象。Tips: 别照着例子背啊,结合自己的情况描述更佳。要是问你看了什么源码,三言两语被问倒了就惨了。2.2.5 你有什么想问我的吗这是面试官在面试最后百分百会问的一个问题。除了对于岗位职责的问题,强烈建议问的一个问题是:如果我这次面试有幸过了的话,大概多久能收到消息?通过问这个问题,从面试官的反应中大概率可以知道自己这次面试过了与否,特别棒的一个小套路。2.3 表达技巧不管我们准备的有多么充分,总会有准备不到的问题。这个时候直接说不会肯定是最差的答案,这个时候就需要一定的表达技巧了。2.3.1 清晰直接不会就是不会,直接说“这块我了解的不多”或者“这块我确实做得不够好”。不要长篇大论说理由和原因,显得很推卸责任,很可能会让面试官厌烦,得不偿失。2.3.2 反转并言之有物当然了,尽管题目不太会或者不太肯定,如果我们有一些想法,也是可以说的。在清晰直接的表达完自己对这块不熟之后,可以依据自己的看法说出思路。举个例子:问题:你是否优化过Vue或者React的源码?回答:我没有优化过,但是,我觉得这个问题需要分两方面来看。首先,现在这些主流框架的代码已经写得比较好了,优化的空间不大。另外,我看过它们的一些源码,比如说Vue nextTick的部分和React fiber的部分,而且我在的业务代码中也针对React的组件渲染做了XXX的优化,blabla…回答要切合题目的主题,不要反转了之后瞎BB,那样还不如单纯的说不会。另外就是要尝试多引导说话,将面试官的问题纳入到你的节奏上来,一旦能达到这一步,恭喜你,本次面试你一定是过了的。3 总结面试中的技术准备一定是最关键的,但是好的软实力准备可以让你更上一层楼。希望这篇文章能给大家的面试多增加一层BUFF。

November 12, 2018 · 1 min · jiezi

【备战春招/秋招系列】美团面经总结基础篇 (附详解答案)

该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb…【强烈推荐!非广告!】阿里云双11褥羊毛活动:https://m.aliyun.com/act/team… 差不多一折,不过仅限阿里云新人购买,不是新人的朋友自己找方法买哦!系列文章:【备战春招/秋招系列1】程序员的简历就该这样写【备战春招/秋招系列2】初出茅庐的程序员该如何准备面试?【备战春招/秋招系列3】Java程序员必备书单这是我总结的美团面经的基础篇,后面还有进阶和终结篇哦!下面只是我从很多份美团面经中总结的在面试中一些常见的问题。不同于个人面经,这份面经具有普适性。每次面试必备的自我介绍、项目介绍这些东西,大家可以自己私下好好思考。我在前面的文章中也提到了应该怎么做自我介绍与项目介绍,详情可以查看这篇文章:【备战春招/秋招系列2】初出茅庐的程序员该如何准备面试?。1. System.out.println(3 | 9);输出什么?正确答案:11.考察知识点:逻辑运算符与(&和&&)或(|和||)&和&&:共同点:它们都表示运算符的两边都是true时,结果为true;不同点: & 表示在运算时两边都会计算,然后再判断;&&表示先运算符号左边的东西,然后判断是否为true,是true就继续运算右边的然后判断并输出,是false就停下来直接输出不会再运行后面的东西。|和||:共同点:它们都表示运算符的两边任意一边为true,结果为true,两边都不是true,结果就为false;不同点:| 表示两边都会运算,然后再判断结果;|| 表示先运算符号左边的东西,然后判断是否为true,是true就停下来直接输出不会再运行后面的东西,是false就继续运算右边的然后判断并输出。回到本题:3 | 9=0011(二进制) | 1001(二进制)=1011(二进制)=11(十进制)2. 说一下转发(Forward)和重定向(Redirect)的区别转发是服务器行为,重定向是客户端行为。转发(Forword) 通过RequestDispatcher对象的forward(HttpServletRequest request,HttpServletResponse response)方法实现的。RequestDispatcher 可以通过HttpServletRequest 的 getRequestDispatcher()方法获得。例如下面的代码就是跳转到 login_success.jsp 页面。request.getRequestDispatcher(“login_success.jsp”).forward(request, response);重定向(Redirect) 是利用服务器返回的状态吗来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302,则浏览器会到新的网址重新请求该资源。从地址栏显示来说: forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址. redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL.从数据共享来说: forward:转发页面和转发到的页面可以共享request里面的数据. redirect:不能共享数据.从运用地方来说: forward:一般用于用户登陆的时候,根据角色转发到相应的模块. redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等从效率来说: forward:高. redirect:低.3. 在浏览器中输入url地址 ->> 显示主页的过程,整个过程会使用哪些协议图解(图片来源:《图解HTTP》):总体来说分为以下几个过程:DNS解析TCP连接发送HTTP请求服务器处理请求并返回HTTP报文浏览器解析渲染页面连接结束具体可以参考下面这篇文章:https://segmentfault.com/a/11900000068797004. TCP 三次握手和四次挥手为了准确无误地把数据送达目标处,TCP协议采用了三次握手策略。漫画图解:图片来源:《图解HTTP》简单示意图:客户端–发送带有 SYN 标志的数据包–一次握手–服务端服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端为什么要三次握手三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常。第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己接收正常,对方发送正常第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送接收正常所以三次握手就能确认双发收发功能都正常,缺一不可。为什么要传回 SYN接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。传了 SYN,为啥还要传 ACK双方通信无误必须是两者互相发送信息都无误。传了 SYN,证明发送方(主动关闭方)到接收方(被动关闭方)的通道没有问题,但是接收方到发送方的通道还需要 ACK 信号来进行验证。断开一个 TCP 连接则需要“四次挥手”:客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号服务器-关闭与客户端的连接,发送一个FIN给客户端客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1为什么要四次挥手任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。上面讲的比较概括,推荐一篇讲的比较细致的文章:https://blog.csdn.net/qzcsu/article/details/728618915. IP地址与MAC地址的区别参考:https://blog.csdn.net/guoweimelon/article/details/50858597IP地址是指互联网协议地址(Internet Protocol Address)IP Address的缩写。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。MAC 地址又称为物理地址、硬件地址,用来定义网络设备的位置。网卡的物理地址通常是由网卡生产厂家写入网卡的,具有全球唯一性。MAC地址用于在网络中唯一标示一个网卡,一台电脑会有一或多个网卡,每个网卡都需要有一个唯一的MAC地址。6. HTTP请求、响应报文格式HTTP请求报文主要由请求行、请求头部、请求正文3部分组成HTTP响应报文主要由状态行、响应头部、响应正文3部分组成详细内容可以参考:https://blog.csdn.net/a19881029/article/details/140022737. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引?为什么要使用索引?通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。可以大大加快 数据的检索速度(大大减少的检索的数据量), 这也是创建索引的最主要的原因。帮助服务器避免排序和临时表将随机IO变为顺序IO可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。索引这么多优点,为什么不对表中的每一个列创建一个索引呢?当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。索引是如何提高查询速度的?将无序的数据变成相对有序的数据(就像查目录一样)说一下使用索引的注意事项避免 where 子句中对宇段施加函数,这会造成无法命中索引。在使用InnoDB时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。将打算加索引的列设置为 NOT NULL ,否则将导致引擎放弃使用索引而进行全表扫描删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 chema_unused_indexes 视图来查询哪些索引从未被使用在使用 limit offset 查询缓慢时,可以借助索引来提高性能Mysql索引主要使用的哪两种数据结构?哈希索引:对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。BTree索引:Mysql的BTree索引使用的是B树中的B+Tree。但对于主要的两种存储引擎(MyISAM和InnoDB)的实现方式是不同的。更多关于索引的内容可以查看我的这篇文章:【思维导图-索引篇】搞定数据库索引就是这么简单什么是覆盖索引?如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。我们知道在InnoDB存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次,这样就会比较慢。覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?进程与线程的区别是什么?线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。另外,也正是因为共享资源,所以线程中执行时一般都要进行同步和互斥。总的来说,进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程间的几种通信方式说一下?管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。管道分为pipe(无名管道)和fifo(命名管道)两种,有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信。信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。消息队列(message queue):消息队列是由消息组成的链表,存放在内核中 并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。信号(signal):信号是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生。共享内存(shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。套接字(socket):套接口也是一种进程间的通信机制,与其他通信机制不同的是它可以用于不同及其间的进程通信。线程间的几种通信方式知道不?1、锁机制互斥锁:提供了以排它方式阻止数据结构被并发修改的方法。读写锁:允许多个线程同时读共享数据,而对写操作互斥。条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。2、信号量机制:包括无名线程信号量与有名线程信号量3、信号机制:类似于进程间的信号处理。线程间通信的主要目的是用于线程同步,所以线程没有象进程通信中用于数据交换的通信机制。9. 为什么要用单例模式?手写几种线程安全的单例模式?简单来说使用单例模式可以带来下面几个好处:对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。懒汉式(双重检查加锁版本)public class Singleton { //volatile保证,当uniqueInstance变量被初始化成Singleton实例时,多个线程可以正确处理uniqueInstance变量 private volatile static Singleton uniqueInstance; private Singleton() { } public static Singleton getInstance() { //检查实例,如果不存在,就进入同步代码块 if (uniqueInstance == null) { //只有第一次才彻底执行这里的代码 synchronized(Singleton.class) { //进入同步代码块后,再检查一次,如果仍是null,才创建实例 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; }}静态内部类方式静态内部实现的单例是懒加载的且线程安全。只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance(只有第一次使用这个单例的实例的时候才加载,同时不会有线程安全问题)。public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } } 10. 简单介绍一下bean。知道Spring的bean的作用域与生命周期吗?在 Spring 中,那些组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 bean。简单地讲,bean 就是由 IOC 容器初始化、装配及管理的对象,除此之外,bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。Spring中的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢? 例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框架之后,每个Action都是单例的,那么对于Spring托管的单例Service Bean,如何保证其安全呢? Spring的单例是基于BeanFactory也就是Spring容器的,单例Bean在此容器内只有一个,Java的单例是基于 JVM,每个 JVM 内只有一个实例。Spring的bean的生命周期以及更多内容可以查看:一文轻松搞懂Spring中bean的作用域与生命周期11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?事务传播行为事务传播行为(为了解决业务层方法之间互相调用的事务问题):当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:支持当前事务的情况:TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)不支持当前事务的情况:TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。其他情况:TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。隔离级别TransactionDefinition 接口中定义了五个表示隔离级别的常量:TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。12. SpringMVC 原理了解吗?客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射 HandlerMapping 解析请求对应的 Handler-> HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑 -> 处理器返回一个模型视图 ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象->前端控制器 DispatcherServlet 渲染数据(Moder)->将得到视图对象返回给用户关于 SpringMVC 原理更多内容可以查看我的这篇文章:SpringMVC 工作原理详解13. Spring AOP IOC 实现原理过了秋招挺长一段时间了,说实话我自己也忘了如何简要概括 Spring AOP IOC 实现原理,就在网上找了一个较为简洁的答案,下面分享给各位。IOC: 控制反转也叫依赖注入。IOC利用java反射机制,AOP利用代理模式。IOC 概念看似很抽象,但是很容易理解。说简单点就是将对象交给容器管理,你只需要在spring配置文件中配置对应的bean以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你需要调用这些bean的类。AOP: 面向切面编程。(Aspect-Oriented Programming) 。AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理。你若盛开,清风自来。 欢迎关注我的微信公众号:“Java面试通关手册”,一个有温度的微信公众号。公众号后台回复关键字“1”,可以免费获取一份我精心准备的小礼物哦! ...

November 11, 2018 · 2 min · jiezi

【备战春招/秋招系列】Java程序员必备书单

该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb…【强烈推荐!非广告!】阿里云双11褥羊毛活动:https://m.aliyun.com/act/team… 差不多一折,不过仅限阿里云新人购买,不是新人的朋友自己找方法买哦!核心基础知识《图解HTTP》(推荐,豆瓣评分 8.1 , 1.6K+人评价): 讲漫画一样的讲HTTP,很有意思,不会觉得枯燥,大概也涵盖也HTTP常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究HTTP相关知识的话,读这本书的话应该来说就差不多了。《大话数据结构》(推荐,豆瓣评分 7.9 , 1K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。《数据结构与算法分析:C语言描述》(推荐,豆瓣评分 8.9,1.6K+人评价):本书是《Data Structures and Algorithm Analysis in C》一书第2版的简体中译本。原书曾被评为20世纪顶尖的30部计算机著作之一,作者Mark Allen Weiss在数据结构和算法分析方面卓有建树,他的数据结构和算法分析的著作尤其畅销,并受到广泛好评.已被世界500余所大学用作教材。《算法图解》(推荐,豆瓣评分 8.4,0.6K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富,图文并茂,以让人容易理解的方式阐释了算法.读起来比较快,内容不枯燥!《算法 第四版》(推荐,豆瓣评分 9.3,0.4K+人评价):Java语言描述,算法领域经典的参考书,全面介绍了关于算法和数据结构的必备知识,并特别针对排序、搜索、图处理和字符串处理进行了论述。书的内容非常多,可以说是Java程序员的必备书籍之一了。Java相关《Effective java 》(推荐,豆瓣评分 9.0,1.4K+人评价):本书介绍了在Java编程中78条极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对Java平台设计专家所使用的技术的全面描述,揭示了应该做什么,不应该做什么才能产生清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。本书内容全面,结构清晰,讲解详细。可作为技术人员的参考用书。《Head First Java.第二版》(推荐,豆瓣评分 8.7,1.0K+人评价): 可以说是我的Java启蒙书籍了,特别适合新手读当然也适合我们用来温故Java知识点。《Java多线程编程核心技术》: Java多线程入门级书籍还不错,但是说实话,质量不是很高,很快就可以阅读完。《JAVA网络编程 第4版》: 可以系统的学习一下网络的一些概念以及网络编程在Java中的使用。《Java核心技术卷1+卷2》(推荐): 很棒的两本书,建议有点Java基础之后再读,介绍的还是比较深入的,非常推荐。这两本书我一般也会用来巩固知识点,是两本适合放在自己身边的好书。《Java编程思想(第4版)》(推荐,豆瓣评分 9.1,3.2K+人评价):这本书要常读,初学者可以快速概览,中等程序员可以深入看看java,老鸟还可以用之回顾java的体系。这本书之所以厉害,因为它在无形中整合了设计模式,这本书之所以难读,也恰恰在于他对设计模式的整合是无形的。《Java并发编程的艺术》(推荐,豆瓣评分 7.2,0.2K+人评价): 这本书不是很适合作为Java并发入门书籍,需要具备一定的JVM基础。我感觉有些东西讲的还是挺深入的,推荐阅读。《实战Java高并发程序设计》(推荐):豆瓣评分 8.3 ,书的质量没的说,推荐大家好好看一下。《Java程序员修炼之道》: 很杂,我只看了前面几章,不太推荐阅读。《深入理解Java虚拟机(第2版)周志明》(推荐,豆瓣评分 8.9,1.0K+人评价):建议多刷几遍,书中的所有知识点可以通过JAVA运行时区域和JAVA的内存模型与线程两个大模块罗列完全。《Netty实战》(推荐,豆瓣评分 7.8,92人评价):内容很细,如果想学Netty的话,推荐阅读这本书!《从Paxos到Zookeeper》(推荐,豆瓣评分 7.8,0.3K人评价):简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了Paxos和ZAB协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解ZooKeeper,并更好地使用和运维ZooKeeper。JavaWeb相关《深入分析Java Web技术内幕》: 感觉还行,涉及的东西也蛮多。《Spring实战(第4版)》(推荐,豆瓣评分 8.3,0.3K+人评价):不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于Spring的新华字典,只有一些基本概念的介绍和示例,涵盖了Spring的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习Spring,这才刚刚开始”。《Java Web整合开发王者归来》(已过时):当时刚开始学的时候就是开的这本书,基本上是完完整整的看完了。不过,我不是很推荐大家看。这本书比较老了,里面很多东西都已经算是过时了。不过,这本书的一个很大优点是:基础知识点概括全面。《Redis实战》:如果你想了解Redis的一些概念性知识的话,这本书真的非常不错。《Redis设计与实现》(推荐,豆瓣评分 8.5,0.5K+人评价)《深入剖析Tomcat》(推荐,豆瓣评分 8.4,0.2K+人评价):本书深入剖析Tomcat 4和Tomcat 5中的每个组件,并揭示其内部工作原理。通过学习本书,你将可以自行开发Tomcat组件,或者扩展已有的组件。 读完这本书,基本可以摆脱背诵面试题的尴尬。《高性能MySQL》(推荐,豆瓣评分 9.3,0.4K+人评价):mysql 领域的经典之作,拥有广泛的影响力。不但适合数据库管理员(dba)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获。深入理解Nginx(第2版):作者讲的非常细致,注释都写的都很工整,对于 Nginx 的开发人员非常有帮助。优点是细致,缺点是过于细致,到处都是代码片段,缺少一些抽象。《RabbitMQ实战指南》:《RabbitMQ实战指南》从消息中间件的概念和RabbitMQ的历史切入,主要阐述RabbitMQ的安装、使用、配置、管理、运维、原理、扩展等方面的细节。如果你想浅尝RabbitMQ的使用,这本书是你最好的选择;如果你想深入RabbitMQ的原理,这本书也是你最好的选择;总之,如果你想玩转RabbitMQ,这本书一定是最值得看的书之一《Spring Cloud微服务实战》:从时下流行的微服务架构概念出发,详细介绍了Spring Cloud针对微服务架构中几大核心要素的解决方案和基础组件。对于各个组件的介绍,《Spring Cloud微服务实战》主要以示例与源码结合的方式来帮助读者更好地理解这些组件的使用方法以及运行原理。同时,在介绍的过程中,还包含了作者在实践中所遇到的一些问题和解决思路,可供读者在实践中作为参考。《第一本Docker书》:Docker入门书籍!操作系统《鸟哥的Linux私房菜》(推荐,,豆瓣评分 9.1,0.3K+人评价):本书是最具知名度的Linux入门书《鸟哥的Linux私房菜基础学习篇》的最新版,全面而详细地介绍了Linux操作系统。全书分为5个部分:第一部分着重说明Linux的起源及功能,如何规划和安装Linux主机;第二部分介绍Linux的文件系统、文件、目录与磁盘的管理;第三部分介绍文字模式接口 shell和管理系统的好帮手shell脚本,另外还介绍了文字编辑器vi和vim的使用方法;第四部分介绍了对于系统安全非常重要的Linux账号的管理,以及主机系统与程序的管理,如查看进程、任务分配和作业管理;第五部分介绍了系统管理员(root)的管理事项,如了解系统运行状况、系统服务,针对登录文件进行解析,对系统进行备份以及核心的管理等。架构相关《大型网站技术架构:核心原理与案例分析+李智慧》(推荐):这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java面试通关手册”回复“大型网站技术架构”即可领取思维导图。《亿级流量网站架构核心技术》(推荐):一书总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。本书分为四部分:概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术,让读者看后能快速运用到实践项目中。《架构解密从分布式到微服务(Leaderus著)》:很一般的书籍,我就是当做课后图书来阅读的。代码优化《重构_改善既有代码的设计》(推荐):豆瓣 9.1 分,重构书籍的开山鼻祖。课外书籍《追风筝的人》(推荐)《穆斯林的葬礼》 (推荐)《三体》 (推荐)《活着——余华》 (推荐)你若盛开,清风自来。 欢迎关注我的微信公众号:“Java面试通关手册”,一个有温度的微信公众号。公众号后台回复关键字“1”,可以免费获取一份我精心准备的小礼物哦!

November 11, 2018 · 1 min · jiezi

10月前端面试题目汇总

背景上家公司经营问题,无奈导致技术团队解散,又再一次奔赴找工作的大潮。谨以此文记录一下面试过程中遇到的问题。(不定时更新)问题详情基础知识CSS 栅格系统的实现原理?什么是 html5,它与 html4 的区别?写出三种及以上的垂直水平居中的方法输入url 到页面渲染的整个过程是怎样的?什么是面向对象?其三大特性是什么?浏览器相关浏览器垃圾回收机制是什么?什么是内存泄漏,有哪些情况?浏览器 Event Loop 机制?new Promise((res,rej) => { console.log(1) setTimeout(()=> { console.log(4) }, 0) res(3)}).then(x => {console.log(x)})console.log(2)// 输出顺序?浏览器的缓存机制?put delete 请求参数与 get post 请求有何不同?请求报文,相应报文的组成有哪些?什么是 websocket ?什么是 web worker ?什么是 PWA ?Js 相关闭包的理解继承的几种实现方式Jsonp 的实现原理用正则获取一段字符串中所有的img标签(需要考虑alt=“内部含有 img 标签"这个情况导致的错误)谈谈你对 es6 的 map 和 set 的理解? weakSet weakmap 与之相比有何区别?你用 js 如何实现 async 和 await ?第三方库webpack 有哪些重要特性?有何优化使用经验Vue 和 React 的区别?实现双向绑定有哪些方式谈谈对 Vue 动态组件和函数组件的理解。Vue 自定义指令有哪些生命周期?Vue 的 diff 算法?Vuex 的事件流?如何设计一个 store ?Vue 的 computed 与 watch 的使用区别?Vue 中我设置一个 for 循环 10 次修改 data 中的值?页面会渲染10次还是一次?从性能上考虑应该是渲染一次,请问vue底层是如何实现渲染一次的?Vue router 的实现原理?功能相关百度如何统计用户对页面每个 a 标签的点击行为?当一个表格有上万条数据时,如何优化使其页面不卡顿?websocket 连接突发失效有何解救方案?单页面应用首页渲染白屏如何解决?单页面商城首页上拉不断加载商品数据,当数据量过大时会造成移动端页面卡顿,如何优化而不失功能完整?前端性能优化?web 攻击有哪些?前端如何提高 web 安全性?奇葩题需要您画两幅画,正面有房子和泳池,背面有树。我们在马路上看到的井盖是圆是方,为什么?如何你是一个铅笔高的人掉进了搅拌机中,如何逃出来?昨天我早上8点从山脚爬山,晚上8点到山顶。第二天我早上8点从山顶下来,晚上8点到山脚。问有没有这样一个时刻,昨天和今天我站在了同一个位置?以上是我遇到的一些前端面试题,分享给大家,共同进步,希望离职的人儿早日找到一份好工作!!我写了一本前端经验小书,也分享给大家。Github and 浏览网址 ...

November 9, 2018 · 1 min · jiezi

【备战春招/秋招系列】初出茅庐的程序员该如何准备面试?

这是【备战春招/秋招系列】的第二篇文章,主要是简单地介绍如何去准备面试。该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb…【强烈推荐!非广告!】阿里云双11褥羊毛活动:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn差不多一折,不过仅限阿里云新人购买,不是新人的朋友自己找方法买哦!不论是校招还是社招都避免不了各种面试、笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的,我这个“有章可循”说的意思只是说应对技术面试是可以提前准备。 我其实特别不喜欢那种临近考试就提前背啊记啊各种题的行为,非常反对!我觉得这种方法特别极端,而且在稍有一点经验的面试官面前是根本没有用的。建议大家还是一步一个脚印踏踏实实地走。1 如何获取大厂面试机会?在讲如何获取大厂面试机会之前,先来给大家科普/对比一下两个校招非常常见的概念——春招和秋招。招聘人数 :秋招多于春招 ;招聘时间 : 秋招一般7月左右开始,大概一直持续到10月底。<font color=“red”>但是大厂(如BAT)都会早开始早结束,所以一定要把握好时间。</font>春招最佳时间为3月,次佳时间为4月,进入5月基本就不会再有春招了(金三银四)。应聘难度 :秋招略大于春招;招聘公司: 秋招数量多,而春招数量较少,一般为秋招的补充。综上,一般来说,秋招的含金量明显是高于春招的。下面我就说一下我自己知道的一些方法,不过应该也涵盖了大部分获取面试机会的方法。关注大厂官网,随时投递简历(走流程的网申);线下参加宣讲会,直接投递简历;找到师兄师姐/认识的人,帮忙内推(能够让你避开网申简历筛选,笔试筛选,还是挺不错的,不过也还是需要你的简历够棒);博客发文被看中/Github优秀开源项目作者,大厂内部人员邀请你面试;求职类网站投递简历(不是太推荐,适合海投);除了这些方法,我也遇到过这样的经历:有些大公司的一些部门可能暂时没招够人,然后如果你的亲戚或者朋友刚好在这个公司,而你正好又在寻求offer,那么面试机会基本上是有了,而且这种面试的难度好像一般还普遍比其他正规面试低很多。2 面试前的准备2.1 准备自己的自我介绍从HR面、技术面到高管面/部门主管面,面试官一般会让你先自我介绍一下,所以好好准备自己的自我介绍真的非常重要。网上一般建议的是准备好两份自我介绍:一份对hr说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节,项目经验,经历那些就一语带过。我这里简单分享一下我自己的自我介绍的一个简单的模板吧:面试官,您好!我叫某某。大学时间我主要利用课外时间学习某某。在校期间参与过一个某某系统的开发,另外,自己学习过程中也写过很多系统比如某某系统。在学习之余,我比较喜欢通过博客整理分享自己所学知识。我现在是某某社区的认证作者,写过某某很不错的文章。另外,我获得过某某奖,我的Github上开源的某个项目已经有多少Star了。2.2 关于着装穿西装、打领带、小皮鞋?NO!NO!NO!这是互联网公司面试又不是去走红毯,所以你只需要穿的简单大方就好,不需要太正式。2.3 随身带上自己的成绩单和简历有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。2.4 如果需要笔试就提前刷一些笔试题平时空闲时间多的可以刷一下笔试题目(牛客网上有很多)。但是不要只刷面试题,不动手code,程序员不是为了考试而存在的。2.5 花时间一些逻辑题面试中发现有些公司都有逻辑题测试环节,并且都把逻辑笔试成绩作为很重要的一个参考。2.6 准备好自己的项目介绍如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:对项目整体设计的一个感受(面试官可能会让你画系统的架构图)在这个项目中你负责了什么、做了什么、担任了什么角色从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。2.7 提前准备技术面试搞清楚自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)2.7 面试之前做好定向复习所谓定向复习就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。举个栗子:在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。3 面试之后复盘如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!你若盛开,清风自来。 欢迎关注我的微信公众号:“Java面试通关手册”,一个有温度的微信公众号。公众号后台回复关键字“1”,可以免费获取一份我精心准备的小礼物哦!

November 5, 2018 · 1 min · jiezi

面试官问:能否模拟实现JS的new操作符

前言用过Vuejs的同学都知道,需要用new操作符来实例化。new Vue({ el: ‘#app’, mounted(){},});那么面试官可能会问是否想过new到底做了什么,怎么模拟实现呢。附上之前写文章写过的一段话:已经有很多模拟实现new操作符的文章,为什么自己还要写一遍呢。学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景。你不一定能看到别人看到的风景,体会到别人的心情。只有自己去登山,才能看到不一样的风景,体会才更加深刻。new 做了什么先看简单例子1:// 例子1function Student(){}var student = new Student();console.log(student); // {}// student 是一个对象。console.log(Object.prototype.toString.call(student)); // [object Object]// 我们知道平时声明对象也可以用new Object(); 只是看起来更复杂// 顺便提一下 new Object(不推荐)和Object()也是一样的效果// 可以猜测内部做了一次判断,用new调用/** if (!(this instanceof Object)) {* return new Object();* }*/var obj = new Object();console.log(obj) // {}console.log(Object.prototype.toString.call(student)); // [object Object]typeof Student === ‘function’ // truetypeof Object === ‘function’ // true从这里例子中,我们可以看出:一个函数用new操作符来调用后,生成了一个全新的对象。而且Student和Object都是函数,只不过Student是我们自定义的,Object是JS本身就内置的。再来看下控制台输出图,感兴趣的读者可以在控制台试试。与new Object() 生成的对象不同的是new Student()生成的对象中间还嵌套了一层__proto__,它的constructor是Student这个函数。// 也就是说:student.constructor === Student;Student.prototype.constructor === Student;小结1:从这个简单例子来看,new操作符做了两件事:创建了一个全新的对象。这个对象会被执行[[Prototype]](也就是__proto__)链接。接下来我们再来看升级版的例子2:// 例子2function Student(name){ console.log(‘赋值前-this’, this); // {} this.name = name; console.log(‘赋值后-this’, this); // {name: ‘轩辕Rowboat’}}var student = new Student(‘轩辕Rowboat’);console.log(student); // {name: ‘轩辕Rowboat’}由此可以看出:这里Student函数中的this指向new Student()生成的对象student。小结2:从这个例子来看,new操作符又做了一件事:生成的新对象会绑定到函数调用的this。接下来继续看升级版例子3:// 例子3function Student(name){ this.name = name; // this.doSth();}Student.prototype.doSth = function() { console.log(this.name);};var student1 = new Student(‘轩辕’);var student2 = new Student(‘Rowboat’);console.log(student1, student1.doSth()); // {name: ‘轩辕’} ‘轩辕’console.log(student2, student2.doSth()); // {name: ‘Rowboat’} ‘Rowboat’student1.proto === Student.prototype; // truestudent2.proto === Student.prototype; // true// proto 是浏览器实现的查看原型方案。// 用ES5 则是:Object.getPrototypeOf(student1) === Student.prototype; // trueObject.getPrototypeOf(student2) === Student.prototype; // true关于JS的原型关系笔者之前看到这张图,觉得很不错,分享给大家。小结3:这个例子3再一次验证了小结1中的第2点。也就是这个对象会被执行[[Prototype]](也就是__proto__)链接。并且通过new Student()创建的每个对象将最终被[[Prototype]]链接到这个Student.protytype对象上。细心的同学可能会发现这三个例子中的函数都没有返回值。那么有返回值会是怎样的情形呢。那么接下来请看例子4// 例子4function Student(name){ this.name = name; // Null(空) null // Undefined(未定义) undefined // Number(数字) 1 // String(字符串)‘1’ // Boolean(布尔) true // Symbol(符号)(第六版新增) symbol // Object(对象) {} // Function(函数) function(){} // Array(数组) [] // Date(日期) new Date() // RegExp(正则表达式)/a/ // Error (错误) new Error() // return /a/;}var student = new Student(‘轩辕Rowboat’);console.log(student); {name: ‘轩辕Rowboat’}笔者测试这七种类型后MDN JavaScript类型,得出的结果是:前面六种基本类型都会正常返回{name: ‘轩辕Rowboat’},后面的Object(包含Functoin, Array, Date, RegExg, Error)都会直接返回这些值。由此得出 小结4:如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象。结合这些小结,整理在一起就是:创建了一个全新的对象。这个对象会被执行[[Prototype]](也就是__proto__)链接。生成的新对象会绑定到函数调用的this。通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上。如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象。new 模拟实现知道了这些现象,我们就可以模拟实现new操作符。直接贴出代码和注释/** * 模拟实现 new 操作符 * @param {Function} ctor [构造函数] * @return {Object|Function|Regex|Date|Error} [返回结果] */function newOperator(ctor){ if(typeof ctor !== ‘function’){ throw ’newOperator function the first param must be a function’; } // ES6 new.target 是指向构造函数 newOperator.target = ctor; // 1.创建一个全新的对象, // 2.并且执行[[Prototype]]链接 // 4.通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上。 var newObj = Object.create(ctor.prototype); // ES5 arguments转成数组 当然也可以用ES6 […arguments], Aarry.from(arguments); var argsArr = [].slice.call(arguments); // 删除ctor构造函数 这个参数 argsArr.shift(); // 3.生成的新对象会绑定到函数调用的this。 // 获取到ctor函数返回结果 var ctorReturnResult = ctor.apply(newObj, argsArr); // 小结4 中这些类型中合并起来只有Object和Function两种类型 typeof null 也是null所以要除去null if(typeof ctorReturnResult === ‘object’ && ctorReturnResult !== null){ return ctorReturnResult; } if(typeof ctorReturnResult === ‘function’){ return ctorReturnResult; } // 5.如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象。 return newObj;}最后用模拟实现的newOperator函数验证下之前的例子3:// 例子3 多加一个参数function Student(name, age){ this.name = name; this.age = age; // this.doSth(); // return Error();}Student.prototype.doSth = function() { console.log(this.name);};var student1 = newOperator(Student, ‘轩辕’, 18);var student2 = newOperator(Student, ‘Rowboat’, 18);// var student1 = new Student(‘轩辕’);// var student2 = new Student(‘Rowboat’);console.log(student1, student1.doSth()); // {name: ‘轩辕’} ‘轩辕’console.log(student2, student2.doSth()); // {name: ‘Rowboat’} ‘Rowboat’student1.proto === Student.prototype; // truestudent2.proto === Student.prototype; // true// proto 是浏览器实现的查看原型方案。// 用ES5 则是:Object.getPrototypeOf(student1) === Student.prototype; // trueObject.getPrototypeOf(student2) === Student.prototype; // true可以看出,很符合new操作符。读者发现有不妥或可改善之处,欢迎指出。回顾这个模拟new函数newOperator实现,最大的功臣当属于Object.create()这个ES5提供的API。Object.create() 用法举例笔者之前整理的一篇文章中也有讲过,可以翻看JavaScript 对象所有API解析MDN Object.create()Object.create(proto, [propertiesObject])方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 它接收两个参数,不过第二个可选参数是属性描述符(不常用,默认是undefined)。对于不支持ES5的浏览器,MDN上提供了ployfill方案。var anotherObject = { name: ‘轩辕Rowboat’};var myObject = Object.create(anotherObject, { age: { value:18, },});// 获得它的原型Object.getPrototypeOf(anotherObject) === Object.prototype; // true 说明anotherObject的原型是Object.prototypeObject.getPrototypeOf(myObject); // {name: “轩辕Rowboat”} // 说明myObject的原型是{name: “轩辕Rowboat”}myObject.hasOwnProperty(’name’); // false; 说明name是原型上的。myObject.hasOwnProperty(‘age’); // true 说明age是自身的myObject.name; // ‘轩辕Rowboat’myObject.age; // 18;到此,文章就基本写完了。感谢读者看到这里。最后总结一下:new做了什么:创建了一个全新的对象。这个对象会被执行[[Prototype]](也就是__proto__)链接。生成的新对象会绑定到函数调用的this。通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上。如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象。怎么模拟实现// 去除了注释function newOperator(ctor){ if(typeof ctor !== ‘function’){ throw ’newOperator function the first param must be a function’; } newOperator.target = ctor; var newObj = Object.create(ctor.prototype); var argsArr = [].slice.call(arguments); argsArr.shift(); var ctorReturnResult = ctor.apply(newObj, argsArr); if(typeof ctorReturnResult === ‘object’ && ctorReturnResult !== null){ return ctorReturnResult; } if(typeof ctorReturnResult === ‘function’){ return ctorReturnResult; } return newObj;}读者发现有不妥或可改善之处,欢迎指出。另外觉得写得不错,可以点个赞,也是对笔者的一种支持。关于作者:常以轩辕Rowboat为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。个人博客segmentfault个人主页掘金个人主页知乎github ...

November 5, 2018 · 3 min · jiezi

【备战春招/秋招系列】程序员的简历就该这样写

该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb…【强烈推荐!非广告!】阿里云双11褥羊毛活动:https://m.aliyun.com/act/team… 差不多一折,不过仅限阿里云新人购买,不是新人的朋友自己找方法买哦!程序员的简历就该这样写1 前言<font color=“red”>一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。</font> 在不夸大自己能力的情况下,写出一份好的简历也是一项很棒的能力。2 为什么说简历很重要?2.1 先从面试前来说假如你是网申,你的简历必然会经过HR的筛选,一张简历HR可能也就花费10秒钟看一下,然后HR就会决定你这一关是Fail还是Pass。假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。另外,就算你通过了筛选,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。所以,简历就像是我们的一个门面一样,它在很大程度上决定了你能否进入到下一轮的面试中。2.2 再从面试中来说我发现大家比较喜欢看面经 ,这点无可厚非,但是大部分面经都没告诉你很多问题都是在特定条件下才问的。举个简单的例子:一般情况下你的简历上注明你会的东西才会被问到(Java、数据结构、网络、算法这些基础是每个人必问的),比如写了你会 redis,那面试官就很大概率会问你 redis 的一些问题。比如:redis的常见数据类型及应用场景、redis是单线程为什么还这么快、 redis 和 memcached 的区别、redis 内存淘汰机制等等。所以,首先,你要明确的一点是:你不会的东西就不要写在简历上。另外,你要考虑你该如何才能让你的亮点在简历中凸显出来,比如:你在某某项目做了什么事情解决了什么问题(只要有项目就一定有要解决的问题)、你的某一个项目里使用了什么技术后整体性能和并发量提升了很多等等。面试和工作是两回事,聪明的人会把面试官往自己擅长的领域领,其他人则被面试官牵着鼻子走。虽说面试和工作是两回事,但是你要想要获得自己满意的 offer ,你自身的实力必须要强。3 下面这几点你必须知道大部分公司的HR都说我们不看重学历(骗你的!),但是如果你的学校不出众的话,很难在一堆简历中脱颖而出,除非你的简历上有特别的亮点,比如:某某大厂的实习经历、获得了某某大赛的奖等等。大部分应届生找工作的硬伤是没有工作经验或实习经历,所以如果你是应届生就不要错过秋招和春招。一旦错过,你后面就极大可能会面临社招,这个时候没有工作经验的你可能就会面临各种碰壁,导致找不到一个好的工作写在简历上的东西一定要慎重,这是面试官大量提问的地方;将自己的项目经历完美的展示出来非常重要。4 必须了解的两大法则①STAR法则(Situation Task Action Result):Situation: 事情是在什么情况下发生;Task:: 你是如何明确你的任务的;Action: 针对这样的情况分析,你采用了什么行动方式;Result: 结果怎样,在这样的情况下你学习到了什么。简而言之,STAR法则,就是一种讲述自己故事的方式,或者说,是一个清晰、条理的作文模板。不管是什么,合理熟练运用此法则,可以轻松的对面试官描述事物的逻辑方式,表现出自己分析阐述问题的清晰性、条理性和逻辑性。下面这段内容摘自百度百科,我觉得写的非常不错:STAR法则,500强面试题回答时的技巧法则,备受面试者成功者和500强HR的推崇。由于这个法则被广泛应用于面试问题的回答,尽管我们还在写简历阶段,但是,写简历时能把面试的问题就想好,会使自己更加主动和自信,做到简历,面试关联性,逻辑性强,不至于在一个月后去面试,却把简历里的东西都忘掉了(更何况有些朋友会稍微夸大简历内容)在我们写简历时,每个人都要写上自己的工作经历,活动经历,想必每一个同学,都会起码花上半天甚至更长的时间去搜寻脑海里所有有关的经历,争取找出最好的东西写在简历上。但是此时,我们要注意了,简历上的任何一个信息点都有可能成为日后面试时的重点提问对象,所以说,不能只管写上让自己感觉最牛的经历就完事了,要想到今后,在面试中,你所写的经历万一被面试官问到,你真的能回答得流利,顺畅,且能通过这段经历,证明自己正是适合这个职位的人吗?②FAB 法则(Feature Advantage Benefit):Feature: 是什么;Advantage: 比别人好在哪些地方;Benefit: 如果雇佣你,招聘方会得到什么好处。简单来说,这个法则主要是让你的面试官知道你的优势、招了你之后对公司有什么帮助。5 项目经历怎么写?简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写:对项目整体设计的一个感受在这个项目中你负责了什么、做了什么、担任了什么角色从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。6 专业技能该怎么写?先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。比如你可以这样写(下面这部分内容摘自我的简历,大家可以根据自己的情况做一些修改和完善):计算机网络、数据结构、算法、操作系统等课内基础知识:掌握Java 基础知识:掌握JVM 虚拟机(Java内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM内存管理):掌握高并发、高可用、高性能系统开发:掌握Struts2、Spring、Hibernate、Ajax、Mybatis、JQuery :掌握SSH 整合、SSM 整合、 SOA 架构:掌握Dubbo: 掌握Zookeeper: 掌握常见消息队列: 掌握Linux:掌握MySQL常见优化手段:掌握Spring Boot +Spring Cloud +Docker:了解Hadoop 生态相关技术中的 HDFS、Storm、MapReduce、Hive、Hbase :了解Python 基础、一些常见第三方库比如OpenCV、wxpy、wordcloud、matplotlib:熟悉7 开源程序员Markdown格式简历模板分享分享一个Github上开源的程序员简历模板。包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板 。Github地址:https://github.com/geekcompany/ResumeSample我的下面这篇文章讲了如何写一份Markdown格式的简历,另外,文中还提到了一种实现 Markdown 格式到PDF、HTML、JPEG这几种格式的转换方法。手把手教你用Markdown写一份高质量的简历8 其他的一些小tips尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。注意排版(不需要花花绿绿的),尽量使用Markdown语法。如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。如果自己的Github比较活跃的话,写上去也会为你加分很多。注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。你若盛开,清风自来。 欢迎关注我的微信公众号:“Java面试通关手册”,一个有温度的微信公众号。公众号后台回复关键字“1”,可以免费获取一份我精心准备的小礼物哦!

November 3, 2018 · 1 min · jiezi

BATJ都爱问的多线程面试题

下面最近发的一些并发编程的文章汇总,通过阅读这些文章大家再看大厂面试中的并发编程问题就没有那么头疼了。今天给大家总结一下,面试中出镜率很高的几个多线程面试题,希望对大家学习和面试都能有所帮助。备注:文中的代码自己实现一遍的话效果会更佳哦!并发编程面试必备:synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比并发编程面试必备:JUC 中的 Atomic 原子类总结并发编程面试必备:AQS 原理以及 AQS 同步组件总结该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb… 【强烈推荐!非广告!】阿里云双11褥羊毛活动:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn 差不多一折,不过仅限阿里云新人购买,不是新人的朋友自己找方法买哦!一 面试中关于 synchronized 关键字的 5 连击1.1 说一说自己对于 synchronized 关键字的了解synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。1.2 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗synchronized关键字最主要的三种使用方式:修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能!下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单利模式的原理呗!”双重校验锁实现对象单例(线程安全)public class Singleton { private volatile static Singleton uniqueInstance; private Singleton() { } public static Singleton getUniqueInstance() { //先判断对象是否已经实例过,没有实例化过才进入加锁代码 if (uniqueInstance == null) { //类对象加锁 synchronized (Singleton.class) { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; }}另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:为 uniqueInstance 分配内存空间初始化 uniqueInstance将 uniqueInstance 指向分配的内存地址但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出先问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。1.3 讲一下 synchronized 关键字的底层原理synchronized 关键字底层原理属于 JVM 层面。① synchronized 同步语句块的情况public class SynchronizedDemo { public void method() { synchronized (this) { System.out.println(“synchronized 代码块”); } }}通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 javac SynchronizedDemo.java 命令生成编译后的 .class 文件,然后执行javap -c -s -v -l SynchronizedDemo.class。从上面我们可以看出:synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。② synchronized 修饰方法的的情况public class SynchronizedDemo2 { public synchronized void method() { System.out.println(“synchronized 方法”); }}synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。1.4 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。关于这几种优化的详细信息可以查看:synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比1.5 谈谈 synchronized和ReenTrantLock 的区别① 两者都是可重入锁两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 APIsynchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。③ ReenTrantLock 比 synchronized 增加了一些高级功能相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。④ 性能已不是选择标准二 面试中关于线程池的 4 连击2.1 讲一下Java内存模型在 JDK1.2 之前,Java的内存模型实现总是从<font color=“red”>主存(即共享内存)读取变量</font>,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存<font color=“red”>本地内存</font>(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成<font color=“red”>数据的不一致</font>。要解决这个问题,就需要把变量声明为<font color=“red”> volatile</font>,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。说白了,<font color=“red”> volatile</font> 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。2.2 说说 synchronized 关键字和 volatile 关键字的区别synchronized关键字和volatile关键字比较volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。三 面试中关于 线程池的 2 连击3.1 为什么要用线程池?线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。 这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处:降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。3.2 实现Runnable接口和Callable接口的区别如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。备注: 工具类Executors可以实现Runnable对象和Callable对象之间的相互转换。(Executors.callable(Runnable task)或Executors.callable(Runnable task,Object resule))。3.3 执行execute()方法和submit()方法的区别是什么呢?1)execute() 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;2)submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。3.4 如何创建线程池《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险**Executors 返回线程池对象的弊端如下:FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。方式一:通过构造方法实现方式二:通过Executor 框架的工具类Executors来实现我们可以创建三种类型的ThreadPoolExecutor:FixedThreadPool : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。SingleThreadExecutor: 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。CachedThreadPool: 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。对应Executors工具类中的方法如图所示:四 面试中关于 Atomic 原子类的 4 连击4.1 介绍一下Atomic 原子类Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。所以,所谓原子类说简单点就是具有原子/原子操作特征的类。并发包 java.util.concurrent 的原子类都存放在java.util.concurrent.atomic下,如下图所示。4.2 JUC 包中的原子类是哪4类?基本类型 使用原子的方式更新基本类型AtomicInteger:整形原子类AtomicLong:长整型原子类AtomicBoolean :布尔型原子类数组类型使用原子的方式更新数组里的某个元素AtomicIntegerArray:整形数组原子类AtomicLongArray:长整形数组原子类AtomicReferenceArray :引用类型数组原子类引用类型AtomicReference:引用类型原子类AtomicStampedRerence:原子更新引用类型里的字段原子类AtomicMarkableReference :原子更新带有标记位的引用类型对象的属性修改类型AtomicIntegerFieldUpdater:原子更新整形字段的更新器AtomicLongFieldUpdater:原子更新长整形字段的更新器AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。4.3 讲讲 AtomicInteger 的使用AtomicInteger 类常用方法public final int get() //获取当前的值public final int getAndSet(int newValue)//获取当前的值,并设置新的值public final int getAndIncrement()//获取当前的值,并自增public final int getAndDecrement() //获取当前的值,并自减public final int getAndAdd(int delta) //获取当前的值,并加上预期的值boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。AtomicInteger 类的使用示例使用 AtomicInteger 之后,不用对 increment() 方法加锁也可以保证线程安全。class AtomicIntegerTest { private AtomicInteger count = new AtomicInteger(); //使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。 public void increment() { count.incrementAndGet(); } public int getCount() { return count.get(); }}4.4 能不能给我简单介绍一下 AtomicInteger 类的原理AtomicInteger 线程安全原理简单分析AtomicInteger 类的部分源码: // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField(“value”)); } catch (Exception ex) { throw new Error(ex); } } private volatile int value;AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。关于 Atomic 原子类这部分更多内容可以查看我的这篇文章:并发编程面试必备:JUC 中的 Atomic 原子类总结五 AQS5.1 AQS 介绍AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。5.2 AQS 原理分析AQS 原理这部分参考了部分博客,在5.2节末尾放了链接。在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加,面试不是背题,大家一定要假如自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。5.2.1 AQS 原理概览AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。看个AQS(AbstractQueuedSynchronizer)原理图:AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。private volatile int state;//共享变量,使用volatile修饰保证线程可见性状态信息通过procted类型的getState,setState,compareAndSetState进行操作//返回同步状态的当前值protected final int getState() { return state;} // 设置同步状态的值protected final void setState(int newState) { state = newState;}//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update);}5.2.2 AQS 对资源的共享方式AQS定义两种资源共享方式Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:公平锁:按照线程在队列中的排队顺序,先到者先拿到锁非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。5.2.3 AQS底层使用了模板方法模式同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。默认情况下,每个方法都抛出 UnsupportedOperationException。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。 以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。推荐两篇 AQS 原理和相关源码分析的文章:http://www.cnblogs.com/watery…https://www.cnblogs.com/cheng...5.3 AQS 组件总结Semaphore(信号量)-允许多个线程同时访问: synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。CountDownLatch (倒计时器): CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。CyclicBarrier(循环栅栏): CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。关于AQS这部分的更多内容可以查看我的这篇文章:并发编程面试必备:AQS 原理以及 AQS 同步组件总结Reference《深入理解 Java 虚拟机》《实战 Java 高并发程序设计》《Java并发编程的艺术》http://www.cnblogs.com/watery…https://www.cnblogs.com/cheng…【强烈推荐!非广告!】阿里云双11褥羊毛活动(10.29-11.12):https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn 。一句话解析该次活动:新用户低至一折购买(1核2g服务器仅8.3/月,比学生机还便宜,真的强烈推荐屯3年)。老用户可以加入我的战队,然后分享自己的链接,可以获得红包和25%的返现,我们的战队目前300位新人,所以可以排进前100,后面可以瓜分百万现金(按拉新人数瓜分现金,拉的越多分的越多!不要自己重新开战队,后面不能参与瓜分现金)。你若盛开,清风自来。 欢迎关注我的微信公众号:“Java面试通关手册”,一个有温度的微信公众号。公众号后台回复关键字“1”,可以免费获取一份我精心准备的小礼物哦! ...

November 2, 2018 · 3 min · jiezi

并发编程面试必备:AQS 原理以及 AQS 同步组件总结

常见问题:AQS 原理?;CountDownLatch和CyclicBarrier了解吗,两者的区别是什么?用过Semaphore吗?本节思维导图:【强烈推荐!非广告!】阿里云双11褥羊毛活动:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn 差不多一折,不过仅限阿里云新人购买,不是新人的朋友自己找方法买哦!1 AQS 简单介绍AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。2 AQS 原理在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加,面试不是背题,大家一定要假如自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。2.1 AQS 原理概览AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。看个AQS(AbstractQueuedSynchronizer)原理图:AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。private volatile int state;//共享变量,使用volatile修饰保证线程可见性状态信息通过procted类型的getState,setState,compareAndSetState进行操作//返回同步状态的当前值protected final int getState() { return state;} // 设置同步状态的值protected final void setState(int newState) { state = newState;}//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update);}2.2 AQS 对资源的共享方式AQS定义两种资源共享方式Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:公平锁:按照线程在队列中的排队顺序,先到者先拿到锁非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在上层已经帮我们实现好了。2.3 AQS底层使用了模板方法模式同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用,下面简单的给大家介绍一下模板方法模式,模板方法模式是一个很容易理解的设计模式之一。模板方法模式是基于”继承“的,主要是为了在不改变模板结构的前提下在子类中重新定义模板中的内容以实现复用代码。举个很简单的例子假如我们要去一个地方的步骤是:购票buyTicket()->安检securityCheck()->乘坐某某工具回家ride()->到达目的地arrive()。我们可能乘坐不同的交通工具回家比如飞机或者火车,所以除了ride()方法,其他方法的实现几乎相同。我们可以定义一个包含了这些方法的抽象类,然后用户根据自己的需要继承该抽象类然后修改 ride()方法。AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。默认情况下,每个方法都抛出 UnsupportedOperationException。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。 以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。推荐两篇 AQS 原理和相关源码分析的文章:http://www.cnblogs.com/watery…https://www.cnblogs.com/cheng...3 Semaphore(信号量)-允许多个线程同时访问synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。示例代码如下:/** * * @author Snailclimb * @date 2018年9月30日 * @Description: 需要一次性拿一个许可的情况 /public class SemaphoreExample1 { // 请求的数量 private static final int threadCount = 550; public static void main(String[] args) throws InterruptedException { // 创建一个具有固定线程数量的线程池对象(如果这里线程池的线程数量给太少的话你会发现执行的很慢) ExecutorService threadPool = Executors.newFixedThreadPool(300); // 一次只能允许执行的线程数量。 final Semaphore semaphore = new Semaphore(20); for (int i = 0; i < threadCount; i++) { final int threadnum = i; threadPool.execute(() -> {// Lambda 表达式的运用 try { semaphore.acquire();// 获取一个许可,所以可运行线程数量为20/1=20 test(threadnum); semaphore.release();// 释放一个许可 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }); } threadPool.shutdown(); System.out.println(“finish”); } public static void test(int threadnum) throws InterruptedException { Thread.sleep(1000);// 模拟请求的耗时操作 System.out.println(“threadnum:” + threadnum); Thread.sleep(1000);// 模拟请求的耗时操作 }}执行 acquire 方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个 release 方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量。 Semaphore经常用于限制获取某种资源的线程数量。当然一次也可以一次拿取和释放多个许可,不过一般没有必要这样做: semaphore.acquire(5);// 获取5个许可,所以可运行线程数量为20/5=4 test(threadnum); semaphore.release(5);// 获取5个许可,所以可运行线程数量为20/5=4除了 acquire方法之外,另一个比较常用的与之对应的方法是tryAcquire方法,该方法如果获取不到许可就立即返回false。Semaphore 有两种模式,公平模式和非公平模式。公平模式: 调用acquire的顺序就是获取许可证的顺序,遵循FIFO;非公平模式: 抢占式的。Semaphore 对应的两个构造方法如下: public Semaphore(int permits) { sync = new NonfairSync(permits); } public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }这两个构造方法,都必须提供许可的数量,第二个构造方法可以指定是公平模式还是非公平模式,默认非公平模式。 由于篇幅问题,如果对 Semaphore 源码感兴趣的朋友可以看下面这篇文章:https://blog.csdn.net/qq_1943…4 CountDownLatch (倒计时器)CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。4.1 CountDownLatch 的两种典型用法①某一线程在开始运行前等待n个线程执行完毕。将 CountDownLatch 的计数器初始化为n :new CountDownLatch(n) ,每当一个任务线程执行完毕,就将计数器减1 countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。②实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 CountDownLatch 对象,将其计数器初始化为 1 :new CountDownLatch(1) ,多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。4.2 CountDownLatch 的使用示例/* * * @author SnailClimb * @date 2018年10月1日 * @Description: CountDownLatch 使用方法示例 /public class CountDownLatchExample1 { // 请求的数量 private static final int threadCount = 550; public static void main(String[] args) throws InterruptedException { // 创建一个具有固定线程数量的线程池对象(如果这里线程池的线程数量给太少的话你会发现执行的很慢) ExecutorService threadPool = Executors.newFixedThreadPool(300); final CountDownLatch countDownLatch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { final int threadnum = i; threadPool.execute(() -> {// Lambda 表达式的运用 try { test(threadnum); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { countDownLatch.countDown();// 表示一个请求已经被完成 } }); } countDownLatch.await(); threadPool.shutdown(); System.out.println(“finish”); } public static void test(int threadnum) throws InterruptedException { Thread.sleep(1000);// 模拟请求的耗时操作 System.out.println(“threadnum:” + threadnum); Thread.sleep(1000);// 模拟请求的耗时操作 }}上面的代码中,我们定义了请求的数量为550,当这550个请求被处理完成之后,才会执行System.out.println(“finish”);。4.3 CountDownLatch 的不足CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。5 CyclicBarrier(循环栅栏)CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。5.1 CyclicBarrier 的应用场景CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。5.2 CyclicBarrier 的使用示例示例1:/* * * @author Snailclimb * @date 2018年10月1日 * @Description: 测试 CyclicBarrier 类中带参数的 await() 方法 /public class CyclicBarrierExample2 { // 请求的数量 private static final int threadCount = 550; // 需要同步的线程数量 private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5); public static void main(String[] args) throws InterruptedException { // 创建线程池 ExecutorService threadPool = Executors.newFixedThreadPool(10); for (int i = 0; i < threadCount; i++) { final int threadNum = i; Thread.sleep(1000); threadPool.execute(() -> { try { test(threadNum); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } }); } threadPool.shutdown(); } public static void test(int threadnum) throws InterruptedException, BrokenBarrierException { System.out.println(“threadnum:” + threadnum + “is ready”); try { cyclicBarrier.await(2000, TimeUnit.MILLISECONDS); } catch (Exception e) { System.out.println("—–CyclicBarrierException——"); } System.out.println(“threadnum:” + threadnum + “is finish”); }}运行结果,如下:threadnum:0is readythreadnum:1is readythreadnum:2is readythreadnum:3is readythreadnum:4is readythreadnum:4is finishthreadnum:0is finishthreadnum:1is finishthreadnum:2is finishthreadnum:3is finishthreadnum:5is readythreadnum:6is readythreadnum:7is readythreadnum:8is readythreadnum:9is readythreadnum:9is finishthreadnum:5is finishthreadnum:8is finishthreadnum:7is finishthreadnum:6is finish……可以看到当线程数量也就是请求数量达到我们定义的 5 个的时候, await方法之后的方法才被执行。 另外,CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties, Runnable barrierAction),用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。示例代码如下:/* * * @author SnailClimb * @date 2018年10月1日 * @Description: 新建 CyclicBarrier 的时候指定一个 Runnable */public class CyclicBarrierExample3 { // 请求的数量 private static final int threadCount = 550; // 需要同步的线程数量 private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> { System.out.println("——当线程数达到之后,优先执行——"); }); public static void main(String[] args) throws InterruptedException { // 创建线程池 ExecutorService threadPool = Executors.newFixedThreadPool(10); for (int i = 0; i < threadCount; i++) { final int threadNum = i; Thread.sleep(1000); threadPool.execute(() -> { try { test(threadNum); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } }); } threadPool.shutdown(); } public static void test(int threadnum) throws InterruptedException, BrokenBarrierException { System.out.println(“threadnum:” + threadnum + “is ready”); cyclicBarrier.await(); System.out.println(“threadnum:” + threadnum + “is finish”); }}运行结果,如下:threadnum:0is readythreadnum:1is readythreadnum:2is readythreadnum:3is readythreadnum:4is ready——当线程数达到之后,优先执行——threadnum:4is finishthreadnum:0is finishthreadnum:2is finishthreadnum:1is finishthreadnum:3is finishthreadnum:5is readythreadnum:6is readythreadnum:7is readythreadnum:8is readythreadnum:9is ready——当线程数达到之后,优先执行——threadnum:9is finishthreadnum:5is finishthreadnum:6is finishthreadnum:8is finishthreadnum:7is finish……5.3 CyclicBarrier和CountDownLatch的区别CountDownLatch是计数器,只能使用一次,而CyclicBarrier的计数器提供reset功能,可以多次使用。但是我不那么认为它们之间的区别仅仅就是这么简单的一点。我们来从jdk作者设计的目的来看,javadoc是这么描述它们的:CountDownLatch: A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.(CountDownLatch: 一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;)CyclicBarrier : A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.(CyclicBarrier : 多个线程互相等待,直到到达同一个同步点,再继续一起执行。)对于CountDownLatch来说,重点是“一个线程(多个线程)等待”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。CyclicBarrier和CountDownLatch的区别这部分内容参考了如下两篇文章:https://blog.csdn.net/u010185…https://blog.csdn.net/tolcf/a...6 ReentrantLock 和 ReentrantReadWriteLockReentrantLock 和 synchronized 的区别在上面已经讲过了这里就不多做讲解。另外,需要注意的是:读写锁 ReentrantReadWriteLock 可以保证多个线程可以同时读,所以在读操作远大于写操作的时候,读写锁就非常有用了。由于篇幅问题,关于 ReentrantLock 和 ReentrantReadWriteLock 详细内容可以查看我的这篇原创文章。ReentrantLock 和 ReentrantReadWriteLock ...

November 2, 2018 · 4 min · jiezi

10.26 酷狗音乐校招前端一面经历

酷狗一面1. 如何实现三栏布局(左右两边固定宽度,中间自适应)?使用flex布局: 父元素设置display: flex,左右两边设置固定宽度,中间设置flex-grow: 12. 如何实现弹窗水平垂直居中?3. ==和 === 的区别===为恒等符:当等号两边的值为相同类型的时候,直接比较等号两边的值,值相同则返回true,若等号两边的值类型不同时直接返回false。==为等值符: 当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较。a、如果一个是null、一个是undefined,那么[相等]。b、如果一个是字符串,一个是数值,把字符串转换成数值再进行比较。c、如果任一值是 true,把它转换成 1 再比较;如果任一值是 false,把它转换成 0 再比较。d、如果一个是对象,另一个是数值或字符串,把对象转换成基础类型的值再比较。对象转换成基础类型,利用它的toString或者valueOf方法。 js核心内置类,会尝试valueOf先于toString;例外的是Date,Date利用的是toString转换。非js核心的对象,令说(比较麻 烦,我也不大懂)e、任何其他组合,都[不相等]。4. 30 == ‘30’ 的过程是怎样的?30为数值类型,而'30’未字符串类型,因此等号两边的数据类型不相等,需要进行转换类型;由于一个是数值,另一个字符串,所以需要将字符串转换成数值再进行比较,即'30’ => 30;这时等号两边同样为数值型数据,即30 == 30,所以返回true5. 以下代码输出的是什么?为什么呢?for (var i=0; i<5; i++) { setTimeout( function timer() { console.log(i); }, 0 );}6. 你有使用过闭包吗?7. 模块化的异步加载怎样做?8. window.onload执行时间?9.图片加载完的时候会执行吗?10. 了解JS继承吗?11. 利用原型链的继承有什么缺点吗?12. 知道如何修改this的指向吗?修改this指向的办法有三种:apply、call和bindapply、call:通过传入需要指向的对象,从而改变this的指向,指向传入的第一个参数;bind:它会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。window.color = ‘red’;var o = { color:‘blue’ };function sayColor(){ console.log(this.color);}var globalSaycolor = sayColor;var objectSaycolor = sayColor.bind(o);globalSaycolor(); // redobjectSaycolor(); // blue补充其实还有一种:new关键字改变this指向因为在new的过程中,其中有一个步骤为将构造函数内部的this指向实例对象,所以通过new关键字也可以改变this的指向。13. apply和call的区别?相同点:可以用来代替另一个对象调用一个方法,将一个函数的对象上下文从初始的上下文改变为由thisObj指定的新对象。不同点:实际上,apply和call的功能是一样的,只是传入的参数列表形式不同。apply:最多只能有两个参数——新this对象和一个数组argArray。如果给该方法传递多个参数,则把参数都写进这个数组里面,当然,即使只有一个参数,也要写进数组里。如果argArray不是一个有效的数组或arguments对象,那么将导致一个TypeError。如果没有提供argArray和thisObj任何一个参数,那么Global对象将被用作thisObj,并且无法被传递任何参数。call:它可以接受多个参数,第一个参数与apply一样,后面则是一串参数列表。这个方法主要用在js对象各方法相互调用的时候,使当前this实例指针保持一致,或者在特殊情况下需要改变this指针。如果没有提供thisObj参数,那么 Global 对象被用作thisObj。14. 有一个按钮是异步生成的,怎样对它进行事件绑定?由于按钮是异步生成的,所以我选择将事件绑定在按钮生成的父元素上,通过事件委托的机制,利用事件冒泡,把事件绑定在父元素上,可以通过判断event.target按钮是否已经生成,从而实现相应的事件。科普补充:事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象;事件捕获则跟事件冒泡相反,事件会从document对象开始发生,直到最具体的元素;15. 跨域有处理过吗?我处理过的跨域有两种情况:一种是在页面中嵌入了一个iframe,因此父子iframe间产生了跨域,要解决这个问题,只需要把document.domain设置成相同的值就可以在两个页面里进行相应的操作了;另外一种情况是用Vue开发涉及到的跨域问题,这个问题只需要修改config文件夹下的index.js中的dev:{}部分中修改proxyTable参数即可,相当于对跨域的url进行了代理,从而可以顺利访问。另外说了一下自己比较熟悉的一种跨域解决方案:JSONPJSONP解决跨域问题的本质其实就是<script> 标签可以请求不同域名下的资源,即 <script> 请求不受浏览器同源策略影响。面试官听到JSONP立刻提出了一个问题:JSONP是否可以支持POST方法?为什么?JSONP只支持GET的请求方法,上面也提到了JSONP原理其实就是利用<script> 标签发送了一个URL给服务器,其实与ajax XmlHttpRequest协议无关了,相当于输入了一个url而已,所以必然只能为GET请求方法。16. POST和GET的区别?17. 浏览器缓存的方式?18. 看你有用过Promise,知道Promise有几种状态?19. 你知道pending状态可以停止吗?20. 那XMLHttpRequest 的pending状态可以停止吗?21. 知道Promise和setTimeout的执行顺序吗?22. vue中生命周期中的钩子函数用过哪些?23. 为什么不把数据放在created函数中?24. 对Vue的数据双向绑定有了解吗?25. 了解重绘和回流吗?页面的加载顺序?26. 如何减少回流、重绘?怎样控制只有一部分回流?27. 了解什么算法?快排?28. 还了解什么排序算法?29. 了解二叉查找树吗?30. 有了解什么后端语言吗?知道面向对象的特性吗?31. 知道数据库连接池吗?32. 未来前端的规划?酷狗二面在面完第一面之后,本来以为终于结束了,没想到迎来的是第二技术面,后面面试官介绍说其实本来是应该两个人同时面我的,因为有一个面试官没空,所以就错开了,就有了"二面"了,其实本质上还是一面而已。1. 自我介绍2. 问了一下笔试的时候不应该错的题3. 理解的HTTP状态码有哪些?4. 正则的题目,对比/^[a-z0-9][a-z]+$/和 /^[a-z0-9][a-z]*$/的区别?5. display:none 和 visibility:hidden的区别?6. CSS选择器的理解,你知道多少选择器?7. CSS3布局,移动端有用过rem吗?布局的话一般怎样布局?8. Flex布局和传统的其他布局有什么优点?9. Flex的居中方式有哪些?其他方式有哪些?10. display设置inline-block的话,多个之间有间隔应该怎样处理? (父节点font-size: 0)11. 更熟悉那方面的技术栈?12. 对自己项目是怎样设计和选型的?13. 有用到vuex吗?14. 组件之间的通讯怎样做到?15. 真的学习前端是什么时候开始?16. 方向是选择全栈还是只做前端?17. 有没有在nodeJS上做过什么?18. 有没有在npm上面做过开源的学习?19. 对毕业之后的学习规划? ...

October 29, 2018 · 1 min · jiezi

【前端面试题】2018 各大公司最近面试题

【前端面试题】2018 各大公司最近面试题阿里使用过的koa2中间件koa-body原理介绍自己写过的中间件有没有涉及到Cluster介绍pm2master挂了的话pm2怎么处理如何和MySQL进行通信React声明周期及自己的理解如何配置React-Router路由的动态加载模块服务端渲染SSR介绍路由的history介绍Redux数据流的流程Redux如何实现多个组件之间的通信,多个组件使用相同状态如何进行管理多个组件之间如何拆分各自的state,每块小的组件有自己的状态,它们之间还有一些公共的状态需要维护,如何思考这块使用过的Redux中间件如何解决跨域的问题常见Http请求头移动端适配1px的问题介绍flex布局其他css方式设置垂直居中居中为什么要使用transform(为什么不使用marginLeft/Top)使用过webpack里面哪些plugin和loaderwebpack里面的插件是怎么实现的dev-server是怎么跑起来项目优化抽取公共文件是怎么配置的项目中如何处理安全问题怎么实现this对象的深拷贝网易介绍redux,主要解决什么问题文件上传如何做断点续传表单可以跨域吗promise、async有什么区别搜索请求如何处理(防抖)搜索请求中文如何请求介绍观察者模式介绍中介者模式观察者和订阅-发布的区别,各自用在哪里介绍react优化介绍http2.0通过什么做到并发请求http1.1时如何复用tcp连接介绍service worker介绍css3中position:stickyredux请求中间件如何处理并发介绍Promise,异常捕获介绍position属性包括CSS3新增浏览器事件流向介绍事件代理以及优缺点React组件中怎么做事件代理React组件事件代理的原理介绍this各种情况前端怎么控制管理路由使用路由时出现问题如何解决React怎么做数据的检查和变化滴滴react-router怎么实现路由切换react-router里的<Link>标签和标签有什么区别标签默认事件禁掉之后做了什么才实现了跳转React层面的性能优化整个前端性能提升大致分几类import { Button } from ‘antd’,打包的时候只打包button,分模块加载,是怎么做到的使用import时,webpack对node_modules里的依赖会做什么JS异步解决方案的发展历程以及优缺点Http报文的请求会有几个部分cookie放哪里,cookie能做的事情和存在的价值cookie和token都存放在header里面,为什么只劫持前者cookie和session有哪些方面的区别React中Dom结构发生变化后内部经历了哪些变化React挂载的时候有3个组件,textComponent、composeComponent、domComponent,区别和关系,Dom结构发生变化时怎么区分data的变化,怎么更新,更新怎么调度,如果更新的时候还有其他任务存在怎么处理key主要是解决哪一类的问题,为什么不建议用索引index(重绘)Redux中异步的请求怎么处理Redux中间件是什么东西,接受几个参数(两端的柯里化函数)柯里化函数两端的参数具体是什么东西中间件是怎么拿到store和action,然后怎么处理state是怎么注入到组件的,从reducer到组件经历了什么样的过程koa中response.send、response.rounded、response.json发生了什么事,浏览器为什么能识别到它是一个json结构或是htmlkoa-bodyparser怎么来解析requestwebpack整个生命周期,loader和plugin有什么区别介绍AST(Abstract Syntax Tree)抽象语法树安卓Activity之间数据是怎么传递的安卓4.0到6.0过程中WebView对js兼容性的变化WebView和原生是如何通信跨域怎么解决,有没有使用过Apache等方案今日头条对async、await的理解,内部原理介绍下Promise,内部实现清除浮动定位问题(绝对定位、相对定位等)从输入URL到页面加载全过程tcp3次握手tcp属于哪一层(1 物理层 -> 2 数据链路层 -> 3 网络层(ip)-> 4 传输层(tcp) -> 5 应用层(http))redux的设计思想接入redux的过程绑定connect的过程connect原理webpack介绍== 和 ===的区别,什么情况下用相等==bind、call、apply的区别动画的了解介绍下原型链(解决的是继承问题吗)对跨域的了解有赞Linux 754 介绍介绍冒泡排序,选择排序,冒泡排序如何优化transform动画和直接使用left、top改变位置有什么优缺点如何判断链表是否有环介绍二叉搜索树的特点介绍暂时性死区ES6中的map和原生的对象有什么区别观察者和发布-订阅的区别react异步渲染的概念,介绍Time Slicing 和 Suspense16.X声明周期的改变16.X中props改变后在哪个生命周期中处理介绍纯函数前端性能优化pureComponent和FunctionComponent区别介绍JSX如何做RN在安卓和IOS端的适配RN为什么能在原生中绘制成原生组件(bundle.js)介绍虚拟DOM如何设计一个localStorage,保证数据的实效性如何设计Promise.all()介绍高阶组件sum(2, 3)实现sum(2)(3)的效果react性能优化两个对象如何比较挖财JS的原型变量作用域链call、apply、bind的区别防抖和节流的区别介绍各种异步方案react生命周期介绍Fiber前端性能优化介绍DOM树对比react中的key的作用如何设计状态树介绍css,xsrfhttp缓存控制项目中如何应用数据结构native提供了什么能力给RN如何做工程上的优化shouldComponentUpdate是为了解决什么问题如何解决props层级过深的问题前端怎么做单元测试webpack生命周期webpack打包的整个过程常用的pluginspm2怎么做进程管理,进程挂掉怎么处理不用pm2怎么做进程管理沪江介绍下浏览器跨域怎么去解决跨域问题jsonp方案需要服务端怎么配合Ajax发生跨域要设置什么(前端)加上CORS之后从发起到请求正式成功的过程xsrf跨域攻击的安全性问题怎么防范使用Async会注意哪些东西Async里面有多个await请求,可以怎么优化(请求是否有依赖)Promise和Async处理失败的时候有什么区别Redux在状态管理方面解决了React本身不能解决的问题Redux有没有做过封装react生命周期,常用的生命周期对应的生命周期做什么事遇到性能问题一般在哪个生命周期里解决怎么做性能优化(异步加载组件…)写react有哪些细节可以优化React的事件机制(绑定一个事件到一个组件上)介绍下事件代理,主要解决什么问题前端开发中用到哪些设计模式React/Redux中哪些功能用到了哪些设计模式JS变量类型分为几种,区别是什么JS里垃圾回收机制是什么,常用的是哪种,怎么处理的一般怎么组织CSS(Webpack)饿了么小程序里面开页面最多多少React子父组件之间如何传值Emit事件怎么发,需要引入什么介绍下React高阶组件,和普通组件有什么区别一个对象数组,每个子对象包含一个id和name,React如何渲染出全部的name在哪个生命周期里写其中有几个name不存在,通过异步接口获取,如何做渲染的时候key给什么值,可以使用index吗,用id好还是index好webpack如何配sass,需要配哪些loader配css需要哪些loader如何配置把js、css、html单独打包成一个文件div垂直水平居中(flex、绝对定位)两个元素块,一左一右,中间相距10像素上下固定,中间滚动布局如何实现[1, 2, 3, 4, 5]变成[1, 2, 3, a, b, 5]取数组的最大值(ES5、ES6)apply和call的区别ES5和ES6有什么区别some、every、find、filter、map、forEach有什么区别上述数组随机取数,每次返回的值都不一样如何找0-5的随机数,95-99呢页面上有1万个button如何绑定事件如何判断是button页面上生成一万个button,并且绑定事件,如何做(JS原生操作DOM)循环绑定时的index是多少,为什么,怎么解决页面上有一个input,还有一个p标签,改变input后p标签就跟着变化,如何处理监听input的哪个事件,在什么时候触发携程对React看法,有没有遇到一些坑对闭包的看法,为什么要用闭包手写数组去重函数手写数组扁平化函数介绍下Promise的用途和性质Promise和Callback有什么区别React生命周期两道手写算法题喜马拉雅ES6新的特性介绍PromisePromise有几个状态说一下闭包React的生命周期componentWillReceiveProps的触发条件是什么React16.3对生命周期的改变介绍下React的Filber架构画Filber渲染树介绍React高阶组件父子组件之间如何通信Redux怎么实现属性传递,介绍下原理React-Router版本号网站SEO怎么处理介绍下HTTP状态码403、301、302是什么缓存相关的HTTP请求头介绍HTTPSHTTPS怎么建立安全通道前端性能优化(JS原生和React)用户体验做过什么优化对PWA有什么了解对安全有什么了解介绍下数字签名的原理前后端通信使用什么方案RESTful常用的Method介绍下跨域Access-Control-Allow-Origin在服务端哪里配置csrf跨站攻击怎么解决前端和后端怎么联调兑吧localStorage和cookie有什么区别CSS选择器有哪些盒子模型,以及标准情况和IE下的区别如何实现高度自适应prototype和——proto——区别_construct是什么new是怎么实现的promise的精髓,以及优缺点如何实现H5手机端的适配rem、flex的区别(root em)em和px的区别React声明周期如何去除url中的#号Redux状态管理器和变量挂载到window中有什么区别webpack和gulp的优缺点如何实现异步加载如何实现分模块打包(多入口)前端性能优化(1js css;2 图片;3 缓存预加载; 4 SSR; 5 多域名加载;6 负载均衡)并发请求资源数上限(6个)base64为什么能提升性能,缺点介绍webp这个图片文件格式介绍koa2Promise如何实现的异步请求,低版本fetch如何低版本适配ajax如何处理跨域CORS如何设置jsonp为什么不支持post方法介绍同源策略React使用过的一些组件介绍Immuable介绍下redux整个流程原理介绍原型链如何继承微医介绍JS数据类型,基本数据类型和引用数据类型的区别Array是Object类型吗数据类型分别存在哪里var a = {name: “前端开发”}; var b = a; a = null那么b输出什么var a = {b: 1}存放在哪里var a = {b: {c: 1}}存放在哪里栈和堆的区别垃圾回收时栈和堆的区别数组里面有10万个数据,取第一个元素和第10万个元素的时间相差多少栈和堆具体怎么存储介绍闭包以及闭包为什么没清除闭包的使用场景JS怎么实现异步异步整个执行周期Promise的三种状态Async/Await怎么实现Promise和setTimeout执行先后的区别JS为什么要区分微任务和宏任务Promise构造函数是同步还是异步执行,then呢发布-订阅和观察者模式的区别JS执行过程中分为哪些阶段词法作用域和this的区别平常是怎么做继承深拷贝和浅拷贝loadsh深拷贝实现原理ES6中let块作用域是怎么实现的React中setState后发生了什么setState为什么默认是异步setState什么时候是同步的为什么3大框架出现以后就出现很多native(RN)框架(虚拟DOM)虚拟DOM主要做了什么虚拟DOM本身是什么(JS对象)304是什么打包时Hash码是怎么生成的随机值存在一样的情况,如何避免使用webpack构建时有无做一些自定义操作webpack做了什么a,b两个按钮,点击aba,返回顺序可能是baa,如何保证是aba(Promise.then)node接口转发有无做什么优化node起服务如何保证稳定性,平缓降级,重启等RN有没有做热加载RN遇到的兼容性问题RN如何实现一个原生的组件RN混原生和原生混RN有什么不同什么是单页项目遇到的复杂业务场景Promise.all实现原理寺库介绍Promise的特性,优缺点介绍ReduxRN的原理,为什么可以同时在安卓和IOS端运行RN如何调用原生的一些功能介绍RN的缺点介绍排序算法和快排原理堆和栈的区别介绍闭包闭包的核心是什么网络的五层模型HTTP和HTTPS的区别HTTPS的加密过程介绍SSL和TLS介绍DNS解析JS的继承方法介绍垃圾回收cookie的引用为了解决什么问题cookie和localStorage的区别如何解决跨域问题前端性能优化宝宝树使用canvas绘图时如何组织成通用组件formData和原生的ajax有什么区别介绍下表单提交,和formData有什么关系介绍redux接入流程rudux和全局管理有什么区别(数据可控、数据响应)RN和原生通信介绍MVP怎么组织介绍异步方案promise如何实现then处理koa2中间件原理常用的中间件服务端怎么做统一的状态处理如何对相对路径引用进行优化node文件查找优先级npm2和npm3+有什么区别海康威视knex连接数据库响应回调介绍异步方案如何处理异常捕获项目如何管理模块前端性能优化JS继承方案如何判断一个变量是不是数组变量a和b,如何交换事件委托多个<li>标签生成的Dom结构是一个类数组类数组和数组的区别dom的类数组如何转成数组介绍单页面应用和多页面应用redux状态树的管理介绍localstorage的API蘑菇街html语义化的理解和的区别对闭包的理解工程中闭包使用场景介绍this和原型使用原型最大的好处react设计思路为什么虚拟DOM比真实DOM性能好react常见的通信方式redux整体的工作流程redux和全局对象之间的区别Redux数据回溯设计思路单例、工厂、观察者项目中实际场景项目中树的使用场景以及了解工作收获酷家乐react生命周期react性能优化添加原生事件不移除为什么会内存泄露还有哪些地方会内存泄露setInterval需要注意的点定时器为什么是不精确的setTimeout(1)和setTimeout(2)之间的区别介绍宏任务和微任务promise里面和then里面执行有什么区别介绍pureComponet介绍Function ComponentReact数据流props和state的区别介绍react context介绍class和ES5的类以及区别介绍箭头函数和普通函数的区别介绍defineProperty方法,什么时候需要用到for..in 和 object.keys的区别介绍闭包,使用场景使用闭包特权函数的使用场景get和post有什么区别百分点React15/16.x的区别重新渲染render会做些什么哪些方法会触发react重新渲染state和props触发更新的生命周期分别有什么区别setState是同步还是异步对无状态组件的理解介绍Redux工作流程介绍ES6的功能let、const以及var的区别浅拷贝和深拷贝的区别介绍箭头函数的this介绍Promise和then介绍快速排序算法:前K个最大的元素海风教育对react看法,它的优缺点使用过程中遇到的问题,如何解决的react的理念是什么(拿函数式编程来做页面渲染)JS是什么范式语言(面向对象还是函数式编程)koa原理,为什么要用koa(express和koa对比)使用的koa中间件ES6使用的语法Promise 和 async/await 和 callback的区别Promise有没有解决异步的问题(promise链是真正强大的地方)Promise和setTimeout的区别(Event Loop)进程和线程的区别(一个node实例就是一个进程,node是单线程,通过事件循环来实现异步)介绍下DFS深度优先介绍下观察者模式观察者模式里面使用的数据结构(不具备顺序 ,是一个list) ...

October 25, 2018 · 1 min · jiezi

【Java】几道常见的秋招面试题

前言只有光头才能变强Redis目前还在看,今天来分享一下我在秋招看过(遇到)的一些面试题(相对比较常见的)0、final关键字简要说一下final关键字,final可以用来修饰什么?这题我是在真实的面试中遇到的,当时答得不太好,现在来整理一下吧。final可以修饰类、方法、成员变量当final修饰类的时候,说明该类不能被继承当final修饰方法的时候,说明该方法不能被重写在早期,可能使用final修饰的方法,编译器针对这些方法的所有调用都转成内嵌调用,这样提高效率(但到现在一般我们不会去管这事了,编译器和JVM都越来越聪明了)当final修饰成员变量时,有两种情况:如果修饰的是基本类型,说明这个变量的所代表数值永不能变(不能重新赋值)!如果修饰的是引用类型,该变量所的引用不能变,但引用所代表的对象内容是可变的!值得一说的是:并不是被final修饰的成员变量就一定是编译期常量了。比如说我们可以写出这样的代码:private final int java3y = new Randon().nextInt(20);你有没有这样的编程经验,在编译器写代码时,某个场景下一定要将变量声明为final,否则会出现编译不通过的情况。为什么要这样设计?在编写匿名内部类的时候就可能会出现这种情况,匿名内部类可能会使用到的变量:外部类实例变量方法或作用域内的局部变量方法的参数class Outer { // string:外部类的实例变量 String string = “”; //ch:方法的参数 void outerTest(final char ch) { // integer:方法内局部变量 final Integer integer = 1; new Inner() { void innerTest() { System.out.println(string); System.out.println(ch); System.out.println(integer); } }; } public static void main(String[] args) { new Outer().outerTest(’ ‘); } class Inner { }}其中我们可以看到:方法或作用域内的局部变量和方法参数都要显示使用final关键字来修饰(在jdk1.7下)!如果切换到jdk1.8编译环境下,可以通过编译的下面我们首先来说一下显示声明为final的原因:为了保持内部外部数据一致性Java只是实现了capture-by-value形式的闭包,也就是匿名函数内部会重新拷贝一份自由变量,然后函数外部和函数内部就有两份数据。要想实现内部外部数据一致性目的,只能要求两处变量不变。JDK8之前要求使用final修饰,JDK8聪明些了,可以使用effectively final的方式为什么仅仅针对方法中的参数限制final,而访问外部类的属性就可以随意内部类中是保存着一个指向外部类实例的引用,内部类访问外部类的成员变量都是通过这个引用。在内部类修改了这个引用的数据,外部类再获取时拿到的数据是一致的!那当你在匿名内部类里面尝试改变外部基本类型的变量的值的时候,或者改变外部引用变量的指向的时候,表面上看起来好像都成功了,但实际上并不会影响到外部的变量。所以,Java为了不让自己看起来那么奇怪,才加了这个final的限制。参考资料:java为什么匿名内部类的参数引用时final?https://www.zhihu.com/question/21395848一、char和varchar的区别char是固定长度,varchar长度可变。varchar:如果原先存储的位置无法满足其存储的需求,就需要一些额外的操作,根据存储引擎的不同,有的会采用拆分机制,有的采用分页机制。char的存储方式是:英文字符占1个字节,汉字占用2个字节;varchar的存储方式是:英文和汉字都占用2个字节,两者的存储数据都非unicode的字符数据。char是固定长度,长度不够的情况下,用空格代替。varchar表示的是实际长度的数据类型选用考量:如果字段长度较短和字符间长度相近甚至是相同的长度,会采用char字符类型二、多个线程顺序打印问题三个线程分别打印A,B,C,要求这三个线程一起运行,打印n次,输出形如“ABCABCABC….”的字符串。原博主给出了4种方式,我认为信号量这种方式比较简单和容易理解,我这里粘贴一下(具体的可到原博主下学习)..public class PrintABCUsingSemaphore { private int times; private Semaphore semaphoreA = new Semaphore(1); private Semaphore semaphoreB = new Semaphore(0); private Semaphore semaphoreC = new Semaphore(0); public PrintABCUsingSemaphore(int times) { this.times = times; } public static void main(String[] args) { PrintABCUsingSemaphore printABC = new PrintABCUsingSemaphore(10); // 非静态方法引用 x::toString 和() -> x.toString() 是等价的! new Thread(printABC::printA).start(); new Thread(printABC::printB).start(); new Thread(printABC::printC).start(); /new Thread(() -> printABC.printA()).start(); new Thread(() -> printABC.printB()).start(); new Thread(() -> printABC.printC()).start();/ } public void printA() { try { print(“A”, semaphoreA, semaphoreB); } catch (InterruptedException e) { e.printStackTrace(); } } public void printB() { try { print(“B”, semaphoreB, semaphoreC); } catch (InterruptedException e) { e.printStackTrace(); } } public void printC() { try { print(“C”, semaphoreC, semaphoreA); } catch (InterruptedException e) { e.printStackTrace(); } } private void print(String name, Semaphore current, Semaphore next) throws InterruptedException { for (int i = 0; i < times; i++) { current.acquire(); System.out.print(name); next.release(); } }}作者:cheergoivan链接:https://www.jianshu.com/p/40078ed436b4來源:简书2018年9月14日18:15:36 yy笔试题就出了..三、生产者和消费者在不少的面经都能看到它的身影哈~~~基本都是要求能够手写代码的。其实逻辑并不难,概括起来就两句话:从生产者角度:如果公共队列满了(while循环判断是否满),则等待。如果公共队列没满,则生产数据并唤醒消费者进行消费。从消费者角度:如果公共队列空了(while循环判断是否空),则等待。如果公共队列没空,则消费数据并唤醒生产者进行生产。基于原作者的代码,我修改了部分并给上我认为合适的注释(下面附上了原作者出处,感兴趣的同学可到原文学习)生产者:import java.util.Random;import java.util.Vector;import java.util.concurrent.atomic.AtomicInteger;public class Producer implements Runnable { // true—>生产者一直执行,false—>停掉生产者 private volatile boolean isRunning = true; // 公共资源 private final Vector sharedQueue; // 公共资源的最大数量 private final int SIZE; // 生产数据 private static AtomicInteger count = new AtomicInteger(); public Producer(Vector sharedQueue, int SIZE) { this.sharedQueue = sharedQueue; this.SIZE = SIZE; } @Override public void run() { int data; Random r = new Random(); System.out.println(“start producer id = " + Thread.currentThread().getId()); try { while (isRunning) { // 模拟延迟 Thread.sleep(r.nextInt(1000)); // 当队列满时阻塞等待 while (sharedQueue.size() == SIZE) { synchronized (sharedQueue) { System.out.println(“Queue is full, producer " + Thread.currentThread().getId() + " is waiting, size:” + sharedQueue.size()); sharedQueue.wait(); } } // 队列不满时持续创造新元素 synchronized (sharedQueue) { // 生产数据 data = count.incrementAndGet(); sharedQueue.add(data); System.out.println(“producer create data:” + data + “, size:” + sharedQueue.size()); sharedQueue.notifyAll(); } } } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupted(); } } public void stop() { isRunning = false; }}消费者:import java.util.Random;import java.util.Vector;public class Consumer implements Runnable { // 公共资源 private final Vector sharedQueue; public Consumer(Vector sharedQueue) { this.sharedQueue = sharedQueue; } @Override public void run() { Random r = new Random(); System.out.println(“start consumer id = " + Thread.currentThread().getId()); try { while (true) { // 模拟延迟 Thread.sleep(r.nextInt(1000)); // 当队列空时阻塞等待 while (sharedQueue.isEmpty()) { synchronized (sharedQueue) { System.out.println(“Queue is empty, consumer " + Thread.currentThread().getId() + " is waiting, size:” + sharedQueue.size()); sharedQueue.wait(); } } // 队列不空时持续消费元素 synchronized (sharedQueue) { System.out.println(“consumer consume data:” + sharedQueue.remove(0) + “, size:” + sharedQueue.size()); sharedQueue.notifyAll(); } } } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } }}Main方法测试:import java.util.Vector;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Test2 { public static void main(String[] args) throws InterruptedException { // 1.构建内存缓冲区 Vector sharedQueue = new Vector(); int size = 4; // 2.建立线程池和线程 ExecutorService service = Executors.newCachedThreadPool(); Producer prodThread1 = new Producer(sharedQueue, size); Producer prodThread2 = new Producer(sharedQueue, size); Producer prodThread3 = new Producer(sharedQueue, size); Consumer consThread1 = new Consumer(sharedQueue); Consumer consThread2 = new Consumer(sharedQueue); Consumer consThread3 = new Consumer(sharedQueue); service.execute(prodThread1); service.execute(prodThread2); service.execute(prodThread3); service.execute(consThread1); service.execute(consThread2); service.execute(consThread3); // 3.睡一会儿然后尝试停止生产者(结束循环) Thread.sleep(10 * 1000); prodThread1.stop(); prodThread2.stop(); prodThread3.stop(); // 4.再睡一会儿关闭线程池 Thread.sleep(3000); // 5.shutdown()等待任务执行完才中断线程(因为消费者一直在运行的,所以会发现程序无法结束) service.shutdown(); }}作者:我没有三颗心脏链接:https://www.jianshu.com/p/3f0cd7af370d來源:简书另外,上面原文中也说了可以使用阻塞队列来实现消费者和生产者。这就不用我们手动去写wait/notify的代码了,会简单一丢丢。可以参考:使用阻塞队列解决生产者-消费者问题:https://www.cnblogs.com/chenpi/p/5553325.html四、算法[1]我现在需要实现一个栈,这个栈除了可以进行普通的push、pop操作以外,还可以进行getMin的操作,getMin方法被调用后,会返回当前栈的最小值,你会怎么做呢?你可以假设栈里面存的都是int整数解决方案:使用一个min变量来记住最小值,每次push的时候,看看是否需要更新min。如果被pop出去的是min,第二次pop的时候,只能遍历一下栈内元素,重新找到最小值。总结:pop的时间复杂度是O(n),push是O(1),空间是O(1)使用辅助栈来存储最小值。如果当前要push的值比辅助栈的min值要小,那在辅助栈push的值是最小值总结:push和pop的时间复杂度都是O(1),空间是O(n)。典型以空间换时间的例子。import java.util.ArrayList;import java.util.List;public class MinStack { private List<Integer> data = new ArrayList<Integer>(); private List<Integer> mins = new ArrayList<Integer>(); public void push(int num) { data.add(num); if (mins.size() == 0) { // 初始化mins mins.add(num); } else { // 辅助栈mins每次push当时最小值 int min = getMin(); if (num >= min) { mins.add(min); } else { mins.add(num); } } } public int pop() { // 栈空,异常,返回-1 if (data.size() == 0) { return -1; } // pop时两栈同步pop mins.remove(mins.size() - 1); return data.remove(data.size() - 1); } public int getMin() { // 栈空,异常,返回-1 if (mins.size() == 0) { return -1; } // 返回mins栈顶元素 return mins.get(mins.size() - 1); }}继续优化:栈为空的时候,返回-1很可能会带来歧义(万一人家push进去的值就有-1呢?),这边我们可以使用Java Exception来进行优化算法的空间优化:上面的代码我们可以发现:data栈和mins栈的元素个数总是相等的,mins栈中存储几乎都是最小的值(此部分是重复的!)所以我们可以这样做:当push的时候,如果比min栈的值要小的,才放进mins栈。同理,当pop的时候,如果pop的值是mins的最小值,mins才出栈,否则mins不出栈!上述做法可以一定避免mins辅助栈有相同的元素!但是,如果一直push的值是最小值,那我们的mins辅助栈还是会有大量的重复元素,此时我们可以使用索引(mins辅助栈存储的是最小值索引,非具体的值)!最终代码:import java.util.ArrayList;import java.util.List;public class MinStack { private List<Integer> data = new ArrayList<Integer>(); private List<Integer> mins = new ArrayList<Integer>(); public void push(int num) throws Exception { data.add(num); if(mins.size() == 0) { // 初始化mins mins.add(0); } else { // 辅助栈mins push最小值的索引 int min = getMin(); if (num < min) { mins.add(data.size() - 1); } } } public int pop() throws Exception { // 栈空,抛出异常 if(data.size() == 0) { throw new Exception(“栈为空”); } // pop时先获取索引 int popIndex = data.size() - 1; // 获取mins栈顶元素,它是最小值索引 int minIndex = mins.get(mins.size() - 1); // 如果pop出去的索引就是最小值索引,mins才出栈 if(popIndex == minIndex) { mins.remove(mins.size() - 1); } return data.remove(data.size() - 1); } public int getMin() throws Exception { // 栈空,抛出异常 if(data.size() == 0) { throw new Exception(“栈为空”); } // 获取mins栈顶元素,它是最小值索引 int minIndex = mins.get(mins.size() - 1); return data.get(minIndex); }}参考资料:【面试现场】如何实现可以获取最小值的栈?作者:channingbreeze 出处: 互联网侦察五、多线程下的HashMap众所周知,HashMap不是一个线程安全的类。但有可能在面试的时候会被问到:如果在多线程环境下使用HashMap会有什么现象发生呢??结论:put()的时候导致的多线程数据不一致(丢失数据)resize()操作会导致环形链表jdk1.8已解决环链的问题(声明两对指针,维护两个连链表)fail-fast机制,对当前HashMap同时进行删除/修改会抛出ConcurrentModificationException异常参考资料:谈谈HashMap线程不安全的体现:http://www.importnew.com/22011.htmljdk1.8 hashmap多线程put不会造成死循环:https://blog.csdn.net/qq_27007251/article/details/71403647六、Spring和Springboot区别一、SpringBoot是能够创建出独立的Spring应用程序的二、简化Spring配置Spring由于其繁琐的配置,一度被人成为“配置地狱”,各种XML、Annotation配置,让人眼花缭乱,而且如果出错了也很难找出原因。Spring Boot项目就是为了解决配置繁琐的问题,最大化的实现convention over configuration(约定大于配置)。提供一系列的依赖包来把其它一些工作做成开箱即用其内置一个’Starter POM’,对项目构建进行了高度封装,最大化简化项目构建的配置。三、嵌入式Tomcat,Jetty容器,无需部署WAR包七、G1和CMSG1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。CMS采用的是标记清除垃圾回收算法,可能会产生不少的内存碎片G1的Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。拓展阅读:G1 垃圾收集器介绍:https://javadoop.com/post/g1八、海量数据解决方案海量数据的处理也是一个经常考的知识点,无论在面试还是在笔试中都是比较常见的。有幸读了下面的文章,摘录了一些解决海量数据的思路:Bloom filter布隆过滤器适用范围:可以用来实现数据字典,进行数据的判重,或者集合求交集Hashing适用范围:快速查找,删除的基本数据结构,通常需要总数据量可以放入内存bit-map适用范围:可进行数据的快速查找,判重,删除,一般来说数据范围是int的10倍以下堆适用范围:海量数据前n大,并且n比较小,堆可以放入内存双层桶划分—-其实本质上就是【分而治之】的思想,重在“分”的技巧上!适用范围:第k大,中位数,不重复或重复的数字数据库索引适用范围:大数据量的增删改查倒排索引(Inverted index)适用范围:搜索引擎,关键字查询外排序适用范围:大数据的排序,去重trie树适用范围:数据量大,重复多,但是数据种类小可以放入内存分布式处理 mapreduce适用范围:数据量大,但是数据种类小可以放入内存详细可参考原文:十道海量数据处理面试题与十个方法大总结:https://blog.csdn.net/v_JULY_v/article/details/6279498九、幂等性9.1HTTP幂等性昨天去做了一套笔试题,经典的HTTP中get/post的区别。今天回来搜了一下,发现跟之前的理解有点出入。如果一个人一开始就做Web开发,很可能把HTML对HTTP协议的使用方式,当成HTTP协议的唯一的合理使用方式。从而犯了以偏概全的错误单纯以HTTP协议规范来说,可能我们之前总结出的GET/POST区别就没用了。(但通读完整篇文章,我个人认为:如果面试中有GET/POST区别,还是默认以Web开发场景下来回答较好,这也许是面试官想要的答案)参考资料:GET和POST有什么区别?及为什么网上的多数答案都是错的。http://www.cnblogs.com/nankezhishi/archive/2012/06/09/getandpost.html其中也学习到了幂等性这么一个概念,于是也做做笔记吧~~~Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.从定义上看,HTTP方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。这里简单说一下“副作用”的意思:指当你发送完一个请求以后,网站上的资源状态没有发生修改,即认为这个请求是无副作用的HTTP的GET/POST/DELETE/PUT方法幂等的情况:GET是幂等的,无副作用比如我想要获得订单ID为2的订单:http://localhost/order/2,使用GET多次获取,这个ID为2的订单(资源)是不会发生变化的!DELETE/PUT是幂等的,有副作用比如我想要删除或者更新ID为2的订单:http://localhost/order/2,使用PUT/DELETE多次请求,这个ID为2的订单(资源)只会发生一次变化(是有副作用的)!但继续多次刷新请求,订单ID为2的最终状态都是一致的POST是非幂等的,有副作用的比如我想要创建一个名称叫3y的订单:http://localhost/order,使用POST多次请求,此时可能就会创建多个名称为3y的订单,这个订单(资源)是会多次变化的,每次请求的资源状态都会变化!题外话:HTTP协议本身是一种面向资源的应用层协议,但对HTTP协议的使用实际上存在着两种不同的方式:一种是RESTful的,它把HTTP当成应用层协议,比较忠实地遵守了HTTP协议的各种规定(充分利用了HTTP的方法);另一种是SOA的,它并没有完全把HTTP当成应用层协议,而是把HTTP协议作为了传输层协议,然后在HTTP之上建立了自己的应用层协议参考资料:理解HTTP幂等性http://www.cnblogs.com/weidagang2046/archive/2011/06/04/2063696.html#!comments如何理解RESTful的幂等性http://blog.720ui.com/2016/restful_idempotent/浅谈HTTP中Get与Post的区别http://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.htmlHTTP 请求中 POST 和 GET 请求的区别?https://www.zhihu.com/question/27622127/answer/376763049.2接口幂等性在查阅资料的时候,可以发现很多博客都讲了接口的幂等性。从上面我们也可以看出,POST方法是非幂等的。但我们可以通过一些手段来令POST方法的接口变成是幂等的。说了那么多,那接口设计成幂等的好处是什么????举个例子说一下非幂等的坏处:3y大一的时候是要抢体育课的,但学校的抢课系统做得贼烂(延迟很高)。我想要抢到课,就开了10多个Chrome标签页去抢(即使某个Chrome标签页崩了,我还有另外的Chrome标签页是可用的)。我想抢到乒乓球或者羽毛球。抢课时间一到,我就轮着点击我要想抢的乒乓球或者羽毛球。如果系统设计得不好,这个请求是非幂等的(或者说事务控制得不好),我手速足够快&&网络足够好,那我很可能抢到了多次乒乓球或者羽毛球的课程了。(这是不合理的,一个人只能选一门课,而我抢到了多门或者多次重复的课)涉及到商城的应用场景可能就是:用户下了多个重复的订单了如果我的抢课接口是幂等的话,那就不会出现这个问题了。因为幂等是多次请求某一个资源应该具有同样的副作用。在数据库后台最多只会有一条记录,不存在抢到多门课的现象了。说白了,设计幂等性接口就是为了防止重复提交的(数据库出现多条重复的数据)!网上有博主也分享了几条常见解决重复提交的方案:同步锁(单线程,在集群可能会失效)分布式锁如redis(实现复杂)业务字段加唯一约束(简单)令牌表+唯一约束(简单推荐)—->实现幂等接口的一种手段mysql的insert ignore或者on duplicate key update(简单)共享锁+普通索引(简单)利用MQ或者Redis扩展(排队)其他方案如多版本控制MVCC 乐观锁 悲观锁 状态机等。。参考资料:分布式系统接口幂等性http://blog.brucefeng.info/post/api-idempotent如何避免下重复订单https://www.jianshu.com/p/e618cc818432关于接口幂等性的总结https://www.jianshu.com/p/6eba27f8fb03使用数据库唯一键实现事务幂等性http://www.caosh.me/be-tech/idempotence-using-unique-key/API接口非幂等性问题及使用redis实现简单的分布式锁https://blog.csdn.net/rariki/article/details/50783819最后如果以上有理解错的地方,或者说有更好的理解方式,希望大家不吝在评论区下留言。共同进步!如果想看更多的原创技术文章,欢迎大家关注我的微信公众号:。公众号还有海量的视频资源哦,关注即可免费领取。可能感兴趣的链接:文章的目录导航(微信公众号端):https://zhongfucheng.bitcron.com/post/shou-ji/wen-zhang-dao-hang文章的目录导航(PC端):http://www.zhongfucheng.bitcron.com/post/shou-ji/pcduan-wen-zhang-dao-hang海量精美脑图:http://www.zhongfucheng.bitcron.com/post/shou-ji/nao-tu-da-quan前言只有光头才能变强Redis目前还在看,今天来分享一下我在秋招看过(遇到)的一些面试题(相对比较常见的)0、final关键字简要说一下final关键字,final可以用来修饰什么?这题我是在真实的面试中遇到的,当时答得不太好,现在来整理一下吧。final可以修饰类、方法、成员变量当final修饰类的时候,说明该类不能被继承当final修饰方法的时候,说明该方法不能被重写在早期,可能使用final修饰的方法,编译器针对这些方法的所有调用都转成内嵌调用,这样提高效率(但到现在一般我们不会去管这事了,编译器和JVM都越来越聪明了)当final修饰成员变量时,有两种情况:如果修饰的是基本类型,说明这个变量的所代表数值永不能变(不能重新赋值)!如果修饰的是引用类型,该变量所的引用不能变,但引用所代表的对象内容是可变的!值得一说的是:并不是被final修饰的成员变量就一定是编译期常量了。比如说我们可以写出这样的代码:private final int java3y = new Randon().nextInt(20);你有没有这样的编程经验,在编译器写代码时,某个场景下一定要将变量声明为final,否则会出现编译不通过的情况。为什么要这样设计?在编写匿名内部类的时候就可能会出现这种情况,匿名内部类可能会使用到的变量:外部类实例变量方法或作用域内的局部变量方法的参数class Outer { // string:外部类的实例变量 String string = “”; //ch:方法的参数 void outerTest(final char ch) { // integer:方法内局部变量 final Integer integer = 1; new Inner() { void innerTest() { System.out.println(string); System.out.println(ch); System.out.println(integer); } }; } public static void main(String[] args) { new Outer().outerTest(’ ‘); } class Inner { }}其中我们可以看到:方法或作用域内的局部变量和方法参数都要显示使用final关键字来修饰(在jdk1.7下)!如果切换到jdk1.8编译环境下,可以通过编译的下面我们首先来说一下显示声明为final的原因:为了保持内部外部数据一致性Java只是实现了capture-by-value形式的闭包,也就是匿名函数内部会重新拷贝一份自由变量,然后函数外部和函数内部就有两份数据。要想实现内部外部数据一致性目的,只能要求两处变量不变。JDK8之前要求使用final修饰,JDK8聪明些了,可以使用effectively final的方式为什么仅仅针对方法中的参数限制final,而访问外部类的属性就可以随意内部类中是保存着一个指向外部类实例的引用,内部类访问外部类的成员变量都是通过这个引用。在内部类修改了这个引用的数据,外部类再获取时拿到的数据是一致的!那当你在匿名内部类里面尝试改变外部基本类型的变量的值的时候,或者改变外部引用变量的指向的时候,表面上看起来好像都成功了,但实际上并不会影响到外部的变量。所以,Java为了不让自己看起来那么奇怪,才加了这个final的限制。参考资料:java为什么匿名内部类的参数引用时final?https://www.zhihu.com/question/21395848一、char和varchar的区别char是固定长度,varchar长度可变。varchar:如果原先存储的位置无法满足其存储的需求,就需要一些额外的操作,根据存储引擎的不同,有的会采用拆分机制,有的采用分页机制。char的存储方式是:英文字符占1个字节,汉字占用2个字节;varchar的存储方式是:英文和汉字都占用2个字节,两者的存储数据都非unicode的字符数据。char是固定长度,长度不够的情况下,用空格代替。varchar表示的是实际长度的数据类型选用考量:如果字段长度较短和字符间长度相近甚至是相同的长度,会采用char字符类型二、多个线程顺序打印问题三个线程分别打印A,B,C,要求这三个线程一起运行,打印n次,输出形如“ABCABCABC….”的字符串。原博主给出了4种方式,我认为信号量这种方式比较简单和容易理解,我这里粘贴一下(具体的可到原博主下学习)..public class PrintABCUsingSemaphore { private int times; private Semaphore semaphoreA = new Semaphore(1); private Semaphore semaphoreB = new Semaphore(0); private Semaphore semaphoreC = new Semaphore(0); public PrintABCUsingSemaphore(int times) { this.times = times; } public static void main(String[] args) { PrintABCUsingSemaphore printABC = new PrintABCUsingSemaphore(10); // 非静态方法引用 x::toString 和() -> x.toString() 是等价的! new Thread(printABC::printA).start(); new Thread(printABC::printB).start(); new Thread(printABC::printC).start(); /new Thread(() -> printABC.printA()).start(); new Thread(() -> printABC.printB()).start(); new Thread(() -> printABC.printC()).start();/ } public void printA() { try { print(“A”, semaphoreA, semaphoreB); } catch (InterruptedException e) { e.printStackTrace(); } } public void printB() { try { print(“B”, semaphoreB, semaphoreC); } catch (InterruptedException e) { e.printStackTrace(); } } public void printC() { try { print(“C”, semaphoreC, semaphoreA); } catch (InterruptedException e) { e.printStackTrace(); } } private void print(String name, Semaphore current, Semaphore next) throws InterruptedException { for (int i = 0; i < times; i++) { current.acquire(); System.out.print(name); next.release(); } }}作者:cheergoivan链接:https://www.jianshu.com/p/40078ed436b4來源:简书2018年9月14日18:15:36 yy笔试题就出了..三、生产者和消费者在不少的面经都能看到它的身影哈~~~基本都是要求能够手写代码的。其实逻辑并不难,概括起来就两句话:如果生产者的队列满了(while循环判断是否满),则等待。如果生产者的队列没满,则生产数据并唤醒消费者进行消费。如果消费者的队列空了(while循环判断是否空),则等待。如果消费者的队列没空,则消费数据并唤醒生产者进行生产。基于原作者的代码,我修改了部分并给上我认为合适的注释(下面附上了原作者出处,感兴趣的同学可到原文学习)生产者:import java.util.Random;import java.util.Vector;import java.util.concurrent.atomic.AtomicInteger;public class Producer implements Runnable { // true—>生产者一直执行,false—>停掉生产者 private volatile boolean isRunning = true; // 公共资源 private final Vector sharedQueue; // 公共资源的最大数量 private final int SIZE; // 生产数据 private static AtomicInteger count = new AtomicInteger(); public Producer(Vector sharedQueue, int SIZE) { this.sharedQueue = sharedQueue; this.SIZE = SIZE; } @Override public void run() { int data; Random r = new Random(); System.out.println(“start producer id = " + Thread.currentThread().getId()); try { while (isRunning) { // 模拟延迟 Thread.sleep(r.nextInt(1000)); // 当队列满时阻塞等待 while (sharedQueue.size() == SIZE) { synchronized (sharedQueue) { System.out.println(“Queue is full, producer " + Thread.currentThread().getId() + " is waiting, size:” + sharedQueue.size()); sharedQueue.wait(); } } // 队列不满时持续创造新元素 synchronized (sharedQueue) { // 生产数据 data = count.incrementAndGet(); sharedQueue.add(data); System.out.println(“producer create data:” + data + “, size:” + sharedQueue.size()); sharedQueue.notifyAll(); } } } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupted(); } } public void stop() { isRunning = false; }}消费者:import java.util.Random;import java.util.Vector;public class Consumer implements Runnable { // 公共资源 private final Vector sharedQueue; public Consumer(Vector sharedQueue) { this.sharedQueue = sharedQueue; } @Override public void run() { Random r = new Random(); System.out.println(“start consumer id = " + Thread.currentThread().getId()); try { while (true) { // 模拟延迟 Thread.sleep(r.nextInt(1000)); // 当队列空时阻塞等待 while (sharedQueue.isEmpty()) { synchronized (sharedQueue) { System.out.println(“Queue is empty, consumer " + Thread.currentThread().getId() + " is waiting, size:” + sharedQueue.size()); sharedQueue.wait(); } } // 队列不空时持续消费元素 synchronized (sharedQueue) { System.out.println(“consumer consume data:” + sharedQueue.remove(0) + “, size:” + sharedQueue.size()); sharedQueue.notifyAll(); } } } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } }}Main方法测试:import java.util.Vector;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Test2 { public static void main(String[] args) throws InterruptedException { // 1.构建内存缓冲区 Vector sharedQueue = new Vector(); int size = 4; // 2.建立线程池和线程 ExecutorService service = Executors.newCachedThreadPool(); Producer prodThread1 = new Producer(sharedQueue, size); Producer prodThread2 = new Producer(sharedQueue, size); Producer prodThread3 = new Producer(sharedQueue, size); Consumer consThread1 = new Consumer(sharedQueue); Consumer consThread2 = new Consumer(sharedQueue); Consumer consThread3 = new Consumer(sharedQueue); service.execute(prodThread1); service.execute(prodThread2); service.execute(prodThread3); service.execute(consThread1); service.execute(consThread2); service.execute(consThread3); // 3.睡一会儿然后尝试停止生产者(结束循环) Thread.sleep(10 * 1000); prodThread1.stop(); prodThread2.stop(); prodThread3.stop(); // 4.再睡一会儿关闭线程池 Thread.sleep(3000); // 5.shutdown()等待任务执行完才中断线程(因为消费者一直在运行的,所以会发现程序无法结束) service.shutdown(); }}作者:我没有三颗心脏链接:https://www.jianshu.com/p/3f0cd7af370d來源:简书另外,上面原文中也说了可以使用阻塞队列来实现消费者和生产者。这就不用我们手动去写wait/notify的代码了,会简单一丢丢。可以参考:使用阻塞队列解决生产者-消费者问题:https://www.cnblogs.com/chenpi/p/5553325.html四、算法[1]我现在需要实现一个栈,这个栈除了可以进行普通的push、pop操作以外,还可以进行getMin的操作,getMin方法被调用后,会返回当前栈的最小值,你会怎么做呢?你可以假设栈里面存的都是int整数解决方案:使用一个min变量来记住最小值,每次push的时候,看看是否需要更新min。如果被pop出去的是min,第二次pop的时候,只能遍历一下栈内元素,重新找到最小值。总结:pop的时间复杂度是O(n),push是O(1),空间是O(1)使用辅助栈来存储最小值。如果当前要push的值比辅助栈的min值要小,那在辅助栈push的值是最小值总结:push和pop的时间复杂度都是O(1),空间是O(n)。典型以空间换时间的例子。import java.util.ArrayList;import java.util.List;public class MinStack { private List<Integer> data = new ArrayList<Integer>(); private List<Integer> mins = new ArrayList<Integer>(); public void push(int num) { data.add(num); if (mins.size() == 0) { // 初始化mins mins.add(num); } else { // 辅助栈mins每次push当时最小值 int min = getMin(); if (num >= min) { mins.add(min); } else { mins.add(num); } } } public int pop() { // 栈空,异常,返回-1 if (data.size() == 0) { return -1; } // pop时两栈同步pop mins.remove(mins.size() - 1); return data.remove(data.size() - 1); } public int getMin() { // 栈空,异常,返回-1 if (mins.size() == 0) { return -1; } // 返回mins栈顶元素 return mins.get(mins.size() - 1); }}继续优化:栈为空的时候,返回-1很可能会带来歧义(万一人家push进去的值就有-1呢?),这边我们可以使用Java Exception来进行优化算法的空间优化:上面的代码我们可以发现:data栈和mins栈的元素个数总是相等的,mins栈中存储几乎都是最小的值(此部分是重复的!)所以我们可以这样做:当push的时候,如果比min栈的值要小的,才放进mins栈。同理,当pop的时候,如果pop的值是mins的最小值,mins才出栈,否则mins不出栈!上述做法可以一定避免mins辅助栈有相同的元素!但是,如果一直push的值是最小值,那我们的mins辅助栈还是会有大量的重复元素,此时我们可以使用索引(mins辅助栈存储的是最小值索引,非具体的值)!最终代码:import java.util.ArrayList;import java.util.List;public class MinStack { private List<Integer> data = new ArrayList<Integer>(); private List<Integer> mins = new ArrayList<Integer>(); public void push(int num) throws Exception { data.add(num); if(mins.size() == 0) { // 初始化mins mins.add(0); } else { // 辅助栈mins push最小值的索引 int min = getMin(); if (num < min) { mins.add(data.size() - 1); } } } public int pop() throws Exception { // 栈空,抛出异常 if(data.size() == 0) { throw new Exception(“栈为空”); } // pop时先获取索引 int popIndex = data.size() - 1; // 获取mins栈顶元素,它是最小值索引 int minIndex = mins.get(mins.size() - 1); // 如果pop出去的索引就是最小值索引,mins才出栈 if(popIndex == minIndex) { mins.remove(mins.size() - 1); } return data.remove(data.size() - 1); } public int getMin() throws Exception { // 栈空,抛出异常 if(data.size() == 0) { throw new Exception(“栈为空”); } // 获取mins栈顶元素,它是最小值索引 int minIndex = mins.get(mins.size() - 1); return data.get(minIndex); }}参考资料:【面试现场】如何实现可以获取最小值的栈?作者:channingbreeze 出处: 互联网侦察五、多线程下的HashMap众所周知,HashMap不是一个线程安全的类。但有可能在面试的时候会被问到:如果在多线程环境下使用HashMap会有什么现象发生呢??结论:put()的时候导致的多线程数据不一致(丢失数据)resize()操作会导致环形链表jdk1.8已解决环链的问题(声明两对指针,维护两个连链表)fail-fast机制,对当前HashMap同时进行删除/修改会抛出ConcurrentModificationException异常参考资料:谈谈HashMap线程不安全的体现:http://www.importnew.com/22011.htmljdk1.8 hashmap多线程put不会造成死循环:https://blog.csdn.net/qq_27007251/article/details/71403647六、Spring和Springboot区别一、SpringBoot是能够创建出独立的Spring应用程序的二、简化Spring配置Spring由于其繁琐的配置,一度被人成为“配置地狱”,各种XML、Annotation配置,让人眼花缭乱,而且如果出错了也很难找出原因。Spring Boot项目就是为了解决配置繁琐的问题,最大化的实现convention over configuration(约定大于配置)。提供一系列的依赖包来把其它一些工作做成开箱即用其内置一个’Starter POM’,对项目构建进行了高度封装,最大化简化项目构建的配置。三、嵌入式Tomcat,Jetty容器,无需部署WAR包七、G1和CMSG1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。CMS采用的是标记清除垃圾回收算法,可能会产生不少的内存碎片G1的Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。拓展阅读:G1 垃圾收集器介绍:https://javadoop.com/post/g1八、海量数据解决方案海量数据的处理也是一个经常考的知识点,无论在面试还是在笔试中都是比较常见的。有幸读了下面的文章,摘录了一些解决海量数据的思路:Bloom filter布隆过滤器适用范围:可以用来实现数据字典,进行数据的判重,或者集合求交集Hashing适用范围:快速查找,删除的基本数据结构,通常需要总数据量可以放入内存bit-map适用范围:可进行数据的快速查找,判重,删除,一般来说数据范围是int的10倍以下堆适用范围:海量数据前n大,并且n比较小,堆可以放入内存双层桶划分—-其实本质上就是【分而治之】的思想,重在“分”的技巧上!适用范围:第k大,中位数,不重复或重复的数字数据库索引适用范围:大数据量的增删改查倒排索引(Inverted index)适用范围:搜索引擎,关键字查询外排序适用范围:大数据的排序,去重trie树适用范围:数据量大,重复多,但是数据种类小可以放入内存分布式处理 mapreduce适用范围:数据量大,但是数据种类小可以放入内存详细可参考原文:十道海量数据处理面试题与十个方法大总结:https://blog.csdn.net/v_JULY_v/article/details/6279498九、幂等性9.1HTTP幂等性昨天去做了一套笔试题,经典的HTTP中get/post的区别。今天回来搜了一下,发现跟之前的理解有点出入。如果一个人一开始就做Web开发,很可能把HTML对HTTP协议的使用方式,当成HTTP协议的唯一的合理使用方式。从而犯了以偏概全的错误单纯以HTTP协议规范来说,可能我们之前总结出的GET/POST区别就没用了。(但通读完整篇文章,我个人认为:如果面试中有GET/POST区别,还是默认以Web开发场景下来回答较好,这也许是面试官想要的答案)参考资料:GET和POST有什么区别?及为什么网上的多数答案都是错的。http://www.cnblogs.com/nankezhishi/archive/2012/06/09/getandpost.html其中也学习到了幂等性这么一个概念,于是也做做笔记吧~~~Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.从定义上看,HTTP方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。这里简单说一下“副作用”的意思:指当你发送完一个请求以后,网站上的资源状态没有发生修改,即认为这个请求是无副作用的HTTP的GET/POST/DELETE/PUT方法幂等的情况:GET是幂等的,无副作用比如我想要获得订单ID为2的订单:http://localhost/order/2,使用GET多次获取,这个ID为2的订单(资源)是不会发生变化的!DELETE/PUT是幂等的,有副作用比如我想要删除或者更新ID为2的订单:http://localhost/order/2,使用PUT/DELETE多次请求,这个ID为2的订单(资源)只会发生一次变化(是有副作用的)!但继续多次刷新请求,订单ID为2的最终状态都是一致的POST是非幂等的,有副作用的比如我想要创建一个名称叫3y的订单:http://localhost/order,使用POST多次请求,此时可能就会创建多个名称为3y的订单,这个订单(资源)是会多次变化的,每次请求的资源状态都会变化!题外话:HTTP协议本身是一种面向资源的应用层协议,但对HTTP协议的使用实际上存在着两种不同的方式:一种是RESTful的,它把HTTP当成应用层协议,比较忠实地遵守了HTTP协议的各种规定(充分利用了HTTP的方法);另一种是SOA的,它并没有完全把HTTP当成应用层协议,而是把HTTP协议作为了传输层协议,然后在HTTP之上建立了自己的应用层协议参考资料:理解HTTP幂等性http://www.cnblogs.com/weidagang2046/archive/2011/06/04/2063696.html#!comments如何理解RESTful的幂等性http://blog.720ui.com/2016/restful_idempotent/浅谈HTTP中Get与Post的区别http://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.htmlHTTP 请求中 POST 和 GET 请求的区别?https://www.zhihu.com/question/27622127/answer/376763049.2接口幂等性在查阅资料的时候,可以发现很多博客都讲了接口的幂等性。从上面我们也可以看出,POST方法是非幂等的。但我们可以通过一些手段来令POST方法的接口变成是幂等的。说了那么多,那接口设计成幂等的好处是什么????举个例子说一下非幂等的坏处:3y大一的时候是要抢体育课的,但学校的抢课系统做得贼烂(延迟很高)。我想要抢到课,就开了10多个Chrome标签页去抢(即使某个Chrome标签页崩了,我还有另外的Chrome标签页是可用的)。我想抢到乒乓球或者羽毛球。抢课时间一到,我就轮着点击我要想抢的乒乓球或者羽毛球。如果系统设计得不好,这个请求是非幂等的(或者说事务控制得不好),我手速足够快&&网络足够好,那我很可能抢到了多次乒乓球或者羽毛球的课程了。(这是不合理的,一个人只能选一门课,而我抢到了多门或者多次重复的课)涉及到商城的应用场景可能就是:用户下了多个重复的订单了如果我的抢课接口是幂等的话,那就不会出现这个问题了。因为幂等是多次请求某一个资源应该具有同样的副作用。在数据库后台最多只会有一条记录,不存在抢到多门课的现象了。说白了,设计幂等性接口就是为了防止重复提交的(数据库出现多条重复的数据)!网上有博主也分享了几条常见解决重复提交的方案:同步锁(单线程,在集群可能会失效)分布式锁如redis(实现复杂)业务字段加唯一约束(简单)令牌表+唯一约束(简单推荐)—->实现幂等接口的一种手段mysql的insert ignore或者on duplicate key update(简单)共享锁+普通索引(简单)利用MQ或者Redis扩展(排队)其他方案如多版本控制MVCC 乐观锁 悲观锁 状态机等。。参考资料:分布式系统接口幂等性http://blog.brucefeng.info/post/api-idempotent如何避免下重复订单https://www.jianshu.com/p/e618cc818432关于接口幂等性的总结https://www.jianshu.com/p/6eba27f8fb03使用数据库唯一键实现事务幂等性http://www.caosh.me/be-tech/idempotence-using-unique-key/API接口非幂等性问题及使用redis实现简单的分布式锁https://blog.csdn.net/rariki/article/details/50783819最后如果以上有理解错的地方,或者说有更好的理解方式,希望大家不吝在评论区下留言。共同进步!如果想看更多的原创技术文章,欢迎大家关注我的微信公众号:。公众号还有海量的视频资源哦,关注即可免费领取。可能感兴趣的链接:文章的目录导航(微信公众号端):https://zhongfucheng.bitcron.com/post/shou-ji/wen-zhang-dao-hang文章的目录导航(PC端):http://www.zhongfucheng.bitcron.com/post/shou-ji/pcduan-wen-zhang-dao-hang海量精美脑图:http://www.zhongfucheng.bitcron.com/post/shou-ji/nao-tu-da-quan ...

October 23, 2018 · 8 min · jiezi

结束了我短暂的秋招,说点自己的感受

该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb…秋招历程流水账总结笔主大四准毕业生,在秋招末流比较幸运地进入了一家自己非常喜欢一家公司——ThoughtWorks.从9-6号投递出去第一份简历,到10-18号左右拿到第一份 offer ,中间差不多有 1 个半月的时间了。可能自己比较随缘,而且自己所在的大学所处的位置并不是互联网比较发达的城市的原因。所以,很少会有公司愿意跑到我们学校那边来宣讲,来的公司也大多是一些自己没听过或者不太喜欢的公司。所以,在前期,我仅仅能够通过网上投递简历的方式来找工作。零零总总算了一下,自己在网上投了大概有 10 份左右的简历,都是些自己还算喜欢的公司。简单说一下自己投递的一些公司:网上投递的公司有:ThoughtWorks、网易、小米、携程、爱奇艺、知乎、小红书、搜狐、欢聚时代、京东;直接邮箱投递的有:烽火、中电数据、蚂蚁金服花呗部门、今日头条;线下宣讲会投递的有:玄武科技。网上投递的大部分简历都是在做完笔试之后就没有了下文了,即使有几场笔试自我感觉做的很不错的情况下,还是没有收到后续的面试邀请。还有些邮箱投递的简历,后面也都没了回应。所以,我总共也只参加了3个公司的面试,ThoughtWorks、玄武科技和中电数据,都算是拿到了 offer。拿到 ThoughtWorks 的 offer之后,后面的一些笔试和少部分面试都拒了。决定去 ThoughtWorks 了,春招的大部队会没有我的存在。我个人对 ThoughtWorks 最有好感,ThoughtWorks 也是我自己之前很想去的一家公司。不光是因为我投递简历的时候可以不用重新填一遍表格可以直接发送我已经编辑好的PDF格式简历的友好,这个公司的文化也让我很喜欢。每次投递一家公司几乎都要重新填写一遍简历真的很让人头疼,即使是用牛客网的简历助手也还是有很多东西需要自己重新填写。说句实话,自己在拿到第一份 offer 之前心里还是比较空的,虽然说对自己还是比较自信。包括自己当时来到武汉的原因,也是因为自己没有 offer ,就感觉心里空空的,我相信很多人在这个时候与我也有一样的感觉。然后,我就想到武汉参加一下别的学校宣讲会。现在看来,这个决定也是不必要的,因为我最后去的公司 ThoughtWorks,虽然就在我租的房子的附近,但之前投递的时候,选择的还是远程面试。来到武汉,简单的修整了一下之后,我就去参加了玄武科技在武理工的宣讲会,顺便做了笔试,然后接着就是技术面、HR面、高管面。总体来说,玄武科技的 HR 真的很热情,为他们点个赞,虽然自己最后没能去玄武科技,然后就是技术面非常简单,HR面和高管面也都还好,不会有压抑的感觉,总体聊得很愉快。需要注意的是 玄武科技和很多公司一样都有笔试中有逻辑题,我之前没有做过类似的题,所以当时第一次做有点懵逼。高管面的时候,高管还专门在我做的逻辑题上聊了一会,让我重新做了一些做错的题,并且给他讲一些题的思路,可以看出高层对于应聘者的这项能力还是比较看重的。中电数据的技术面试是电话进行的,花了1个多小时一点,个人感觉问的还是比较深的,感觉自己总体回答的还是比较不错的。这里我着重说一下 ThoughtWorks,也算是给想去 ThoughtWorks 的同学一点小小的提示。我是 9.11 号在官网:https://join.thoughtworks.cn/ 投递的简历,9.20 日邮件通知官网下载作业,作业总体来说不难,9.21 号花了半天多的时间做完,然后就直接在9.21 号下午提交了。然后等了挺长时间的,可能是因为 ThoughtWorks 在管理方面比较扁平化的原因,所以总体来说效率可能不算高。因为我选的是远程面试,所以直接下载好 zoom 之后,等HR打电话过来告诉你一个房间号,你就可以直接进去面试就好,一般技术面试有几个人看着你。技术面试的内容,首先就是在面试官让你在你之前做的作业的基础上新增加一个或者两个功能(20分钟)。所以,你在技术面试之前一定要保证你的程序的扩展性是不错的,另外就是你在技术面试之前最好能重构一下自己写的程序。重构本身就是你自己对你写的程序的理解加强很好的一种方式,另外重构也能让你发现你的程序的一些小问题。然后,这一步完成之后,面试官可能会问你一些基础问题,比较简单,所以我觉得 ThoughtWorks 可能更看重你的代码质量。ThoughtWorks 的 HR 面和其他公司的唯一不同可能在于,他会让你用英语介绍一下自己或者说自己的技术栈啊这些。关于面试一些重要的问题总结另外,再给大家总结一些我个人想到一些关于面试非常重要的一些问题。面试前如何准备运筹帷幄之后,决胜千里之外!不打毫无准备的仗,我觉得大家可以先从下面几个方面来准备面试:自我介绍。(你可千万这样介绍:“我叫某某,性别,来自哪里,学校是那个,自己爱干什么”,记住:多说点简历上没有的,多说点自己哪里比别人强!)自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、面试中自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)自己的简历该如何写。另外,如果你想去类似阿里巴巴、腾讯这种比较大的互联网公司的话,一定要尽早做打算。像阿里巴巴在7月份左右就开始了提前批招聘,到了9月份差不多就已经招聘完毕了。所以,秋招没有参加到阿里的面试还是很遗憾的,毕竟面试即使失败了,也能从阿里难度Max的面试中学到很多东西。关于着装穿西装、打领带、小皮鞋?NO!NO!NO!这是互联网公司面试又不是去走红毯,所以你只需要穿的简单大方就好,不需要太正式。关于自我介绍如果你简历上写的基本信息就不要说了,比如性别、年龄、学校。另外,你也不要一上来就说自己爱好什么这方面内容。因为,面试官根本不关心这些东西。你直接挑和你岗位相关的重要经历和自己最突出的特点讲就好了。比如:面试官,您好!我叫某某。大学时间我主要利用课外时间学习某某。在校期间参与过一个某某系统的开发,另外,自己学习过程中也写过很多系统比如某某系统。在学习之余,我比较喜欢通过博客整理分享自己所学知识。我现在是某某社区的认证作者,写过某某很不错的文章。另外,我获得过某某奖,我的Github上开源的某个项目已经有多少Star了。提前准备面试之前可以在网上找找有没有你要面试的公司的面经。在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。面试中面试的时候一定要自信,千万不要怕自己哪里会答不出来,或者说某个问题自己忘记怎么回答了。面试过程中,很多问题可能是你之前没有碰到过的,这个时候你就要通过自己构建的知识体系来思考这些问题。如果某些问题你回答不上来,你也可以让面试官给你简单的提示一下。总之,你要自信,你自信的前提是自己要做好充分的准备。下面给大家总结一些面试非常常见的问题:SpringMVC 工作原理说一下自己对 IOC 、AOP 的理解Spring 中用到了那些设计模式,讲一下自己对于这些设计模式的理解Spring Bean 的作用域和生命周期了解吗Spring 事务中的隔离级别Spring 事务中的事务传播行为手写一个 LRU 算法知道那些排序算法,简单介绍一下快排的原理,能不能手写一下快排String 为什么是不可变的?String为啥要设计为不可变的?Arraylist 与 LinkedList 异同HashMap的底层实现HashMap 的长度为什么是2的幂次方ConcurrentHashMap 和 Hashtable 的区别ConcurrentHashMap线程安全的具体实现方式/底层具体实现如果你的简历写了redis 、dubbo、zookeeper、docker的话,面试官还会问一下这些东西。比如redis可能会问你:为什么要用 redis、为什么要用 redis 而不用 map/guava 做缓存、redis 常见数据结构以及使用场景分析、 redis 设置过期时间、redis 内存淘汰机制、 redis 持久化机制、 缓存雪崩和缓存穿透问题、如何解决 Redis 的并发竞争 Key 问题、如何保证缓存与数据库双写时的数据一致性。一些简单的 Linux 命令。为什么要用 消息队列关于 Java多线程,在面试的时候,问的比较多的就是①悲观锁和乐观锁②synchronized 和 ReenTrantLock 区别以及 volatile 和 synchronized 的区别,③可重入锁与非可重入锁的区别、④多线程是解决什么问题的、⑤线程池解决什么问题,为什么要用线程池 ⑥Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 ReenTrantLock 对比;⑦线程池使用时的注意事项、⑧AQS 原理以及 AQS 同步组件:Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock、⑨ReentranLock源码,设计原理,整体过程 等等问题。关于 Java 虚拟机问的比较多的是:①Java内存区域、②虚拟机垃圾算法、③虚拟机垃圾收集器、④JVM内存管理、⑤JVM调优这些问题。面试后如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!你若盛开,清风自来。 欢迎关注我的微信公众号:“Java面试通关手册”,一个有温度的微信公众号。公众号后台回复关键字“1”,可以免费获取一份我精心准备的小礼物哦! ...

October 23, 2018 · 1 min · jiezi

【Java】广州三本秋招经历

前言只有光头才能变强离上次发文章已经快两个月时间了,最近一直忙着秋招的事。今天是2018年10月22日,对于互联网行业来说,秋招就基本结束了。我这边的流程也走完了(不再笔试/面试了),所以来写写我的秋招经历吧这次的秋招可谓收获很多:成功收到了4399、唯品会等几十家公司的感谢信,成功加入人才库!与多家知名企业在广州高校面对面交流,并获得了企业的精美小礼品!一、秋招能得到的面试机会/offer背景:广州三本,学校期间无比赛经历。一段小公司的实习经历,唯一的亮点可能就是我这个博客了。在秋招中投了非常非常多的简历,得到的面试机会有:阿里(内推->一面挂)快手(内推->一面挂)广州妈妈网(笔试->群面挂)蘑菇街(内推->通过三面->拿到意向书)广州三星研究院(笔试->一个下午的面试->通过所有的面试->得知最后要机试->放弃机会)流利说(笔试->三轮面试->拿到offer)去哪儿(笔试->通知去长沙现场面试->放弃机会)便利蜂(笔试->三轮面试->拿到offer)4399(现场宣讲会->得到面试直通卡->一面挂)小米(笔试->得到面试机会->时间冲突->放弃机会)中途也面了一些(广州/上海)某(创业/传统行业)公司,最终拒绝下一轮面试机会(部分offer)二、真实的秋招整个秋招来说,我的压力还是挺大的。具体的时间线:从7月中旬,我开始关注牛客网的秋招内推信息。第一次面试由阿里打响,第一次笔试由拼多多打响。直至十月中下旬结束秋招。(得到的offer都是十月底才确定下来的)从上面也可以看出,我整个秋招得到的面试机会可以说是很少的,但我的简历投递的数量是非常多的。7月中旬到9月中旬的历程:当时得到的面试机会可能就5、6家左右,看着牛客网已经有不少大佬(“神仙”)已经拿到意向书(offer)了。而此时的我就仅仅面试了几家,而且并没有后续了。所以那时候挺急的了。只要牛客网出现过的内推信息,我都投递了期间被各大厂的笔试题血虐。曾经也试过一天参加4个公司的在线笔试。后来觉得这样下去并不行啊,面试机会太少了。所以去知乎搜了一下《广州值得加入的互联网公司有哪些》,一个一个地在拉钩网/前程无忧搜了一下,觉得岗位合适的投递了一波,再后来一些广州小企业也海投了一波。一个星期过去了,仅仅收到一份面试机会。最后在群面时被各大佬的学历和大厂实习经历碾压同时,开始跑宣讲会了。(最开始的时候其实没打算跑宣讲会的,由于被各大厂的笔试题轮炸,也没啥面试机会。臆想着线下笔试题可能会更简单一些,希望多拿一个面试机会)跑宣讲会对我来说还是挺麻烦的:一、宣讲会一般的地点在广州大学城,从我学校过去将近3个小时,来回将近6个小时了。中午11点半吃完饭,坐公交地铁过去企业的3点宣讲会,宣讲会结束之后回到学校就8点多了。二、跑宣讲会时,参加宣讲会的基本都是中大、华工、广工的大佬,在宣讲会签到名单上瑟瑟发抖。三、也有企业在宣讲会的时候明确指出:“xxx岗位可以降低到非985、211的学历,但其他岗位还是希望能招聪明的人”。于是在场下的我就知道又白跑了一天。四、有的企业说是有初试,去完之后发现:讲座两小时,面试5分钟。也曾经一天跑过两场宣讲会,一场在下午3点的广外(大学城校区),一场在晚上7点的广外(白云校区)。大学城校区的宣讲会完了急忙过去白云校区时已经6点50分了。还没来得及吃饭又开始了,最后被笔试题血虐完已经9点多了。在陌生的校园跟着人流走出了校门,发现已经错过了回学校的尾班车现在回想起来自己也是心大:当时0 offer的情况下,收到了去哪儿的面试机会,得知需要去长沙现场面试。我当时候拒绝了(想希望通过电话/视频来进行面试)但我后来发现很多人都是坐高铁去各种城市面试的…国庆前的希望:原以为已经挂掉的蘑菇街,通知在国庆的前两天hr面试。十月中旬至今:在十月中旬拿到蘑菇街的意向书,心算是稳了一点下来。(但意向书未必就有offer,在网上也曾经见过有意向书没offer的案例[其他的公司])同时得到便利蜂的一面面试机会,前两天得到offer。(便利蜂面试的效率真是好,一个多星期就完了)在上个星期得到了流利说的二面(距离一面已经有三个星期了),我还以为已经挂掉了。被二面面试官血虐了以后,hr告诉我还是通过了面试,今天得到offer。ps:整个秋招遗憾的是没能拿到广州/深圳的offer。也很感谢便利蜂/蘑菇街/流利说给予了机会三、秋招/春招的准备此部分写给还没经历过秋招/春招的同学们以我个人的秋招经历(Java方向)来给大家一些意见,仅供参考。之前也写过自己在广州找Java实习的经历,那次把面试的过程都具体贴出来了。这次虽然我也有记录每次的面试问题,但不打算贴出来。因为网上真的很多面经,搜一下牛客网一大堆~~我总结了几点:提前了解互联网秋招/春招的流程。知道具体的时间是什么时候,面向的人群是哪些。之前看到牛客网有人发帖子说:为啥2018年秋招不是招2018年毕业的啊,而是招2019年毕业的啊。明明2019年的还有那么久才毕业啊..如果今年大三,也应该了解一下2019年的春招了,不少的企业会在2019年的春招招聘2020年毕业的实习生。(我今年就完美错过了春招实习经历)。当然了,2019年的春招招聘的对象还是以2019年的毕业生为主的。巩固基础:操作系统/计算机网络/数据结构与算法/数据库相信不少同学可能在学习的时候查阅《想成为一名程序员,如何入门?》,都会有人给出这样的观点:“语言只是工具,学计算机还是以基础为主,操作系统/计算机网络/数据结构与算法/数据库这些都得懂才能走得更远”。从校园招聘的角度上来看:操作系统/计算机网络/数据结构与算法/数据库这些如果你没复习或者说概念不懂,校招会十分难受(我就是一个典型的例子)..因为校招的笔试几乎都是考这几个知识点的,具体的语言(如Java)是占很少的份量的。学历很重要,但菜是原罪。有个好学校背景可能意味着简历不会那么容易被刷,意味着在相同能力下,录用的机会可以高很多(免笔试,直达面试…等等的优势)。在我认为,这是公平的(人家高考比你考高100多分,你当时干啥去了?)在互联网行业,学历还是相对没那么看重的。比如说,国内一二线互联网企业:百度/阿里/腾讯/头条/美团/滴滴等这些企业都给了我笔试的机会。只是我太菜了,不会做(再次说明操作系统/计算机网络/数据结构与算法/数据库这些是真的重要)。我面试机会少,就是笔试做的贼烂(不能完全归结于学历)尽可能提高自己的技术水平吧!加分项:实际项目经验(实习经历),技术博客(GitHub开源项目),在校比赛经历,学历调整心态,坚持,加油。3.1面试时常考知识点Java基础:Java集合多线程JVM设计模式Spring:Spring基础概念IOC和AOP的原理Spring中Bean的生命周期数据库:基本SQL的编写能力数据库锁和索引的原理事务操作系统:死锁进程和线程CPU/内存/硬盘的工作流程(原理)计算机网络:http(包括http版本区别,https原理)从浏览器输入地址到响应的整个过程(越详细越好,这个问题可以涵盖整个计算机网络了)数据结构与算法:刷题为主,leetcode刷起来看过总结得不错的资料:https://www.nowcoder.com/discuss/125248?type=2&order=3&pos=21&page=1总的来说,校招是看重基础的。如果时间不是足够充裕的同学,不建议学过多的框架,还是以基础为主。这些框架的使用对校招来说可能并不是那么地看重,而且学习时会耗费不少的时间。四、最后在秋招时间没有发过任何的博客,很多人从微信公众号/知乎/QQ/微信找到我询问目前的秋招状态,并给予支持和鼓励,真的非常感谢现在秋招结束了,离去公司实习还是有一段时间的。目前打算学Redis,后续也会写一些关于Redis的文章~如果想看更多的原创技术文章,欢迎大家关注我的微信公众号:。公众号还有海量的视频资源哦,关注即可免费领取。可能感兴趣的链接:文章的目录导航(微信公众号端):https://zhongfucheng.bitcron.com/post/shou-ji/wen-zhang-dao-hang文章的目录导航(PC端):http://www.zhongfucheng.bitcron.com/post/shou-ji/pcduan-wen-zhang-dao-hang海量精美脑图:http://www.zhongfucheng.bitcron.com/post/shou-ji/nao-tu-da-quan

October 23, 2018 · 1 min · jiezi

我的前端面试总结(套路篇)

前言2018年的秋天,金九银十的秋招季????,很荣幸,我也加入了跳槽求职的大部队????,虽然招聘热潮没有每年年初那样疯狂,但是还是看得出很多公司都在大量的求贤,和以往的跳槽不同,这次我主要通过猎头去寻找面试机会,因为在上一份工作中从猎头那里尝到了一点甜头,那就是会帮你要薪资嘛????。但是也有不好的地方,我把我的简历在猎聘等招聘平台上公开后,每天都有不计其数的猎头打电话过来推荐职位,而且每个猎头都要加到你的微信,可能会出现的问题是,他们给你推荐的职位不一定是你喜欢的公司,也不一定是适合你的,有的公司当你去面试了才发现你并不喜欢,这就浪费了双方的时间成本。所以最近面了很多家,大厂、中厂、小厂、创业的都面过,见识到了各种面试官,也被问到各种面试题,也收到几个offer,但自己满意的确甚少,回想最近的经历,想做一个总结,总结一下面试中可以用的一些套路,和可能会遇到的一些套路。正文我把整个面试分为面试前、面试中、和面试后三个阶段,每个阶段都有每个阶段的套路。我想到的和遇到的套路有限,后续会补充更新,所以这条“路”暂时还没有很长。。。话不多数,上车????。。。面试前1. 简历的准备好的简历是获取更多面试机会的前提,关于如何写一份优质的简历和简历优化等方面的话题我也不是专家,在这里说一下自己的总结。简历中的重要部分你目前掌握的技能,每个技能点是了解、熟悉、熟练使用、还是精通。项目经历,其中要包括项目描述、你的角色职责、使用技术、业绩收获等。如果你有写博客的习惯,放上你的博客地址,是非常不错的加分项。简历中废话不要太多,要言简意赅,亮点技能着重突出,这可以让HR筛选简历时被吸引到,篇幅页数不要太多,最好不要超过三页,因为很多面试官在面试你的时候事前是没有浏览过你的简历的,简单直接地表达你的技能和经历能让面试官快速的了解你。格式和排版格式最好 PDF 吧,比较稳定。至于排版等样式问题,我觉得这不太重要,因为猎头再给你推荐简历的时候都会把你的简历内容写进他们的推荐报告了,所以版式都是各个猎头公司的版式,我习惯从拉钩网上在线编辑简历,然后导出 PDF,因为对比了几家招聘平台的在线简历,好像也就拉钩的样式比较好看。要不要针对性的写多份简历如果有自己心仪的公司,并且了解到了需求的技能,为了提高简历审核通过率,可以针对性的优化自己的技能树,但是如果通过猎头来寻找面试机会的话,我感觉就没什么必要这么做了,原因如下:猎头会根据企业的招聘需求去平台上筛选优质的候选人,猎头既然给你打电话就说明你符合当前企业的用人标准,契合度已经有了。现实情况,肯定会有很多猎头打电话来,如果很多企业约面的话,准备有针对性的简历也是技术活,耗时间,有空咋不去准备面试题呢?简历上别给自己挖坑面试官一般会按照你简历中提到的技能去提问,所以表达技能的时候用词要恰当。2. 跟猎头的沟通筛选合适的公司公开简历前,有几个问题自己要确定,求职公司的行业有何要求或倾向、规模有何要求或倾向、考虑的位置范围、期望薪资、技术栈的倾向。这些问题的答案可以作为筛选猎头推荐给你职位的筛选标准,同时也会被写进他们的推荐报告中,所以回答要爽快。拒绝不合适不喜欢的公司当猎头推荐给你的职位和公司各方面都引不起你的兴趣,可以从多方面再去了解公司,不满足你的求职期望的一律 pass,也许猎头会说不如去试试嘛,当做经验也好,是的,我去试了好几家这种,但是最终还是不感兴趣,浪费了双方时间。所以,要学会拒绝。。。约面企业通过简历后,猎头来跟你确认面试时间。§套路一: 心仪的公司稍微往后放,其他的放在前边可以当做总结经验。3. 约面后的准备仔细研究岗位 JD,分析岗位的技能需求点,对比自己的技能点,JD 中强调的技能,提前全面熟悉一下。了解面试流程,是否有笔试题?一般几轮面试?是否在当天敲定结果?如果有笔试题,那最好事先刷一刷笔试题库,一面的笔试题重在考察基础以及对技能了解的全面性。最好通过猎头问清楚到底有几轮面试,这很重要,一般一线大厂技术面试都会有三到四轮,如果聊得好,整个过程要很长时间,这时分享一个经验:§套路二: 如果是一线大厂面试,最好不要约在上午,最适宜时间:下午两点。理由:三到四轮的面试如果顺利,可能面到三面就已经两个多小时了,而大部分互联网公司上班时间是十点左右,最早也只能约到上午十点,可能会碰到的情况是,二面结束了,结果三面技术官吃饭去了,那你只能回去,等通知下次再来,是不是觉得很不爽? 嗯,是的。我就碰到了????。所以下午两点开始面试,聊到四面也差不多五点左右,顺利的话,当天还能聊 HR。§套路三: 如果了解到面试流程是一次敲定的那种,选个接近饭点的时间????。理由:聊一会就到饭点了嘛,这时候面试官可能因为肚子饿了,精神也不太集中了,也许会早点下决定???? ,此条套路带有心理学因素,参考即可,并不完全适用。面试中1. 自我介绍简历中有的信息就别重复闲扯啦,简单的由近到远介绍公司经历,然后介绍项目,突出项目中用到的技术点,你的职责,你的业绩,引导面试官去提问你熟练掌握的技能。2. 不要给自己挖坑举例子:面试官让你把一个数组乱序,然后你说了可以用数组的sort方法快速实现,在比较函数里传入一个随机数,function arrRandom(arr) { arr.sort(function () { return 0.5 - Math.random() })}“还有别的实现吗?”“有啊,可以用 for 循环实现,不过也是借助Math.random(), 再就是两者写法上时间和空间复杂度可能有些许区别吧。”“哟,听到时间复杂度的概念,看来对算法有所了解啊,来来来,给我仔细讲讲”………????其实我对算法真的仅仅是了解而已啊。这个例子仅仅是想说,不要在面试中扯出一些你不熟悉的技能,面试官听到后都会跟着追问你。3. 减少沉默的尴尬了解问题后,快速想思路,想不到就换种方式回答,或者回答一个类似技能点的答案。 不然一直没想出来,沉默了半天,不仅会造成气氛尴尬,还会增加你的紧张,后边脑子就更不好使啦。4. 分析面试官话语中想表达的想法经常 “嗯嗯, 可以。。。”–鉴定安全 ????短暂沉默,对你的回答不做评价。–可能让他失望了 ????现在插入一波 被套路 汇总:套路一:“今天面试要不就先到这,后续有流程我们会在通知你的,谢谢你。。。”“你的情况我大概了解了,我要和我们老大汇报一下,商量过后,后续面试再通知你。。。”这是经典的拒绝你的语言模板!!!????????????汇报是真的,他们总会对比候选人,但后续通知你可能是假的。套路二:“感谢你今天能来面试。。。”面试结束后,即使你感觉和面试官聊得很愉快,时间也挺久的,但他最后送你的时候说了这句话,就要注意了,这可能是一种委婉的说了句抱歉。套路三:“我们二面技术官好像不在,要不你先回去等通知吧。。。”有可能是真的不在,也有可能是说辞。。。套路四:“你有什么想要问我的吗?”一看表,才半小时啊?面试时间小于半小时,那…. 基本凉凉了,不想再了解了。套路五:一面或者二面聊得很好,面试官让你稍等一下,你以为会二面或三面,结果一会 hr 过来让你回去等通知。罢了,当做凉凉看待吧。以上是常见套路解析,面试后分析根据以上分析结果,表现好但没进入二面三面,就别抱希望了,如果后续通知你再去面试,惊喜也比失落好吧。5. 二面或三面常问的问题你未来一到三年的一个职业规划是什么?你都是怎么去学习和关注新技术的?你近几年工作中有哪些心得或总结?你觉得你在工作中的优缺点是什么?你过来我们公司,你的优势是什么?你有那些你觉得你写的不错的代码块,分享一下。有些过开源项目吗?写过 npm 包吗,写过 webpack 插件吗?看过哪些框架或者类库的源码,有什么收获?…..这类问题都是开放性问题,没有标准答案,想好再说,不然有可能给自己挖坑。面试后1. 记录面试中问到的问题和笔试题把面试中遇到的问题,都记录下来,查阅总结每个问题考察的技术点,这些问题在后续的面试中也会遇到,再者,面试中问的问题一般是开发中常会遇到的,如果你没回答好,他可能认为你没遇到或者经验不足,这是很大的坑。2. 总结自己的表现,找可以优化的空间换位思考,如果你是面试官,评估自己的回答,语言表达、逻辑条理有没有优化的空间 。3. 及时和猎头跟进面试进度负责人的猎头都会跟进你的面试进度,面试后可以向他们讲述你的经过和结果,他们可以第一时间获取面试官的反馈和跟进后续流程。说到猎头,我想吐槽一下。4. 吐槽猎头收到很多职位推荐,同时也被很多猎头加微信,刚才说到负责任的猎头在你的面试前会对你进行一个简单的辅导,面试中跟进进度,每个阶段提醒你后续流程的准备,面试后不管结果如何,也会给你要一个反馈结果,即使失败了,也会要到技术官给你的建议和反馈。相对也有我认为不太负责任的猎头,面试后也没有问你面的如何,后续的结果没有通知你,甚至你去问了他,也很久才回复你,这种情况,结果肯定是没通过。但是,这种感觉很不爽,给人的感觉像是,当前候选人没通过,就去寻找下一个候选人,这个后边就不管了。。。。碰见这种猎头,我都会互删好友????最后本篇文章总结最近面试的一些感受,和经常会遇到的套路,可能有些的方废话有些多,后续会补充更新更多的套路。TODO二面三面常问的问题后续写新文章总结答案汇总更多套路,大家也可以评论贡献。????

October 19, 2018 · 1 min · jiezi

09.27 顺丰一面经历

算法题:现在有一组数据,一组因子a,b,c,利用公式 xa+yb+z*c可以得到一个结果。问怎样可以求出因子a,b,c对结果的影响?2. 介绍自己技术方面的能力3. ES6的了解?4. Promise有哪三个状态?怎样才可以到catch?5. 组件化怎样理解?6. Vue组件通信有多少种方法?7. localStorage和vuex的区别?区别:vuex存储在内存,localstorage(本地存储)则以文件的方式存储在本地,永久保存;sessionstorage( 会话存储 ) ,临时保存。localstorage和sessionstorage只能存储字符串类型,对于复杂的对象可以使用ECMAScript提供的JSON对象的stringify和parse来处理应用场景:vuex用于组件之间的传值,localstorage,sessionstorage则主要用于不同页面之间的传值。(其他页面更新数据了,当前页面要刷新才能相应更新,非响应式的)永久性:当刷新页面(这里的刷新页面指的是 –> F5刷新,属于清除内存了)时vuex存储的值会丢失,sessionstorage页面关闭后就清除掉了,localstorage不会。注:很多人觉得用localstorage可以代替vuex, 对于不变的数据确实可以,但是当两个组件共用一个数据源(对象或数组)时,如果其中一个组件改变了该数据源,希望另一个组件响应该变化时,localstorage,sessionstorage无法做到,原因就是区别1。8. 正则表达式有了解过吗?如何匹配手机号?9.学软件工程的为什么要做前端?10. 如何理解前端?

October 17, 2018 · 1 min · jiezi

项目经验不丰富、技术不突出的程序员怎么打动面试官?

前言相信不少的程序员都有过类似的困惑:如果我没有大型的项目经历,也不能靠技术征服面试官,那我要怎么才能给面试官留下一个好印象呢?按照本人的面试经验来说,面试主要看几点:项目经验+基本技术+个人潜力关于项目经验我认为方腾飞讲的一段话非常好:介绍产品时面试官会考察应聘者的沟通能力和思考能力,我们大部分情况都是做产品的一个功能或一个模块,但是即使是这样,自己有没有把整个系统架构或产品搞清楚,并能介绍清楚,为什么做这个系统?这个系统的价值是什么?这个系统有哪些功能?优缺点有哪些?如果让你重新设计这个系统你会如何设计?我觉得这就已经足以概括了。也许你仅仅工作一年,也许你做的是项目中微不足道的模块,当然这些一定是你的劣势且无法改变,但是如何弥补这个劣势,从方老师的话中我总结几点:明确你的项目到底是做什么的,有哪些功能明确你的项目的整体架构,在面试的时候能够清楚地画给面试官看并且清楚地指出从哪里调用到哪里、使用什么方式调用明确你的模块在整个项目中所处的位置及作用明确你的模块用到了哪些技术,更好一些的可以再了解一下整个项目用到了哪些技术在你无法改变自己的工作年限、自己的不那么有说服力的项目经验的情况下(这一定是扣分项),可以通过这种方式来一定程度上地弥补并且增进面试官对你的好感度。补充一点,在面试中聊你的项目的时候,有一个问题90%是绕不过的:谈一下你在项目中解决过的比较复杂的问题。这需要在工作中不断去发现和探索,不需要多,在你自己目前的项目中只要你找到一两个能说的问题就行。一个小技巧是,即使问题不是你解决的而是别人解决的,但是你把这个问题弄懂、搞透了,在面试的时候你一样可以把这个问题当作是你自己解决的来说—-毕竟,谁来管这个问题当时到底是不是你解决的呢?关于基本技术说完了项目经验,接下来说一说1-3年的Java程序员应该具备的技术能力,这些能力你掌握的越多,给面试官的感觉和最终拿到的薪资也就越高。1、基本语法这包括static、final、transient等关键字的作用,foreach循环的原理等等。今天面试我问你static关键字有哪些作用,如果你答出static修饰变量、修饰方法我会认为你合格,答出静态块,我会认为你不错,答出静态内部类我会认为你很好,答出静态导包我会对你很满意,因为能看出你非常热衷研究技术。最深入的一次,记得面试官直接问到了我volatile关键字的底层实现原理(顺便插一句,面试和被面试本身就是相对的,面试官能问这个问题同时也让面试者感觉到面试官也是一个喜爱研究技术的人,增加了面试者对公司的好感,我最终选择的就是问了这个问题的公司),不要觉得这太吹毛求疵了—-越简单的问题越能看出一个人的水平,别人对你技术的考量绝大多数都是以深度优先、广度次之为标准的,切记。2、集合非常重要,也是必问的内容。基本上就是List、Map、Set,问的是各种实现类的底层实现原理,实现类的优缺点。集合要掌握的是ArrayList、LinkedList、Hashtable、HashMap、ConcurrentHashMap、HashSet的实现原理,能流利作答,当然能掌握CopyOnWrite容器和Queue是再好不过的了。另外多说一句,ConcurrentHashMap的问题在面试中问得特别多,大概是因为这个类可以衍生出非常多的问题,关于ConcurrentHashMap,我给网友朋友们提供三点回答或者是研究方向:ConcurrentHashMap的锁分段技术ConcurrentHashMap的读是否要加锁,为什么ConcurrentHashMap的迭代器是强一致性的迭代器还是弱一致性的迭代器3、设计模式本来以为蛮重要的一块内容,结果只在阿里巴巴B2B事业部面试的时候被问了一次,当时问的是装饰器模式。当然咱们不能这么功利,为了面试而学习,设计模式在工作中还是非常重要、非常有用的,23种设计模式中重点研究常用的十来种就可以了,面试中关于设计模式的问答主要是三个方向:你的项目中用到了哪些设计模式,如何使用知道常用设计模式的优缺点能画出常用设计模式的UML图4、多线程这也是必问的一块了。因为三年工作经验,所以基本上不会再问你怎么实现多线程了,会问得深入一些比如说Thread和Runnable的区别和联系、多次start一个线程会怎么样、线程有哪些状态。当然这只是最基本的,出乎意料地,几次面试几乎都被同时问到了一个问题,问法不尽相同,总结起来是这么一个意思:假如有Thread1、Thread2、Thread3、Thread4四条线程分别统计C、D、E、F四个盘的大小,所有线程都统计完毕交给Thread5线程去做汇总,应当如何实现?聪明的网友们对这个问题是否有答案呢?不难,java.util.concurrent下就有现成的类可以使用。另外,线程池也是比较常问的一块,常用的线程池有几种?这几种线程池之间有什么区别和联系?线程池的实现原理是怎么样的?实际一些的,会给你一些具体的场景,让你回答这种场景该使用什么样的线程池比较合适。说到这里顺便给大家推荐一个Java后端方面的交流学习社区:586446657,里面不仅可以交流讨论,还有面试经验分享以及免费的资料下载,包括Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。相信对于已经工作和遇到技术瓶颈的码友,在这里会有你需要的内容。最后,虽然这次面试问得不多,但是多线程同步、锁这块也是重点。synchronized和ReentrantLock的区别、synchronized锁普通方法和锁静态方法、死锁的原理及排查方法等等。5、IOIO分为File IO和Socket IO,File IO基本上是不会问的,问也问不出什么来,平时会用就好了,另外记得File IO都是阻塞IO。Socket IO是比较重要的一块,要搞懂的是阻塞/非阻塞的区别、同步/异步的区别,借此理解阻塞IO、非阻塞IO、多路复用IO、异步IO这四种IO模型,Socket IO如何和这四种模型相关联。这是基本一些的,深入一些的话,就会问NIO的原理、NIO属于哪种IO模型、NIO的三大组成等等,这有些难,当时我也是研究了很久才搞懂NIO。提一句,NIO并不是严格意义上的非阻塞IO而应该属于多路复用IO,面试回答的时候要注意这个细节,讲到NIO会阻塞在Selector的select方法上会增加面试官对你的好感。如果用过Netty,可能会问一些Netty的东西,毕竟这个框架基本属于当前最好的NIO框架了(Mina其实也不错,不过总体来说还是比不上Netty的),大多数互联网公司也都在用Netty。6、JDK源码要想拿高工资,JDK源码不可不读。上面的内容可能还和具体场景联系起来,JDK源码就是实打实地看你平时是不是爱钻研了。我面试过程中被问了不少JDK源码的问题,其中最刁钻的一个是:String的hashCode()方法是怎么实现的,幸好咱平时String源代码看得多,答了个大概。JDK源码其实没什么好总结的,纯粹看个人,总结一下比较重要的源码:List、Map、Set实现类的源代码ReentrantLock、AQS的源代码AtomicInteger的实现原理,主要能说清楚CAS机制并且AtomicInteger是如何利用CAS机制实现的线程池的实现原理Object类中的方法以及每个方法的作用这些其实要求蛮高的,去年一整年基本把JDK中重要类的源代码研究了个遍,真的花费时间、花费精力,当然回头看,是值得的—-不仅仅是为了应付面试。7、框架老生常谈,面试必问的东西。一般来说会问你一下你们项目中使用的框架,然后给你一些场景问你用框架怎么做,比如我想要在Spring初始化bean的时候做一些事情该怎么做、想要在bean销毁的时候做一些事情该怎么做、MyBatis中$和#的区别等等,这些都比较实际了,平时积累得好、有多学习框架的使用细节自然都不成问题。如果上面你的问题答得好,面试官往往会深入地问一些框架的实现原理。问得最多的就是Spring AOP的实现原理,当然这个很简单啦,两句话就搞定的的事儿,即使你不会准备一下就好了。我遇到的最变态的是让LZ画一下Spring的Bean工厂实现的UML图,当然面对这样一个有深度的问题,按目前的水准是绝对答不出来的/(ㄒoㄒ)/~~8、数据库数据库十有八九也都会问到。一些基本的像union和union all的区别、left join、几种索引及其区别就不谈了,比较重要的就是数据库性能的优化,如果对于数据库的性能优化一窍不通,那么有时间,还是建议你在面试前花一两天专门把SQL基础和SQL优化的内容准备一下。不过数据库倒是不用担心,一家公司往往有很多部门,如果你对数据库不熟悉而基本技术又非常好,九成都是会要你的,估计会先把你放到对数据库使用不是要求非常高的部门锻炼一下。9、数据结构和算法分析数据结构和算法分析,对于一名程序员来说,会比不会好而且在工作中绝对能派上用场。数组、链表是基础,栈和队列深入一些但也不难,树挺重要的,比较重要的树AVL树、红黑树,可以不了解它们的具体实现,但是要知道什么是二叉查找树、什么是平衡树,AVL树和红黑树的区别。记得某次面试,某个面试官和我聊到了数据库的索引,他问我:你知道索引使用的是哪种数据结构实现吗?我答到用的Hash表吧,答错。他又问,你知道为什么要使用树吗?我答到因为Hash表可能会出现比较多的冲突,在千万甚至是上亿级别的数据面前,会大大增加查找的时间复杂度。而树比较稳定,基本保证最多二三十次就能找到想要的数据,对方说不完全对,最后我们还是交流了一下这个问题,我也明白了为什么要使用树,这里不说,网友朋友们觉得索引为什么要使用树来实现呢?至于算法分析,不会、不想研究就算了,记得某次面试对方问我,Collections.sort方法使用的是哪种排序方法,额,吐血三升。当然为了显示自己的博学,对算法分析也有一定的研究(⊙﹏⊙)b,我还是硬着头皮说了一句可能是冒泡排序吧。当然答案肯定不是,有兴趣的网友朋友们可以去看一下Collections.sort方法的源代码,用的是一种叫做TimSort的排序法,也就是增强型的归并排序法。10、Java虚拟机出乎我的意料,Java虚拟机应该是很重要的一块内容,结果在这几家公司中被问到的概率几乎为0。要知道,LZ去年可是花了大量的时间去研究Java虚拟机的,光周志明老师的《深入理解Java虚拟机:JVM高级特性与最佳实践》,我就读了不下五遍。言归正传,虽然Java虚拟机没问到,但我觉得还是有必要研究的,就简单地列一个提纲吧,谈谈Java虚拟机中比较重要的内容:Java虚拟机的内存布局GC算法及几种垃圾收集器类加载机制,也就是双亲委派模型Java内存模型happens-before规则volatile关键字使用规则也许面试无用,但在走向大牛的路上,不可不会。11、Web方面的一些问题Java主要面向Web端,因此Web的一些问题也是必问的。我碰到过问得最多的两个问题是:谈谈分布式Session的几种实现方式常用的四种能答出来自然是让面试官非常满意的,另外一个常问的问题是:讲一下Session和Cookie的区别和联系以及Session的实现原理这两个问题之外,web.xml里面的内容是重点,Filter、Servlet、Listener,不说对它们的实现原理一清二楚吧,至少能对它们的使用知根知底。另外,一些细节的方面比如get/post的区别、forward/重定向的区别、HTTPS的实现原理也都可能会被考察到。最后,如果有兴趣有时间,建议学习、研究一下SOA和RPC,面向服务体系,大型分布式架构必备,救命良方、包治百病、屡试不爽。关于个人潜力关于这一点我觉得是应该是贯穿于整个面试过程的。如果你能做到如下几点,相信会在面试官心里留下一个好印象。面试时能够保持良好、平稳的心态。条理清晰地回答面试官的问题,同时体现自己有较浓的技术兴趣。整个面试过程态度积极向上,不要有任何悲观消极的态度(尤其在谈到以前公司情况的时候,即使有再多的不满),就不会有问题。关于HR面,不能轻视。很多公司HR对面试者都有一票否决权。因此碰到一些难处理的问题一定要慎重回答,四个字:滴水不漏。不是所有的程序员都是大牛,都能靠技术征服面试官。大部分程序员没有太好的项目经验、技术也不是特别突出,不必妄自菲薄,因为还有很多人和你一样。重要的是:怎么在和你差不多的程序员中脱颖而出?我相信,只要你在面试过程中体现出对技术的热情、积极向上的态度、不卑不亢的风貌就能给面试官留下一个良好的印象,而当你具备了这些特质时,那你已经变成准大牛了。试问,还有哪家求贤若渴的公司会因为一些表面的问题刁难你呢?还有哪个公司会拒绝你成为储备人才呢?结语想起一句话——你若盛开,清风自来。献给正在程序员修炼路上奋力前行的你们!共勉!

October 15, 2018 · 1 min · jiezi

【Leetcode】81. 搜索旋转排序数组 II

题目假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。示例 1:输入: nums = [2,5,6,0,0,1,2], target = 0输出: true示例 2:输入: nums = [2,5,6,0,0,1,2], target = 3输出: false进阶:这是 搜索旋转排序数组 的延伸题目,本题中的 nums 可能包含重复元素。这会影响到程序的时间复杂度吗?会有怎样的影响,为什么?题解这道题与之前Search in Rotated Sorted Array类似,问题只在于存在dupilcate。那么和之前那道题的解法区别就是,不能通过比较A[mid]和边缘值来确定哪边是有序的,会出现A[mid]与边缘值相等的状态。当中间值与边缘值相等时,让指向边缘值的指针分别往前移动,忽略掉这个相同点,再用之前的方法判断即可。而如果解决掉重复之后,利用一个性质,旋转后两部分一定有一部分有序(自己找两个例子画画看),那么通过判断左边还是右边有序分为两种情况。然后再判断向左走还是向右走。这一改变增加了时间复杂度,试想一个数组有同一数字组成{1,1,1,1,1},target=2, 那么这个算法就会将整个数组遍历,时间复杂度由O(logn)升到O(n)。javaclass Solution { public boolean search(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right) { int mid = (left + right) / 2; if (nums[mid] == target) return true; if (nums[mid] == nums[left]) left++; //重复了,跳过 else if (nums[mid] > nums[left]) { //左边有序 if (nums[mid] > target && nums[left] <= target) right = mid - 1; else left = mid + 1; } else { //右边有序 if (nums[mid] < target && nums[right] >= target) left = mid + 1; else right = mid - 1; } } return false; }}pythonclass Solution: def search(self, nums, target): """ :type nums: List[int] :type target: int :rtype: bool """ left = 0 right = len(nums) - 1 while left <= right: mid = (left + right) // 2 if nums[mid] == target: return True # 重复了,跳过 if nums[mid] == nums[left]: left += 1 elif nums[mid] > nums[left]: if nums[mid] > target >= nums[left]: right = mid - 1 else: left = mid + 1 else: if nums[mid] < target <= nums[right]: left = mid + 1 else: right = mid - 1 return False相关阅读【设计模式】单例模式布隆过滤器 ...

October 12, 2018 · 2 min · jiezi

Vue 常见面试问题,你可能都知道,但能答好吗?

GitHub 地址,后面继续补充,star不迷路。计算属性(computed)、方法(methods)和侦听属性(watch)的区别与使用场景Vue 生命周期的理解Vue 双向绑定,为什么不能通过修改下标来通知视图发生变化简述 Vue 中的 MVVM 模型Vue 路由中 hash 模式和 history 模式区别Vue 路由中 $route 和 $router 的区别计算属性(computed)、方法(methods)和侦听属性(watch)的区别与使用场景methods VS 计算属性我们可以将同一函数定义为一个 method 而不是一个计算属性。对于最终的结果,两种方式确实是相同的。然而,不同的是计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。相比而言,只要发生重新渲染,method 调用总会执行该函数。总之,重新计算开销很大的话请选计算属性,不希望有缓存的请选methods。watch VS 计算属性当你在模板内使用了复杂逻辑的表达式时,你应当使用计算属性。侦听属性是一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。 当你有一些数据需要随着其它数据变动而变动时,或者当需要在数据变化时执行异步或开销较大的操作时,你可以使用 watch。Vue 生命周期的理解如何解释vue的生命周期才能令面试官满意?beforeCreate在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。created在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。beforeMount在挂载开始之前被调用:相关的 render 函数首次被调用。 该钩子在服务器端渲染期间不被调用。以下周期在服务端渲染期间都不被调用。mountedel 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted。beforeUpdate数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。updated由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。activatedkeep-alive 组件激活时调用。deactivatedkeep-alive 组件停用时调用。beforeDestroy实例销毁之前调用。在这一步,实例仍然完全可用。destroyedVue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。Vue 双向绑定,为什么不能通过修改下标来通知视图发生变化。Vue为什么不能检测数组变动变异方法:Vue 包含一组观察数组的变异方法,所以它们也将会触发视图更新。这些方法如下:push(), pop(), shift() ,unshift(), splice(), sort(), reverse()替换数组:filter(), concat() 和 slice() 。这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组:注意事项:由于 JavaScript 的限制,Vue 不能检测以下变动的数组:当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue当你修改数组的长度时,例如:vm.items.length = newLength变通方法:Vue.set(vm.items, indexOfItem, newValue)vm.items.splice(indexOfItem, 1, newValue)简述 Vue 中的 MVVM 模型Vue是以数据为驱动的,Vue自身将DOM和数据进行绑定,一旦创建绑定,DOM和数据将保持同步,每当数据发生变化,DOM会跟着变化。ViewModel是Vue的核心,它是Vue的一个实例。Vue实例是作用在某个HTML元素上的,这个HTML元素可以是body,也可以是某个id所指代的元素。 DOM Listeners和Data Bindings是实现双向绑定的关键。DOM Listeners监听页面所有View层DOM元素的变化,当发生变化,Model层的数据随之变化;Data Bindings监听Model层的数据,当数据发生变化,View层的DOM元素随之变化。Vue 路由中 hash 模式和 history 模式区别hash模式hash 出现在 URL 中,但不会被包含在 http 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,hash不会重加载页面。history模式history 利用了 html5 history interface 中新增的 pushState() 和 replaceState() 方法。这两个方法应用于浏览器记录栈,在当前已有的 back、forward、go 基础之上,它们提供了对历史记录修改的功能。只是当它们执行修改时,虽然改变了当前的 URL ,但浏览器不会立即向后端发送请求。原理:hash 模式的原理是 onhashchange 事件,可以在 window 对象上监听这个事件。 history :hashchange 只能改变 # 后面的代码片段,history api (pushState、replaceState、go、back、forward) 则给了前端完全的自由,通过在window对象上监听popState()事件。Vue 路由中 $route 和 $router 的区别$route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。 $router是“路由实例”对象包括了路由的跳转方法,钩子函数等。 ...

October 8, 2018 · 1 min · jiezi

百度阿里网易大疆等大小厂前端校招面筋

自我介绍下:某985硕士,程序媛,接触前端一年时间。从八月份开始校招面试笔试,前前后后大厂小厂也都面了挺多,不过大厂基本都被我挂完了,哭晕我,还是太菜啊。面过的公司:ThoughtWorks,大疆,阿里,网易,百度,电信it研发中心,深信服,华为,小米,搜狗。拿了offer的公司目前是:大疆、电信、深信服(已拒),前辈们也可以留下选择哪个公司的建议,感激不尽。下面总结了这段时间的面筋和挂筋大疆大疆是我校招面的第一家公司,从六月份投简历,然后笔试面试到拿到录用意向书,前后用了近四个月,来之不易啊。一面二面因为时间太久,就直接放在一起了,问的都是基础吧,讲真,大疆前端面试不难,都是很基础的,就是时间长,等的捉急。一面是电话面,两个面试官轮流问;二面是视频面,是三个面试官一起微信视频,视频面还是蛮累的,上下左右都得顾上;终面是去的现场面,就跟一个面试官聊了十几分钟人生。1. meta标签meta标签:提供给页面的一些元信息(名称/值对), 比如针对搜索引擎和更新频度的描述和关键词。name:名称/值对中的名称。常用的有author、description、keywords、generator、revised、others。 把 content 属性关联到一个名称。http-equiv:没有name时,会采用这个属性的值。常用的有content-type、expires、refresh、set-cookie。把content属性关联到http头部。content: 名称/值对中的值, 可以是任何有效的字符串。 始终要和 name 属性或 http-equiv 属性一起使用。scheme: 用于指定要用来翻译属性值的方案。2. css哪些属性可以继承字体相关:line-height, font-family, font-size, font-style, font-variant, font-weight, font文本相关: letter-spacing, text-align, text-indent, text-transform, word-spacing列表相关:list-style-image, list-style-position, list-style-type, list-style颜色:color3. css3有哪些新属性(1)边框:border-radius:圆角边框,border-radius:25px;box-shadow:边框阴影,box-shadow: 10px 10px 5px #888888;border-image:边框图片,border-image:url(border.png) 30 30 round;(2)背景:background-size:规定背景图片的尺寸,background-size:63px 100px;background-origin:规定背景图片的定位区域,背景图片可以放置于 content-box、padding-box 或 border-box 区域。background-origin:content-box;CSS3 允许您为元素使用多个背景图像。background-image:url(bg_flower.gif),url(bg_flower_2.gif);(3)文本效果:text-shadow:向文本应用阴影,可以规定水平阴影、垂直阴影、模糊距离,以及阴影的颜色。text-shadow: 5px 5px 5px #FF0000;word-wrap:允许文本进行换行。word-wrap:break-word;(4)字体:CSS3 @font-face 规则可以自定义字体。(5)2D 转换(transform)translate():元素从其当前位置移动,根据给定的 left(x 坐标) 和 top(y 坐标) 位置参数。 transform: translate(50px,100px);rotate():元素顺时针旋转给定的角度。允许负值,元素将逆时针旋转。transform: rotate(30deg);scale():元素的尺寸会增加或减少,根据给定的宽度(X 轴)和高度(Y 轴)参数。transform: scale(2,4);skew():元素翻转给定的角度,根据给定的水平线(X 轴)和垂直线(Y 轴)参数。transform: skew(30deg,20deg);matrix(): 把所有 2D 转换方法组合在一起,需要六个参数,包含数学函数,允许您:旋转、缩放、移动以及倾斜元素。transform:matrix(0.866,0.5,-0.5,0.866,0,0);(6)3D 转换rotateX():元素围绕其 X 轴以给定的度数进行旋转。transform: rotateX(120deg);rotateY():元素围绕其 Y 轴以给定的度数进行旋转。transform: rotateY(130deg);(7)transition:过渡效果,使页面变化更平滑transition-property :执行动画对应的属性,例如 color,background 等,可以使用 all 来指定所有的属性。transition-duration:过渡动画的一个持续时间。transition-timing-function:在延续时间段,动画变化的速率,常见的有:ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier 。transition-delay:延迟多久后开始动画。简写为:transition: [<transition-property> || <transition-duration> || <transition-timing-function> || <transition-delay>];(8)animation:动画使用CSS3 @keyframes 规则。animation-name: 定义动画名称animation-duration: 指定元素播放动画所持续的时间长animation-timing-function:ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier(<number>, <number>, <number>, <number>): 指元素根据时间的推进来改变属性值的变换速率,说得简单点就是动画的播放方式。animation-delay: 指定元素动画开始时间animation-iteration-count:infinite | <number>:指定元素播放动画的循环次animation-direction: normal | alternate: 指定元素动画播放的方向,其只有两个值,默认值为normal,如果设置为normal时,动画的每次循环都是向前播放;另一个值是alternate,他的作用是,动画播放在第偶数次向前播放,第奇数次向反方向播放。animation-play-state:running | paused :控制元素动画的播放状态。简写为: animation:[<animation-name> || <animation-duration> || <animation-timing-function> || <animation-delay> || <animation-iteration-count> || <animation-direction>]这里只列出了一部分,详情可以去看w3school的CSS3 教程。4. 闭包是什么,什么时候闭包会消除?因为作用域链,外部不能访问内部的变量和方法,这时我们就需要通过闭包,返回内部的方法和变量给外部,从而就形成了一个闭包。JavaScript是一门具有自动垃圾回收机制的编程语言,主要有两种方式:标记清除(最常用)垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。引用计数引用计数(reference counting)的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。导致问题:会导致循环引用的变量和函数无法回收。解决:将用完的函数或者变量置为null。5. 怎么理解js是单线程的主要说一下异步以及事件循环机制,还有事件队列中的宏任务、微任务。macrotask:主代码块,setTimeout,setInterval、setImmediate等。microtask:process.nextTick(相当于node.js版的setTimeout),Promise 。process.nextTick的优先级高于Promise。更详细可以看这篇博客:这一次,彻底弄懂 JavaScript 执行机制,讲的非常清晰。6. 有哪些排序算法,时间复杂度是多少?什么时候快排的效率最低?排序算法最坏事件复杂度平均时间复杂度稳定度空间复杂度冒泡排序O(n^2)O(n^2)稳定O(1)插入排序O(n^2)O(n^2)稳定O(1)选择排序O(n^2)O(n^2)稳定O(1)快速排序O(n^2)O(nlog2n)不稳定O(log2n)~O(n)二叉树排序O(n^2)O(nlog2n)不一定O(n)堆排序O(nlog2n)O(nlog2n)不稳定O(1)整个序列已经有序或完全倒序时,快排的效率最低。7. 原生ajax的请求过程创建全平台兼容的XMLHttpRequest对象:function getXHR(){ var xhr = null; if(window.XMLHttpRequest) {// 兼容 IE7+, Firefox, Chrome, Opera, Safari xhr = new XMLHttpRequest(); } else if (window.ActiveXObject) { try { xhr = new ActiveXObject(“Msxml2.XMLHTTP”);// 即MSXML3 } catch (e) { try { xhr = new ActiveXObject(“Microsoft.XMLHTTP”);// // 兼容 IE6, IE5,很老的api,虽然浏览器支持,功能可能不完善,故不建议使用 } catch (e) { alert(“您的浏览器暂不支持Ajax!”); } } } return xhr;}Ajax请求数据的过程:var xhr = getXHR();xhr.open(‘GET’, url/file,true); //设置请求方式,url,以及是否异步xhr.onreadystatechange = function() { //设置回调监听函数 if(xhr.readyState==4){ if(xhr.status==200){ var data=xhr.responseText; console.log(data); }};xhr.onerror = function() { console.log(“Oh, error”);};xhr.send(); //发送请求8. http状态码,cookie字段,cookie一般存的是什么,session怎么存在的?这部分可以参考我的博客:HTTP协议知识点总结9. http请求方式有哪些?HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。更多请看:HTTP请求方法10. 怎么用原生js实现一个轮播图,以及滚动滑动之前我使用轮播图都是用的antd的组件,所以我大致说了一下思路,用定时器去实现,以及如何实现平滑的滚动效果。详情请看: 原生js实现轮播图11. 用过哪些开源的组件说了antd和element-ui。12. 怎么实现上传下载的功能主要说了下form表单和input标签。13. react生命周期,以及diff算法,diff算法是对树的深度优先遍历还是广度优先遍历?对React、Redux、React-Redux详细剖析是深度优先遍历。 diff的实现14. 强缓存和协商缓存参考:HTTP协议知识点总结15. react-router的原理react-router就是控制不同的url渲染不同的组件。react-router在history库的基础上,实现了URL与UI的同步。原理:DOM渲染完成之后,给window添加onhashchange事件监听页面hash的变化,并且在state属性中添加了route属性,代表当前页面的路由。具体步骤:当点击链接,页面hash改变时,触发绑定在 window 上的 onhashchange 事件;在 onhashchange 事件中改变组件的 state中的 route 属性,react组件的state属性改变时,自动重新渲染页面;页面随着 state 中的route属性改变,自动根据不同的hash给Child变量赋值不同的组件,进行渲染。参考:react-router的实现原理16. 怎么用无人机捕获天空上的鸟这个题目我也不造啊,毕竟我没用过无人机,有知道的大神可以在评论中回答一下终面终面是去的现场,在深圳总部那边,基本就是闲聊了二十来分钟吧,面完还有hr小姐姐给我们介绍和参观了无人机,酷炫~做的项目中,哪个做的最深入最久为什么要做前端,喜欢做前端么未来的职业规划了解大疆么,大疆的文化是什么除了实习,还做过哪些项目如果生活富足,衣食无忧,你会选择干什么阿里巴巴阿里是提前批,找人内推了菜鸟网络,面了六轮,面的我怀疑人生了,中途四面本来已经挂了,后面三面面试官又捞起来给我加面了一轮,不过最后还是挂在了hr。一面1. css选择器,怎么选择相同的类id、class、标签、伪类、通配符等。可以用getElementsByClassName()选择相同的类。2. css3有哪些伪类,伪类选择器有哪些这里要区分一下伪类和伪元素的概念。根本区别在于它们是否创造了新的元素(抽象)。伪类:用于向某些选择器添加特殊的效果。例如,a标签的:link, :visited, :hover, :active; 以及 :first-child, :last-child。伪元素:是html中不存在的元素,用于将特殊的效果添加到某些选择器。例如:before,:after, :first-letter, :first-line。css3只新增了一个伪元素 ::selection(改变用户所选取部分的样式)。参考: CSS3 选择器——伪类选择器3. OSI七层网络模型OSI七层模型作用对应协议对应设备应用层它是计算机用户,以及各种应用程序和网络之间的接口HTTP, FTP, SMTP, POP3计算机设备表示层信息的语法语义以及它们的关系,如加密解密、转换翻译、压缩解压缩IPX, LPP, XDP会话层建立、维护、管理应用程序之间的会话SSL, TLS, DAP, LDAP 传输层服务点编址,分段与重组、连接控制、流量控制、差错控制TCP, UDP防火墙网络层为网络设备提供逻辑地址,进行路由选择、分组转发IP ARP RARP ICMP IGMP路由器数据链路层物理寻址,同时将原始比特流转变为逻辑传输路线PPTP, ARP, RARP交换机物理层机械、电子、定时接口通道信道上的原始比特流传输IEEE 802.2, Ethernet v.2, Internetwork网卡参考: 一张非常强大的OSI七层模型图解4. MVC和MVVM的区别Model用于封装和应用程序的业务逻辑相关的数据以及对数据的处理方法;View作为视图层,主要负责数据的展示;Controller定义用户界面对用户输入的响应方式,它连接模型和视图,用于控制应用程序的流程,处理用户的行为和数据上的改变。MVC将响应机制封装在controller对象中,当用户和你的应用产生交互时,控制器中的事件触发器就开始工作了。MVVM把View和Model的同步逻辑自动化了。以前Controller负责的View和Model同步不再手动地进行操作,而是交给框架所提供的数据绑定功能进行负责,只需要告诉它View显示的数据对应的是Model哪一部分即可。也就是双向数据绑定,就是View的变化能实时让Model发生变化,而Model的变化也能实时更新到View。参考: 浅析前端开发中的 MVC/MVP/MVVM 模式5. 用过哪些设计模式(1)单例模式定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。实现方法:先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。适用场景:一个单一对象。比如:弹窗,无论点击多少次,弹窗只应该被创建一次。(2)发布/订阅模式定义:又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。场景:订阅感兴趣的专栏和公众号。(3)策略模式定义:将一个个算法(解决方案)封装在一个个策略类中。优点:策略模式可以避免代码中的多重判断条件。策略模式很好的体现了开放-封闭原则,将一个个算法(解决方案)封装在一个个策略类中。便于切换,理解,扩展。策略中的各种算法可以重复利用在系统的各个地方,避免复制粘贴。策略模式在程序中或多或少的增加了策略类。但比堆砌在业务逻辑中要清晰明了。违反最少知识原则,必须要了解各种策略类,才能更好的在业务中应用。应用场景:根据不同的员工绩效计算不同的奖金;表单验证中的多种校验规则。(4)代理模式定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。应用场景:图片懒加载(先通过一张loading图占位,然后通过异步的方式加载图片,等图片加载好了再把完成的图片加载到img标签里面。)(5)中介者模式定义:通过一个中介者对象,其他所有相关对象都通过该中介者对象来通信,而不是互相引用,当其中的一个对象发生改变时,只要通知中介者对象就可以。可以解除对象与对象之间的紧耦合关系。应用场景: 例如购物车需求,存在商品选择表单、颜色选择表单、购买数量表单等等,都会触发change事件,那么可以通过中介者来转发处理这些事件,实现各个事件间的解耦,仅仅维护中介者对象即可。(6)装饰者模式定义:在不改变对象自身的基础上,在程序运行期间给对象动态的添加方法。应用场景: 有方法维持不变,在原有方法上再挂载其他方法来满足现有需求;函数的解耦,将函数拆分成多个可复用的函数,再将拆分出来的函数挂载到某个函数上,实现相同的效果但增强了复用性。参考: JavaScript设计模式6. Http状态码7. https怎么加密参考: HTTP协议知识点总结8. es6相比es5有哪些优点大概说一下:let、const,模板字符串,箭头函数,做异步处理的promise、generator、async await,es6模块等。参考: 阮一峰 —— ECMAScript 6 入门9. ajax请求过程不多说,上面有。10. 有哪些性能优化参考:嗨,送你一张Web性能优化地图前端优化不完全指南11. 懒加载怎么实现场景:一个页面中很多图片,但是首屏只出现几张,这时如果一次性把图片都加载出来会影响性能。这时可以使用懒加载,页面滚动到可视区在加载。优化首屏加载。实现:img标签src属性为空,给一个data-xx属性,里面存放图片真实地址,当页面滚动直至此图片出现在可视区域时,用js取到该图片的data-xx的值赋给src。优点:页面加载速度快,减轻服务器压力、节约流量,用户体验好。12. 项目中写过什么组件,组件有哪些功能主要介绍了下实习项目写过的组件,说了下如何实现的。二面1. react框架有哪些设计的好的地方主要介绍了以下几个部分:JSX语法组件化react单项数据流虚拟DOMreact生命周期2. react是怎么工作的,怎么提高性能主要还是说了下react的生命周期,还有shouldComponentUpdate这个函数,以及diff算法。3. redux有哪些需要改进,你觉得你用的不怎么舒服的地方?我当时说的是redux的subscribe方法有点麻烦,每次更新数据都要手动的subscribe一下,所以觉得react-redux的api封装的更好,用起来比较简单。参考:这段时间研究了下Redux,写写自己对它的感觉Redux数据流管理架构有什么致命缺陷,未来会如何改进?4. 怎么设计一个类似于antd 的 react 组件库这个问题把我给问懵了额,我是按照软件工程的生命周期流程来答的。5. 你做的最自豪的一个项目这个略过…言之有理即可6. mysql 的左关联和右关联左关联:保留左表中所有的元组,右表中没有的属性填充NULL。右关联:保留右表中所有的元组,左表中没有的属性填充NULL。7. 有没有折腾过后端直接说了没有,之前学了点PHP,不过都快忘得差不多了额。8. 学习方法和未来的学习路线言之有理即可。9. 浏览器页面渲染机制解析html建立dom树解析css构建render树(将CSS代码解析成树形的数据结构,然后结合DOM合并成render树)布局render树(Layout/reflow),负责各元素尺寸、位置的计算绘制render树(paint),绘制页面像素信息浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上。参考: 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理10. XSS和CSRF防范措施(1)XSS:跨站脚本攻击攻击方式:在URL或者页面输入框中插入JavaScript代码。防范:设置httpOnly,禁止用document.cookie操作;输入检查:在用户输入的时候进行格式检查;对输出转义。(2)CSRF:跨站点伪造请求攻击方式:攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。主要是拿到了用户的登录态。防范:检查 Referer 字段:这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer 字段应和请求的地址位于同一域名下。添加校验 Token:这种数据通常是表单中的一个数据项。服务器生成token并附加在表单中,其内容是一个伪乱数。当客户端通过表单提交请求时,这个伪乱数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪乱数,而通过 CSRF 传来的欺骗性攻击中,攻击者无从事先得知这个伪乱数的值,服务器端就会因为校验 Token 的值为空或者错误,拒绝这个可疑请求。通过输入验证码来校验合法请求。三面这一面基本问的是个人知识沉淀了,如实回答就可以了。在项目中的难点,怎么解决的你的优势是什么redux 源码学到了什么,怎么看源码的了解哪些前端的前沿技术平时看什么书,兴趣爱好是什么异步有哪些方法博客写了什么除了实习经历,还做过哪些项目四面这一面是在杭州菜鸟现场面的,尴尬的是通知我的小姐姐一直强调是hr面,我天真的以为是hr面了,然鹅问了很多技术,当时候想的是阿里的hr都这么厉害了,都能直接问技术了。临走之前,特意问了面试官是hr面么,他说是技术,然后我……大概就知道自己凉了。1. mysql的索引用的什么,介绍一下b树,b+树,红黑树这些mysql的索引用的是B+树。参考: 数据结构中常见的树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)2. Mysql的基本写法参考: 一千行 MySQL 学习笔记3. 估算下杭州上空现在有多少架飞机这个题目,也真的是为难我了额。在网上搜到了个答案,可以参考下:高盛的面试题4. 两组数据,都存储五亿条url,内存有4G,如何找出相同的两条url参考: 面试- 阿里-. 大数据题目- 给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url?5. 如何找到一个字符串中最长的两个字符串解法:后缀数组。首先生成字符串的所有后缀数组,在进行排序,找出相邻两个最长的公共子串(从第一位开始相同的)例如:abcdeabc生成后缀数组:【abcdeabc,bcdeabc,cdeabc,deabc,eabc,abc,bc,c】再排序:【abcdeabc,abc,bcdeabc,bc,cdeabc,c,deabc,eabc】找出相邻的最长公共子串:【abc,bc,c】因此,最长的串是abc。6. 在白板上画出这个项目的整个架构画了下项目的功能架构什么的。7. XSS, CSRF,token 怎么来的,sql 注入知道么sql注入:攻击方式:服务器上的数据库运行非法的 SQL 语句,主要通过拼接字符串的形式来完成,改变sql语句本身的语义。通过sql语句实现无账号登陆,甚至篡改数据库。防御:使用参数化查询:使用预编译语句,预先编译的 SQL 语句,并且传入适当参数多次执行。由于没有拼接的过程,因此可以防止 SQL 注入的发生。 使用preparedStatement的参数化sql,通过先确定语义,再传入参数,就不会因为传入的参数改变sql的语义。(通过setInt,setString,setBoolean传入参数)单引号转换:将传入的参数中的单引号转换为连续两个单引号,PHP 中的 Magic quote 可以完成这个功能。检查变量数据类型和格式。使用正则表达式过滤传入的参数,对特殊符号过滤或者转义处理。8. 怎么设计一个ant的组件9. 你觉得你实习做的项目有什么改进的地方10. 你做过印象最深刻的项目11. 算法了解过吗就知道一些基本的排序额…12. Setstate 会发生什么setState会引发一次组件的更新过程,从而引发页面的重新绘制。主要会涉及以下几个生命周期函数:shouldComponentUpdate(被调用时this.state没有更新;如果返回了false,生命周期被中断,虽然不调用之后的函数了,但是state仍然会被更新)componentWillUpdate(被调用时this.state没有更新)render(被调用时this.state得到更新)componentDidUpdate13. 平时处理过什么兼容性参考: web前端兼容性问题总结14. 了解分布式和负载均衡么然鹅我并不了解呃。参考: 服务器负载均衡的基本功能和实现原理五面第四面确实是挂了,没面hr就让我走了,后面过了两天之后,三面面试官又把我捞起来了,说我计算机基础还有数据库基础不怎么好,然后说问我几个简单的,之后给了我机会面了hr,感谢三面面试官让我体验了阿里的整个面试流程,很满足了。1. 进程和线程的区别根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)。内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。2. 冒泡排序和快速排序的区别简述了下冒泡和快排的思想,以及冒泡和快排的时间复杂度。3. OSI七层模型以及作用上面有写噢,不知道的往上翻。4. 你有哪些优势,或者打动他的呃,最怕这种自夸的问题额,然后就是夸了一顿,手动捂脸。5. 面向对象和非面向对象有什么区别面向对象三大特性:封装,继承,多态。面向对象的好处:将对象进行分类,分别封装它们的数据和可以调用的方法,方便了函数、变量、数据的管理,方便方法的调用(减少重复参数等),尤其是在编写大型程序时更有帮助。用面向对象的编程可以把变量当成对象进行操作,让编程思路更加清晰简洁,而且减少了很多冗余变量的出现参考: 面向对象(一)|面向对象概念及优点6. 设计模式有哪些,说下装饰者模式和代理模式前面有总结,往前翻。7. 重载和重写有什么区别方法重写(overriding):也叫子类的方法覆盖父类的方法,要求返回值、方法名和参数都相同。子类抛出的异常不能超过父类相应方法抛出的异常。(子类异常不能超出父类异常)子类方法的的访问级别不能低于父类相应方法的访问级别(子类访问级别不能低于父类访问级别)。方法重载(overloading):重载是在同一个类中的两个或两个以上的方法,拥有相同的方法名,但是参数却不相同,方法体也不相同,最常见的重载的例子就是类的构造函数。参考: 方法重载和重写的区别hr面为什么选择前端开发什么事情让你最有成就感什么让你最有挫败感为什么选择阿里平时是怎么学习的职业发展百度二面三面都有手写代码的环节,对于我这种动手能力弱的人来说还是挺吃力。当时提前批投递的是深圳百度,总共只招五个前端,没过也很正常。后面正式批笔试过了,但是要去北京面试,也就直接放弃了。1. 为什么要用flex布局,align-items和justify-content的区别传统布局基于盒模型,非常依赖 display属性 、position属性 、float属性。而flex布局更灵活,可以简便、完整、响应式地实现各种页面布局,比如水平垂直居中。align-items:定义在垂直方向上的对齐方式;justify-content:定义在水平方向上的对齐方式。2. webpack是怎么打包的,babel又是什么?把项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。babel将es6、es7、es8等语法转换成浏览器可识别的es5或es3语法。3. saas和less不同于普通css的地方定义变量,可以把反复使用的css属性值定义成变量,然后通过变量名来引用它们,而无需重复书写这一属性值;嵌套写法,父子关系一目了然;使用运算符,可以进行样式的计算;内置一些颜色处理函数用来对颜色值进行处理,例如加亮、变暗、颜色梯度等;继承:为多个元素定义相同样式的时候,我们可以考虑使用继承的做法;Mixins (混入):有点像是函数或者是宏,当某段 CSS经常需要在多个元素中使用时,可以为这些共用的 CSS 定义一个Mixin,然后只需要在需要引用这些 CSS 地方调用该 Mixin 即可。4. es 6模块和其他模块不同的地方对比了一下es6模块和CommonJS模块:区别CommonJSes6加载原理第一次加载模块就会执行整个模块,再次用到时,不会执行该模块,而是到缓存中取值。不会缓存运行结果,动态的去被加载的模块中取值,并且变量总是绑定其所在模块。输出值的拷贝(模块中值的改变不会影响已经加载的值)值的引用(静态分析,动态引用,原来模块值改变会改变加载的值)加载方式运行时加载(加载整个模块,即模块中的所有接口)编译时加载(只加载需要的接口)this指向指向当前模块指向undefined循环加载只输出已经执行的部分,还未执行的部分不会输出遇到模块加载命令import时不会去执行模块,而是生成一个动态的只读引用,等到真正用到时再去模块中取值。只要引用存在,代码就能执行。5. 有没有用过es6的一些异步处理函数Promise,generator,async await6. redux怎么处理异步操作可以引入Redux-thunk或者redux-promise这种中间件,可以延迟事件的派发。其中的原理:是因为他们用了applymiddleware()包装了store的dispatch方法,拥有可以处理异步的能力。7. 为什么reducer要是个纯函数,纯函数是什么?纯函数:对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。原因:Redux只通过比较新旧两个对象的存储位置来比较新旧两个对象是否相同(浅比较)。如果你在reducer内部直接修改旧的state对象的属性值,那么新的state和旧的state将都指向同一个对象。因此Redux认为没有任何改变,返回的state将为旧的state。两个state相同的话,页面就不会重新渲染了。因为比较两个Javascript对象所有的属性是否相同的的唯一方法是对它们进行深比较。但是深比较在真实的应用当中代价昂贵,因为通常js的对象都很大,同时需要比较的次数很多。因此一个有效的解决方法是作出一个规定:无论何时发生变化时,开发者都要创建一个新的对象,然后将新对象传递出去。同时,当没有任何变化发生时,开发者发送回旧的对象。也就是说,新的对象代表新的state。8. 高阶函数是什么,怎么去写一个高阶函数高阶函数:参数值为函数或者返回值为函数。例如map,reduce,filter,sort方法就是高阶函数。编写高阶函数,就是让函数的参数能够接收别的函数。9. vue跟react的区别是什么没有用过vue,所以就只说了vue具有双向绑定,react是单向数据流。参考: Vue.js与React的全面对比10. nodejs处理了什么问题可以处理高并发的I/O,也能与websocket配合,开发长连接的实时交互应用程序。11. 响应式布局,怎么做移动端适配使用媒体查询可以实现响应式布局。移动端适配方案:(1)meta viewport:让当前viewport的宽度等于设备的宽度,同时不允许用户手动缩放。<meta name=“viewport” content=“width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0”>width=device-width: 让当前viewport宽度等于设备的宽度user-scalable=no: 禁止用户缩放initial-scale=1.0: 设置页面的初始缩放值为不缩放maximum-scale=1.0: 允许用户的最大缩放值为1.0minimum-scale=1.0: 允许用户的最小缩放值为1.0(2)媒体查询(响应式)(3)动态 rem 方案参考: 移动端是怎么做适配的?二面1. 怎么做一个实时的聊天系统使用WebSocket和nodejs,《nodejs实战》这本书详细介绍了这个项目。2. 当消息有延迟的时候,怎么保证消息的正确顺序每个消息在被创建时,都将被赋予一个全局唯一的、单调递增的、连续的序列号(SerialNumber,SN)。可以通过一个全局计数器来实现这一点。通过比较两个消息的SN,确定其先后顺序。3. 怎么做一个登陆窗口,input有哪些兼容性使用form表单。4. input按钮如何校验使用正则表达式。5. 如何实现水平垂直居中,relative、absolute、fixed我说了三种方式:(1)使用表格.container{ width: 600px; height: 600px; background: #eee; display: table-cell; text-align: center; vertical-align: middle;}.center{ background: blue;}(2)css3的transform属性.container{ width: 100%; height: 400px; background: #eee; position: relative;}.center{ background: blue; position:absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);}(3)flex布局.container{ width: 100%; height: 400px; background: #eee; display: flex; justify-content: center; align-items: center;}.center{ width: 100px; height: 100px; background: blue; text-align: center;}relative:相对于自己的定位;absolute:相对于最近一级定位元素的定位;fixed:相对于窗口的定位。6. 写一个函数,1s之后依次输出1,2,3,4,5直接使用了let和定时器。for(let i = 1 ; i < 6; i++){ setTimeout(() => { conosle.log(i) }, 1000)}7. 事件队列(宏任务、微任务)参考::这一次,彻底弄懂 JavaScript 执行机制8. 如何每隔三个数加一个逗号,还要考虑小数点的情况这道题就是大疆的笔试题,当时候笔试题也是瞎写的,后面也没仔细看,没想到又成了一道面试题。function transform(number){ var num = number.toString() var numArr = num.split(’.’) var [num, dotNum] = numArr var operateNum = num.split(’’).reverse() var result = [], len = operateNum.length for(var i = 0; i< len; i++){ result.push(operateNum[i]) if(((i+1) % 3 === 0) && (i !== len-1)){ result.push(’,’) } } if(dotNum){ result.reverse().push(’.’, …dotNum) return result.join(’’) }else{ return result.reverse().join(’’) }}9. webpack有配置过吗?原理知道吗参考前面。10. 父子组件如何通信,子组件怎么跟父组件通信?父组件把state作为props传递给子组件进行通信。父组件写好state和处理该state的函数,同时将函数名通过props属性值的形式传入子组件,子组件调用父组件的函数,同时引起state变化。11. 简单说一下pwa面试的这个部门就是做pwa的,所以说了下自己对pwa的理解。三面1. 手写indexOffunction indexOf(str, val){ var strLen = str.length, valLen = val.length for(var i = 0; i < strLen; i++){ var matchLen = i + valLen var matchStr = str.slice(i, matchLen) if(matchLen > strLen){ return -1 } if(matchStr === val){ return i } } return -1}2. 实现 JS 的继承function A () { this.name = ‘a’; }A.prototype.getName = function () { return this.name;}function B () {}// B 如何继承 A参考: JS实现继承的几种方式3. 从url输入到页面显示会有哪些步骤(1)DNS服务器解析域名,找到对应服务器的IP地址;(2)和服务器建立TCP三次握手连接;(3)发送HTTP请求,服务器会根据HTTP请求到数据服务器取出相应的资源,并返回给浏览器;(4)浏览器处理响应加载:浏览器对一个html页面的加载顺序是从上而下的。当加载到外部css文件、图片等资源,浏览器会再发起一次http请求,来获取外部资源。当加载到js文件,html文档会挂起渲染(加载解析渲染同步)的线程,等待js文件加载、解析完毕才可以恢复html文档的渲染线程。解析:解析DOM树和CSSDOM树。渲染:构建渲染树,将DOM树进行可视化表示,将页面呈现给用户。4. method有哪些方法,分别是什么意思?post和put的区别post:上传资源put:修改资源5. https有几次握手6. http2比http1好的地方主要是考察http2的几个特性。参考:HTTP协议知识点总结7. 页面刷新不出来,是有哪些问题可以从第三题的每个步骤进行分析,大概是:域名不存在,或者ip地址错误网络问题,不能建立正常的tcp连接服务器找不到正确的资源8. 上一次系统性的学习是什么时候,怎么学习的学习react的时候,看文档、博客,照着网上写了点小项目。9. 你觉得项目中最自豪的是什么10. 上家公司有哪些不好的地方网易网易是在杭州网易大厦面的,一天面完三轮,然后录用排序,择优录取的吧。我投的是网易考拉,哭唧唧,后面被拒了之后还伤心的卸载了考拉。之后正式批投了杭研,过了笔试,要去武汉面,本来海康也是在武汉面的,考虑到还要住一晚上,有点怕怕,就没去了。1.css3动画2. flex布局3. 实现callFunction.prototype.call2 = function (context) { var context = Object(context) || window context.fn = this var args = [] for (var i = 1; i < arguments.length; i++) { args.push(‘arguments[’ + i +’]’) } var res = eval(‘context.fn(’ + args + ‘)’) delete context.fn return res}4. 图片懒加载data-src 5. Promise异步6. 水平垂直居中7. 数组有哪些方法,哪些会改变原数组改变原数组的方法:pop、push、reverse、shift、sort、splice、unshift,以及两个ES6新增的方法copyWithin 和 fill;不改变原数组(复制):concat、join、slice、toString、toLocaleString、indexOf、lastIndexOf、未标准的toSource以及ES7新增的方法includes;循环遍历:forEach、every、some、filter、map、reduce、reduceRight 以及ES6新增的方法entries、find、findIndex、keys、values。8. 操作dom有哪些方法创建: createDocumentFragment() //创建一个DOM片段<br> createElement() //创建一个具体的元素<br> createTextNode() //创建一个文本节点<br> 添加:appendChild()移出:removeChild()替换:replaceChild()插入:insertBefore()复制:cloneNode(true)查找: getElementsByTagName() //通过标签名称<br> getElementsByClassName() //通过标签名称<br> getElementsByName() //通过元素的Name属性的值<br> getElementById() //通过元素Id,唯一性9. 左边定宽右边自适应(1)左盒子左浮动,右盒子width=100%(2)左盒子左浮动,右盒子margin-left=左盒子宽度(3)左盒子左浮动,右盒子右浮动,设置calc(100vw-盒子宽度)(4)父容器设置display=flex,右盒子flex:110. 事件代理利用事件冒泡的原理,让自己的所触发的事件,让他的父元素代替执行。打个比方:一个button对象,本来自己需要监控自身的点击事件,但是自己不来监控这个点击事件,让自己的父节点来监控自己的点击事件。11. 后端了解么直接说了不了解,笑哭。二面1. 节流和防抖,手写一下代码(1)防抖:定义: 合并事件且不会去触发事件,当一定时间内没有触发这个事件时,才真正去触发事件。原理:对处理函数进行延时操作,若设定的延时到来之前,再次触发事件,则清除上一次的延时操作定时器,重新定时。场景: keydown事件上验证用户名,输入法的联想。实现:function debounce(fn, delay) { var timer return function () { var that = this var args = arguments clearTimeout(timer) timer = setTimeout(function () { fn.apply(that, args) }, delay) }}(2)节流:定义: 持续触发事件时,合并一定时间内的事件,在间隔一定时间之后再真正触发事件。每间隔一段时间触发一次。原理:对处理函数进行延时操作,若设定的延时到来之前,再次触发事件,则清除上一次的延时操作定时器,重新定时。场景: resize改变布局时,onscroll滚动加载下面的图片时。实现:方法一:使用时间戳。当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为0),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。缺陷:第一次事件会立即执行,停止触发后没办法再激活事件。function throttle(fn, interval) { var previousTime = +new Date() return function () { var that = this var args = arguments var now = +new Date() if (now - previousTime >= interval) { previousTime = now fn.apply(that, args) } }}方法二:使用定时器当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。缺陷:第一次事件会在n秒后执行,停止触发后依然会再执行一次事件。function throttle(fn, interval) { var timer return function (){ var that = this var args = arguments if(!timer){ timer = setTimeout(function () { fn.apply(that, args) timer = null }, interval) } }}方法三:优化鼠标移入能立刻执行,停止触发的时候还能再执行一次。var throttle = function(func,delay){ var timer = null; var startTime = Date.now(); return function(){ var curTime = Date.now(); var remaining = delay-(curTime-startTime); var context = this; var args = arguments; clearTimeout(timer); if(remaining<=0){ func.apply(context,args); startTime = Date.now(); }else{ timer = setTimeout(func,remaining); } }}2. 知道哪些性能优化3. react为什么比其他要快,虚拟dom知道吗4. 写过什么组件5. 平时怎么学习的6. node,webpack了解么7. 模块化,commonjs,es6模块8. redux怎么实现的hr面项目上有哪些难点,项目中学到了什么不喜欢跟什么样的人共事平时怎么学习为什么来杭州职业发展搜狗搜狗是内推的,面试也很迷,第一面到第二面中间隔了二十几天,然后二面完了也毫无反馈。一面1. 说一下项目,整个网络过程,从前端到后台2. Ajax 底层实现,readystate 有哪些0-(未初始化)还没有调用send()方法1-(载入)已调用send()方法,正在发送请求2-(载入完成)send()方法执行完成,已经接收到全部响应内容3-(交互)正在解析响应内容4-(完成)响应内容解析完成,可以在客户端调用了3. 状态码有哪些,100,3074. OSI七层模型5. TCP三次握手6. SSL握手过程7. jQuery 有哪些方法8. display 有哪些属性,说一下flex的属性9. Es6的async awiat ,generator 10. Map有哪些方法Map的方法:set, get, has, delete, clear遍历方法:keys():返回键名的遍历器。values():返回键值的遍历器。entries():返回所有成员的遍历器。forEach():遍历 Map 的所有成员。参考: Set 和 Map 数据结构11. 正则用过吗?exec, 匹配一个手机号12. css3动画了解吗,怎么写一个loading动画13. 怎么实现跨域,cors涉及哪些请求字段14. 编程: 判断两个网络地址是否属于同一个子网掩码用与运算符就可以了。当时是在牛客网的面试系统上写的,一直AC不出,也是很迷了额。15. 怎么上传文件二面1. 怎么计算在一个页面上的停留时间方案1:websocket,前端开个长连接,后台统计长连接时间。方案2:ajax轮询,隔几秒发一个查询,后台记录第一与最后一个查询间隔时间。方案3: 关闭窗口或者跳转的时候会触发window.onbeforeunload函数,可以在该函数中做处理(有兼容性问题);统计完数据记录到本地cookies中,一段时间后统一发送。2. 给你一亿个数,是连续的,怎么找出两个不存在的数 用bitmap就能搞定了,存在为1,不存在为0。3. 一个搜索框的输入联想,会遇到什么问题?如果第一个请求延迟,第二个请求先到,请问怎么处理?键盘输入太快,每次输入都去联想,需要多次发送请求,会导致用户体验太差,可以使用防抖优化。在前端做判断,判断此时的值是否与返回的值相同,不同就丢弃,相同就显示在页面。4. Http的缓存5. 二维码怎么工作的,扫描pc端的二维码,怎么让pc端登录?pc端随机生成一个含有唯一uid的二维码,并与服务器建立一个长连接;手机扫描二维码,解析出二维码中的uid,并把这个uid和手机端的用户密码进行绑定,上传给服务器;服务器获得客户端信息之后,pc端的长连接轮询操作会获得该消息,显示该账号的信息;pc端会再开一个长连接与手机端保持通信,等待手机端确认登陆后,获得服务器授权的token,就可以在pc端登陆进行正常通信了。6. Promise 做什么的,有哪几种状态异步处理的,有三个状态:resolve,pending,reject。7. 项目有哪些难点,怎么处理的8. 遇到过哪些性能优化电信IT研发中心当时听说电信对学历要求很高,本科基本都是211起的,想着自己本科太渣,就直接放弃了网上的笔试。之后电信来了学校宣讲会,跟朋友吃完饭看到了,就去说凑凑热闹,刚好有笔试也就做了。做完之后笔试居然考了最高分,比第二名高出二十分,手动捂脸额。一面完分数也挺高的,有95分,运气爆棚。重点是今年电信开的薪资实在太高了,目前还在纠结选哪个。1. Xhtml和html的区别XHTML 元素必须被正确地嵌套。XHTML 元素必须被关闭。标签名必须用小写字母。XHTML 文档必须拥有根元素。2. 遇到过哪些兼容性问题3. 浏览器内核有哪些,移动端用的是哪个Trident内核:IE,MaxThon,TT,The Word,360,搜狗浏览器等。[又称为MSHTML]Gecko内核:Netscape6及以上版本,FF,MozillaSuite/SeaMonkey等;Presto内核:Opera7及以上。[Opera内核原为:Presto,现为:Blink]Webkit内核:Safari,Chrome等。[Chrome的:Blink(Webkit的分支)]对于Android手机而言,使用率最高的就是Webkit内核。4. 怎么实现标签页的通信5. Cookie、session,localstorage,sessionstorage6. React 和jquery 之间的区别,哪个好用7. 怎么实现继承8. Es6,es7有哪些特性9. 怎么跨域10. Commonjs用的js哪个特性? 因为js之前只能在浏览器运行,为了能让js能在服务器上运行,所以设计了commonjs规范,而且js之前没有模块化的概念。11. 选择器优先级12. 伪类知道吗,有哪些13. 块级元素有哪些,怎么转成行内元素14. 一个完整的http请求,页面渲染过程,js和css文件怎么渲染二面一面问的都很常规的,不知道为啥给了这么高的分。二面的时候三个面试官,总共就问了三个问题,然后就说面试结束了,不超过五分钟。1. TCP怎么工作的三次握手2. OSI七层模型,路由器工作在哪一层?网络层3. 平时用什么语言,用过哪些框架深信服深信服给的薪资居然比电信还低,而且加班还严重,就直接拒了。一面1. 跨域,同源策略,webpack里面有个跨域的方式知道么2. 怎么把es6转成es5,babel怎么工作的解析:将代码字符串解析成抽象语法树变换:对抽象语法树进行变换操作再建:根据变换后的抽象语法树再生成代码字符串3. 反向代理知道么,Nginx 4. 继承有哪些方式5. 怎么实现一个sleep ,手写一个promise 6. 能写一个二叉树么,怎么去遍历7. 深拷贝怎么写(1)var new_arr = JSON.parse( JSON.stringify(arr) ); (2)for in 加递归function isObj(obj) {//判断是否为对象或者函数,但不是null return (typeof obj === ‘object’ || typeof obj === ‘function’) && obj !== null}function deepCopy(obj) { let newObj = Array.isArray(obj) ? [] : {} for(let key in obj) { newObj[key] = isObj(obj[key]) ? deepCopy(obj[key]) : obj[key] } return newObj}(3)$.extend()(4)函数库lodash,提供_.cloneDeep()8. 在公司除了完成上级交待的任务,还做了什么9. 怎么实现垂直中间布局10. Call和apply,哪个性能开销大在思否上提问了,已有大神回答。参考: call和apply的哪个性能更好11. 正则写一个手机号,全局匹配是什么12. 删除一个数组中的某个数splice方法13. 模块化介绍一下,什么是编译时优化14. 有哪些网络安全名词,怎么防范15. 平时怎么学习二面二面小哥哥问了几个问题之后,就一直跟我介绍深信服内部的一些管理、技术氛围、晋升机制什么的,全程都是笑脸额。1. git push -u 是什么意思绑定默认提交的远程版本库,加了参数-u后,以后即可直接用git push 代替git push origin master2. git rebase解释下有test和dev两个分支,分别有两个commit,此时执行下列命令:git checkout testgit rebase dev以dev为基准将test的提交进行回放,挨个的应用到dev上去,然后test的那些提交就会废弃。 等价于git merge dev。git merge 和git rebase区别:merge不会修改提交历史,rebase会修改提交历史。rebase只应用于本地没有提交的代码,如果应用到已经提交到远程的分支不要应用,不然会非常的麻烦,git merge 可以应用于远程分支。3. linux命令,怎么打开一个文件 cat abc.txt4. 你的上级给你review 代码时会提什么建议5. 怎么看待加班和工作效率6. get和post分别进行几次数据交互get请求过程:(2次交互)浏览器请求tcp连接(第一次握手) 服务器答应进行tcp连接(第二次握手) 浏览器确认,并发送get请求头和数据(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送) 服务器返回200 ok响应。post请求过程:(3次交互)浏览器请求tcp连接(第一次握手) 服务器答应进行tcp连接(第二次握手) 浏览器确认,并发送post请求头(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送) 服务器返回100 continue响应 浏览器开始发送数据 服务器返回200 ok响应7. 怎么打断点,如何确定一个结果来自于哪个函数ThoughtWorksTW是内推的,做了内推作业后,就面了技术和文化面。技术面是在作业的基础上加两个功能,只写出来一个,后面一个没时间写了,然后就只讲了下思路。文化面面了快一个小时,听说TW不加班,对女程序员还很友好,挺中意的公司,不过最后还是挂了额。华为华为的面试就不多说了,基本不问前端的,进去是随机分岗的。华为的面试阵仗是我见过的最大的,听说要招一万人,在万达那里面的,全是人啊,阔怕。现在正泡在offer池里,估计国庆后发正式offer吧。二面碰到的是个女面试官,太恐怖了,一直在怼我,最怕碰到女面试官了,惨。小米小米是内推的,电话面了一面,国庆后要我去武汉现场面,那会学校刚好有事应该也不会去了。1. redux主要做什么的,用过redux的一些中间件吗,简单说一下2. react生命周期说一下,diff算法说一下3. setstate时会合并修改,是在哪个函数里修改的?宏事件和微事件setstate是异步更新的,通过一个队列机制实现state的更新,当执行setState时,会将需要更新的state合并后放入状态队列,而不会立即更新,队列可以高效的批量更新state。4. let、const、var的区别;如果const定义的是个对象,能够修改对象的属性吗? const实际上保证的并不是变量的值不得改动,而是变量指向的那个指针不得改动,可以给对象添加属性。如果真的想将对象冻结,应该使用Object.freeze方法。5. Object.freeze和Object.seal的区别Object.preventExtension:禁止对象添加新属性并保留已有属性;Object.seal:在一个现有对象上调用 Object.preventExtensions(..) 并把所有现有属性标记为 configurable:false;Object.freeze:在一个现有对象上调用 Object.seal(..) 并把所有“数据访问”属性标记为 writable:false。6. 说一下防抖,应用场景是什么7. 快速排序算法说下,基点怎么选?如果一个数组是已经排序好的怎么选基点?数组元素随机,取固定基准;数组元素已排序或逆序,取随机基准;更好的方法:三数取中,选取数组开头,中间和结尾的元素,通过比较,选择中间的值作为快排的基准。8. 算法的稳定性,冒泡、快排9. lodash,underscore的库了解么?有哪些方法10. 整个项目的架构,包括前端、后台、运营11. sort的底层实现机制,看过源码么? 数组长度<=22时采用插入排序,大于22用快排。12. 怎么调试bug?打过断点么?如果前端代码被压缩,如何去找到相应元素? chromre控制台下,在 Scripts 面板下面有个 Pretty print 按钮(这种符号 {}),点击会将压缩 js 文件格式化缩进规整的文件,这时候在设定断点可读性就大大提高了。写在最后目前offer是:大疆和电信,前辈们如果有建议的话可以留下,感激不尽。其他资料这些都是我的学习笔记,也可以参考:2019届校招前端面试题整理——HTML、CSS篇JavaScript相关的基础知识可以看这两篇思维导图: 你不知道的JavaScript(上卷) , 你不知道的JavaScript(中卷)第一部分HTTP协议知识点总结 ...

October 4, 2018 · 5 min · jiezi

前端系列——查找字符串B的字符任意一种组合是否是字符串A的子串

题目要求这道算法题在前端面试中可能遇到,据说某条出过这题。查找字符串B的字符任意一种组合是否是字符串A的子串。例如 A=abc123,B=cba,则B的其中一种组合abc是A的子串,然后返回true。算法思路题目的出处已经无从考究,接下来我们从JavaScript的角度来封装这样一个功能函数。穷举一开始看到这道题,你会想到什么?我想到的是先列举出B的所有排列组合,存到数组里面,然后遍历,判断是否有组合包含在A中,如果有返回true,否则返回false。如果从题目给出的例子来穷举,一共6种组合,很容易穷举出来,但是字符串长度非常大的时候,怎么办呢?所以,穷举的办法被我排除了。标记删除法这名字听起来很奇怪,怎么个思路呢?1、A的排序肯定是不变的,既然可变的我们很难下手,那么可以从不变的地方入手,以不变应万变。 2、看字符串可能不太习惯,我把A和B都转换成数组。let a = A.split(’’) // [a, b, c, 1, 2, 3]let b = B.split(’’) // [c, b, a]3、先过滤数组为空的情况,如果a或者b为空,那么不需要比较,返回false。if (a.length === 0 || b.length === 0) { return false}4、只看数组b,可以有6种排列组合,[c,b,a],[a,b,c],[a,c,b],[b,a,c],[b,c,a],[c,a,b]。还记得第1步说的,我们不管b有多少种组合,都从a入手。// a = [a, b, c, 1, 2, 3]for (let j = 0; j < a.length; j++) { }5、遍历a有什么作用呢?接下来我为大家揭晓何为标记删除法,允许我小小解释一下该方法,分为2个核心,“标记”和“删除”,“标记”是指标记当前数组a遍历的位置,“删除”是指删除数组b中的元素。这样说可能不太懂,先不看代码,我用数组来模拟一下执行过程。初始化的值a = [a, b, c, 1, 2, 3]b = [c, b, a]==================================================当遍历a的时候,j从0开始,遍历到a.length-1结束==================================================j = 0 // 给a里的字符加’’,做个标记,表示当前遍历的下标位置a = [‘a’, b, c, 1, 2, 3]==================================================然后我们发现数组b存在当前的字符’a’,执行删除操作b = [c, b]==================================================j = 1 // 数组a遍历到第二个字符a = [a, ‘b’, c, 1, 2, 3] // 标记b = [b] // 删除==================================================j = 1 // 数组a遍历到第三个字符a = [a, b, ‘c’, 1, 2, 3] // 标记b = [] // 删除==================================================现在我们看到b数组变成空了,则证明b的任意一种排列存在于a中6、上一步描述的情况是最简单的状态,刚好在A字符中存在不间断的字符组合。我们把A改一下,变成 A = a1b2c3abc。即使变复杂了,我们依旧可以使用标记删除发来做,只是稍微做一点处理。初始化的值a = [a, 1, b, 2, c, 3, a, b, c]b = [c, b, a]==================================================当遍历a的时候,j从0开始,遍历到a.length-1结束==================================================j = 0 // 给a里的字符加’’,做个标记,表示当前遍历的下标位置a = [‘a’, 1, b, 2, c, 3, a, b, c]==================================================然后我们发现数组b存在当前的字符’a’,执行删除操作b = [c, b]==================================================j = 1 // 数组a遍历到第二个字符a = [a, ‘1’, b, 2, c, 3, a, b, c] // 标记// 突然发现第2个字符是1,现在该怎么办?我们只需要把数组b恢复初始状态即可b = [c, b, a] // 恢复初始状态 ==================================================接下来继续遍历,重复上面的处理步骤,直到数组b为空或者数组a遍历完成,返回结果7、JavaScript代码实现下面是第6步说明的代码实现,从代码中可以看到,不管B字符有多少排列组合,我们始终只需要遍历A的所有字符即可,内部实现也是用空间换时间。// 遍历数组 a for (let j = 0; j < a.length; j++) { // 数组 b不为空,下一步 if (b.length > 0) { // 数组a存在当前遍历的数组b的元素 if (b.indexOf(a[j]) > -1) { // 删除b数组中匹配到的第一个对应下标的元素 b.splice(b.indexOf(a[j]), 1) if (b.length === 0) { // 如果数组b全部被删除了,则证明b是a的子串 return true } } else { // 数组b不存在当前遍历的数组b的元素,恢复b数组 b = B.split(’’) } } else { // 数组 b为空返回true return true } }总结与其他前端工程师的交流中,我还了解到了其他的解题思路,有些很有趣,比如考虑使用Map或Set、滑块区间比较等,不过我没有去用代码实现过,如果你有其他的方法,可以在下面留言交流。完整源码评论区有人指出不能覆盖某些场景的测试用例,所以我对上面的具体过程做了改进,下面是改进后的源码。增加了2个字段,isBack和isRestart,isRestart用来标记是否重新在当前位置遍历,isBack判断是否对数组遍历的下标进行回退一个单位。var A = ‘abc123’ , B = ‘cba’function interface(A, B) { // 将A和B转成数组 let a = A.split(’’) let b = B.split(’’) if (a.length === 0 || b.length === 0) { return false } let isBack = false, isRestart = 0 // 遍历数组 a for (let j = 0; j < a.length; j++) { // 数组 b不为空,下一步 if (b.length > 0) { isBack = false // 数组a存在当前遍历的数组b的元素 if (b.indexOf(a[j]) > -1) { // 删除b数组中匹配到的第一个对应下标的元素 b.splice(b.indexOf(a[j]), 1) if (b.length === 0) { // 如果数组b全部被删除了,则证明b是a的子串 return true } } else { if (isRestart !== 0) { isBack = false } else { isBack = true } // 数组b不存在当前遍历的数组b的元素,恢复b数组 b = B.split(’’) if (isBack) { j -= 1 isRestart = 0 } isRestart++ } } else { // 数组 b为空返回true return true } } return false}interface(A, B) ...

September 26, 2018 · 2 min · jiezi

值得保存的 synchronized 关键字总结

该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb…本文是对 synchronized 关键字使用、底层原理、JDK1.6之后的底层优化以及和ReenTrantLock对比做的总结。如果没有学过 synchronized 关键字使用的话,阅读起来可能比较费力。两篇比较基础的讲解 synchronized 关键字的文章:《Java多线程学习(二)synchronized关键字(1)》《Java多线程学习(二)synchronized关键字(2)》synchronized 关键字的总结synchronized关键字最主要的三种使用方式的总结修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能!synchronized 关键字底层实现原理总结synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 在 Java 早期版本中,synchronized 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。所有用户程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如从硬盘读取数据, 或者从键盘获取输入等. 而唯一可以做这些事情的就是操作系统,synchronized关键字底层优化总结JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。偏向锁引入偏向锁的目的和引入轻量级锁的目的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是:轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉。偏向锁的“偏”就是偏心的偏,它的意思是会偏向于第一个获得它的线程,如果在接下来的执行中,该锁没有被其他线程获取,那么持有偏向锁的线程就不需要进行同步!关于偏向锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。轻量级锁倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的)。轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作。 关于轻量级锁的加锁和解锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁!自旋锁和自适应自旋轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。互斥同步对性能最大的影响就是阻塞的实现,因为挂起线程/恢复线程的操作都需要转入内核态中完成(用户态转换到内核态会耗费时间)。一般线程持有锁的时间都不是太长,所以仅仅为了这一点时间去挂起线程/恢复线程是得不偿失的。 所以,虚拟机的开发团队就这样去考虑:“我们能不能让后面来的请求获取锁的线程等待一会而不被挂起呢?看看持有锁的线程是否很快就会释放锁”。为了让一个线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就叫做自旋。百度百科对自旋锁的解释:何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,“自旋"一词就是因此而得名。自旋锁在 JDK1.6 之前其实就已经引入了,不过是默认关闭的,需要通过–XX:+UseSpinning参数来开启。JDK1.6及1.6之后,就改为默认开启的了。需要注意的是:自旋等待不能完全替代阻塞,因为它还是要占用处理器时间。如果锁被占用的时间短,那么效果当然就很好了!反之,相反!自旋等待的时间必须要有限度。如果自旋超过了限定次数任然没有获得锁,就应该挂起线程。自旋次数的默认值是10次,用户可以修改–XX:PreBlockSpin来更改。另外,在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是:自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定,虚拟机变得越来越“聪明”了。锁消除锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。锁粗化原则上,我们再编写代码的时候,总是推荐将同步快的作用范围限制得尽量小——只在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。大部分情况下,上面的原则都是没有问题的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗。ReenTrantLock 和 synchronized 关键字的总结推荐一篇讲解 ReenTrantLock 的使用比较基础的文章:《Java多线程学习(六)Lock锁的使用》两者都是可重入锁两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 APIsynchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。ReenTrantLock 比 synchronized 增加了一些高级功能相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。性能已不是选择标准在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量岁线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作。参考《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版第13章《实战Java虚拟机》https://blog.csdn.net/javazej…https://blog.csdn.net/qq83864...http://cmsblogs.com/?p=2071你若盛开,清风自来。 欢迎关注我的微信公众号:“乐趣区”,一个有温度的微信公众号。公众号后台回复关键字“1”,你可能看到想要的东西哦! ...

September 7, 2018 · 1 min · jiezi

前端面试题系列 - 继承

大概会用一个系列,讲一下面试过程中经常会问的一些问题,以及我觉得应该可以怎么回答。当然,我的回答也并不是标准答案,只是我自己的一些理解,也欢迎其他人发表自己的想法。作为本系列的第一篇文章,就先讲讲被问的最多的 js 继承问题,但是应该不会写原型链相关的东西,先列举一个最简单的问题:写一个 inherit(superClass, subClass) 方法,实现subClass 继承 superClass题目隐含的内容继承有哪些特征,如何检测一个继承是否成功?子类可以使用父类的方法和属性子类可以自定义方法和属性,但应该不影响父类和其他继承同一个父类的子类子类的原型链上可以找到父类(子类的__proto__应指向父类)子类的实例可以通过 foo instanceof superClass 测试常见的解法es5function inherit(p, s) { s.prototype = Object.create(p.prototype, { constructor: { value: s, enumerable: false, writebale: true, configurable: true } }) Object.setPrototypeOf ? Object.setPrototypeOf(s, p) : s.proto = p} es4function inherit(p, s) { var f = new Function () f.prototype = new p() var r = new f() s.prototype = r s.prototype.constructor = s s.proto = p f = null r = null}引申的问题:Object.create 是什么?怎么使用?Object.create(proto, [propertiesObject])Object.create 提供了一个创建对象的方法,使用现有的对象作为新创建对象的__proto__,同时可以传入添加到新对象的可枚举属性, 这些属性可以对应到Object.defineProperties 的第二个参数中。返回值为所创建的新对象.例如:s.prototype = Object.create(f.prototype, { constructor: { value: s, enumberable: false, writealble: true, configurale: true }})Object.defineProperties 是什么?怎么使用?可以列举一个 Object.definProperties 的实际应用吗?Object.defineProperties可以直接在一个对象上定义或修改属性,并返回该对象。例如:target = Object.defineProperties(target, props)本质上 Object.defineProperties 是对Object.defineProperty 的集中调用,可以理解为是Object.definePeropety的复数版。Object.defineProperty 的使用方法为:target = Object.defineProperty(target, prop, descriptor)所以本质上Object.defineProperties 就是如下代码:Object.keys(props).forEach(function (prop) { let descriptor = props[prop] Object.defineProperty(target, prop, descriptor)})其中 descriptor 的可选值有以下集中:configurable: 当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。enumerable: 当且仅当该属性的enumerable为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。value: 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefinedwritable: 当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false。get: 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。set: 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。如果一个 descriptor 不具有 value, writebale, get 和 set 任意一个关键字,那么将会被认为是一个数据描述符。如果一个描述符同时具有(value或writbale)和(get 或set),将会产生一个异常.继承多个父类怎么做?继承多个父类的话,可以使用 Object.assign 方法。例如:targe = Object.assign({}, superClassA, superClassB, …)但是继承多个父类的话,子类就不能通过 son instanceof superClass 这样的验证了.Object.assign 是什么?怎么用?用的时候有哪些需要注意?Object.assign 方法用于将所有可枚举属性从一个或多个源对象复制到目标对象,并返回目标对象,例如:target = Object.assign(target, source)如果具有同名属性,那么在后面对象中的属性,将会覆盖目标对象中的属性。需要注意以下几点:继承属性和不可枚举属性是不能拷贝的。原始类型会被包装为对象,null, undefined 会被忽略,并且只有字符串的包装对象才可能有自身可枚举的属性.可以拷贝 symbol 类型的属性Object.assign 会调用 setter 和 getter 吗?调用的是哪里的setter 和getter ?Object.assign 会调用源对象的 getter,并把源对象的 getter 的返回值当做新对象的该属性的值。setter 则是会直接加在新创建的对象中,而不会沿用源对象的 setter.Object.getOwnPropertyDescriptor 是什么?主要用来做什么?Object.getOwnPropertyDescriptor 返回直到对象上一个自有属性对应的描述符,例如:Object.getOwnPropertyDescriptor(obj, prop) ...

September 7, 2018 · 1 min · jiezi

细数那些不懂Spring底层原理带来的伤与痛

什么是spring?Spring 是个Java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。2. 使用Spring框架的好处是什么?轻量:Spring 是轻量的,基本的版本大约2MB。控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。容器:Spring 包含并管理应用中对象的生命周期和配置。MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)。异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。3. Spring由哪些模块组成?以下是Spring 框架的基本模块:Core moduleBean moduleContext moduleExpression Language moduleJDBC moduleORM moduleOXM moduleJava Messaging Service(JMS) moduleTransaction moduleWeb moduleWeb-Servlet moduleWeb-Struts moduleWeb-Portlet module4. 核心容器(应用上下文) 模块。这是基本的Spring模块,提供spring 框架的基础功能,BeanFactory 是 任何以spring为基础的应用的核心。Spring 框架建立在此模块之上,它使Spring成为一个容器。5. BeanFactory – BeanFactory 实现举例。Bean 工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从正真的应用代码中分离。最常用的BeanFactory 实现是XmlBeanFactory 类。6. XMLBeanFactory最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory ,它根据XML文件中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。7. 解释AOP模块AOP模块用于发给我们的Spring应用做面向切面的开发, 很多支持由AOP联盟提供,这样就确保了Spring和其他AOP框架的共通性。这个模块将元数据编程引入Spring。8. 解释JDBC抽象和DAO模块。通过使用JDBC抽象和DAO模块,保证数据库代码的简洁,并能避免数据库资源错误关闭导致的问题,它在各种不同的数据库的错误信息之上,提供了一个统一的异常访问层。它还利用Spring的AOP 模块给Spring应用中的对象提供事务管理服务。9. 解释对象/关系映射集成模块。Spring 通过提供ORM模块,支持我们在直接JDBC之上使用一个对象/关系映射映射(ORM)工具,Spring 支持集成主流的ORM框架,如Hiberate,JDO和 iBATIS SQL Maps。Spring的事务管理同样支持以上所有ORM框架及JDBC。10. 解释WEB 模块。Spring的WEB模块是构建在application context 模块基础之上,提供一个适合web应用的上下文。这个模块也包括支持多种面向web的任务,如透明地处理多个文件上传请求和程序级请求参数的绑定到你的业务对象。它也有对Jakarta Struts的支持。11. Spring配置文件Spring配置文件是个XML 文件,这个文件包含了类信息,描述了如何配置它们,以及如何相互调用。12. 什么是Spring IOC 容器?Spring IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。13. IOC的优点是什么?IOC 或 依赖注入把应用的代码量降到最低。它使应用容易测试,单元测试不再需要单例和JNDI查找机制。最小的代价和最小的侵入性使松散耦合得以实现。IOC容器支持加载服务时的饿汉式初始化和懒加载。15. ApplicationContext通常的实现是什么?FileSystemXmlApplicationContext :此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。16. Bean 工厂和 Application contexts 有什么区别?Application contexts提供一种方法处理文本消息,一个通常的做法是加载文件资源(比如镜像),它们可以向注册为监听器的bean发布事件。另外,在容器或容器内的对象上执行的那些不得不由bean工厂以程序化方式处理的操作,可以在Application contexts中以声明的方式处理。Application contexts实现了MessageSource接口,该接口的实现以可插拔的方式提供获取本地化消息的方法。17. 一个Spring的应用看起来象什么?一个定义了一些功能的接口。这实现包括属性,它的Setter , getter 方法和函数等。Spring AOP。Spring 的XML 配置文件。使用以上功能的客户端程序。依赖注入18. 什么是Spring的依赖注入?依赖注入,是IOC的一个方面,是个通常的概念,它有多种解释。这概念是说你不用创建对象,而只需要描述它如何被创建。你不在代码里直接组装你的组件和服务,但是要在配置文件里描述哪些组件需要哪些服务,之后一个容器(IOC容器)负责把他们组装起来。19. 有哪些不同类型的IOC(依赖注入)方式?构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。20. 哪种依赖注入方式你建议使用,构造器注入,还是 Setter方法注入?你两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。Spring Beans21.什么是Spring beans?Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中<bean/> 的形式定义。Spring 框架定义的beans都是单件beans。在bean tag中有个属性”singleton”,如果它被赋为TRUE,bean 就是单件,否则就是一个 prototype bean。默认是TRUE,所以所有在Spring框架中的beans 缺省都是单件。22. 一个 Spring Bean 定义 包含什么?一个Spring Bean 的定义包含容器必知的所有配置元数据,包括如何创建一个bean,它的生命周期详情及它的依赖。23. 如何给Spring 容器提供配置元数据?这里有三种重要的方法给Spring 容器提供配置元数据。XML配置文件。基于注解的配置。基于java的配置。24. 你怎样定义类的作用域?当定义一个<bean> 在Spring里,我们还能给这个bean声明一个作用域。它可以通过bean 定义中的scope属性来定义。如,当Spring要在需要的时候每次生产一个新的bean实例,bean的scope属性被指定为prototype。另一方面,一个bean每次使用的时候必须返回同一个实例,这个bean的scope 属性 必须设为 singleton。25. 解释Spring支持的几种bean的作用域。Spring框架支持以下五种bean的作用域:singleton : bean在每个Spring ioc 容器中只有一个实例。prototype:一个bean的定义可以有多个实例。request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。缺省的Spring bean 的作用域是Singleton.26. Spring框架中的单例bean是线程安全的吗?不,Spring框架中的单例bean不是线程安全的。27. 解释Spring框架中bean的生命周期。Spring容器 从XML 文件中读取bean的定义,并实例化bean。Spring根据bean的定义填充所有的属性。如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法。如果Bean 实现了 BeanFactoryAware 接口, Spring传递beanfactory 给setBeanFactory 方法。如果有任何与bean相关联的BeanPostProcessors,Spring会在postProcesserBeforeInitialization()方法内调用它们。如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。如果有BeanPostProcessors 和bean 关联,这些bean的postProcessAfterInitialization() 方法将被调用。如果bean实现了 DisposableBean,它将调用destroy()方法。28. 哪些是重要的bean生命周期方法? 你能重载它们吗?有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。The bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。29. 什么是Spring的内部bean?当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean,为了定义inner bean,在Spring 的 基于XML的 配置元数据中,可以在 <property/>或 <constructor-arg/> 元素内使用<bean/> 元素,内部bean通常是匿名的,它们的Scope一般是prototype。30. 在 Spring中如何注入一个java集合?Spring提供以下几种集合的配置元素:<list>类型用于注入一列值,允许有相同的值。<set> 类型用于注入一组值,不允许有相同的值。<map> 类型用于注入一组键值对,键和值都可以为任意类型。<props>类型用于注入一组键值对,键和值都只能为String类型。31. 什么是bean装配?装配,或bean 装配是指在Spring 容器中把bean组装到一起,前提是容器需要知道bean的依赖关系,如何通过依赖注入来把它们装配到一起。32. 什么是bean的自动装配?Spring 容器能够自动装配相互合作的bean,这意味着容器不需要<constructor-arg>和<property>配置,能通过Bean工厂自动处理bean之间的协作。33. 解释不同方式的自动装配 。有五种自动装配的方式,可以用来指导Spring容器用自动装配方式来进行依赖注入。no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。byName:通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。byType::通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。constructor:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。autodetect:首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。34.自动装配有哪些局限性 ?自动装配的局限性是:重写: 你仍需用 <constructor-arg>和 <property> 配置来定义依赖,意味着总要重写自动装配。基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。35. 你可以在Spring中注入一个null 和一个空字符串吗?可以。Spring注解36. 什么是基于Java的Spring注解配置? 给一些注解的例子.基于Java的配置,允许你在少量的Java注解的帮助下,进行你的大部分Spring配置而非通过XML文件。以@Configuration 注解为例,它用来标记类可以当做一个bean的定义,被Spring IOC容器使用。另一个例子是@Bean注解,它表示此方法将要返回一个对象,作为一个bean注册进Spring应用上下文。37. 什么是基于注解的容器配置?相对于XML文件,注解型的配置依赖于通过字节码元数据装配组件,而非尖括号的声明。开发者通过在相应的类,方法或属性上使用注解的方式,直接组件类中进行配置,而不是使用xml表述bean的装配关系。38. 怎样开启注解装配?注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在Spring配置文件中配置 <context:annotation-config/>元素。39. @Required 注解这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required注解的bean属性未被设置,容器将抛出BeanInitializationException。40. @Autowired 注解@Autowired 注解提供了更细粒度的控制,包括在何处以及如何完成自动装配。它的用法和@Required一样,修饰setter方法、构造器、属性或者具有任意名称和/或多个参数的PN方法。41. @Qualifier 注解当有多个相同类型的bean却只有一个需要自动装配时,将@Qualifier 注解和@Autowire 注解结合使用以消除这种混淆,指定需要装配的确切的bean。Spring数据访问42.在Spring框架中如何更有效地使用JDBC?使用SpringJDBC 框架,资源管理和错误处理的代价都会被减轻。所以开发者只需写statements 和 queries从数据存取数据,JDBC也可以在Spring框架提供的模板类的帮助下更有效地被使用,这个模板叫JdbcTemplate (例子见这里here)43. JdbcTemplateJdbcTemplate 类提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。44. Spring对DAO的支持Spring对数据访问对象(DAO)的支持旨在简化它和数据访问技术如JDBC,Hibernate or JDO 结合使用。这使我们可以方便切换持久层。编码时也不用担心会捕获每种技术特有的异常。45. 使用Spring通过什么方式访问Hibernate?在Spring中有两种方式访问Hibernate:控制反转 Hibernate Template和 Callback。继承 HibernateDAOSupport提供一个AOP 拦截器。46. Spring支持的ORMSpring支持以下ORM:HibernateiBatisJPA (Java Persistence API)TopLinkJDO (Java Data Objects)47.如何通过HibernateDaoSupport将Spring和Hibernate结合起来?用Spring的 SessionFactory 调用 LocalSessionFactory。集成过程分三步:配置the Hibernate SessionFactory。继承HibernateDaoSupport实现一个DAO。在AOP支持的事务中装配。48. Spring支持的事务管理类型Spring支持两种类型的事务管理:编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。49. Spring框架的事务管理有哪些优点?它为不同的事务API 如 JTA,JDBC,Hibernate,JPA 和JDO,提供一个不变的编程模式。它为编程式事务管理提供了一套简单的API而不是一些复杂的事务API如它支持声明式事务管理。它和Spring各种数据访问抽象层很好得集成。50. 你更倾向用那种事务管理类型?大多数Spring框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。Spring面向切面编程(AOP)51. 解释AOP面向切面的编程,或AOP, 是一种编程技术,允许程序模块化横向切割关注点,或横切典型的责任划分,如日志和事务管理。52. Aspect 切面AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。53. 在Spring AOP 中,关注点和横切关注的区别是什么?关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。54. 连接点连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个AOP切面,它实际上是个应用程序执行Spring AOP的位置。55. 通知通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码段。Spring切面可以应用五种类型的通知:before:前置通知,在一个方法执行前被调用。after: 在方法执行之后调用的通知,无论方法执行是否成功。after-returning: 仅当方法成功完成后执行的通知。after-throwing: 在方法抛出异常退出时执行的通知。around: 在方法执行之前和之后调用的通知。56. 切点切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。57. 什么是引入?引入允许我们在已存在的类中增加新的方法和属性。58. 什么是目标对象?被一个或者多个切面所通知的对象。它通常是一个代理对象。也指被通知(advised)对象。59. 什么是代理?代理是通知目标对象后创建的对象。从客户端的角度看,代理对象和目标对象是一样的。60. 有几种不同类型的自动代理?BeanNameAutoProxyCreatorDefaultAdvisorAutoProxyCreatorMetadata autoproxying61. 什么是织入。什么是织入应用的不同点?织入是将切面和到其他应用类型或对象连接或创建一个被通知对象的过程。织入可以在编译时,加载时,或运行时完成。62. 解释基于XML Schema方式的切面实现。在这种情况下,切面由常规类以及基于XML的配置实现。63. 解释基于注解的切面实现在这种情况下(基于@AspectJ的实现),涉及到的切面声明的风格与带有java5标注的普通java类一致。Spring 的MVC64. 什么是Spring的MVC框架?Spring 配备构建Web 应用的全功能MVC框架。Spring可以很便捷地和其他MVC框架集成,如Struts,Spring 的MVC框架用控制反转把业务对象和控制逻辑清晰地隔离。它也允许以声明的方式把请求参数和业务对象绑定。65. DispatcherServletSpring的MVC框架是围绕DispatcherServlet来设计的,它用来处理所有的HTTP请求和响应。66. WebApplicationContextWebApplicationContext 继承了ApplicationContext 并增加了一些WEB应用必备的特有功能,它不同于一般的ApplicationContext ,因为它能处理主题,并找到被关联的servlet。67. 什么是Spring MVC框架的控制器?控制器提供一个访问应用程序的行为,此行为通常通过服务接口实现。控制器解析用户输入并将其转换为一个由视图呈现给用户的模型。Spring用一个非常抽象的方式实现了一个控制层,允许用户创建多种用途的控制器。说到这里顺便给大家推荐一个Java架构方面的交流学习社群:650385180,里面不仅可以交流讨论,还有面试经验分享以及免费的资料下载,包括Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。相信对于已经工作和遇到技术瓶颈的码友,在这个群里会有你需要的内容。68. @Controller 注解该注解表明该类扮演控制器的角色,Spring不需要你继承任何其他控制器基类或引用Servlet API。69. @RequestMapping 注解该注解是用来映射一个URL到一个类或一个特定的方处理法上。by:老李

September 5, 2018 · 2 min · jiezi

LeetCode算法系列_0862_和至少为K的最短子数组

0862_和至少为 K 的最短子数组题目描述返回 A 的最短的非空连续子数组的长度,该子数组的和至少为 K 。如果没有和至少为 K 的非空子数组,返回 -1 。示例1:输入:A = [1], K = 1输出:1示例2:输入:A = [1,2], K = 4输出:-1示例3:输入:A = [2,-1,2], K = 3输出:3Note:1. 1 <= A.length <= 500002. -10 ^ 5 <= A[i] <= 10 ^ 53. 1 <= K <= 10 ^ 9暴力算法func shortestSubarray(A []int, K int) int { minLen := 50001 tail := len(A) - 1 for i := 0; i <= tail; i++ { sum := A[i] if sum >= K { minLen = 1 break } for j := i + 1; j <= tail; j++ { sum += A[j] if sum >= K { l := j - i + 1 if l < minLen { minLen = l } break } } } if minLen != 50001 { return minLen } return -1}高性能版本算法func shortestSubarray(A []int, K int) int { size := len(A) sums := make([]int, size+1) for i, n := range A { if n >= K { return 1 } sums[i+1] = sums[i] + n } res := size + 1 //存储0—-i,有可能是符合条件的最短子串的head deque := make([]int, 0, 0) for i := 0; i < size+1; i++ { for len(deque) > 0 && (sums[i]-sums[deque[0]] >= K) { // i 递增 // 可能有 j>i 使得 sums[j] - sums[deque[0]] >= K // 但是由于 j>i,所以deque[0]—i是以deque[0]作为头的最短的符合条件的子串 // 把 deque[0] 删除 res = min(res, i-deque[0]) deque = deque[1:] } for len(deque) > 0 && (sums[i] <= sums[deque[len(deque)-1]]) { // 如果存在 j>i>deque[r] 使得 sums[j] - sums[deque[r]] >= K // 由于 sums[deque[r]] >= sums[i] 则 // sums[j] - sums[i] >= K 一定成立,并且 j-i < j-deque[r] // 所以,以deque[r]作为头,不可能是最短的符合条件子串,删除 deque = deque[:len(deque)-1] } deque = append(deque, i) } if res == size+1 { return -1 } return res}个人思路相对于暴力破解,重复计算两个元素之间的和,所以此处必须进行优化,只需要循环一遍数组,存储0-i元素的和假如a<b,sums[b]-sums[a]>=K,b-a则是子串长度deque(双端队列)存储0-i,可能符合条件的最短子串的head遍历sums,取deque的[0],判断该元素作为子串的head,是否符合条件,如果符合条件,那么它将是该数组元素作为head的符合条件的子串中,最短的子串,从deque中剔除[0],减少后面的sums比次数取deque的尾,值为r,sums[r]>=sums[i],那么如果后面存在sums[j]-sums[r]>=k,那么j>i>r,i->j之间的子串肯定也符合条件,并且更短,已r为head的子串,不可能是最短的字段,在这里可以删除deque的这个尾,减少比对次数总结在leetcode社区中,有Java版本的算法代码,思路和此处思路完全一致,但是时间效率比Golang版的搞,Golang版170ms左右,Java版本40+ms笔者经过对比代码,主要区别在于deque这个存储容器,在JDK中有对应的数据结构Deque双端队列,数据结构的插入效率导致的,笔者猜测Java中该结构用的双向链表实现的,在插入元素过程中,不需要扩容,内存分配效率很高Golang版本代码,deque用的slice切片,在append过程中,不断的扩容,进行内存分配,导致效率低有兴趣的朋友可以在此处优化deque这个数据结构,采用struct对象,构建双向链表结构,相信,效率和Java版本的应该差不多,在此,篇幅限制,双向链表也不是该算法的核心,笔者就到此为止数据结构作为数据的存储容易,对算法的影响也是巨大的GitHub项目源码在这里笔者会一直维护该项目,对leetcode中的算法题进行解决,并写下自己的思路和见解,致力于人人都能看懂的算法个人公众号喜欢的朋友可以关注,谢谢支持记录90后码农的学习与生活 ...

September 2, 2018 · 2 min · jiezi