web前端常见的面试题汇总
文章目录
- web前端常见的面试题汇总
- 1. 什么是事件?IE和火狐的事件机制有什么区别?如何阻止事件冒泡
- 2.三种事件模型是什么?
- 3. 事件委托是什么?
- 4. new操作符具体干了什么?如何实现的?
- 5.对于json的了解?
- 6.延迟加载的方式有哪些(优化js)?
- 7. Ajax是什么?如何创建一个Ajax?
- 8.解决Ajax中浏览器缓存问题
- 9.同步和异步的区别
- 10. 如何解决跨域问题
- 11.简单谈一下cookie?
- 12.JS中几种模块化规范
- 13 Require.js的核心原理是什么?
- 14.ECMScript6中怎样写class,为什么会出现class?
- 15. document.write和innerHTML的区别
- 16.javascript中类数组对象的定义
- 17.类数组对象转换为数组对象的常见的方法
- 18.数组中有哪些原生的方法,举例以下?
- 19. 哪些操作会造成内存泄漏
- 19.1意外的全部变量的使用
- 19.2被遗忘的定时器或回调函数
- 19.3脱离DOM的引用
- 19.4闭包
- 20. 如何判断当前脚本运行在浏览器(browser)还是在node环境种?
- 21.移动端的点击事件有延迟,时间 是多少,为什么会有?怎么解决这个问题?
- 22. 什么是“前端路由”?什么时候适合使用”前端路由”?“前端路由”有哪些优缺点?
- 23.检测浏览器版本有哪些方式?
- 24. 什么是Ployfill
- 25.使用JS实现获取文件扩展名
- 26. 介绍以下js中的节流和防抖
- 27. Object.is()与原来的比较操作符“===” 与“\==”的区别
- 28.浏览器中的事件循环机制(Event Looping)
- 29.instanceof的原理
- 30. 手写封装一个jsonp
- 31.有几种方式可以实现存储功能?分别有什么有优缺点?什么是Service Worker?
- 32.浏览器中的缓存机制
- 33. 插入几万个DOM,如何实现页面不卡顿?
- 34.什么情况下会产生渲染堵塞?
- 35. 重绘和回流
- 36. 减少重绘和回流
- 37. 什么是XSS攻击?如何防止XSS攻击?
- 38. 什么是CSRF攻击?如何防范CSRF攻击?
- 29. 什么是点击劫持?如何防范点击劫持?
- 30. 性能优化
- 31. Webpack的性能优化
- 32. 路由原理(什么是前端路由?两种实现方式有什么区别?)
- 33. 开发中常见的几种Content-Type
- 34.什么时MVVM、MVC?有什么区别?
1. 什么是事件?IE和火狐的事件机制有什么区别?如何阻止事件冒泡
1.1 事件
- 事件是用户操作网页时发生的交互动作。如click/move等。事件即可以由用户触发的动作产生,也可以是文档加载,窗口滚动和大小调整。
- 事件被封装到一个event对象中,event对象中包含了该事件发生时的所有信息(event的属性),以及可以对该事件进行操作(event的方法)
1.2 事件机制
1)IE:IE浏览器支持冒泡事件
2)火狐:狐火浏览器支持冒泡事件和捕获事件
1.3 如何阻止冒泡事件
1)在IE浏览器中使用:event.cancleBubble=true;
2)在火狐浏览器中使用:event.stopPropagation();
2.三种事件模型是什么?
2.1 第一种是最早的DOM0模型
- 该模型时所有浏览器都兼容的;
2.2 第二种是IE事件模型
- 该模型包括两个阶段,一是事件处理阶段,二是事件冒泡阶段;
- 在事件的处理阶段会先执行目标元素绑定的监听函数
- 在事件的冒泡阶段即是从目标元素冒泡到document,一次检查节点是否绑定了事件监听函数,如果绑定了则执行;
- 这种事件模型由attachEvent(type:加了on的事件类型,handler:监听事件函数)
PS:可以添加多个监听函数,会按照顺序依次执行
2.3 第三种是DOM2级事件模型
- 该模型包括三个阶段:一是事件捕获阶段,二是事件处理阶段,三是事件冒泡阶段
- 捕获阶段是指事件从document向下传播到目标元素,依次检查节点是否绑定了监听函数,若有则执行;后面的两个阶段和IE事件模型相同
- 该事件的绑定函数是addEventListener(type:不带on的事件类型,handler:事件监听函数,false|true)------第三个参数表示是否在捕获阶段执行监听事件
3. 事件委托是什么?
- 是什么:事件委托是基于浏览器的事件冒泡机制,因为事件在冒泡的过程中可以上传到父节点,并且父节点可以同个事件对象获取到目标节点,因此可以将子节点的监听函数绑定在父节点上,由父节点监听函数统一处理多个元素的事件,这种方式称为事件代理(委托);
- 做什么:
-使用事件代理可以不用为每个子元素都绑定一个监听事件,这样减少了内存的消耗;
-使用事件代理可以实现事件的动态绑定,比如新增一个子节点,我们并不需要单独为其添加监听事件,它所发生的事件会交给父元素中的监听函数来处理
4. new操作符具体干了什么?如何实现的?
/*构造函数中有个默认返回值,即return this---不写返回值时,默认返回该对象*/
function Func(){
}
var func=new Func()
1)首先是创建了一个空对象
var obj = new OBject()
2)设置原型链,即是使这个实例对象的原型proto指向构造函数Func的原型Func(这时便创建了obj对象的原型链:obj —>Func.prototype—>Object.prototype—>null
obj.__proto__=Func.prototype
3)改变this的指向,即是使构造函数Func中this的指向指向所创建的对象obj,并指向构造函数体
var result=Func.call(this)
4)判断构造函数的返回值,当返回值为值类型时,返回所创建的对象;如果时引用类型,就返回这个引用类型的对象
if(typeof(result)==='object'){
//如果返回值为引用类型,则返回该引用类型
func=result;
}else{
//如果值是值类型,则返回所创建的对象
func=obj;
}
5.对于json的了解?
- json是一个基于文本的轻量级的数据交换格式,它可以被任何编程需要读取和作为数据格式来传递;
- 我们将JSON作为前后段数据交互的方式,在前端我们将一个符合JSON格式的数据结构转换为JSON字符串,然后将其传递给后端,后端通过JSON格式的字符串解析后生成对应的数据结构,从此以来实现了前后端的数据传递;
- 由于JSON的语法是基于js的,因此很容易将json数据格式与js对象弄混淆,但是需要注意json格式的数据与js对象不是一回事,不如json格式的属性不允许出现函数,不允许出现NaN值等,(json格式的属性需要弄’‘引号,而js对象不需要)
- 在js中提供了两种方法来实现js数据结构和json格式的转换处理;
1)JSON.stringify函数,该函数通过传入一个符合JSON格式的数据结构,将其转换为一个JSON格式字符串。在前端向后端发送数据时,可以调用该函数将数据结构对象转换为JSON格式的数据,然后后端再将该数据解析成数据结构的数据进行存储;
2)JSON.parse(),这个函数是将JSON格式的字符串转换为js数据结构,如果传入的数据不符合JSON格式,那么会抛出错误;------当我们接收到后端发送过来的JSON格式的数据时,便可以使用该函数来将JSON格式的数据转换成js数据结构,以此进行数据的访问
总结:
---------js数据结构---->JSON-------->js数据结构--------
一个特点:基于文本的轻量级的数据交换格式
两个方法(JSON.stringify() JSON.parse() )
- 在向后端发送数据时,先使用JSON.stringify(data)将数据转换成JSON格式的数据,后端解析JSON个数的数据生成js数据结构,进而再将数据进行存储
- 前端接收到后端发送过来的数据时,使用JSON.parse(data)将数据转换成为js格式的数据,进而进行数据的读取;
6.延迟加载的方式有哪些(优化js)?
6.1 引入问题
- js的加载、解析和执行会组设页面的渲染过程,因此我们希望将js脚本能够尽可能的延迟加载,提高页面的渲染速度;
- 对于js的优化(js的延迟加载),就是等待页面完全加载完成之后再加载js脚本,好处是有助于提高页面的加载速度
- 之所以要优化是因为HTML元素是按其在页面中出现的次序调用的,如果用javascript管理页面上的dom(使用文档对象模型),那么当js加载于操作的HTML之前时,便会出现代码错误。也就是说,我们写了js语句来获取DOM对象,但是由于DOM结构还没加载完成,因此获取到的时空对象,举例如下:
<head>
<script type="text/javascript">
var ul = document.getElementsByTagName('ul')[0]; //获取ul
var list = ul.getElementsByTagName('li');
for(var i =0;i<list.length;i++){
ul.appendChild(document.createElement('li'));
}
</script>
</head>
<body>
<ul>
<li>111</li>
<li>222</li>
<li>333</li>
</ul>
</body>
</html>
报错如下:
出错原因:这就是因为js加载执行于DOM结构之前,所以获取不到。简单的解决办法是把
6.2 解决问题
js中延迟加载的流种方式
- 让js最后加载
- defer属性
- async属性
- 动态创建Dom方式
- 调用jQuery中的getscript()方法
- 使用定时器方式延迟加载
6.2.1 让js最后加载
实现:
- 让js最后加载就是将js脚本代码放在文档底端,实现js脚本代码的最后加载
问题:
- 当项目中有大量的js代码时,可能会带来显著的性能消耗
6.2.2 defer属性
实现:
<script src="...." defer></script>
说明:
- 使用defer属性的原理即是,使得带有defer属性的js脚本在页面完全加载之后再执行js脚本代码,便不会阻塞页面的加载
- 多个defer属性的脚本按照页面中出现的顺序执行
6.2.3 async属性
实现:
<script src="...." async></script>
说明:
- 使用async属性,使得HTML和js脚本一并加载(异步),即不会阻塞页面的解析;即浏览器遇到async时不会阻塞页面的解析,而是直接下载和解析
问题:
- 由于使用async的脚本也是加载完成之后立即执行的,这个时候如果文档还没解析完成的话同样会堵塞,并且多个acsync属性的脚本的执行时顺序是不可预测的;
- 所以当多个js脚本代码独立,且不依赖于页面的任何脚本时,使用async最优!
6.2.4 总结defer与async的异同以及使用场景
- 相同点:
- 不会堵塞页面的加载
- 不适用于内部的js(即是再页面中引入外部文件时使用)
- 这两个属性的js脚本代码不能使用document.write()方法
2)不同点
- defer是HTML4中script的属性
- async是HTML5中script中的属性
- 使用了defer属性的脚本代码是在页面完全加载完毕之后再执行
- 使用了async属性的脚本代码和页面一并加载(异步),当该js脚本下载完成时立即执行
3)使用场景
- 如果脚本需要等待页面解析,且依赖于其他js脚本,那么使用defer属性,使用时将脚本按所需顺序放置于html中
- 如果脚本不需要等待页面解析,且不依赖于其他脚本,那么使用async;即表示使用了async的你脚本会在下载完成后立即执行,也会在window.onload执行之前执行
6.2.5 使用动态创建DOM的方式
实现:
- 即我们可以对文档的加载事件设置事件监听,当文档加载完成后再动态创建script标签引入js脚本
代码如下:
//这些代码应被放置在</body>标签前(接近HTML文件底部)
<script type="text/javascript">
//创建script标签引入js脚本
function downloadJSAtOnload(){
var element = document.createElement("script");
element.src = "defer.js";
document.body.appendChild(element);
}
//为文档的加载事件设置监听
if (window.addEventListener) //添加监听事件
window.addEventListener("load",downloadJSAtOnload,false); //事件在冒泡阶段执行
else if (window.attachEvent)
window.attachEvent("onload",downloadJSAtOnload);
else
window.onload =downloadJSAtOnload;
</script>
6.2.6 使用定时器setTimeout的方式
代码如下:
<script type="text/javascript">
function A(){
$.post("/lord/login",{name:username,pwd:password},function(){
alert("Hello World!");
})
}
$(function (){
setTimeout("A()",1000); //延迟1秒
})
</script>
6.2.7 使用jQuery中的getscript函数
getScript() 方法通过 HTTP GET 请求载入并执行 JavaScript 文件。
语法:jQuery.getScript(url,success(response,status))
url(必写):将要请求的 URL 字符串
success(response,status)(可选):规定请求成功后执行的回调函数。
其中的参数
response - 包含来自请求的结果数据
status - 包含请求的状态(“success”, “notmodified”, “error”, “timeout” 或 “parsererror”)
//加载并执行 test.js:
$.getScript("test.js");
//加载并执行 test.js ,成功后显示信息
$.getScript("test.js", function(){
alert("Script loaded and executed.");
});
7. Ajax是什么?如何创建一个Ajax?
详细解析看我写的写篇文章:深入了解XMLHttpRequest对象
是什么:
- 我的理解时Ajax是一种进行异步通信的方法,Ajax的核心是XMLHttpRequest对象。使用Ajax方法请求数据,可以使得页面不重新刷新或者跳转的情况下加载更新数据。
- 其通过直接由js脚本向服务器发送http请求,然后服务器返回响应数据,更新网页相应的部分
怎么创建:
1)首先是创建一个异步调用对象,即XMLHttpRequest()对象;
2)然后调用open()方法建立与服务器的连接,open()方法接收三个参数,第一个参数是method即请求的方法(get、post、put等),第二个参数是请求url,第三个参数接收一个布尔值,用来表示是否发送异步请求;此时嗲用open()方法并不是真正的向服务器发送了请求,而是一个准备阶段;
3)在发送请求前,我们可以为这个对象添加一些信息和监听函数,比如可以通过setRequestHeader方法来添加请求头信息等。一个XMLHttpRequest对象一共有五种状态,为这个对象状态的变化添加监听函数。由于向服务器发送了请求之后,响应状态时一直在改变的,且每一次改变,都会触发触发onreadstatechange事件,通常情况下对readySteta=4时进行判断,代表服务器返回的数据全部接收完成;同时判断服务器的响应状态码大于等于200或者小于300时代表返回值横穿,我们便可以对服务器返回的数据进行相应的处理。进而对页面进行更新;(以上也可以使用onload事件进行判断)
3)当对象的属性和监听函数设置完成之后,调用send()方法,向服务器发送请求。send()方法可以接收参数作为发送的数据体,该参数类型可以为json、blob等,当你不需要向服务器发送数据时,传入null值;
创建:
//一般实现
const xhr = new XMLHttpRequest();
xhr.open('get', '/server', true);
//设置响应类型
xhr.responseType = 'json'
//设置请求头信息
xhr.setRequestHeader('MyHeader', 'MyValue');
//设置请求失败监听函数
xhr.onload = function () {
console.log('请求失败了...')
}
//为XMLHttpRequest对象的状态设置监听事件(监听onreadystatechange)
xhr.onreadystatechange = function () {
if (this.readyState !== 4) {
return;
}
if (this.status >= 200 && this.status < 300) {
//响应正常
console.log(this.responseText)
} else {
console.log(this.statusText)
}
}
//发送请求
xhr.send(null);
8.解决Ajax中浏览器缓存问题
说明:
- Ajax能提高页面的加载速度主要原因是通过Ajax减少了重复数据的载入,也即在载入数据的同时将数据缓存到内存中,一旦数据被加载,只要没有刷新页面,这些数据就会一直被缓存在内存中。
- 当提交的URL与历史的URL一致时,就不需要提交给服务器,即不需要从服务器中获取数据,这样虽然降低了服务器的负载,提高了用户的的体验,但不能及时更新数据。
- 为了保证读取的数据是最新的,需要禁止其缓存功能
解决思路:
1.禁掉缓存
2.更新请求URL(但不是改变URL),因为只要URL不变,那么浏览器就不会取检查服务器是否已经更新,那么浏览器用的数据仍是换粗中的数据
解决:
1)在ajax发送请求前加上
//本质是让缓存过期
anyAjaxObj.setRequestHeader("If-Modified-Since","0");
2)在ajax发送请求前加上
//本质是设置不允许产生缓存
anyAjaxObj.setRequestHeader("Cache-Control","no-cache");
3)在URL上加上一个随机数,
"fresh"=Math.random()
4)在URL上加上时间戳
"nowtime"=+new Date().getTime();
//如:request.jsp?q=q&date=(new Date()).getTime()
//运用实例:
//例如在用户登录验证的时候,点击验证码更新,只需要在发送验证码的请求上加上时间戳或者随机数的形式以更行URL,那么就可以请求得到新的数据
//又如短信发送的状态,如果隔几秒查询以此,URL不变的话,显示出来的状态则不会发生改变
5)如果是使用jQuery,直接适应$.ajacSetuo({cache:false})。这样页面的所有ajax就会执行这条语句就不需要保存缓存记录;
9.同步和异步的区别
- 同步就是值当一个进程在执行某个请求的时候,当这个请求需要等待一定的时间才能返回数据,那么这个进程就会一直处于等待状态,直到这个请求返回信息才继需往下执行
- 异步是指当在一个进程中执行某个请求时,且这个请求也需要等待一定的时间才能返回,而此时这个进程会继续往下执行,即不会堵塞等待信息的返回,
通俗的讲,同步是值客户端向服务端发送请求,在等待客户端响应请求时,客户端不能进行其他操作,直到服务器返回请求;
异步就是客户端向服务器发送请求,在等待服务端响应的时候,客户端不必等到服务端响应结束,可以进行其他操作;
10. 如何解决跨域问题
详细解析可以参看我写的这篇文章:前端常见的跨域解决方法
11.简单谈一下cookie?
详细解析可以查看我写的这篇文章:聊一聊cookie
在这里主要描述一下面试时应该如何回答:
- 我的理解是,cookie是服务端用来维护会话状态信息的数据。当浏览器向服务器发送带有验证的请求时,服务器进行验证,生成cookie;然后服务器响应请求,并通过响应头的set-Cookie字段将生成的cookie返回给浏览器,那次浏览器提取cookie,然会将其保存在本地内存中。当下次再请求时,则将已经验证的cookie发送至服务器,服务器验证通过便响应请求;这样一来便可以实现一些用户登录状态等功能;
- cookie的大小一般为4kb,并且只能够被同源的页面共享访问
- 服务器可以适应set-Cookie的相应头来配置cookie信息,一个cookie包括了5个属性。分别为expires、domain、path、secure、HttpOnly。其中expries定义的cookie的期限,domain是域名、path是路径,domain和path共同约束了cookie可以被哪些url访问。secure定义cookie是否在安全协议下发送,HttpOnly规定cookie只能被服务器反问,浏览器的js不能访问cookie。
- 在发送xhr跨域请求时,cookie不会自动添加到请求头部(Request Header),但是可以通过服务器和客户端进行限行设置
12.JS中几种模块化规范
对于CommonJS、AMD、CMD以及ES6 Module模块规范详细解析可以参考我写的这篇文章:JS中的几种模块化规范
在这里再总结一下:
- 第一种是Commonjs模块化规范:CommonJS模块化规范,它通过Require来引入模块,通过module.exports来定义模块的输出接口。其主要在服务端使用(比如Nodejs),因为其原理是同步加载,再服务端js代码都是存储在本地内存的,所以读取较快。而在浏览器中的模块的加载往往是需要通过网络请求,往往是需要通过异步加载的方式实现;
- 第二种是AMD方案:AMD方案使用的原理是异步加载模块,且其遵循的是依赖前置,即在定义模块时就要定义相关的依赖,并且这些模块会在加载完成之后直接执行,require.js实现了AMD规范
- 第三种是CMD模块,CMD模块加载模块的原理也是异步加载的,其遵循的是就近原则,对于需要加载的模块使用reqire path引入即可,并且模块的执行也是遇到requie才执行。
- 第四种是ES6 Module模块,其使用export形式定义模块,使用import的方式导入模块,ES6模块的加载时在编译时加载
13 Require.js的核心原理是什么?
-
概念
requireJS时基于AMD模块化加载规范,使用回调函数来解决模块化加载问题 -
原理
requireJS是使用script元素,通过script元的的src属性来引入模块 -
特点
实现js文件的异步加载
管理模块之间的依赖,便于代码的编程和维护
总:requireJS的核心原理是通过动态创建script脚本来异步引入模块,然后对每个脚本的load事件进行监听,如果每个脚本都加载完成了,再调用回调函数执行模块!
14.ECMScript6中怎样写class,为什么会出现class?
1.class的基本语法
创建类实例对象不同于构造方法,需要使用new关键字来创建
class Point{
constrctor(){}
}
let p=new Point();
2.class是ES6新增的语法,我的理解是为了补充js中缺少的一些面向对象语言的特性。通过class可以有利于我们更好的组织代码,但class还是具有原型继承的思想,比如class中属性和方法可以添加在原型上,为所有类的实例对象所共享;
再详细一点关于ES6中class的说明可以参考我写的这篇文章:
ES6中的class
15. document.write和innerHTML的区别
- 使用document.write的内容会替代掉整个文档的内容,即会重写整个页面
- 使用innerHTML只是替代指定元素的内容,只会重写页面中部分内容
- 使用
document.write(‘xxxx’);
element.innerHTML=“xxxx”;
16.javascript中类数组对象的定义
1)类对象:拥有length属性和若干索引属性的对象都可以被称为类数组;
2)类数组对象和数组对象类似,只是类数组对象不能使用数组对象特有的方法(若想使用,需要将类数组对象转换为数组对象)
3)常见的数组是对象:arguments、DOM方法返回结果(NodeList、HTMLCollection)
17.类数组对象转换为数组对象的常见的方法
- Array.prototype.slice.call(类数组);
- [].slice.call(类数组);
- Array.from(类数组)
- […类数组]----扩展运算符
function fun() {
console.log('arguments', arguments);
//Array.prototype.slice.call(类数组);
const arr1 = Array.prototype.slice.call(arguments)
console.log('arr1', arr1)
//[].slice.call(类数组);
const arr2 = [].slice.call(arguments)
console.log('arr2', arr2)
//Array.from
const arr3 = Array.from(arguments)
console.log('arr3', arr3)
//扩展运算符
const arr4 = [...arguments]
console.log('arr4', arr4)
}
fun(1, 2, 3, 4)
18.数组中有哪些原生的方法,举例以下?
- 数组和字符串的转换方法:
toString(),join()
,其中join可以接收特定字符串参数,将作为将数组转换为字符串时的分割符 - 数组尾部操作的方法:
pop(),push()
- 数组首部操作方法:
unshift(),shift()
- 数组重排序的方法:
reverse(),sort()
,其中sort几首一个函数作为参数,该函数规定了排序的方式 - 数组中的迭代方法:
every(),forEach(),filter(),map()
- 数组中查找特定索引的方法:
indexOf(),indexLastOd()
- 数组链接方法:
concat()
,返回的是拼接好的数组,不影响原数组 - 数组中截取方法:
slice()
,用于截取数组的一部分返回,不影响原数组 - 数组插入方法:
splice()
,影响原数组
19. 哪些操作会造成内存泄漏
19.1意外的全部变量的使用
即当我们使用一个未声明的变量时,实质上意外的创建了一个全局变量,而这个变量会一直留在内存中无法被收回
- 示例1:
Javascript处理未定义变量的方式比较宽松:未定义的变量会在全局对象创建一个新变量,在浏览器中,全局变量时window。
function foo(arg) {
bar = "this is a hidden global variable";
}
真相是:
function foo(arg) {
window.bar = "this is an explicit global variable";
}
函数foo内部忘记使用var,意外创建了一个全局变量,而使这个变量一直保存在内存中;
- 示例2:
另一种意外的全局变量可能由this创建:
function foo() {
this.variable = "potential accidental global";
}
// Foo 调用自己,this 指向了全局对象(window)
// 而不是 undefined
foo();
如何避免这种情况呢?
在javascript文件头部加上’use strict‘,可以避免此类错误的发生。使用严格模式解析javascript,避免意外的全局变量
19.2被遗忘的定时器或回调函数
即当我们设置了定时器如setInterval,而忘记取消它的时候,如果循环函数有对外局部变量的引用的话,那么这个变量会一直保存再保存着
19.3脱离DOM的引用
即是当我们获取一个DOM元素的引用的时候,而后面这个DOM元素被删除,我由于我们一直保留着这个元素的引用,所以它也无法被回收
19.4闭包
闭包的不合理使用,从而导致某些变量一直被留在内存中
20. 如何判断当前脚本运行在浏览器(browser)还是在node环境种?
if(typeof window !== 'undefined'){
console.log('browser');
}
if(typeof global !== 'undefined'){
console.log('node');
}
或者
this===window?console.log('browser'):console.log('node');
通过判断Global对象是否未window,使window则允许再浏览器种,如果为undefined,则当前脚本运行再node环境下
21.移动端的点击事件有延迟,时间 是多少,为什么会有?怎么解决这个问题?
移动端的点击延迟
移动端的点击延迟时间是300ms,因为移动端有默认的双击缩放操作,浏览器通过判断用户第一个此点击与第二次点击的时间,来确认用户是否是需要进行双击缩放的操作。(即浏览器点击后要等待300ms,看用户有没有下一次点击,来判断是不是双击)
怎么解决点击延迟
1)使用meta标签来禁用页面的缩放
ps:但是这种方式的缺点是牺牲了页面的缩放,我们不想要的只是双击双击缩放行为,我们还是希望能够通过双指来实现缩放的,
<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">
2)调用FastClick库
FastClick库的相关介绍:FastClick是专门为解决移动端浏览器300毫秒点击延迟问题所开发的一个轻量级的库。FastClick的实现原理是在检测到touched事件的时候,会通过DOM自定义事件立即触发模拟一个click事件,并把浏览器在300ms之后的click事件阻止掉,实现如下:
//引入fastclick
<script src="js/fastclick.min.js"></script>
// 原生js初始化
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
总:使用FastClick是最好的方案,不存在点击创投现象(对于点击穿透不熟悉的可以自行查一下资料)
可以参考这篇文章:https://blog.csdn.net/qq_42532128/article/details/106176195
22. 什么是“前端路由”?什么时候适合使用”前端路由”?“前端路由”有哪些优缺点?
1.前端路由:前端路由就是把路由对应的内容或者页面的任务交给前端来做,以前是通过服务器根据url的不同返回不同的页面实现的。
2.使用:在单页面应用中,大部分页面结构不变,只改变内容的使用。
3.一般有两种形式的前端路由,一是hash模式(#),二是pushState的方式
4.优缺点:
- 优点:用户体验好,不需要每次都从服务器获取,快速展现给用户
- 缺点:单页面无法记住之前滚动的位置,无法在前进、后退的时候记住滚动的位置
23.检测浏览器版本有哪些方式?
有两种方式:
1.通过检测window.navigator.userAgent的值,但是这种方式不是很可靠,因此这个值可以被改变(如早期的ei会伪装userAgent的值为Mozilla来躲过服务器的检测)
2.可以通过功能检测,根据每个浏览器独有的特性进行判断,如ie下独有的ActiveAObject
24. 什么是Ployfill
- Ployfill的作用是为了解决API的兼容问题,比如在不同的浏览器中,支持的API有所差异,但是若想在不支持某API的浏览器中实现这个功能,则需要引入Ployfill(当然也可以自己写)
举个例子:(有些旧的浏览器不支持Number.isNan方法mPloyfill可以是这样的:)
if(!Number.isNaN){
Number.isNaN=function(num){
//(假如浏览器没有Number.isNan方法,那么就给它添加上去)
return (num!=num)
}
}
25.使用JS实现获取文件扩展名
- 方式1(使用split与pop方法)
<!DOCTYPE>
<html>
<head>
<script type="text/javascript">
function fnGetExtension() {
//通过id获得文件输入元件
var fileInput = document.getElementById('myFile');
//获得文件名
var fileName = fileInput.files[0].name;
//获得文件扩展名
var fileExtension = fileName.split('.').pop();
alert(fileExtension);
}
</script>
</head>
<body>
<form action="/uploadFile" onsubmit="return false;">
<input id="myFile" type="file" name="file">
<button onclick="fnGetExtension()">获得扩展名</button>
</form>
</body>
</html>
- 方式2 使用正则表达式
###Vue部分
https://zhuanlan.zhihu.com/p/111310865
26. 介绍以下js中的节流和防抖
26.1 节流(通过设置perTime与nowTime)
节流技术是用于设定在固定事件内,执行特定的功能代码,如果同一单位事件内某事件被多次触发,那么只有一次生效(节流可以用在scroll函数的事件监听上,通过事件节流来降低事件调用的频率)
26.2 防抖(通过定时器)
- 在时间触发n秒后再执行回调,如果这n秒内事件又被触发,则重新计时
- 比如在搜索功能时,用户输入关键字搜索,不用每次输入都发送请求显示数据,只有等待用户停止输入后等待一定的时间(确保用户不再输入),再执行特定功能的代码。
所用的技术是定时器,和闭包技术
1.防抖的实现
//防抖的实现
function debounce(fun, wait) {
let timer = null;
//所以下面的匿名函数适应的函数外部的变量,这里是闭包的作用
return function () {
//清空计时器是为了当事件又被触发时,重新计时
clearInterval(timer)
timer = setInterval(() => {
fun.apply(this)
}, wait)
}
}
var result = debounce(fun1, 1000)
result()
2.节流的实现
function limtfun(fun, wait) {
var perTime = 0;
return function () {
var nowTime = new Date()
if (nowTime - perTime > wait) {
fun.call(this)
perTime = nowTime//由于闭包的原因,这里的nowTime不会被清空
}
}
}
27. Object.is()与原来的比较操作符“===” 与“==”的区别
- “==”两等号在进行比较时,若两边的类型不一致,则会进行隐式的类型转换(比如‘1’->1、true->1,false->0、{}->[Object Object]
- 三等号比较时不进行隐式类型转换,类型和值有一不同则会返回false
- 而Object.is()则是在三等好的判别基础上做了特殊的处理,使得-0和+0不再相当,而NaN与NaN会返回true
- Object.is()被认为具有特殊用途,但不能说时Object.is()比其他的相等对比更加严格或者宽松
28.浏览器中的事件循环机制(Event Looping)
JS在执行的过程中会产生一个执行环境,js的代码在这个执行环境中按顺序执行,当遇到异步代码时,会被挂起到事件队列中(Task),对于这个队列中的任务则会涉及到微任务和宏任务,当执行栈的为空时,由于事件循环机制,就会从事件队列中拿出需要执行的代码到执行栈中执行,此时执行的循序时先执行微任务,当微任务执行完毕之后,再执行宏任务。
29.instanceof的原理
- instanceof可以正确判断对象的类型
- 用于判断某个实例是否属于某个构造函数
- 在继承关系中用来判断一个实例是否属于它的父类型或者祖先类型的实例;
- 实现原理是通过判断实例对象的原型属性__proto__和构造函数或者父类的原型对象prototype是否相等,循环遍历,相等则返回true;(简单的说就是只要左边的变量原型链上有右边变量的prototype属性即可)
1)语法
var obj=new Object()
obj instanceof Object //true
2)底层原理
function myInstanceof(left, right) {
left = left.__proto__;
right = right.prototype;
while (true) {
if (left === null || left === undefined) {
return false
}
if (left === right) {
return true
}
//即上面均不满足时,继续往原型链上查找
left = left.__proto__;
}
}
参考文章:https://zhuanlan.zhihu.com/p/105487552
30. 手写封装一个jsonp
function jsonp(url, data, callback) {
//产出随机的回调函数名(因为可能有多个请求,这里可以区别开来)
var funcName = 'jsonp_' + Date.now() + Math.random().toString().substr(2, 5);
//如果存在其他传入参数,需要进行拼接
if (typeof data === 'object') {
var tempArr = [];
for (var key in data) {
var value = data[key];
tempArr.push(key + '=' + value)
}
//将数组以&拼接成字符串,如name=h$age=21
data = tempArr.join('&')
}
var script = document.createElement('script')
script.src = url + '?' + data + 'callback=' + funcName
document.body.appendChild(script);
//这里得到请求的数据,在回调函数中执行处理
window[funcName] = function (data) {
callback(data)
}
}
//使用
jsonp('http:127.0.0.1:3000/api', {}, function (res) {
console.log(res)
})
参考文章:
https://blog.csdn.net/weixin_40483654/article/details/106434990
31.有几种方式可以实现存储功能?分别有什么有优缺点?什么是Service Worker?
31.1 实现的存储功能
实现存储功能:Cookie、localStorage、sessionStorage、indexDB
特性 | Cookie | localStroage | sessionStorage | indexDB |
---|---|---|---|---|
数据生命周期 | 一般由服务器生成,可以设置过期事件 | 除非被清理,否则一直存在 | 页面关闭时就被清理 | 除非被清理,否则一直存在 |
数据存储大小 | 4kb | 5mb | 5mb | 无限 |
与服务端的通信 | 会一直携带在请求头 | 不参与下 | 不参与 | 不参与 |
PS:没有大量的数据需求的话,可以使用localStorage和sessionStorage,对于不怎么改变的数据,尽量使用localStorage。
31.2 什么是Service Worker
- Service Worker是运行在浏览器背后的独立线程,一般可以用来实现缓存功能,使用Service Worker的话,传输协议必须使用HTTPS。
- 使用Service Worker实现缓存一般分为三个步骤:
1)首先要注册Service
2)监听install,拿到需要缓存的文件
3)下次用户访问的时候就可以通过拦截请求方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据
32.浏览器中的缓存机制
- 缓存可以是性能优化中简单高效的一种方式
- 对于一个数据请求,分为3个阶段,分为网络请求、后端处理、浏览器响应。浏览器缓存可以帮助我们在第一和第三步中优化性能。比如直接使用缓存而不发请求,或者发请求但后端的数据与前端一致,那么就没有必要再将数据回传回来,这样就减少可数据响应。
接下来我们将通过以下几个方面来探讨浏览器的缓存机制
- 缓存位置
- 缓存策略
- 实际场景应用缓存策略
32.1 缓存位置
我知道的浏览器的缓存位置有Service Worker、网络请求,并且有各自的优先级,当一次查找缓存并且都没有命中的时候,才会请求网络。
32.2 缓存策略
通常浏览器的缓存策略可以分为两种:强缓存和协商缓存。并且这两种缓存策略都是通过设置 HTTP Header 来实现的
1)强缓存(有两种设置方式)
-
Expires:缓存受限于本地的时间
-
Cache-Control:通过max-age来设置缓存的时间期限
2)协商缓存(两种设置方式)
简单的说,如果缓存过了,就需要发起请求验证资源是否更新。即当浏览器发送请求验证时,如果资源没有发生改变,那么服务器就返回304状态码,并更新浏览器的缓存有效期。(相当于浏览器和服务器进行协商,是否返回新的数据)
-
Last-Modified
Last-Modified表示本地文件最后修改日期,当 Last-Modified的值发送给服务器,循环服务器在该日期后资源是否有变更,有则返回新的资源,否则返回304状态码 -
ETag
当ETag的值发送给服务器,询问该资源是否有更新,有则返回新的资源(并且ETag的资源比Last-Modified高)
32.3 实际场景应用缓存策略
一般来说,现在都会使用工具来打包代码,那么我们可以对文件名进行哈希处理,只有当代码修改之后生产新的文件名。基于此,我们就可以给代码设置缓存有效期一年Cache-Control:max-age=3153600,这样只有html文件中引入的文件名发生了改变,才回去下载新的代码,否则就一直使用缓存。
33. 插入几万个DOM,如何实现页面不卡顿?
- 使用分页技术
- 若不分页,则使用虚拟滚动技术(比如elment-ui中的scrollbar可以实现)
虚拟滚动:原理时只渲染可视区域内的内容,非可视区的则不渲染,当用户在滚动的时候就实时去替换渲染的内容。
34.什么情况下会产生渲染堵塞?
我们知道,在浏览器的渲染线程和JS引擎线程时互斥的,因此在渲染时,若遇到script标签时,则此时会渲染会停止下来,等待script代码加载完毕,再从暂停的地方重新渲染。也就是说,当你想首屏渲染越快,那么就不应该再首屏加载js文件,也就是建议将script标签放在body标签底部的原因。当然,你也可以给script标签添加defer或者async属性,那么此时的script可以放在任意位置,因为此时js文件会并行下载,若时defer,会等待页面渲染结束才执行,而async会在加载完毕之后立即执行。
只是defer和async表示js文件的加载和解析不会阻塞渲染。‘
35. 重绘和回流
- 重绘是节点更改了外观而不影响布局,比如更改元素的color属性
- 回流是改变了节点的布局或者几何属性,比如改变元素的宽高等属性
- 回流必然发生重绘,重绘不一定引发回流。
36. 减少重绘和回流
1.使用transform替换top
2.使用visibility替换display:none,因为前者只会引发重绘,而后者会引发回流
3.少用table布局,因为table布局很小的一个改动会引发整个table的重新布局
4.将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。(比如video标签,浏览器会自动将该节点变为图层)
37. 什么是XSS攻击?如何防止XSS攻击?
1.XSS攻击
-
XSS(Cross-Site Scripting)—跨站脚本攻击,简称XSS,是一种代码注入攻击,攻击者通过在目标网址注入恶意脚本,使之在用户的的浏览器上运行。利用这些恶意脚本,攻击者获取用户敏感信息如Cookie、SessionID等,进而危害数据安全
-
XSS的本质:恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行
2. 如何防止
由于问题的源头是js代码的注入,那么我们便想办法不让js生效
方式1:使用转义字符
即需要注意两头的防范:
1)输入
在提交表单时,前端最好将文本内容转成html实体编码,也就是过滤掉<script>、<a>这样的标签,然后再提交到后台去
2)输出
在显示文本时,最好也要做一次html实体编码转换后再显示,防止<script>生效
方式2:CSP建立白名单(什么是CSP)
CSP指的是安全策略,它的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行。(我们只需要配置规则,如何拦截是由浏览器自己实现的)
通常可以通过两种方式来开启CSP:
1)设置HTTP Header中的Content-Security-Policy
2)设置meta标签的方式
方式3:敏感信息保护
比如cookie使用http-only,使得脚本无法获取,也可以使用验证码,避免脚本伪装成用户执行一些操作
38. 什么是CSRF攻击?如何防范CSRF攻击?
1. CSRF攻击
CSRF中文名为跨站请求伪造,原理是攻击者诱导受害者进入第三方网站,在第三方网站中向被攻击网站发送跨站请求,利用受害者在被攻击已获取的注册凭证,绕过后台的验证,达到冒充用户对被攻击的网站执行某项操作的目的。
几种常见的攻击类型
1)GET类型的CSRF利用非常简单,只需要一个HTTP请求,一般会这样利用
<img src="http://bank.example/withdraw?amount=10000&for=hacker" >
在受害者访问这个img页面之后,浏览器会自动向http://bank.example/withdraw?amount=10000&for=hacker
发送一次http请求,bank.example就会收到包含受害者登录信息的一次跨域请求(因此此时受害访问了A网站,那么其是包含着认证信息的,攻击者利用这一特性,隐形中让受害者向上网站(B)发起HTTP请求,那么此时请求头自然也会包含了受害者的认证信息,那么攻击者的目的便达到!)
2)POST类型的CSRF攻击,比如说构建一个表单,然后隐藏它,当用户进入页面时,自动提交表单。
3)第三种时链接型的CSRF,比如说在a标签的href属性里构建一个请求,然后诱导用户去点击
2. 如何防范
CSRF通常是第三方网站发起的,被攻击者的网站无法防止攻击的发生,只能通过增强自己的网站针对CSRF的防护能力来提升安全性
SCRF的两个特点:
- SCRF(通常)发生在第三方域名
- SCRF攻击者不能取到Cookie等信息,只是冒用
针对以上两个特点,我们可以专门指定防护策略,如下:
-
阻止不明外域的访问
- 同源检测
- Samesite Cookie
-
提交时要附加本域才能获取信息
- CSRF Token
- 双重Cookie验证
验证Referer
- 可以通过Referer来判断该请求是否为第三方网站发起的,若是,则阻止。
SameSite
- 可以对Cookie设置SameSite属性,该属性表示Cookie不随跨域请求发送,可以大程度减少CSRF的攻击,但是该属性目前不是所有浏览器都支持
CSRF Token
- 前面讲的CSRF的一特征是,攻击者无法直接窃取到用户的信息(Cookie、Header,网站内容等,仅仅是冒用Cookie中的信息)
- 而CSRF攻击之所以能成功,是因为服务器误把攻击者发送的请求当成了用户的请求,那么所有用户请求都会携带一个CSRF攻击者无法获取到的Token,服务器便可以通过校验请求是否携带正确的Token,来把正常的请求和攻击请求区分开来,也可以防范CSRF的攻击
参考文章:https://my.oschina.net/meituantech/blog/2243958
29. 什么是点击劫持?如何防范点击劫持?
点击劫持就是攻击者利用iframe将受害者网站嵌入自己的网页中,并将iframe设置为透明,在页面中透出一个按钮诱导用户点击。
通常可以使用以下两种方式进行防御:
1)设置HTTP响应头:X-FRAME-OPTIONS
这个响应头主要是用来防御iframe嵌套的点击劫持,其有三个可选值,分别是:
- DENY:表示页面不允许通过iframe的方式展示
- SAMEORINGIN:表示页面可以在相同的域名下通过iframe的方式展示
- ALLOW-FROM:表示页面可以在指定来源iframe中展示
2)通过JS防御
因为有些浏览器不支持1)的方式
JS防御的原理是通过iframe的方式加载页面时,攻击者的页面直接不显示内容。
30. 性能优化
1.在请求以及文件大小方面
- 尽可能的将js、css文件合并为一个文件,减少了向服务器发送请求(webpack)
- 若浏览器支持,尽可能使用webp格式的图片代替其他图片格式
- 尽量使用字体图标或者SVG图代替传统的png图
- 尽量使用精灵图或者说时雪碧图(一方面可以减少向服务器发送请求,另一方面则是降低了资源占用空间大小)
- 可以适当的使用缓存技术,比如对于不怎么发生变化的数据,使用h5新增等的localstorage、sessionStorage,以减少发送请求的次数
2.对于代码优化相关
- 闭包-------在js中尽量减少使用闭包(因为闭包所在的上下文不会被释放)
- 重绘和回流-----减少对DOM的操作,主要时减少DOM的重绘和回流
-scc与js代码的位置----- 把css代码放在body上,把js代码放在body下面(以提高首屏加载的速度,当然也可以对script标签使用defer和async属性) - css的导入方式-----css的导入尽量减少使用@import,因为@import是同步操作,可以使用link导入
- 懒执行-----对某些特定的逻辑代码进行懒执行操作。主要用于某些耗时的逻辑操作,且不需要在首屏就使用时,便可以使用定时器或者时间调用来唤醒。
- 懒加载-------懒加载是将不关键的资源延后加载
31. Webpack的性能优化
3.减少webpack的打包时间
1.优化Loader
- 对于Loader来说,影响打包效率首当其冲必属Bable,因为Bable会将代码转为字符串生产AST,然后再对AST继续进行转变量再生成新的代码。当你的项目越大,那么转换代码就越多,效率就越低下。因此我们一般只对js文件使用Bable转换格式(可以再配置文件中通过rules来进行配置。
2.实现按需加载
比如在一个SPA项目中,项目会有十几个甚至更更多的路由页面,我们可以实现按需加载,以减少一些不必要代码的加载
32. 路由原理(什么是前端路由?两种实现方式有什么区别?)
1. 前端路由
- 前端路由的本质是根据url的不同,匹配路由规则的变化,然后显示相应的页面,并且不需要重新刷新页面。目前前端使用的路由只有两种方式(Hash模式和History模式)
1)Hash模式
Hash模式是根据#后面哈希值的变化时,触发了hashchange事件来监听到url的变化,从而进行页面的跳转。并且无论哈希值怎么样变化,服务器接收到的url永远都是#前面部分的url
如www.test.com/#/---------->www.test.com
2)History模式
History模式是H5新推出来的功能,主要使用history.pushState和history.repalceState改变url,同样,通过History模式改变url也不会引起页面的刷新,只会更新浏览器的历史记录
3)两种方式的区别
- Hash模式只可以更改#后面的内容,History模式可以通过API设置任意的同源URL
- Hash不需要后端配置,兼容性好。History模式再用户手动输入地址或者刷新的时候会发送url请求,后端需要配置index.html页面用于匹配不到的静态页面资源。
33. 开发中常见的几种Content-Type
1) application/x-www-form-urlencoded
- 在原生的form表单中,如何步设置enctype,则默认以application/x-www-form-urlencoded方式提交数据.
2) mutiipart/form-data
- 该种方式是一个常见的post提交方式,通常表单文件上传时使用该方式
3) application/json
- 告诉服务器消息主体时序列化后的JSON字符串
4) text/xml
- 该种方式主要时用来提交xml格式的数据
34.什么时MVVM、MVC?有什么区别?
34.1 说明
MVC、MVVM都是常见的软件加购模式,主要通过关注分离关注点的方式来组织代码结构,优化我们的开发效率。
比如我们项目开发使用当页面时,往往一个路由页面对应了一个脚本文件,所有的页面逻辑都在一个脚本文件里。页面的渲染,数据的获取,对用户事件的响应所有的应用逻辑都混合在一起。这样一来,对于简单的项目,可能没有什么大问题,但是项目一旦变得复杂,那么整个文件就会变得冗长,混乱,这样对我们的项目开发和后期维护都是非常不利的
34.2 MVC
MVC通过分离Model、View、Controller的方式来组织代码结构,其中View负责页面的显示逻辑,model负责存储页面的业务数据,以及对相应数据的操作。并且view和model应用了观察模式,当model层发生改变时候,它会通知有关view层更新页面。controller层是view层和model层的纽带,它主要是负责用户与应用的响应操作,当用户与页面产生交互的时候,controller。
34.3 MVVM
MVVM中的VM,指的是ViewModel,其思想是通过双向数据绑定,将View和model的同步更新自动化了。当Model发生变化的时候,ViewModel就会自动更新;ViewModel变化了,View也会更新。