一、js
1.1 对象
- js监听对象属性的改变?
- 获取对象属性有哪些方法?
for in 获取自身及继承对象的所有可枚举属性;Object.keys()只能获取自身的可枚举属性,不能获取继承来的属性值;Object.getOwnPropertyNames()获取对象的可枚举和不可枚举属性,但不包括 Symbol 属性。 - 如何实现对象的深浅拷贝?
深浅拷贝的区别在于复杂对象类型的情况,如果对象嵌套了引用类型,浅拷贝只能拷贝第一层,而深拷贝可以拷贝与原对象一致的对象,会申请新的内存空间。
浅拷贝的实现方式有for循环遍历key并赋值、assign、es6的扩展运算符。
深拷贝的实现方式有:(1)JSON.parse(JSON.Stringify()),但是该方法有局限性:会忽略symbol、undefined,函数不能序列化,不能解决循环引用的对象。(2)递归循环遍历 - js判断类型的方法有哪些?
typeof可以检测数据类型;使用typeof运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回“object”。这就需要用到instanceof来检测某个对象是不是另一个对象的实例。(都属于操作符) - json对象和form-data数据格式转换方法? form-data转换成json
let formJson = {};
formData.forEach((value,key) =>{
formJson[key] = value;
})
const formData = new FormData();
Object.keys(params).forEach((key) => {
formData.append(key, params[key]);
});
1.2 数组
- 判断数组的方法有哪些?
[1,2,3] instanceof Array;
<!--数组自带的方法-->
Array.isArray([1,2,3]);
Object.prototype.toString.call([1,2,3])=="[object Array]"
- 数组去重的方法有哪些?
var arr1 = [4,2,3,3,4,4]
<!--indexof-->
function unique(){
var arr2 = []
for(var i=0;i<arr1.length;i++){
if(arr2.indexOf(arr1[i])==-1){
arr2.push(arr1[i])
}
}
return arr2;
}
console.log(unique())
<!--es6的new Set-->
[...new Set(arr1)]
- 数组[1,2,3,5]的累加怎么实现?
利用reduce传入一个函数,reduce为数组的一个高阶函数。
var a = [1,2,3,5] function compose(total,cur){ return total +cur; } a.reduce(compose) - 数组实现排序的方法?
sort函数传入一个比较函数。
var points = [40, 100, 1, 5, 25, 10];
points.sort(function(a, b){return a - b});
1.3 浏览器
-
有几种方式可以实现存储功能,分别有什么优缺点?什么是Service Worker?
可以从存储大小、时间以及是否与服务器通信分析:cookie可以和服务端通信,跨域情况下:
Service Worker是运行在浏览器背后的独立线程,用于实现缓存功能!注意:使用Service Worker,传输协议必须是https,因为涉及到请求拦截。
Service Worker实现缓存主要有三个步骤:注册,监听到install事件并在回调函数中实现缓存,在下一次用户访问时通过缓存拦截查询是否有缓存,存在则直接读取,否则去请求数据。if (navigator.serviceWorker) { navigator.serviceWorker .register('sw.js') .then(function(registration) { console.log('service worker 注册成功') }) .catch(function(err) { console.log('servcie worker 注册失败') }) } // sw.js // 监听 `install` 事件,回调中缓存所需文件 self.addEventListener('install', e => { e.waitUntil( caches.open('my-cache').then(function(cache) { return cache.addAll(['./index.html', './index.js']) }) ) }) // 拦截所有请求事件 // 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据 self.addEventListener('fetch', e => { e.respondWith( caches.match(e.request).then(function(response) { if (response) { return response } console.log('fetch source') }) ) }) -
什么是跨域?为什么浏览器要使用同源策略? http协议、域名、端口有一个不同就是跨域。主要用于防止CSFR攻击,CSRF攻击是利用用户的登录状态发起恶意请求。也就是说,如果没有同源策略,其他来源的请求可以获取到用户的任何信息。
-
你有几种方式可以解决跨域问题? 跨域的解决方法主要有jsonp、代理服务器、CORS、postMessage。
- Jsonp的原理是利用script标签没有跨域限制的漏洞。通过script标签指向一个需要访问的地址并提供一个回调函数来接收数据当需要通讯时。JSONP使用简单且兼容性不错,但是只限于 get 请求;
- 利用本地服务器(跟前端项目同协议,同域名,同端口)来代理转发,利用的是同源策略是只发生在浏览器,而两个服务端是不会出现跨域问题的,webpack种配置跨域就是这个原理;
- CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过XDomainRequest 来实现。主要通过后端实现,设置响应报头的Access-Control-Allow-Origin,允许哪些域名可以访问域名。ps:非简单请求需预检。
- postMessage
postMessage是html5引入的API,postMessage()方法允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档,多窗口,跨域消息传递.多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案。参考网址:www.w3cschool.cn/fetch_api/f…
//语法 targetWindow.postMessage(message,targetOrigin,[ transfer ]); //otherWindow可以通过执行以下JavaScript来侦听已分派的消息: window.addEventListener("message", receiveMessage, false); function receiveMessage(event) { if (event.origin !== "http://example.org:8080") return; // ... } -
谈谈浏览器缓存机制?
从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络:Service Worker、Memory Cache、Disk Cache、Push Cache、网络请求。
如果所有缓存都没有命中,就会发起请求获取资源。为了性能上的考虑,大部分的接口都应该选择好缓存策略。通常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的。- 强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的network选项中可以看到该请求返回200的状态码,并且size显示from disk cache或from memory cache;
- 协商缓存:向服务器发送请求,服务器会根据这个请求的request header的一些参数来判断是否命中协商缓存,如果命中,则返回304状态码并带上新的response header通知浏览器从缓存中读取资源;
两者的共同点是,都是从客户端缓存中读取资源;区别是强缓存不会发请求,协商缓存会发请求。
1.4 DOM
- 什么情况阻塞渲染?
渲染的前提是渲染树,所以HTML和css肯定会阻塞渲染,如果想渲染加快,就需要降低文件大小,并且减少css层级嵌套、优化选择器。script放在header会导致阻塞,因为浏览器解析到script标签,会暂停构建DOM。可以将script放在body后或者添加defer,对于没有任何依赖的 JS 文件可以加上 async 属性,表示 JS 文件下载和解析不会阻塞渲染。 - 谈谈重绘和回流?
重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能。 重绘是当节点更改外观但是不影响布局,比如改变color,渲染树需要重新渲染的过程;回流指的是当渲染树的一部分因为元素的尺寸、布局、隐藏等改变需要重新构建。而DOM树和样式结构体结合生成渲染树,当渲染树重新构建自然也会引起重新渲染,也就是回流。
回流必定会引发重绘,而重绘不一定会引发回流。回流所需的成本更高,改变子节点可能会引发父节点的一系列回流。引发回流的操作:window的resize;增添删除DOM节点;元素尺寸位置变化。 - 怎样减少重绘和回流?
- 使用transform代替postion;
- 使用visibility:hidden代替display:none;
- CSS 选择符从右往左匹配查找,避免节点层级过多;
- 不要把节点的属性值放在一个循环里当成循环里的变量;
- 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局;
- 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame;
- 插入几万个 DOM,如何实现页面不卡顿?
从原因出发解决问题,页面卡顿的原因主要是因为每次循环操作DOM的次数太多、每次循环时间太长而显示器刷新时间较短。减少操作次数可以利用DocumentFragment创建虚拟节点,而缩短循环时间可以利用RequestAnimationFrame,它会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。因此可以利用RequestAnimationFrame循环插入DOM。另一种方法就是利用虚拟滚动,原理就是只渲染可视区的内容,当用户滚动时,再实时地替换渲染的内容。var total = 100,batchSize = 4,doneCount = 0,batchCount=100/4; function appendItems(){ var fragNode = document.createDocumentFragment(); for (var i = 1;i<=batchSize;i++){ var childNode = document.createElement('li'); var textNode = document.createTextNode(doneCount*batchSize+i); childNode.append(textNode); fragNode.append(childNode) } document.getElementById('container').append(fragNode); doneCount++; if(doneCount<batchCount){ requestAnimationFrame(appendItems) } } appendItems() - 在不考虑缓存和优化网络协议的前提下,考虑可以通过哪些方式来最快的渲染页面,也就是常说的关键渲染路径,这部分也是性能优化中的一块内容。
说说前端中的事件流?
事件流描述的是页面接收事件的顺序,DOM2级事件流包括下面几个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
1.5 函数
- this指向?
全局、在对象内部、构造函数实例等的函数调用,apply、call、bind显示改变this指向,bind。(bind和call参数一样,但是bind返回函数)
let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)()
注意不管给函数bind多少次,永远由第一次bind决定。
- 手写call、apply、bind函数?
Function.prototype.call = function(context){
if(typeof this !=='function'){
throw new TypeError('type error')
}
const args = [...arguments].slice(1);
context.fn = this;
const results = context.fn(args);
delete context.fn;
return results;
}
核心:改变this的指向在于改变函数的调用对象。因此需要设置context.fn=this,此时,调用函数的对象为context。
首先call是被函数调用,因此需要写在Function的原型上。判断执行call的对象是否为函数,取出传入的参数,context创建一个fn属性,将当前函数赋值给context.fn,接下来传入参数。
apply与call的区别在于传入的参数为数组。
Function.prototype.myApply = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
context = context || window
context.fn = this
let result
// 处理参数和 call 有区别
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
// bind 源码
Function.prototype.bind = function(context){
if(typeof this !== 'function'){
throw new TypeError('type error')
}
context.fn = this;
var args = [...arguments].slice(1)
//bind返回一个函数
return function fun(){
context.fn(args)
}
}
- 说一下闭包?
闭包就是能够读取其他函数内部变量的函数。外层函数执行完后,一般函数执行完后,会从执行栈中出栈,并销毁作用域,由于内层函数还在调用,所以外层函数的作用域不会被释放。
1.6 面向对象
- new Object()实现原理?
function create(){
var obj = {};
var con = Array.prototype.shift.call(arguments);
//取出第一个参数,arguments删除第一个参数。
obj = object.create(con.prototype);
var results = con.prototype.call(obj,...arguments);
return typeof results === 'object'?results:obj;
}
首先新建一个对象,取出构造函数,将构造函数的原型对象复制给新对象,将this指向新对象执行构造函数并传参,最后确保返回的结果为对象。
- 如何理解原型和原型链?
每构建一个函数,都会有一个prototype属性(指针),指向函数的原型对象,所以的原型对象会自动创建一个constructor属性,而这个属性会指回函数。当调用构造函数创建实例后,实例的内部会有一个内部属性[[prototype]](指针),它会指向原型对象。而原型对象又是Object构造函数的一个实例,所以原型对象的[[prototype]]会指向Object构造函数的原型对象。由此可见,对象的属性[[prototype]]会指向原型,将对象和原型连接起来组成了原型链。 - 原型如何实现继承?Class 如何实现继承?Class 本质是什么?
继承实际是子类继承父类实例的属性和原型。 组合继承
function Parent(name){
this.name = name
}
Parent.prototype = {
getname:function(){
console.log(this.name)
}
}
function Child(name){
Parent.call(this,name)
}
Child.prototype = new Parent()
var child1 = new Child('jucy')
child1.getname()
通过Parent.call(this,name)继承父实例的属性,并且可以传参;将父实例赋值给子构造函数的原型对象,从而继承了父级的方法。但是有一个缺陷就是父构造函数会被实例化。寄生式继承可以解决这个问题。
function Parent(name){
this.name = name
}
Parent.prototype = {
getname:function(){
console.log(this.name)
}
}
function Child(name){
Parent.call(this,name)
}
Child.prototype = Object.create(Parent.prototype,{
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true
}
})
var child1 = new Child('jucy')
child1.getname()
class实现extends关键字实现继承,通过super()继承父类实例的属性和原型。class的本质是构造函数,是一个语法糖。
class Parent{
constructor(val){
this.name = val
}
getname(){
console.log(this.name)
}
}
class Child extends Parent{
constructor(val){
super(val)
}
}
var child1 = new Child('jucy')
child1.getname()
1.7 异步操作
- 函数防抖和节流一般用在什么情况之下呢?
一般用在,连续的事件只需触发一次回调的场合。具体有:搜索框搜索输入。只需用户最后一次输入完,再发送请求;用户名、手机号、邮箱输入验证;浏览器窗口大小改变后,只需窗口调整完后,再执行 resize 事件中的代码,防止重复渲染。
鼠标不断点击触发,mousedown(单位时间内只触发一次);监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断。 - 谈谈js的节流和防抖?
防抖是指触发后,延迟设定时间后执行,如果在中途触发,定时器会被清除,重新开启定时器。也就是说,只有触发间隔大于定时器delay时间,才能执行真正的函数。function debounce(fn,delay){ var timer=null; return function(){ clearTimeout(timer) timer = setTimeout(()=>{ fn.call(this) },delay) } };
节流是指触发后,真正的函数会在设定的延迟时间后被执行。如果在中途触发,也不会重新计时或者清除。如果真正的函数执行完毕后,会将flag设置为true,重新开启定时器。
function throttle(fn,delay){
var flag = true;
return function(){
if(!flag){
return;
}
flag = false;
setTimeout(()=>{
fn.call(this);
flag = true;
},delay)
}
}
- setTimeout、setInterval和requestAnimationFrame之间的区别?
大多数电脑显示器的刷新频率为60hz,大概相当于每秒钟重绘60次。因此,最平滑动画的最佳循环间隔是1000ms/60,约等于16.6ms。 而setTimeout这类计时器的回调函数是等js主线程清空栈后再执行的异步任务,因此等待的时间不等于设置的时间。再者,设置的时间间隔可能与显示器刷新时间并不一致,例如10ms移动1px,显示器频率为16.6ms执行一次,此时只能显示16.6ms移动1px;33.2ms移动3px,丢失了一帧。
而requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。- 特点:1.requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率;2.在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量;3.requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销。
- 实现方法
requestAnimationFrame递归执行回调函数,实现循环。
var t = 0; function step(){ t++; var g = requestAnimationFrame(step) if(t>=10){ //停止动画 cancelAnimationFrame(g) } } step() - 你理解的Generator函数是什么?
在js中,函数运行之后会执行到最后,或者遇到return。而Generator会交出函数执行权,遇到yield会暂停执行。Generator执行后会返回一个迭代器,调用next()可以恢复执行,next会将yield表达式替换为一个值,next的参数代表上一个yield表达式的返回值。 - Promise 的特点是什么?Promise是什么,缺点是什么?Promise 构造函数执行和 then 函数执行有什么区别? promise有三种状态,pending(进行中)、fulfilled(已成功)和rejected(已失败);pending一旦变成其他状态就不能更改了。promise是一个构造函数,用于生成promise实例,接收两个参数resolve, reject,用于更改promise对象的状态。Promise 构造函数执行会立即执行,then会返回一个promise实例,如果then里面有异步操作,会等操作完成后继续后面的then。promise存在的缺点:比如无法取消Promise,错误需要通过回调函数捕获。
- 实现一个promise
- 谈谈eventLoop机制?
eventLoop主要流程:
1.全局Script代码执行完毕后,执行栈会被清空。
2.从微队列microtask queue的队首取出回调任务,放入执行栈中执行,执行完后microtask queue长度减1,再从队首取出放入执行栈,直到所有任务执行完毕。如果在执行microtask的过程中,又产生了microtask,会加入到队列的末尾,也会在这个周期被调用执行。
3.microtask queue中的所有任务都执行完毕,此时microtask queue为空队列,调用栈Stack也为空。 4.执行UI渲染,判断是否需要渲染。(因为浏览器是 60Hz 的刷新率,每 16.6ms 才会更新一次。) 5.宏队列macrotask一次只从队列中取一个任务执行,执行完毕后,调用栈Stack为空;查询是否有微任务,有则执行完后就去执行微任务队列中的任务;
6.重复2-5步骤...
1.8 作用域和内存
- js的垃圾回收机制是怎样的?
js具有自动回收垃圾机制,也就是说执行环境会管理代码执行过程中使用的内存。原理:垃圾收集器会定期找出那些不再使用的变量,然后释放其内存。主要有两种实现方式:引用计数和标记清除。
引用计数会追踪值被引用的次数,当引用类型的数据被赋给其他变量时,引用次数会加1。当引用计数为0时,就可以将其占用的内存空间回收。循环引用时会导致垃圾无法回收,浪费大量内存系统,例如obj1.a=ojb1;(你中有我,我中有你,会造成obj1.a的无限循环) 标记清除是指当变量进入环境时,这个变量标记为“进入环境”,而当变量离开环境时,则将其标记为“离开环境”,最后垃圾收集器销毁带标记的值和回收它们占用的内存空间。
1.9 模块化
- 为什么要使用模块化?都有哪几种方式可以实现模块化,各有什么特点? 实现模块化的好处有:避免命名冲突,污染全局变量;提高代码的复用性和可维护性。实现模块化的方法有AMD、CMD、common.js、es6的模块。
- 说一下common.js、ADM和CMD的区别?
Commonjs:开始于服务器端的模块化,同步定义的模块化,每个模块都是一个单独的作用域,模块输出,modules.exports,模块加载require()引入模块; - common.js和es6模块的区别?
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块是运行时加载,ES6模块是编译时输出接口。 - webpack和gulp的区别?
- 设计模式?
二、http
- UDP 与 TCP 的区别是什么?
UDP是面向无连接的,不需要像TCP一样在发送数据之前进行三次握手建立连接。UDP不排序要发送的数据,不保证有序且不丢失的传送到对端。UDP的网络开销比TCP小得多。因此UDP适用于实时性强、允许出错的场景,例如:QQ、视频聊天等。
TCP是全双工、面向连接、可靠而且精准控制的协议,主要用于对实时性要求不高但传输要求高的应用,例如:资源下载等。 ps:TCP 的可靠性含义:接收方收到的数据是完整、有序、无差错的;UDP不可靠性含义:接收方接收到的数据可能存在部分丢失,顺序也不一定能保证。 - TCP建立连接的三次握手过程?
- 为什么 TCP 建立连接需要三次握手,明明两次就可以建立起连接?
- 用户输入url到显示页面这个过程发生了什么?
从输入URL到渲染出整个页面的过程包括三个部分:
1、DNS解析URL的过程
2、浏览器发送请求与服务器交互的过程
3、浏览器对接收到的html页面渲染的过程- DNS解析URL的过程
DNS解析的过程就是寻找哪个服务器上有请求的资源。因为ip地址不容易记忆,一般会使用URL域名(如www.baidu.com)作为网址。DNS解析就是将域名翻译成IP地址的过程。 具体过程:
1)浏览器缓存:浏览器会按照一定的频率 缓存DNS记录
2)操作系统缓存:如果浏览器缓存中找不到需要的DNS记录,就会取操作系统中找
3)路由缓存:路由器也有DNS缓存
4)ISP的DNS服务器:ISP有专门的DNS服务器应对DNS查询请求
5)根服务器:ISP的DNS服务器找不到之后,就要向根服务器发出请求,进行递归查询。 - 浏览器与服务器交互过程
1)首先浏览器利用tcp协议通过三次握手与服务器建立连接
http请求包括header和body。header中包括请求的方式(get和post)、请求的协议 (http、https、ftp)、请求的地址ip、缓存cookie。body中有请求的内容。
2)浏览器根据解析到的IP地址和端口号发起http的get请求。
3)服务器接收到http请求之后,开始搜索html页面,并使用http返回响应报文
4)若状态码为200显示响应成功,浏览器接收到返回的html页面之后,开始进行页面的渲染 - 浏览器页面渲染过程
1)浏览器根据深度遍历的方式把html节点遍历成dom 树
2)将css解析成CSS DOM树
3)将dom树和CSS DOM树构造成render树
4)JS根据得到的render树 计算所有节点在屏幕中的位置,进行布局(回流) 5)遍历render树并调用硬件API绘制所有节点(重绘)
构造render渲染树的过程 从DOM树的根节点开始遍历每个可见的节点。 对于每个可见的节点,找到CSS树中的对应的规则,并且应用他们。 根据每个可见的节点及其对应的样式,组合生成渲染树。
- DNS解析URL的过程
- get和post请求有什么区别?
- get参数通过url传递,在浏览器上url有长度限制;post放在request body中。
- get会被浏览器主动缓存,而post需要设置。
- GET产生一个TCP数据包,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);POST产生两个TCP数据包,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
- Post 支持更多的编码类型且不对数据类型限制
- 谈谈http缓存机制?
- HTTP与HTTPS有什么区别?
- HTTP请求中,几种常见的Content-Type类型?
application/json、application/x-www-form-urlencoded、multipart/form-data。 application/x-www-form-urlencoded:请求参数在Form Data中,键值对,用间隔分开,如: name1=value1&name2=value2; multipart/form-data:请求参数在Request Payload 中, 可以上传文件,也可以上传键值对,最后会转化为一条由boundary字符串分隔的信息体; application/json 请求参数在Request Payload中, 参数形式:{key:value}
- 什么是 XSS 攻击?如何防范 XSS 攻击?什么是 CSP?
xss就是攻击者将可以执行的代码注入到网页中,分为持久型和非持久型;持久型会将攻击的代码会被服务端写入数据库中,危害性很大,非持久型一般通过修改url参数的方式加入攻击代码。
对于xss攻击通常有两种方式防御:转义字符、CSP。
CSP的本质就是建立白名单,开发者明确告诉浏览器哪些资源可以加载和执行。通常有两种方式执行CSP。
设置 HTTP Header 中的 Content-Security-Policy
设置 meta 标签的方式 <meta http-equiv="Content-Security-Policy">
<!--只允许加载本站资源-->
Content-Security-Policy: default-src ‘self’
<!--只允许加载 HTTPS 协议图片-->
Content-Security-Policy: img-src https://*
<!--允许加载任何来源框架-->
Content-Security-Policy: child-src 'none'
- 什么是 CSRF 攻击?如何防范 CSRF 攻击?
CSRF中文名叫跨站请求伪造,原理就是攻击者仿造出一个后端请求地址,诱导用户点击或者通过某些方法自动触发请求。如果用户是在登录状态下的话,后端就以为是用户在操作,从而进行相应的逻辑。
防范 CSRF 攻击可以遵循以下几种规则:
1、get请求不对数据进行修改;
2、不让第三方访问cookie,可以对 Cookie 设置 SameSite 属性。该属性表示 Cookie 不随着跨域请求发送,可以很大程度减少 CSRF 的攻击,但是该属性目前并不是所有浏览器都兼容。
3、阻止第三方网站请求接口,对于需要防范 CSRF 的请求,我们可以通过验证 Referer 来判断该请求是否为第三方网站发起的。
4、请求附带验证信息,如token或者验证码。 - 什么是点击劫持?如何防范点击劫持?
攻击者将需要攻击的网站通过 iframe 嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,在页面中透出一个按钮诱导用户点击。对于这种攻击方式,推荐防御的方法有两种:
X-FRAME-OPTIONS 是一个 HTTP 响应头,在现代浏览器有一个很好的支持。这个 HTTP 响应头 就是为了防御用 iframe 嵌套的点击劫持攻击。
该响应头有三个值可选,分别是: DENY,表示页面不允许通过 iframe 的方式展示 SAMEORIGIN,表示页面可以在相同域名下通过 iframe 的方式展示 ALLOW-FROM,表示页面可以在指定来源的 iframe 中展示。- JS 防御
<head> <style id="click-jack"> html { display: none !important; } </style> </head> <body> <script> if (self == top) { var style = document.getElementById('click-jack') document.body.removeChild(style) } else { top.location = self.location } </script> </body> - 什么是中间人攻击?如何防范中间人攻击?
中间人攻击是攻击方同时与服务端和客户端建立起了连接,并让对方认为连接是安全的,但是实际上整个通信过程都被攻击者控制了。攻击者不仅能获得双方的通信信息,还能修改通信信息。
通常来说不建议使用公共的 Wi-Fi,因为很可能就会发生中间人攻击的情况。 防御中间人攻击其实并不难,只需要增加一个安全通道来传输信息。HTTPS 就可以用来防御中间人攻击,但是并不是说使用了 HTTPS 就可以高枕无忧了,因为如果你没有完全关闭 HTTP 访问的话,攻击方可以通过某些方式将 HTTPS 降级为 HTTP 从而实现中间人攻击。
三、算法
四、es6
- let、const、var有哪些区别?
let和const在声明前使用会报错,var有声明提前,会提升到当前作用域(函数提升到作用域顶部);let和const不能重复声明;let和const不是window属性;let和const的区别是,const声明的变量不能再次赋值。 - Proxy 可以实现什么功能?
五、性能优化、兼容性问题
- 常见的兼容性问题?
- 兼容性问题:
1、安卓和ios转换时间戳的形式。
ios需要将时间格式转为‘/’
function formatTimeStamp (time) { return Date.parse(new Date('2018/03/30 12:00:00')) || Date.parse(new Date('2018-03-30 12:00:00')) } - 兼容性问题:
2、ios position:fixed卡顿
-webkit-overflow-scrolling:touch
3、ios click事件有延迟
2004年苹果为了实现在移动端双击缩放功能,给click事件增加了300ms延迟。解决方法:禁止缩放、fastclick。
4、ios系统会将数字当成电话号码,导致变色
<meta name="format-detection" content="telephone=no">
<meta http-equiv="x-rim-auto-match" content="none">
5、input输入框在聚焦的时候,ios有时候会出现outline或者阴影,安卓则是显示正常的。那么解决方案:
input:focus{outline:none} input:{-webkit-appearance: none;}
- 针对浏览器兼容的常用手法主要分两类:
1、编写代码时的兼容指在编写代码时,向源代码中加入兼容代码,实现兼容性。
手动为HTML添加注释性的兼容代码、利用JS兼容HTML的缺失功能、动编写CSS的各种prefixer或CSS Hack、手动为JS的新特性编写或引入相应的polyfill。
2、集成代码时的兼容
Babel中的preset-env、Babel中的polyfill、postcss中的autoprefixer。 - 性能优化的方法?
- 图片加载优化:小图用base64或者svg、雪碧图。
- DNS 预解析:DNS解析也需要时间,可以通过预解析的方式来预先获得域名所对应的 IP。
<link rel="dns-prefetch" href="//yuchengkai.cn">- 节流、防抖
- 预加载:预加载其实是声明式的 fetch ,强制浏览器请求资源,并且不会阻塞 onload 事件,可以使用以下代码开启预加载。
<link rel="preload" href="http://example.com">- 预渲染
可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染
<link rel="prerender" href="http://example.com">- 懒加载
懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。对于图片来说,先设置图片标签的 src 属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 src 属性,这样图片就会去下载资源,实现了图片懒加载。 - CDN
可以将静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。
六、样式
- link标签和import标签的区别有什么?
- @import 是CSS提供的语法规则,只有导入样式表的作用; link 是HTML提供的标签,不仅可以加载CSS文件,还可以定义RSS,rel连接属性等。
- 加载页面时, link标签引入的css被同时加载;而@import 引入的css将在页面加载完毕后进行加载。
- link引入的样式的权重大于 @import 引入的样式。