面试了这么多,结果不是太好,有点儿受打击,也促使我近期静下心来反思自己的问题:哪些技术知识掌握的还不错,哪些还有待提高,哪些是需要去恶补的。
阿里面试了三个部门,都是在二面挂的,网易和滴滴也是各两轮技术面试,加一起就是十次面试经历。在此回忆总结一下,既是给社区朋友的一个参考,反馈社区,更是给自己一个好好的总结。
autocomplete属性规定form或input域应该拥有自动完成功能。当用户在自动完成域中开始输入时,浏览器应该在该域中显示填写的选项:实例:
结果:
1)canvas可以动态地绘制各种效果精美的图形,结合js就能让网页图形动起来
2)SVG绘制可伸缩的矢量图形
3)audio和video可以不依赖任何插件播放音频和视频
离线缓存WebStorage(为HTML5开发移动应用提供了基础)
传统的web应用程序中,数据处理都由服务器处理,html只负责展示数据,cookie只能存储少量的数据,跨域通信只能通过web服务器。
HTML5扩充了文件存储的能力,多达5MB,支持WebSQL等轻量级数据库,可以开发支持离线web应用程序。
HTML5WebStorageAPI可以看做是加强版的cookie,不受数据大小限制,有更好的弹性以及架构,可以将数据写入到本机的ROM中,还可以在关闭浏览器后再次打开时恢复数据,以减少网络流量。
同时,这个功能算得上是另一个方向的后台“操作记录”,而不占用任何后台资源,减轻设备硬件压力,增加运行流畅性。
新增GeolocationAPI,可以通过浏览器获取用户的地理位置,不再需要借助第三方地址数据库或专业的开发包,提供很大的方便。
如果还其它方式,请在回复中给出,我会及时更新!!
上面的例子,我再加两行代码,创建两个BFC:
#c1{overflow:hidden;}#c2{overflow:hidden;}效果如下:
上面创建了两个布局环境BFC。内部子元素的左浮动不会影响到外部元素。所以#c1和#c2没有受浮动的影响,仍然各自占据一行!
a、不和浮动元素重叠
如果一个浮动元素后面跟着一个非浮动的元素,那么就会产生一个覆盖的现象,很多自适应的两栏布局就是这么做的。
看下面一个例子
很明显,.aside和.mian重叠了。试分析一下,由于两个box都处在同一个BFC中,都是以BFC边界为起点,如果两个box本身都具备BFC的话,会按顺序一个一个排列布局,现在.main并不具备BFC,按照规则2,内部元素都会从左边界开始,除非它本身具备BFC,按上面规则4拥有BFC的元素是不可以跟浮动元素重叠的,所以只要为.mian再创建一个BFC,就可以解决这个重叠的问题。上面已经说过创建BFC的方法,可以根据具体情况选用不同的方法,这里我选用的是加overflow:hidden。
由于ie的原因需要再加一个解发haslayout的zoom:1,有关haslayout后面会讲到。
b、清除元素内部浮动
只要把父元素设为BFC就可以清理子元素的浮动了,最常见的用法就是在父元素上设置overflow:hidden样式,对于IE6加上zoom:1就可以了(IEHaslayout)。
看下面例子:
所以,触发外部容器BFC,高度将重新计算。比如给outer加上属性overflow:hidden触发其BFC。
c、解决上下相邻两个元素重叠
所以解这个问题的办法就是,把两个容器分别放在两个据有BFC的包裹容器中,IE里就是触发layout的两个包裹容器中!
上面的例子中我们用到了IE的zoom:1;实际上是触发了IE的layout。Layout是IE浏览器渲染引擎的一个内部组成部分。在IE浏览器中,一个元素要么自己对自身的内容进行组织和计算大小,要么依赖于包含块来计算尺寸和组织内容。为了协调这两种方式的矛盾,渲染引擎采用了‘hasLayout’属性,属性值可以为true或false。当一个元素的‘hasLayout’属性值为true时,我们说这个元素有一个布局(layout),或拥有布局。可以通过hasLayout属性来判断一个元素是否拥有layout,
如object.currentStyle.hasLayout。
hasLayout与BFC有很多相似之处,但hasLayout的概念会更容易理解。在InternetExplorer中,元素使用“布局”概念来控制尺寸和定位,分为拥有布局和没有布局两种情况,拥有布局的元素由它控制本身及其子元素的尺寸和定位,而没有布局的元素则通过父元素(最近的拥有布局的祖先元素)来控制尺寸和定位,而一个元素是否拥有布局则由hasLayout属性告知浏览器,它是个布尔型变量,true代表元素拥有布局,false代表元素没有布局。简而言之,hasLayout只是一个IE下专有的属性,hasLayout为true的元素浏览器会赋予它一系列的效果。
特别注意的是,hasLayout在IE8及之后的IE版本中已经被抛弃,所以在实际开发中只需针对IE8以下的浏览器为某些元素触发hasLayout。
一个元素触发hasLayout会影响一个元素的尺寸和定位,这样会消耗更多的系统资源,因此IE设计者默认只为一部分的元素触发hasLayout(即默认有部分元素会触发hasLayout,这与BFC基本完全由开发者通过特定CSS触发并不一样),这部分元素如下:
通过为元素设置以下任一CSS,可以触发hasLayout(即把元素的hasLayout属性设置为true)。
hasLayout表现出来的特性跟BFC很相似,所以可以认为是IE中的BFC。上面的规则几乎都遵循,所以上面的问题在IE里都可以通过触发hasLayout来解决。
虽然hasLayout也会像BFC那样影响着元素的尺寸和定位,但它却又不是一套完整的标准,并且由于它默认只为某些元素触发,这导致了IE下很多前端开发的bugs,触发hasLayout更大的意义在于解决一些IE下的bugs,而不是利用它的一些“副作用”来达到某些效果。另外由于触发hasLayout的元素会出现一些跟触发BFC的元素相似的效果,因此为了统一元素在IE与支持BFC的浏览器下的表现,Kayo建议为触发了BFC的元素同时触发hasLayout,当然还需要考虑实际的情况,也有可能只需触发其中一个就可以达到表现统一,下面会举例介绍。
这里首先列出触发hasLayout元素的一些效果:
a、阻止外边距折叠
如上面例子:
运行效果如下:
2.调用
拖拽的基本原理就是根据鼠标的移动来移动被拖拽的元素。鼠标的移动也就是x、y坐标的变化;元素的移动就是style.position的top和left的改变。当然,并不是任何时候移动鼠标都要造成元素的移动,而应该判断鼠标左键的状态是否为按下状态,是否是在可拖拽的元素上按下的。
部分实例代码:
CSS部分
为了避免这两个缺点,我们可以使用setTimeout()来实现重复的定时器
在浏览器动画程序中,我们通常使用一个定时器来循环每隔几毫秒移动目标物体一次,来让它动起来。如今有一个好消息,浏览器开发商们决定:“嗨,为什么我们不在浏览器里提供这样一个API呢,这样一来我们可以为用户优化他们的动画。”所以,这个requestAnimationFrame()函数就是针对动画效果的API,你可以把它用在DOM上的风格变化或画布动画或WebGL中。
浏览器可以优化并行的动画动作,更合理的重新排列动作序列,并把能够合并的动作放在一个渲染周期内完成,从而呈现出更流畅的动画效果。比如,通过requestAnimationFrame(),JS动画能够和CSS动画/变换或SVGSMIL动画同步发生。另外,如果在一个浏览器标签页里运行一个动画,当这个标签页不可见时,浏览器会暂停它,这会减少CPU,内存的压力,节省电池电量。
谷歌浏览器,火狐浏览器,IE10+都实现了这个函数,即使你的浏览器很古老,上面的对requestAnimationFrame封装也能让这个方法在IE8/9上不出错。
Promise对象有两个重要的方法,一个是then,另一个是resolve:
Promise常用方式如下:
varp=newPromise(function(resolve,reject){...//事务触发resovle(xxx);...});p.then(function(value){//满足},function(reason){//拒绝}).then().then()...示意图如下:
1.Promise其实就是一个状态机。按照它的定义,我们可从如下基础代码开始:
varPENDING=0;//进行中varFULFILLED=1;//成功varREJECTED=2;//失败functionPromise(){//存储PENDING,FULFILLED或者REJECTED的状态varstate=PENDING;//存储成功或失败的结果值varvalue=null;//存储成功或失败的处理程序,通过调用`.then`或者`.done`方法varhandlers=[];//成功状态变化functionfulfill(result){state=FULFILLED;value=result;}//失败状态变化functionreject(error){state=REJECTED;value=error;}}2.下面是Promise的resolve方法实现:
注意:resolve方法可接收的参数有两种:一个普通的值/对象或者一个Promise对象。如果是普通的值/对象,则直接把结果传递到下一个对象;如果是一个Promise对象,则必须先等待这个子任务序列完成。
functionPromise(){...functionresolve(result){try{varthen=getThen(result);//如果是一个promise对象if(then){doResolve(then.bind(result),resolve,reject);return;}//修改状态,传递结果到下一个事务fulfill(result);}catch(e){reject(e);}}}两个辅助方法:
/***CheckifavalueisaPromiseand,ifitis,*returnthe`then`methodofthatpromise.**@param{Promise|Any}value*@return{Function|Null}*/functiongetThen(value){vart=typeofvalue;if(value&&(t==='object'||t==='function')){varthen=value.then;if(typeofthen==='function'){returnthen;}}returnnull;}/***Takeapotentiallymisbehavingresolverfunctionandmakesure*onFulfilledandonRejectedareonlycalledonce.**Makesnoguaranteesaboutasynchrony.**@param{Function}fnAresolverfunctionthatmaynotbetrusted*@param{Function}onFulfilled*@param{Function}onRejected*/functiondoResolve(fn,onFulfilled,onRejected){vardone=false;try{fn(function(value){if(done)return;done=true;onFulfilled(value);},function(reason){if(done)return;done=true;onRejected(reason);});}catch(ex){if(done)return;done=true;onRejected(ex);}}3.上面已经完成了一个完整的内部状态机,但我们并没有暴露一个方法去解析或则观察Promise。现在让我们开始解析Promise:
functionPromise(fn){...doResolve(fn,resolve,reject);}如你所见,我们复用了doResolve,因为对于初始化的fn也要对其进行控制。fn允许调用resolve或则reject多次,甚至抛出异常。这完全取决于我们去保证promise对象仅被resolved或则rejected一次,且状态不能随意改变。
4.目前,我们已经有了一个完整的状态机,但我们仍然没有办法去观察它的任何变化。我们最终的目标是实现then方法,但done方法似乎更简单,所以让我们先实现它。
我们的目标是实现promise.done(onFullfilled,onRejected):
5.现在我们已经实现了done方法,下面实现then方法就很容易了。需要注意的是,我们要在处理程序中新建一个Promise。
this.then=function(onFulfilled,onRejected){varself=this;returnnewPromise(function(resolve,reject){returnself.done(function(result){if(typeofonFulfilled==='function'){try{//onFulfilled方法要有返回值!returnresolve(onFulfilled(result));}catch(ex){returnreject(ex);}}else{returnresolve(result);}},function(error){if(typeofonRejected==='function'){try{returnresolve(onRejected(error));}catch(ex){returnreject(ex);}}else{returnreject(error);}});});}测试完成了上面的代码,测试就很容易啦。偷个懒,测试实例来自MDN:
vardata={name:'kindeng'};observe(data);data.name='dmq';//哈哈哈,监听到值变化了kindeng-->dmqfunctionobserve(data){if(!data||typeofdata!=='object'){return;}//取出所有属性遍历Object.keys(data).forEach(function(key){defineReactive(data,key,data[key]);});};functiondefineReactive(data,key,val){observe(val);//监听子属性Object.defineProperty(data,key,{enumerable:true,//可枚举configurable:false,//不能再defineget:function(){returnval;},set:function(newVal){console.log('哈哈哈,监听到值变化了',val,'-->',newVal);val=newVal;}});}这样我们已经可以监听每个数据的变化了,那么监听到变化之后就是怎么通知订阅者了,所以接下来我们需要实现一个消息订阅器,很简单,维护一个数组,用来收集订阅者,数据变动触发notify,再调用订阅者的update方法,代码改善之后是这样:
//...省略functiondefineReactive(data,key,val){vardep=newDep();observe(val);//监听子属性Object.defineProperty(data,key,{//...省略set:function(newVal){if(val===newVal)return;console.log('哈哈哈,监听到值变化了',val,'-->',newVal);val=newVal;dep.notify();//通知所有订阅者}});}functionDep(){this.subs=[];}Dep.prototype={addSub:function(sub){this.subs.push(sub);},notify:function(){this.subs.forEach(function(sub){sub.update();});}};那么问题来了,谁是订阅者?怎么往订阅器添加订阅者?没错,上面的思路整理中我们已经明确订阅者应该是Watcher,而且vardep=newDep();是在defineReactive方法内部定义的,所以想通过dep添加订阅者,就必须要在闭包内操作,所以我们可以在getter里面动手脚:
因为遍历解析的过程有多次操作dom节点,为提高性能和效率,会先将跟节点el转换成文档碎片fragment进行解析编译操作,解析完成,再将fragment添加回原来的真实dom节点中
functionCompile(el){this.$el=this.isElementNode(el)el:document.querySelector(el);if(this.$el){this.$fragment=this.node2Fragment(this.$el);this.init();this.$el.appendChild(this.$fragment);}}Compile.prototype={init:function(){this.compileElement(this.$fragment);},node2Fragment:function(el){varfragment=document.createDocumentFragment(),child;//将原生节点拷贝到fragmentwhile(child=el.firstChild){fragment.appendChild(child);}returnfragment;}};compileElement方法将遍历所有节点及其子节点,进行扫描解析编译,调用对应的指令渲染函数进行数据渲染,并调用对应的指令更新函数进行绑定,详看代码及注释说明:
Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:1、在自身实例化时往属性订阅器(dep)里面添加自己2、自身必须有一个update()方法3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。如果有点乱,可以回顾下前面的思路整理
MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化->视图更新;视图交互变化(input)->数据model变更的双向绑定效果。
一个简单的MVVM构造器是这样子:
functionMVVM(options){this.$options=options;vardata=this._data=this.$options.data;observe(data,this);this.$compile=newCompile(options.el||document.body,this)}但是这里有个问题,从代码中可看出监听的数据对象是options.data,每次需要更新视图,则必须通过varvm=newMVVM({data:{name:'kindeng'}});vm._data.name='dmq';这样的方式来改变数据。
显然不符合我们一开始的期望,我们所期望的调用方式应该是这样的:varvm=newMVVM({data:{name:'kindeng'}});vm.name='dmq';
所以这里需要给MVVM实例添加一个属性代理的方法,使访问vm的属性代理为访问vm._data的属性,改造后的代码如下:
functionMVVM(options){this.$options=options;vardata=this._data=this.$options.data,me=this;//属性代理,实现vm.xxx->vm._data.xxxObject.keys(data).forEach(function(key){me._proxy(key);});observe(data,this);this.$compile=newCompile(options.el||document.body,this)}MVVM.prototype={_proxy:function(key){varme=this;Object.defineProperty(me,key,{configurable:false,enumerable:true,get:functionproxyGetter(){returnme._data[key];},set:functionproxySetter(newVal){me._data[key]=newVal;}});}};这里主要还是利用了Object.defineProperty()这个方法来劫持了vm实例对象的属性的读写权,使读写vm实例的属性转成读写了vm._data的属性值,达到鱼目混珠的效果,哈哈
本文主要围绕“几种实现双向绑定的做法”、“实现Observer”、“实现Compile”、“实现Watcher”、“实现MVVM”这几个模块来阐述了双向绑定的原理和实现。并根据思路流程渐进梳理讲解了一些细节思路和比较关键的内容点,以及通过展示部分关键代码讲述了怎样一步步实现一个双向绑定MVVM。
DNS递归查找如下图所示:
DNS有一点令人担忧,这就是像wikipedia.org或者facebook.com这样的整个域名看上去只是对应一个单独的IP地址。还好,有几种方法可以消除这个瓶颈:
大多数DNS服务器使用Anycast来获得高效低延迟的DNS查找。
因为像Facebook主页这样的动态页面,打开后在浏览器缓存中很快甚至马上就会过期,毫无疑问他们不能从中读取。
所以,浏览器将把一下请求发送到Facebook所在的服务器:
用来看原始HTTP请求及其相应的工具很多。作者比较喜欢使用fiddler,当然也有像FireBug这样其他的工具。这些软件在网站优化时会帮上很大忙。
图中所示为Facebook服务器发回给浏览器的响应:
为什么服务器一定要重定向而不是直接发会用户想看的网页内容呢?这个问题有好多有意思的答案。
还有一个是用不同的地址会造成缓存友好性变差。当一个页面有好几个名字时,它可能会在缓存里出现好几次。
服务器接收到获取请求,然后处理并返回一个响应。
这表面上看起来是一个顺向的任务,但其实这中间发生了很多有意思的东西-就像作者博客这样简单的网站,何况像facebook那样访问量大的网站呢!
委托工作给批处理是一个廉价保持数据更新的技术。举例来讲,Fackbook得及时更新新闻feed,但数据支持下的“你可能认识的人”功能只需要每晚更新(作者猜测是这样的,改功能如何完善不得而知)。批处理作业更新会导致一些不太重要的数据陈旧,但能使数据更新耕作更快更简洁。
图中为服务器生成并返回的响应:
HTTP/1.1200OKCache-Control:private,no-store,no-cache,must-revalidate,post-check=0,pre-check=0Expires:Sat,01Jan200000:00:00GMTP3P:CP="DSPLAW"Pragma:no-cacheContent-Encoding:gzipContent-Type:text/html;charset=utf-8X-Cnection:closeTransfer-Encoding:chunkedDate:Fri,12Feb201009:05:55GMT2b3Tn@[...]整个响应大小为35kB,其中大部分在整理后以blob类型传输。
请注意报头中把Content-type设置为“text/html”。报头让浏览器将该响应内容以HTML形式呈现,而不是以文件形式下载它。浏览器会根据报头信息决定如何解释该响应,不过同时也会考虑像URL扩展内容等其他因素。
在浏览器没有完整接受全部HTML文档时,它就已经开始显示这个页面了:
在浏览器显示HTML时,它会注意到需要获取其他地址内容的标签。这时,浏览器会发送一个获取请求来重新获得这些文件。
下面是几个我们访问facebook.com时需要重获取的几个URL:
这些地址都要经历一个和HTML读取类似的过程。所以浏览器会在DNS中查找这些域名,发送请求,重定向等等...
静态内容往往代表站点的带宽大小,也能通过CDN轻松的复制。通常网站会使用第三方的CDN。例如,Facebook的静态文件由最大的CDN提供商Akamai来托管。
在Web2.0伟大精神的指引下,页面显示完成后客户端仍与服务器端保持着联系。
提起这个模式,就必须要讲讲"AJAX"--“异步JavaScript和XML”,虽然服务器为什么用XML格式来进行响应也没有个一清二白的原因。再举个例子吧,对于异步请求,Facebook会返回一些JavaScript的代码片段。
除了其他,fiddler这个工具能够让你看到浏览器发送的异步请求。事实上,你不仅可以被动的做为这些请求的看客,还能主动出击修改和重新发送它们。AJAX请求这么容易被蒙,可着实让那些计分的在线游戏开发者们郁闷的了。(当然,可别那样骗人家~)
Facebook聊天功能提供了关于AJAX一个有意思的问题案例:把数据从服务器端推送到客户端。因为HTTP是一个请求-响应协议,所以聊天服务器不能把新消息发给客户。取而代之的是客户端不得不隔几秒就轮询下服务器端看自己有没有新消息。
这些情况发生时长轮询是个减轻服务器负载挺有趣的技术。如果当被轮询时服务器没有新消息,它就不理这个客户端。而当尚未超时的情况下收到了该客户的新消息,服务器就会找到未完成的请求,把新消息做为响应返回给客户端。
希望看了本文,你能明白不同的网络模块是如何协同工作的
webpack其实也是必问的,由于我说还没使用过webpack,只是了解,写过demo,面试官就没问太深。如果你的简历中有提到webpack,请提前准备好,比如webpack打包原理、如何写webpack插件等。
面试阿里云那个岗位的时候,有要求算法和数据结构,有能力者多多准备吧。
阿里、网易的面试几乎都是围绕项目展开的,所以提醒自己搬砖的时候多想想、多看看,多站在一个高度去看整个项目:用到什么技术,技术实现原理是什么,项目框架怎么搭建的,采取安全措施了吗…
虽然五次面试都没成功,但自己也收获了很多很多:认识了大牛hb,一个超有文艺气息的资深前端;多谢fw大大帮我内推阿里,十分感谢您对我的认可;也见到了平时只能在视频上看到的cjf老师,谢谢您的指点;对高级前端工程师所具备的技能有了更清晰的认识;肯定也增加了很多面试经验…