其实感觉前端的面试就是一些零碎的知识点,最后上升为架构而已。这篇博客我也会将架构设计融入其中。大致通过以下几个分类进行分析:
- HTML/CSS部分
- JavaScript部分
- 数据结构部分(算法)
- 框架之Angular.js
- 框架之Vue.js
- 框架之React.js
- 移动端
- HTTP
- WEB安全
- WEB渗透之Kali Linux
- 前端性能
- 设计模式
- 正则表达式
- 前端架构设计
一、HTML/CSS部分
1.1 什么是盒子模型?
在网页中,一个元素占有空间的大小由几个部分构成,其中包括元素的内容(content),元素的内边距(padding),元素的边框(border),元素的外边距(margin)四个部分。这四个部分占有的空间中,有的部分可以显示相应的内容,而有的部分只用来分隔相邻的区域或区域。4个部分一起构成了css中元素的盒模型。
1.2 什么是css Hack?
一般来说,css Hack就是针对不同的浏览器写不同css样式。css hack一般来说是为了兼容IE浏览器的。
IE浏览器Hack一般又分为三种: 条件Hack、属性Hack、选择器Hack。
// 1.条件Hack
// 2.属性Hack
.test {
color: #090\9; /* For IE8+ */
*color: #f00; /* For IE7 and earlier */
_color: #ff0; /* For IE6 and earlier */
}
// 3.选择器Hack
*html .test{
color: #090; /* For IE6 and earlier */
}
*+html .test {
color: #ff0; /* For IE7 */
}
1.3 什么是同步?异步?
当我在学习前端的时候,看到这个问题的时候,让不不禁想起了学习Linux时,一直想的一个问题。什么是异步,同步?什么是阻塞,非阻塞当时这个问题困惑了我很久,一直搞不清楚有什么区别,异步不就是非阻塞吗,同步不就是阻塞吗?下面咱们就唠唠嗑来说说这里面的玄机。在这里咱们主要来以网络I/O为例:
1.3.1 同步和异步
实际上同步与异步是针对***应用程序***与***内核***的交互而言的。同步过程中进程触发IO操作并等待或者轮询的去查看IO操作是否完成。异步过程中进程触发IO操作后,直接返回,做自己的事情,IO交给内核来处理,完成后内核通知进程IO完成。
(这里大家不禁要问了,什么是IO,如果你是做后端开发的,IO对于你来说很容易理解,但是纯做前端的话就不容易理解了。IO就是input/output,输入输出。比如文件的读写操作,网络的请求和得到数据的操作等等。不懂的童鞋要去学习操作系统了)
直接上图吧:
1.3.2 阻塞与非阻塞
简单理解为需要做一件事情能不能立即得到返回应答。如果不能立刻获得返回,需要等待,那就阻塞了,否则就可以理解为非阻塞。详细区别如下图所示:
需要注意了, 这里的立即返回,是针对内核来说的。就是说这个应用程序的某些代码会不会阻塞内核(CPU)的执行。而同步异步是针对于应用程序来说的,到底某些代码会不会影响下面代码的执行。
1.4 px和rem和em的区别?
px和rem和em都是长度单位, 区别是px的值是固定的,指定是多少就是多少,计算比较容易。em的值不是固定的,它的值是继承父级元素的字体大小(其实也不能说是父级,应该说是自身,如果自身没有设置font-size, 则去找父级,然后父级没设置,在往上找,如果没有找到,就是浏览器默认的字体)。下面举例说明:
/* 1. em的案列 */
.test {
font-size: 20px; /* 这句话声明之后就代表了1em = 20px;但是后面的代码与这个没有关系 */
text-indent: 2em; /* 单位转换之后2em = 40px */
}
/* 2.rem的案列 -- rem是根据html的font-size大小定的,一般用于移动端网站开发时使用,进行屏幕适配。*/
html {
font-size: 40px;
}
.test {
font-size: 1rem; /* 单位转换后为40px */
}
1.5 浏览器的内核分别是什么?
IE: trident内核
Firefox: gecko内核
Safari: webkit内核
Opera: 以前是presto内核,Opera现已改用Google Chrome的Blink内核。
Chrome: Blink(基于Webkit内核,google和Opera公同开发)
1.6 清除浮动的影响的方法?
这个问题之前一直说一直说,没有时间去总结。咱们从下面几种方式进行探究。
- 1.空的div方式(不是特别好,如果清除浮动地方多了,就会有多个空标签)
/* 在要清除浮动的地方加上 */
.clearfix {
clear: both;
height: 0px;
}
- 2.使用overflow方式进行清除浮动带来的影响。
/* 想要使用overflow清除浮动带来的影响,一般分为两步骤:
1. 给它加一个父元素
2. 在父元素上写overflow: hidden;
*/
- 3.使用伪元素的方式进行清除浮动带来的影响。
/* 这里说明一下,一般使用这种方式也是需要给它加一个父元素的... */
.test::after {
content:"";
display:block;
height:0;
clear:both;
/* visibility:hidden; */
}
1.7 margin折叠现象?
要想解释这个现象,还是要了解BFC的。这里的我就不啰嗦了,就直接说一下解决方案了。(我经常使用的方案, 其他方案自行百度)
/* 这个很简单和上一个题目一样
*/
在父层加上overflow:hidden;
其他CSS的问题待续吧(CSS3动画、渐进、Canvas、SVG等等先忽略),特别是less、sass、stylus等预处理语言,面试中也是常问的重点。
二、JavaScript部分
这一部分涉及的内容就多了,那么我会从ES5和ES6都会讲解到。
先列个目录
2.1 如何快速合并两个数组?
这个问题看起来很简单,实则没有那么简单。给大家提几个问题,合并数组什么方式最好也就是说性能最佳?合并之后用不用去重?等等都需要考虑。网上给出了push和concat的对比。(温馨提示:如果不懂call和apply的童鞋,自行学习。)
2.1.1. 先使用代码来对比(concat函数与Array.prototype.push.apply)
function testClass() {
var testArray1 = [];
var testArray2 = [];
this.resetArray = function() {
for (var i = 0; i < 100000; i++) {
testArray1.push(i);
testArray2.push(i + 100000);
}
}
this.applyTest = function() {
var startTime = 0,
endTime = 0;
console.log('开始合并的时间是:' + (startTime = new Date().getTime()));
var aa = Array.prototype.push.apply(testArray1, testArray2);
console.log(aa);
console.log('合并完成的时间是:' + (endTime = new Date().getTime()));
console.log('合并数组所用的时间是:' + (endTime - startTime));
}
this.concatTest = function() {
var startTime = 0,
endTime = 0;
console.log('开始合并的时间是:' + (startTime = new Date().getTime()));
var aa = testArray1.concat(testArray2);
console.log(aa.length);
console.log('合并完成的时间是:' + (endTime = new Date().getTime()));
console.log('合并数组所用的时间是:' + (endTime - startTime));
}
}
var apply = new testClass();
apply.resetArray();
apply.applyTest();
var concat = new testClass();
concat.resetArray();
concat.concatTest();
通过测试结果可知:
使用Array.prototype.push.apply 方式性能非常低下,在20w的数据下,执行时间将近是concat方法的11-17倍左右。
2.1.2 Array.prototype.concat.apply 和 concat对比
很多程序员喜欢直接使用Array构造函数下的原型对象上的方法,以此缩短搜索函数的时间,这种方式是否能够带来性能的提升?
在这里只需要将第一个函数中的代码改成
var aa = Array.prototype.concat.apply(testArray1, testArray2);
根据测试的结果,使用Array.prototype.concat.apply的方式还是比直接使用concat函数慢些。之前我也没有注意过,这次测试,我感觉问题大了。难道之前想错了,经过又一轮的测试,证明了我的猜测。下一轮的对比您应该想到了,就是call和apply的性能。(这个对比大家自行研究,结论就是call的性能在chrome浏览上明显比apply性能要高很多)
2.1.3 Array.prototype.concat.call 和 concat对比
将第一个函数中的代码改成如下:
var aa = Array.prototype.concat.call(testArray1, testArray2);
最后的测试结果验证使用Array.prototype.concat.call比直接使用concat方法性能提升了一倍。因此最后的得出的结论就是数据合并性能最高的方式就是使用Array.prototype.concat.call。
各位兄弟姐妹,这只是我自己的测试结果,如果有出入,欢迎指正。至于去重的算法及其性能优化放到算法一节说吧。
2.2 说说你对原型链的理解?
这个问题也是相当有点难度的问题。首先要搞清楚什么是原型对象、什么是构造函数、什么是实例化对象。如果和我一样都清楚的童鞋,您只需看图即可。
这张图表明了如果数组实例化之后,它原型的整个指向。还有一种情况就是当实例化对象时,采用var a = {}或者new Object();它的指向如下:
2.3 说说你对js中面向对象编程的理解?
在没有学习面向对象编程的时候,大家更多的是函数(就是一些功能性代码的集合),而面向对象的思想是从另外一个角度出发来解决函数的局限性。它把对象作为程序的基本单元,其实对象就是对事物的一种抽象描述。
人们发现,现实世界中的事物,都可以用「数据」和「能力」来描述。比如我要描述一个人,「数据」就是他的年龄、性别、身高体重,「能力」就是他能做什么工作,承担什么样的责任。描述一台电视,「数据」就是它的屏幕尺寸、亮度,「能力」就是播放《葫芦娃》。
面向对象的世界里,到处都是对象。对象不光有「数据」和「能力」,还可以接受命令。例如你可以让「狗」这个对象「吃狗粮」,就可以把「吃狗粮」的命令发给「狗」让其执行,然后我们就实现了「狗吃狗粮」的需求。
现在对象有了,如何进行面向对象的编程呢?很简单,依次向不同的对象发送命令就可以了。比如产品经理说要把大象装进冰箱里,用面向对象来实现,我们会先定义一个「冰箱」对象,它的「数据」就是当前的冷冻温度,或者该冰箱已经有了多少头大象,「能力」就是开门、关门。还有一个「大象」对象,它的「数据」可以是大象的智商、体积,「能力」就是「自己跑到冰箱里去」。然后我们依次:
向冰箱下达「开门」的命令。
向大象下达「进冰箱」的命令。
向冰箱下达「关门」的命令。
大家好好理解一下就可以整明白,当然还有其他的编程思想。比如面向接口、面向切面都是常用编程思想。(具体到后面再讲解吧)
2.4 JavaScript(ES5)中封装、多态、继承的实现方式?
2.4.1 JavaScript实现封装
→原始模式
var cat = {
name: '',
color: ''
}
这种方式缺点是: 1.如果生成的对象很多就会很麻烦。2.不能看出实例之间的联系
→简单工厂模式
function Cat(name, color) {
var obj = {};
obj.name = name;
obj.color = color;
return obj;
}
var cat1 = Cat("金毛", "黄色");
var cat2 = Cat("泰迪", "棕色");
这种方式可以解决代码重复的问题,但是依然不能反映出实例对象之间的关系。
→构造函数模式
function Cat(name, color) {
this.name = name;
this.color = color;
}
var cat1 = new Cat("金毛", "黄色");
var cat2 = new Cat("泰迪", "棕色");
构造函数的方式确实好用,但是存在一个浪费内存的问题。所以就要使用原型模式。
→原型模式
function Cat(name, color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "猫科动物";
Cat.prototype.eat = function(){alert("吃老鼠")};
还有等等一些模式进行封装,大家自行去查询即可。
2.4.2 JavaScript中继承的实现
→原型继承
// 基类
var Person = function() {
this.name = '张三';
this.age = 20;
};
Person.prototype = {
say: function() {
console.log('Person.prototype');
}
};
// 子类
var Student = function() {
};
// 这样就可以继承了Person里面的所有的方法。
Student.prototype = new Person();
Student.prototype.getTeacher = function(){
console.log('Student.prototype.getTeacher');
};
这种继承存在一些问题,就是无法通过参数定义对象。
→构造函数实现继承
function Person(){
this.race = '人类';
}
Person.prototype={
eat:function(){
alert('123')
}
};
/*学生对象*/
function Student(name, skin) {
// People(),
Person.apply(this, arguments);
this.name = name;
this.skin = skin;
}
构造函数这种方式的继承利用apply这个方法改变this的指针来完成继承的。
→组合模式实现继承
组合模式就是将上述的原型继承和构造函数继承的组合。
function Person(name, age) {}
Person.prototype.say = function() {}
function Student(name, age, no) {
/*会自动调用Person的方法,同时将name age传递过去*/
Person.call(this, name, age);
//自己的属性
this.no = no;
}
Student.prototype = new Person();
通过这种方式进行继承会更加完善。至于其他的继承方式在这就不一一列举了,比如寄生虫组合继承、拷贝继承等等。
2.4.3 JavaScript中重载的实现
这里只说一种方式实现重载(根据arguments个数)
function add() {
var sum = 0 ;
for ( var i = 0 ; i < arguments.length; i ++ ) {
sum += arguments[i];
}
return sum;
}
alert(add());
alert(add( 1 , 2 ));
alert(add( 1 , 2 , 3 ));
内容太多了,请移步web前端面试宝典-基础篇(二)
有问题请联系QQ:136062834,一直感觉良好的攻城狮。