一、参考资源
1、前端面试题及答案整理(一)
2、2017年前端面试题整理汇总100题
3、2018最新Web前端经典面试试题及答案
4、【javascript常见面试题】常见前端面试题及答案
5、Web前端常见面试题及答案
6、常见前端面试题及答案(上)
7、web前端面试题及答案
8、web前端经典面试题
二、面试题汇总
网上搜到一大堆面试题,重复的不少,全部归纳一下,也加上自己的理解
(一)、问答类
1、 JavaScript基础数据类型
JavaScript数据类型包括原始类型和引用类型,原始类型有五个: Number(数值) String(字符串) Boolean(布尔) Null(空) Undefined(未定义)
引用类型有一个:Object(对象)
通过typeof(x)可以返回一个变量x的数据类型“number”、“string”、“boolean”、“undefined”、"object",这里要注意一点:typeof运算符对于null类型返回的是object。
2 、谈一谈JavaScript作用域链
作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window
对象即被终止,作用域链向下访问变量是不被允许的。
当执行一段JavaScript代码(全局代码或函数)时,JavaScript引擎会为其创建一个作用域又称为执行上下文(Execution Context),在页面加载后会首先创建一个全局的作用域,然后每执行一个函数,会建立一个对应的作用域,从而形成了一条作用域链。每个作用域都有一条对应的作用域链,链头是全局作用域,链尾是当前函数作用域。
作用域链的作用是用于解析标识符,当函数被创建时(不是执行),会将this、arguments、命名参数和该函数中的所有局部变量添加到该当前作用域中,当JavaScript需要查找变量X的时候(这个过程称为变量解析),它首先会从作用域链中的链尾也就是当前作用域进行查找是否有X属性,如果没有找到就顺着作用域链继续查找,直到查找到链头,也就是全局作用域链,仍未找到该变量的话,就认为这段代码的作用域链上不存在x变量,并抛出一个引用错误(ReferenceError)的异常。
3 、如何理解JavaScript原型链
JavaScript中的每个对象都有一个prototype
属性,我们称之为原型,而原型的值也是一个对象,因此它也有自己的原型,这样就串联起来了一条原型链,原型链的链头是object,它的prototype比较特殊,值为null。
原型链的作用是用于对象继承,函数A的原型属性(prototype property)是一个对象,当这个函数被用作构造函数来创建实例时,该函数的原型属性将被作为原型赋值给所有对象实例,比如我们新建一个数组,数组的方法便从数组的原型上继承而来。
当访问对象的一个属性时, 首先查找对象本身, 找到则返回; 若未找到, 则继续查找其原型对象的属性(如果还找不到实际上还会沿着原型链向上查找, 直至到根). 只要没有被覆盖的话, 对象原型的属性就能在所有的实例中找到,若整个原型链未找到则返回undefined;
4、 JavaScript变量声明提前
《JavaScript权威指南》中是这样解释的:JavaScript变量在声明之前已经可用,JavaScript的这个特性被非正式的称为声明提前
(hoisting),即JavaScript函数中声明的所有变量(但不涉及赋值)都被“提前”至函数的顶部。
5 、如何理解和应用JavaScript闭包
各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
function f1(){ var n=999; nAdd=function(){n+=1} function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999 nAdd(); result(); // 1000 //nAdd前面没有使用var关键字,因此nAdd是一个全局变量 //nAdd的值是一个匿名函数,而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
深入理解闭包:学习Javascript闭包
6 、new构建对象的本质
通过new操作符,实际上在构造函数User中完成了如下操作:
- 创建一个新的对象,这个对象的类型是object;
- 设置这个新的对象的内部、可访问性和prototype属性为构造函数(指prototype.construtor所指向的构造函数)中设置的;
- 执行构造函数;
- 返回新创建的对象。
function User(){ //this = {}; //this.constructor = User; this.name = "Vicfeel"; this.age = 23; //return this; } var user = new User();
如果构造函数默认返回的新创建的this对象,如果手动return 一个变量的话,如果该变量是原始类型则无效,如果是对象,则返回该对象。
7 、JavaScript代理
当我们需要对很多元素添加事件的时候,可以通过将事件添加到它们的父节点而将事件委托给父节点来触发处理函数。
比如我们需要向一个ul中动态添加很多个li,需要遍历li逐个添加点击事件
众所周知,DOM操作是十分消耗性能的。所以重复的事件绑定简直是性能杀手。而事件代理的核心思想,就是通过尽量少的绑定,去监听尽量多的事件。如何做呢?答案是利用事件冒泡机制,对其父节点ul进行事件绑定(Event Bubble),然后通过event.target来判断是哪个节点触发的事件,从而减少很多EventHandler的绑定。
var count = 100; var ulList = document.getElementById("list"); //动态构建节点 for(var i = count;i--;){ var liDom = document.createElement('li'); ulList.appendChild(liDom); } //绑定点击事件 var liNode = ulList.getElementByTagName("li"); liNode.onClick = function(e){ if(e.target && e.target.nodeName.toUpperCase == "LI") { // li点击事件 } }
8、position的值, relative和absolute分别是相对于谁进行定位的?
-
absolute
:生成绝对定位的元素, 相对于最近一级的 定位不是 static 的父元素来进行定位。 -
fixed
(老IE不支持)生成绝对定位的元素,通常相对于浏览器窗口或 frame 进行定位。 -
relative
生成相对定位的元素,相对于其在普通流中的位置进行定位。 -
static
默认值。没有定位,元素出现在正常的流中 -
sticky
生成粘性定位的元素,容器的位置根据正常文档流计算得出
9、如何解决跨域问题
由于同源策略的限制,XmlHttpRequest
只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过script
标签实现跨域请求,然后在服务端输出JSON数据并执行回调函数,从而解决了跨域的数据请求。
优点是兼容性好,简单易用,支持浏览器与服务器双向通信。缺点是只支持GET请求。
JSONP
:json+padding
(内填充),顾名思义,就是把JSON填充到一个盒子里
<script> function createJs(sUrl){ var oScript = document.createElement('script'); oScript.type = 'text/javascript'; oScript.src = sUrl; document.getElementsByTagName('head')[0].appendChild(oScript); } createJs('jsonp.js'); box({ 'name': 'test' }); function box(json){ alert(json.name); } </script>
其他跨域方法见
10、创建ajax过程
(1)创建XMLHttpRequest对象,也就是创建一个异步调用对象. (2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息. (3)设置响应HTTP请求状态变化的函数. (4)发送HTTP请求. (5)获取异步调用返回的数据. (6)使用JavaScript和DOM实现局部刷新
11、渐进增强和优雅降级
渐进增强 :针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。
优雅降级 :一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。
12、常见web安全及防护原理
(1)sql注入原理:就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
1.永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等。 2.永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取。 3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。 4.不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息
(2)XSS原理及防范
Xss(cross-site scripting)攻击指的是攻击者往Web页面里插入恶意 html
标签或者javascript
代码。比如:攻击者在论坛中放一个看似安全的链接,骗取用户点击后,窃取cookie
中的用户私密信息;或者攻击者在论坛中加一个恶意表单,当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点。
XSS防范方法:
首先代码里对用户输入的地方和变量都需要仔细检查长度和对”<”,”>”,”;”,”’”等字符做过滤;其次任何内容写到页面之前都必须加以encode,避免不小心把html tag 弄出来。这一个层面做好,至少可以堵住超过一半的XSS 攻击。 首先,避免直接在cookie 中泄露用户隐私,例如email、密码等等。 其次,通过使cookie 和系统ip 绑定来降低cookie 泄露后的危险。这样攻击者得到的cookie 没有实际价值,不可能拿来重放。 如果网站不需要再浏览器端对cookie 进行操作,可以在Set-Cookie 末尾加上HttpOnly 来防止javascript 代码直接获取cookie 。 尽量采用POST 而非GET 提交表单
13、HTTP和HTTPS
HTTP
协议通常承载于TCP协议之上,在HTTP
和TCP
之间添加一个安全协议层(SSL
或TSL
),这个时候,就成了我们常说的HTTPS。
默认HTTP的端口号为80,HTTPS
的端口号为443。
为什么HTTPS
安全
因为网络请求需要中间有很多的服务器路由器的转发。中间的节点都可能篡改信息,而如果使用HTTPS
,密钥在你和终点站才有。https
之所以比http
安全,是因为他利用ssl/tls
协议传输。它包含证书,卸载,流量转发,负载均衡,页面适配,浏览器适配,refer传递等。保障了传输过程的安全性
14、对前端模块化的认识
AMD 是 RequireJS
在推广过程中对模块定义的规范化产出。
CMD 是 SeaJS
在推广过程中对模块定义的规范化产出。
AMD
是提前执行,CMD
是延迟执行。
AMD
推荐的风格通过返回一个对象做为模块对象,CommonJS
的风格通过对module.exports
或exports
的属性赋值来达到暴露模块对象的目的。
15、Javascript垃圾回收方法
这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”。
垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
在IE中虽然JavaScript
对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的,
也就是说只要涉及BOM
及DOM就会出现循环引用问题。
16、你觉得前端工程的价值体现在哪
为简化用户使用提供技术支持(交互部分)
为多个浏览器兼容性提供支持
为提高用户浏览速度(浏览器性能)提供支持
为跨平台或者其他基于webkit或其他渲染引擎的应用提供支持
为展示数据提供支持(数据接口)
17、谈谈性能优化问题
代码层面:避免使用css表达式,避免使用高级选择器,通配选择器。
缓存利用:缓存Ajax,使用CDN,使用外部js和css文件以便缓存,添加Expires头,服务端配置Etag,减少DNS查找等
请求数量:合并样式和脚本,使用css图片精灵,初始首屏之外的图片资源按需加载,静态资源延迟加载。
请求带宽:压缩文件,开启GZIP,
用hash-table来优化查找
少用全局变量
用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能
用setTimeout来避免页面失去响应
缓存DOM节点查找的结果
避免使用CSS Expression
避免全局查询
避免使用with(with会创建自己的作用域,会增加作用域链长度)
多个变量声明合并
避免图片和iFrame等的空Src。空Src会重新加载当前页面,影响速度和效率
尽量避免写在HTML标签中写Style属性
移动端性能优化
- 尽量使用css3动画,开启硬件加速。
- 适当使用
touch
事件代替click
事件。 - 避免使用
css3
渐变阴影效果。 - 可以用
transform: translateZ(0)
来开启硬件加速。 - 不滥用Float。Float在渲染时计算量比较大,尽量减少使用
- 不滥用Web字体。Web字体需要下载,解析,重绘当前页面,尽量减少使用。
- 合理使用requestAnimationFrame动画代替setTimeout
- CSS中的属性(CSS3 transitions、CSS3 3D transforms、Opacity、Canvas、WebGL、Video)会触发GPU渲染,请合理使用。过渡使用会引发手机过耗电增加
相关阅读:如何做到一秒渲染一个移动页面
18、什么是Etag?
19、栈和队列、堆的区别?
栈的插入和删除操作都是在一端进行的,而队列的操作却是在两端进行的。
队列先进先出,栈先进后出。
栈只允许在表尾一端进行插入和删除,而队列只允许在表尾一端进行插入,在表头一端进行删除
栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。
堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。
堆(数据结构):堆可以被看成是一棵树,如:堆排序;
栈(数据结构):一种先进后出的数据结构
20、你觉得jQuery或zepto源码有哪些写的好的地方
jQuery
源码封装在一个匿名函数的自执行环境中,有助于防止变量的全局污染,然后通过传入window对象参数,可以使window对象作为局部变量使用,好处是当jquery
中访问window对象的时候,
就不用将作用域链退回到顶层作用域了,从而可以更快的访问window
对象。同样,传入undefined
参数,可以缩短查找undefined时的作用域链。
(function( window, undefined ) { //用一个函数域包起来,就是所谓的沙箱 //在这里边var定义的变量,属于这个函数域内的局部变量,避免污染全局 //把当前沙箱需要的外部变量通过函数参数引入进来 //只要保证参数对内提供的接口的一致性,你还可以随意替换传进来的这个参数 window.jQuery = window.$ = jQuery; })( window );
jquery将一些原型属性和方法封装在了jquery.prototype
中,为了缩短名称,又赋值给了jquery.fn
,这是很形象的写法。
有一些数组或对象的方法经常能使用到,jQuery将其保存为局部变量以提高访问速度。
jquery
实现的链式调用可以节约代码,所返回的都是同一个对象,可以提高代码效率。
21、ES6的了解
新增模板字符串(为JavaScript提供了简单的字符串插值功能)、箭头函数(操作符左边为输入的参数,而右边则是进行的操作以及返回的值Inputs=>outputs
。)for-of
(用来遍历数据—例如数组中的值。)arguments
对象可被不定参数和默认参数完美代替。ES6
将promise
对象纳入规范,提供了原生的Promise
对象。增加了let
和const
命令,用来声明变量。增加了块级作用域。let命令实际上就增加了块级作用域。
ES6规定,var
命令和function
命令声明的全局变量,属于全局对象的属性;let
命令、const
命令、class
命令声明的全局变量,不属于全局对象的属性。。还有就是引入module
模块的概念
22、js继承方式及其优缺点
原型链继承的缺点:一是字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。
借用构造函数(类式继承)
(二)、手写代码
1 、数组去重的实现
//方法一(基本方法):思路:定义一个新数组,并存放原数组的第一个元素,然后将元素组一一和新数组的元素对比,若不同则存放在新数组中。 function unique(arr){ var res = [arr[0]]; for(var i=1;i<arr.length;i++){ var repeat = false; for(var j=0;j<res.length;j++){ if(arr[i] == res[j]){ repeat = true; break; } } if(!repeat){ res.push(arr[i]); } } return res; }
//思路:先将原数组排序,在与相邻的进行比较,如果不同则存入新数组
function unique(arr){
var arr2 = arr.sort();
var res = [arr2[0]];
for(var i=1;i<arr2.length;i++){
if(arr2[i] !== res[res.length-1]){
res.push(arr2[i]);
}
}
return res;
}
//先排序后去重
Array.prototype.unique = function(){
var result = [this[0]]; this.sort(); this.forEach(function(v){ v != result[result.length - 1] && result.push(v); //仅与result最后一个元素比较 }); } //利用ES6 Set去重 //ES6环境下 Array.prototype.unique = function(){ return [...new Set(this)]; }
2 、快速排序的实现
“快速排序”的思想很简单,整个排序过程只需要三步:
(1)在数据集之中,找一个基准点
(2)建立两个数组,分别存储左边和右边的数组
(3)利用递归进行下次比较
<script type="text/javascript"> function quickSort(arr){ if(arr.length<=1){ return arr;//如果数组只有一个数,就直接返回; } var num = Math.floor(arr.length/2);//找到中间数的索引值,如果是浮点数,则向下取整 var numValue = arr.splice(num,1);//找到中间数的值 var left = []; var right = []; for(var i=0;i<arr.length;i++){ if(arr[i]<numValue){ left.push(arr[i]);//基准点的左边的数传到左边数组 } else{ right.push(arr[i]);//基准点的右边的数传到右边数组 } } return quickSort(left).concat([numValue],quickSort(right));//递归不断重复比较 } alert(quickSort([32,45,37,16,2,87]));//弹出“2,16,32,37,45,87” </script>
//方法一(尽可能不用js数组方法) function quickSort(arr){ qSort(arr,0,arr.length - 1); } function qSort(arr,low,high){ if(low < high){ var partKey = partition(arr,low,high); qSort(arr,low, partKey - 1); qSort(arr,partKey + 1,high); } } function partition(arr,low,high){ var key = arr[low]; //使用第一个元素作为分类依据 while(low < high){ while(low < high && arr[high] >= arr[key]) high--; arr[low] = arr[high]; while(low < high && arr[low] <= arr[key]) low++; arr[high] = arr[low]; } arr[low] = key; return low; }
//方法二(使用js数组方法) function quickSort(arr){ if(arr.length <= 1) return arr; var index = Math.floor(arr.length/2); var key = arr.splice(index,1)[0]; var left = [],right = []; arr.forEach(function(v){ v <= key ? left.push(v) : right.push(v); }); return quickSort(left).concat([key],quickSort(right)); } //另外要知道,快速排序的平均时间复杂度O(nlogn),最坏情况是有序的情况,时间复杂度为n的平方,另外快速排序是不稳定的。