randyjin 2017-09-27
昨天去京东面试了一下前端.可惜没有面试成功.回来根据回忆把这次失败的经历记录下来,来让自己静下心.也可以成为别人的面试参考.
首先介绍一下我的整个面试过程下来对京东前端技术这部分的映象.在面试过程中我了解到京东的后台是用java写的,而且前后端并未实现分离,所以要想去京东面试,必须对java有一定的了解,以及java写前端的一些框架比如:SSH.以及mvc框架的实现原理和机制,这块内容虽然并未在招聘上面写明,不过如果你会这方面的内容,是一个很好很强大的加分项.
其实面试的时候的提问无非就是一些基本的html知识,和css的一些知识,然后就是js的一些基本点,和难点.
第一部分:css
在面试之前,我以为面试css会从盒模型,position和float之间的影响,动画,或者一些兼容情况来给你出难题让你来解决.但是实际情况问得都是最基本的.我记忆中,css部分问得比较少,几个方面如下:
1:css可以在html中写吗?
这个就算是最最基础的问题了,一般做前端的人都了解,在html中可以写在内联样式中(style).或者直接link引入css文件.还有一个import.有的同学可能没有听说过@import,平时只用过link.其实@import确实是不被推荐使用的,因为在一些浏览器中会有兼容性问题,比如ie5;另一个原因:link是xhtml标签啊,除了能引入css样式文件之外,还能定义RSS,rel等,功能很强大的;最后一个原因:在加载网页的时候link是随着网页的加载同步进行的,而@import是网页加载完毕之后进行的.对用户体验差别很大的.
2:css的优先级.
这个也简单,!important>内联样式>外联样式(id>class>tag);
3:css优化有几种方式.
这个问题在实际项目中其实是处理过的,但是回答的时候,不知道怎么去表达.回来之后,我从网上查找相关资料,先总结如下:
第一点:从实际出发,从用户角度出发.如果一些很炫的技术,在实际用户体验时并不好,或者根本不需要.那么还是舍去比较好一点.
第二点:提高css的加载性能.
严格让样式和结构分离,也就是说在head中引入css,不要写内联样式.减小css文件的大小,尽可能利用html缓存.也可以对css进行压缩来减少它的体积(这个应该是常用的).能用一句话解决的样式就不要多写.
第三点:合理使用选择器
尽量不要使用通配符选择器.
*{}
能继承的属性就不要再在子元素中重复书写了.能继承的属性如下:
font-size font-family color, UL LI DL DD DT;
不能继承的属性如下:
border padding margin width height ;
第四点:提高渲染性能
对与浮动和定位要考虑再三之后再使用.最好有css书写规范.
关于位置的css尽量放在最前面,之后是大小,文字,背景边框,等.
第五点:要利于后期的维护.
第二部分:html
1.平时用到html5的那些特性.
我平是用到的最多的其实就是新的标签和语义化了.在html5的规范中,其实对行内元素和快级元素的分类已经弱化了.或者说没有了.平时用到的语义化标签如下:
<hx> <ul><li><ol> <p> <em> <strong> <table> <site> <blockquote>块引用 <header> <footer> <article> <section> <nav> <aside> //下面是表单的一些东西 <table>使用场景是表状数据 <label>输入控件定义文本标签 <fieldset>把表格中的相关控件集中起来 <legend>定义控件组的标题
2为什么要html5语义化开发?
第一点:更容易让结构和样式分离,可以用正确的标签来处理正确的事情,从而使浏览器,和搜索引擎解析方便.
第二点:在所有的css样式文件不存在的情况下,对网页的结构也是一目了然;
第三点:搜索引擎的爬虫依赖于html标记去确认上下文以及各个关键字的权重,从而有利于搜索引擎的优化.
第三部分:js
1.js的this指针理解
在这里我引用一个别的面试题来更好的说明一下关于this指针的问题(引用自一个微信公众号的内容,名字是:web前端课程):
function Foo () { getName = function (){ alert(1) }; return this; }; Foo.getName = function (){ alert(2); }; Foo.prototype.getName = function (){ alert(3); }; var getName =function (){ alert(4); }; function getName(){ alert(5); }
通过以下方式进行调用:
Foo.getName(); getName(); Foo().getName(); getName(); new Foo.getName(); new Foo().getName(); new new Foo().getName();
最后的结果是:2,4,1,1,2,3,3.
其实可以从答案来说明this的指向;
第一问的Foo.getName();其实就是在调用Foo函数的属性getName(),答案其实就是该函数的输出:2
第二问的getName();就是在调用getName这个函数.输出:4;但是为什么不是5呢?这其实和var这个声明(所有的声明变量或者函数都会提升到当前函数的顶部)有关.下面是整个的处理过程.
//声明变量和函数 var getName;//声明变量 function getName () { alert(5); }//声明函数 function Foo () { getName = function (){ alert(1); } return this; } Foo.getName = function (){ alert(2); }; Foo.prototype.getName()=function () { alert(3) }; getName = function (){ alert(4) };//赋值操作.(其实是覆盖了alert(5));
所以根据这个过程来看.getName()最后的结果就是4;
第三问的Foo().getName();执行过程是这样的:首先执行Foo()函数,发现Foo函数的作用域中并没有声明getName;所以会在外层作用域中查找getName变量,发现在外层作用域中有该变量的声明(如果没有会一直网上找,找到window还没有就会自己创建),于是对该变量进行赋值操作,也就是
getName = function () { alert(1); }
,到这里就该执行Foo函数的renturn this 语句,这里的this由于是直接的调用,所以调用结果就是window,
所以,现在Foo().getName()函数就相当于,window.getName();而由于上面的赋值操作,所以window.getName()的结果就是1
第四问的getName函数,相当于window.getName().也就是第三问的结果,所以是1
第五问的new Foo.getName();其实就是在调用Foo.getName()这个函数,结果是2.(因为'.'运算符的优先级高于'new').
第六问的new Foo().getName(),因为'()'运算符的优先级高于'.'高于'new';所以实际执行顺序是这样的:(new Foo()).getName(); js的构造函数是可以有返回值,也可以没有的,如果没有返回值则返回实例化对象,如果有返回值则判断这个返回值是不是引用的类型,对于基本的数据类型而言,返回实例化对象;对于引用的类型而言(注意:对象也是引用的类型)则返回该引用类型;
function test (){ } new test();//结果是test{}
function test (){ return 0} new test () { }//结果是test{};
function test () { return {a:0} } new test();//结果是{a:0}
所以new Foo()的结果是该实例化对象(return this )(this 就指向该实例化对象,而不是window,因为new改变了this 的指向);所以(new Foo()).getName()就相当于 Foo.prototype.getName().所以结果是3;
第七问的new new Foo().getName();的执行结果其实就相当于new ((new Foo()).getName()),其实就相当于new(Foo.prototype.getName());所以结果就是3;
2.对js或者jQuery的事件代理的了解;
其实在遇到这个问题的时候我脑袋有点懵,因为在我以前的项目中,并没有遇到过这种事情.所以回来之后,我就对事件代理进行了了解,其实说起来也是比较容易理解的.
js的事件代理和事件委托 其实和dom树以及浏览器的事件冒泡机制有关系.简而言之就是浏览器处理dom事件的机制是:事件捕获,事件目标,事件气泡(这是标准的,ie有不同的机制).
事件捕获:当一个元素触发某个事件的时候,顶层的document对象会发出一个事件流从DOM树的节点向下面对的目标元素节点流动,直到到达触发事件的真正的节点,在此过程中事件对应的监听函数是不会起作用的;
事件目标:找到目标元素之后执行目标元素对应的处理函数,没有则不执行;
事件起泡:从目标元素开始往上层开始流动,如果中途有节点绑定了相应的事件,则触发事件.如果想阻止事件的触发,则e.stopPropagation() (Firefox)或者e.cancelBubble=true (IE);
它的主要用途是用于:当对很多元素添加事件的时候,我们可以把事件委托给它的父节点 来触发处理的函数,这样就减少了函数绑定的个数.
//jquery的事件代理 $(selector).delegate(childSelector,event,data,function) //childSelector是必须的,附加事件处理程序的子元素 //event是必需的,有效事件 //data可选的,传递的数据 //必选的,事件发生时的运行的函数.
3.对js闭包的理解
当问到这个问题的时候,我没有回答好,因为我的理解是,所有的js中所有的函数不都是闭包吗?这如何谈理解呢?
但是当我回到住的地方的时候,我才想起来,这应该是问我,词法作用域,作用域链和闭包;其实所有的js函数的确都是闭包的.它们都是对象.通常用到闭包其实就是函数套嵌多个函数的时候,也就是说,调用函数时的作用域链和定义函数时的作用域链不是同一个作用域链的时候,会出现一些问题.
说到这里就会说道变量作用域,在一些说更底层的编程语言中,比如c,一个函数的局部变量的定义以及存储都是存储在电脑的栈里,当函数返回的时候,它们就已经不存在了.但是在js中并非如此,js中的作用域其实更像一个对象列表(作用域链)没有绑定在栈,函数定义时的作用域链在函数调用的时候依然存在,每一次对js对象的调用,又会重新创建一个新的对象,去保存局部变量,然后把这个对象添加到作用域链之中.当函数返回的时候就会删除相应的对象.所以,如果没有嵌套的函数,也没有其他的引用来指向这个绑定对象的话,它就会被当成垃圾来回收处理掉.但是如果有嵌套的函数,每个嵌套的函数都有各自的作用域链,并且该作用域链指向一个变量绑定对象.当这些嵌套的函数在外部的作用域之中被保存了下来,也就是说虽然有嵌套但是没有其他的指引来指向绑定变量,这个时候,该函数和该函数指向的绑定变量一起被当作垃圾回收,所以在使用闭包的时候,这里是需要注意的地方.所以在使用闭包的时候,定义嵌套函数时,将它作为返回值返回或者存在某处的属性中,这个时候就会有外部引用指向这个嵌套的函数,就不会被当作垃圾来处理了(包括绑定的对象).
function counter () {
var n = 0; return { count:function(){return n++;}, reset:function(){n = 0;} } } var c = counter(),d = counter(); c.counter()//0 d.counter()//0 c.reset()//reset()和count()共享 d.counter()//1 c.counter()//0 //引自js权威指南
这个能很好的说明闭包.count()和reset() 都能访问到私有变量n,在每次调用count()的时候,都会重新定义一个作用域链和新的私有变量,所以,调用counter()几次就会得到几个对象,所以c和d是互不干扰的两个对象,并且拥有互不干扰的私有变量.所以各自调用count和reset并不会影响另外一个.
4,对js的apply(),call(),bind()的理解
在平时的工作之中遇到过使用apply的场景,是因为当时的写的api的一个方法的this指针并没有调用过来.所以用apply方法来使用的.但是当面试的时候,确实谈不了太多,因为确实是自己认识的比较少.
其实说到apply()和call(),就还得说道js语言的根本上,js是面向对象的语言,js中的任何都可以当成是对象(包括函数),当this可以更改的时候,任何函数就都可以作为任何对象的方法来调用,也就是说apply()和call()可以实现简介调用函数,即便这个函数与那个对象并没有什么关系.而call()方法与apply()方法的最直观的区别就是:call()使用它自己的实参列表当函数的实参.apply()方法要求一数组的形式传入参数.
bind()方法的主要作用是可以将函数绑定至某一个对象
function f(y){ return this.x+y; } var o = {x:1}; var g = f.bind(o); g(2)//=>3 //引自js权威指南
这就是bind()的使用方法.
当然也可以通过apply来模仿这个方法:
function bind (f,o){] if(f.bind){return f.bind(o)}; else return function (){ return f.apply(o,arguments); }} //引自js权威指南
但是模仿的这个方法是不能和bind方法等同的.因为bind()的实参会绑定至this.其实这也被称之为柯里化编程.
4.如何理解柯里化(curry)编程
这个在面试的时候,脑子里隐隐约约有一些映象.
curry化编程,其实就是只给函数传递一部分参数来调用它,让它返回一个函数去处理剩下的参数.
function curryBefore (a,b){ if(b === undefined){ return function (z){ return a+z }else{ reuturn a+b } } }
上面写的这个函数是没有curry之前的一个函数,当你调用这个函数的时候,
curryBefore(1,2);和curryBefore(1)(2)的调用结果是一样的.但是执行的过程不一样,第二中调用方式其实用到了闭包的特性,记录了a的值,而第一种调用方式是直接调用的else后面的表达式.那么这个curry是怎么来处理的呢?
//此处代码引自<<JavaScript: Novice to Ninja>> function curry (func){ var fixedArgs = [].slice.call(arguments,1); return function (){ args = fixedArgs.concat([].slice.call(arguments)) return func.apply(null,args); }; }
下面说明curry的运行过程.
function multiplication (x,y){ return x*y } course = curry(multiplication,2); course(1);
执行了course(1),其实执行了curry(multiplication,2)(1);curry通过[].slice去掉了第一个参数:
function multiplication (x,y){ return x*y},然后保留了变量x=2,返回一个新的函数,这时候传入变量y=1,
然后通过:
fixedArgs.concat([].slice.call(arguments))
将x和y组成一个新的数组,然后在执行multiplication.其实就是通过curry来整理参数,最后通过apply来调用.
background-color: blue;background-color: yellow;<input type="button" value="变蓝" @click="changeColorT