前端面试题汇总
一、Html、css、js
1.Html5新增的语义化标签有哪些:
语义化标签的优点:
header页面头部,main页面主要内容,footer页面底部,Nav导航栏,aside侧边栏,article,加载页面的一块独立内容,section相当于div,figure加载独立内容(上图下文)figcaption是figure的标题。
video加载视频,audio加载音频。
2.Border-box与content-box的区别?
-
content-box 标准盒模型,width不包括padding和border -
border-box怪异盒模型,width包括padding 和border. //css3盒子模型使用方法
box-sizing:border-box;
3.Ajax如何使用?
一个完整的AJAX请求包括五个步骤
4.如何判断一个数据是不是NaN
- 利用NaN 的定义,typeof为number,并且判断是否满足isNaN;
const n = NaN;
console.log(typeof n);//number
console.log(isNaN(n));//true
-
利用NaN是唯一一个不等于任何自身的特点 n!==n console.log(n !== n);//true
-
利用ES6中提供的Object.is()方法(判断两个值是否相等) n==nan console.log(Object.is(n == NaN));//false
5.null与underfined的区别?
- 相同点:用if判断时,两者都会被转换成false;
- 不同点:number转换的值不同,null被转换成为0,而underfined被转换为NaN;
console.log(Number(null));//0
console.log(Number(undefined));//NaN
? null:表示一个值被定义了,但是这个值是空值;
? undefined:表示变量声明但是未赋值。
6.闭包是什么?有什么特性?对页面有什么影响?
7.js中常见的内存泄露
内存泄漏简单理解:无用的内存还在占用,得不到释放和归还。比较严重时,无用的内存会持续递增,从而导致整个系统卡顿,甚至崩溃。
内存溢出,就是系统内存不够使用时,你需要的内存的多,而系统给你的内存小,这是溢出。
function count(number) {
basicCount = 2;
return basicCount + number;
}
-
被遗忘的计时器或回调函数 <template>
<div></div>
</template>
<script>
export default {
methods: {
refresh() {
},
},
mounted() {
setInterval(function() {
this.refresh()
}, 2000)
},
}
</script>
上面的组件销毁的时候,setInterval 还是在运行的,里面涉及到的内存都是没法回收的(浏览器会认为这是必须的内存,不是垃圾内存),需要在组件销毁的时候清除计时器,如下: <template>
<div></div>
</template>
<script>
export default {
methods: {
refresh() {
},
},
mounted() {
this.refreshInterval = setInterval(function() {
this.refresh()
}, 2000)
},
beforeDestroy() {
clearInterval(this.refreshInterval)
},
}
</script>
-
被遗忘的事件监听器 <template>
<div></div>
</template>
<script>
export default {
mounted() {
window.addEventListener('resize', () => {
})
},
}
</script>
上面的组件销毁的时候,resize 事件还是在监听中,里面涉及到的内存都是没法回收的(浏览器会认为这是必须的内存,不是垃圾内存),需要在组件销毁的时候移除相关的事件,如下: <template>
<div></div>
</template>
<script>
export default {
mounted() {
this.resizeEventCallback = () => {
}
window.addEventListener('resize', this.resizeEventCallback)
},
beforeDestroy() {
window.removeEventListener('resize', this.resizeEventCallback)
},
}
</script>
-
被遗忘的ES6 set成员
需要改成这样,才没内存泄漏: let map = new Set();
let value = { test: 22};
map.add(value);
map.delete(value);
value = null;
有个更便捷的方式,使用 WeakSet,WeakSet 的成员是弱引用,内存回收不会考虑到这个引用是否存在。 let map = new WeakSet();
let value = { test: 22};
map.add(value);
value = null;
-
被遗忘的 ES6 MAP键明 如下是有内存泄漏的(键值是引用类型的,即对象): let map = new Map();
let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
key = null;
需要改成这样,才没内存泄漏: let map = new Map();
let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
map.delete(key);
key = null;
被遗忘的订阅发布事件监听器 这个跟上面的被遗忘的事件监听器的道理是一样的。 假设订阅发布事件有三个方法 emit 、on 、off 三个方法。 还是继续使用 vue 组件做例子。 <template>
<div @click="onClick"></div>
</template>
<script>
import customEvent from 'event'
export default {
methods: {
onClick() {
customEvent.emit('test', { type: 'click' })
},
},
mounted() {
customEvent.on('test', data => {
console.log(data)
})
},
}
</script>
上面的组件销毁的时候,自定义 test 事件还是在监听中,里面涉及到的内存都是没法回收的(浏览器会认为这是必须的内存,不是垃圾内存),需要在组件销毁的时候移除相关的事件,如下:
<template>
<div @click="onClick"></div>
</template>
<script>
import customEvent from 'event'
export default {
methods: {
onClick() {
customEvent.emit('test', { type: 'click' })
},
},
mounted() {
customEvent.on('test', data => {
console.log(data)
})
},
beforeDestroy() {
customEvent.off('test')
},
}
</script>
function closure() {
const name = 'xianshannan'
return () => {
return name
.split('')
.reverse()
.join('')
}
}
const reverseName = closure()
reverseName();
上面有没有内存泄漏?
上面是没有内存泄漏的,因为name 变量是要用到的(非垃圾)。这也是从侧面反映了闭包的缺点,内存占用相对高,量多了会有性能影响。
但是改成这样就是有内存泄漏的:
function closure() {
const name = 'xianshannan'
return () => {
return name
.split('')
.reverse()
.join('')
}
}
const reverseName = closure()
在当前执行环境未结束的情况下,严格来说,这样是有内存泄漏的,name 变量是被 closure 返回的函数调用了,但是返回的函数没被使用,这个场景下 name 就属于垃圾内存。name 不是必须的,但是还是占用了内存,也不可被回收。
-
脱离DOM的引用 每个页面上的 DOM 都是占用内存的,假设有一个页面 A 元素,我们获取到了 A 元素 DOM 对象,然后赋值到了一个变量(内存指向是一样的),然后移除了页面的 A 元素,如果这个变量由于其他原因没有被回收,那么就存在内存泄漏,如下面的例子: class Test {
constructor() {
this.elements = {
button: document.querySelector('#button'),
div: document.querySelector('#div'),
span: document.querySelector('#span'),
}
}
removeButton() {
document.body.removeChild(this.elements.button)
}
}
const a = new Test()
a.removeButton()
上面的例子 button 元素 虽然在页面上移除了,但是内存指向换为了 this.elements.button ,内存占用还是存在的。所以上面的代码还需要这样写: this.elements.button = null ,手动释放这个内存。
8.事件委托是什么?如果确定事件源(Event.target 谁调用谁就是事件源)
- 事件委托就是利用事件冒泡,只制定一个时间处理程序,就可以管理某一类型的所有事件。
- 事件委托,称事件代理,是js中很常用的绑定事件的技巧,事件委托就是把原本需要绑定在子元素的响应事件委托给父元素,让父元素担当事件监听的职务,事件委托的原理是DOM元素的事件冒泡
9.什么是事件冒泡:
- 个事件触发后,会在子元素和父元素之间传播,这种传播分为三个阶段
- 捕获阶段(从window对象传导到目标节点(从外到里),这个阶段不会响应任何事件),目标阶段,(在目标节点上触发),冒泡阶段(从目标节点传导回window对象(从里到外)),事件委托/事件代理就是利用事件冒泡的机制把里层需要响应的事件绑定到外层
10.本地存储和cookies的区别
-
Cookie 是小甜饼的意思。顾名思义,cookie 确实非常小,它的大小限制为4KB左右。它的主要用途有保存登录信息,比如你登录某个网站市场可以看到“记住密码”,这通常就是通过在 Cookie 中存入一段辨别用户身份的数据来实现的。 -
本地存储
-
localStorage localStorage 是 HTML5 标准中新加入的技术,它并不是什么划时代的新东西。早在 IE 6 时代,就有一个叫 userData 的东西用于本地存储,而当时考虑到浏览器兼容性,更通用的方案是使用 Flash。而如今,localStorage 被大多数浏览器所支持,如果你的网站需要支持 IE6+,那以 userData 作为你方案是种不错的选择。LoaclStorage 存储是永久的,除非手动清除 -
sessionStorage sessionStorage 与 localStorage 的接口类似,但保存数据的生命周期与 localStorage 不同。做过后端开发的同学应该知道 Session 这个词的意思,直译过来是“会话”。而 sessionStorage 是一个前端的概念,它只是可以将一部分数据在当前会话中保存下来,刷新页面数据依旧存在。但当页面关闭后,sessionStorage 中的数据就会被清空。 -
使用方法: localStorage.getItem(key)
localStorage.setItem(key, value)
localStorage.removeItem(key)
localStorage.clear()
sessionStorage.getItem(key)
sessionStorage.setItem(key, value)
sessionStorage.removeItem(key)
sessionStorage.clear()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cGJXyydI-1631108961610)(C:\Users\吕晓亮\AppData\Roaming\Typora\typora-user-images\1630925687653.png)]
11.let 与var与const的区别:
Var声明的变量会挂载在window上,而let和const声明的变量不会
Var声明的变量存在变量提升,let和const不存在变量提升
同一作用域下var可以声明同名变量,let和const、不可以
Let和const声明会形成块级作用域
Let暂存死区
Const一旦声明必须赋值,不能用null占位,声明后不能再修改,如果声明的是复合类型数据,可以修改属性。
12.数组的方法有哪些,请简述?
push()
arr.pop()
arr.shift()
arr.unshift()
arr.splice(i,n)
arr.concat()
str.split()
arr.sort()
arr.reverse()
arr.slice(start,end)
arr.forEach(callback)
arr.map(callback)
arr.filter(callback)
13.什么事面向对象,请简述?
14.普通函数和构造函数的区别:
- 构造函数也是一个普通函数,创建方式和普通函数一样,但是构造函数习惯上首字母大写
- 调用方式不一样,普通函数直接调用,构造函数要用关键字new来调用
- 调用时,构造函数内部会创建一个新对象,就是实例,普通函数不会创建新对象
- 构造函数内部的this指向实例,普通函数内部的this指向调用函数的对象(如果没有对象调用,默认为window)
- 构造函数默认的返回值是创建的对象(也就是实例),普通函数的返回值由return语句决定
- 构造函数的函数名与类名相同
15.请简述原型、原型链、原型继承
-
什么是原型? 任何对象实例都有一个原型,也叫原型对象,这个原型对象由对象的内置属性_proto_指向它的构造函数的prototype指向的对象,即任何对象都是由一个构造函数创建的,但是不是每一个对象都有prototype,只有方法才有prototype。 -
什么是原型链? 原型链基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。我们知道,每个构造函数都有一个原型对象,每个原型对象都有一个指向构造函数的指针,而实例又包涵一个指向原型对象的内部指针。 -
? 原型链的核心就是依赖对象的_proto_的指向,当自身不存在的属性时,就一层层的扒出创建对象的构造函数,直至到Object时,就没有_proto_指向了。 -
因为_proto_实质找的是prototype,所以我们只要找这个链条上的构造函数的prototype。其中Object.prototype是没有_proto_属性的,它==null。 -
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含指向原型对象内部的指针。我们让原型对象(1)等于另一个原型对象的实例(2),此时原型对象(2)将包含一个指向原型对象(1)的指针,再让原型对象(2)的实例等于原型对象(3),如此层层递进就构成了实例和原型的链条,这就是原型链的概念。 -
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数想指针(constructor),而实例对象都包含一个指向原型对象的内部指针(proto)。如果让原型对象等于另一个原型对象的实例,此时的原型对象将包含一个指向另一个原型的指针(proto),另一个原型也包含着一个指向另一个构造函数的指针(constructor)。假如另一个原型又是另一个类型的实例……这就构成了实例与原型的链条。也叫原型链。 -
原型继承是js的一种继承方式,原型链作为实现继承的主要方法,其基本思路是利用原型让一个引用类型继承另一个引用类型的属性和方法。 -
原型继承:利用原型中的成员可以被其相关的对象共享这一特性,可以实现继承,这种实现继承的方式,就叫做原型继承.
16.promise的理解
1.什么是promise?我们用promise解决什么问题?
- 我们都知道,Promise是承诺的意思,承诺它过一段时间会给你一个结果。
Promise 是一种解决异步编程的方案,相比回调函数和事件更合理和更强大。 从语法上讲,promise是一个对象,从它可以获取异步操作的消息;
2.promise的状态
- promise有三种状态:pending 初始状态也叫等待状态,fulfiled成功状态,rejected失败状态;状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
3.promise的两个特点
- promise对象不受外界的影响,
- promise的状态一但改变,就不会在变,任何时候都可以得到这个结果,状态不可逆。
4.promise的三个缺点
- 无法取消promise,一但新建他就会立即执行,无法中途取消。
- 如果不设置回调函数,promise内部抛出的错误,不会反映到外部。
- 当处于pending(等待)状态时,无法得知目前发展到哪一个阶段,是刚刚开始,还是即将完成。
5.promise是用来解决两个问题的
- 回调地狱,代码难以维护,常常第一个函数的输出是第二个函数的输入这种现象。
- promise可以支持多并发的请求,获取并发送请求中的数据。
- 这个promise可以解决异步的问题,本身不能说promise是异步的。
17.请简述async 的用法
没有掌握(具体可以看这个文章)—>https://www.jianshu.com/p/2a8cd4170765
- Async就是generation和promise的语法糖,async就是将generator的*换成async,将yiled换成await
- 函数前必须加一个async,异步操作方法前加一个await关键字,意思就是等一下,执行完了再继续走,注意:await只能在async函数中运行,否则会报错
- Promise如果返回的是一个错误的结果,如果没有做异常处理,就会报错,所以用try…catch捕获一下异常就可以了
18.一个页面从输入URL到页面加载显示完成,这个过程都发生写什么?
- 当发送一个 URL 请求时,不管这个 URL 是 Web 页面的 URL 还是 Web 页面上每个资源的 URL,浏览器都会开启一个线程来处理这个请求,同时在远程 DNS 服务器上启动一个 DNS 查询。这能使浏览器获得请求对应的 IP 地址。
- 浏览器与远程 Web 服务器通过 TCP 三次握手协商来建立一个 TCP/IP 连接。该握手包括一个同步报文,一个同步-应答报文和一个应答报文,这三个报文在 浏览器和服务器之间传递。该握手首先由客户端尝试建立起通信,然后服务器响应并接受客户端的请求,最后由客户端发出该请求已经被接受的报文。
- 一旦 TCP/IP 连接建立,浏览器会通过该连接向远程服务器发送 HTTP 的 GET 请求。远程服务器找到资源并使用 HTTP 响应返回该资源。
- 此时,Web 服务器提供资源服务,客户端开始下载资源。
二、jQuery相关的知识
1.Css预处理sass,less是什么?为什么使用他们?
- Sass和less都是css预处理器,是css上的一种抽象层,是一种特殊的语法,最终会编译成css,less是一种动态样式语言,给css赋予了动态语言的特性,比如:变量,继承,嵌套。Less既可以在客户端运行,在可以在服务端运行(需要借助node)。
2.js中call()和apply()的区别
- apply:调用一个对象的一个方法,用另一个对象替换当前对象。
- call:调用一个对象的一个方法,用另一个对象替换当前对象。
- 从定义中可以看出,call和apply都是调用一个对象的一个方法,用另一个对象替换当前对象。而不同之处在于传递的参数,apply最多只能有两个参数——新this对象和一个数组argArray,如果arg不是数组则会报错。
- **相同点:**两个方法产生的作用是完全一样的。call, apply作用就是借用别人的方法来调用,就像调用自己的一样。
- **不同点:**方法传递的参数不同
foo.call(obj,"张三",20);
foo.apply(obj,["张三",20]);
foo.bind(obj)("张三",20);
- bind 会返还一个新的函数,而且得在后面加括号传递参数。
bind 会比call和apply 多执行一次,因为第一次是返回一个新的函数,第二次传递参数执行新的函数。
3.为什么会造成跨域/请简述同源策略
-
出现跨域问题的原因: 在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域访问问题。在请求的过程中我们要想回去数据一般都是post/get请求,所以…跨域问题出现。 -
跨域问题来源于JavaScript的同源策略,即只有 协议+主机名+端口号(如存在)相同,则允许相互访问。也就是说JavaScript只能访问和操作自己域下的资源,不能访问和操作其他域下的资源。 -
同源策略 是由NetScape提出的一个著名的安全策略。所谓的同源,指的是协议,域名,端口相同。浏览器处于安全方面的考虑,只允许本域名下的接口交互,不同源的客户端脚本,在没有明确授权的情况下,不能读写对方的资源。
4.请说出三种减少页面加载时间的方式
-
优化图片 (图片懒加载) -
图像格式的选择(GIF:提供的颜色较少,可用在一些对颜色要求不高的地方) 。 -
优化CSS(压缩合并css,如 margin-top, margin-left…) -
网址后加斜杠(如www.campr.com/目录,会判断这个目录是什么文件类型,或者是目录。) cdn托管 -
标明高度和宽度(如果浏览器没有找到这两个参数,它需要一边下载图片一边计算大小,如果图片很多,浏览器需要不断地调整页面。这不但影响速度,也影响浏览体验。 当浏览器知道了高度和宽度参数后,即使图片暂时无法显示,页面上也会腾出图片的空位,然后继续加载后面的内容。从而加载时间快了,浏览体验也更好了) -
减少http请求(合并文件,合并图片)例如精灵图
5.this 指向
在JavaScript中,this通常指向的是我们正在执行的函数本身,或者是,指向该函数所属的对象。
- 全局的this → 指向的是Window
- 对象中的this → 指向其本身
- 事件中this → 指向事件对象
6.什么是jsonp工作原理是什么?他为什么不是真正的ajax
https://juejin.cn/post/6844903790760427528
https://zh.wikipedia.org/wiki/JSONP
- Jsonp其实就是一个跨域解决方案。
- Js跨域请求数据是不可以的,但是js跨域请求js脚本是可以的。
- 所以可以把要请求的数据封装成一个js语句,做一个方法的调用。
- 跨域请求js脚本可以得到此脚本。得到js脚本之后会立即执行。
- 可以把数据做为参数传递到方法中。就可以获得数据。从而解决跨域问题。
- jsonp原理:(动态创建script标签,回调函数)
- 浏览器在js请求中,是允许通过script标签的src跨域请求,可以在请求的结果中添加回调方法名,在请求页面中定义方法,就可获取到跨域请求的数据。
function jsonp(url, opts) {
return new Promise((resolve, reject) => {
let count = 0;
const {
prefix = '__jp',
param = 'callback',
timeout = 60000,
data = {}
} = opts;
let name = prefix + count++;
let timer;
function cleanup() {
if (script.parentNode) {
script.parentNode.removeChild(script);
window[name] = null;
if (timer) {
clearTimeout(timer);
}
}
}
if (timeout) {
timer = setTimeout(() => {
cleanup();
reject('timeout');
}, timeout);
}
window[name] = res => {
if (window[name]) {
cleanup();
}
resolve(res);
}
let str = '';
for (const key in data) {
const value = data[key] !== undefined ? data[key] : '';
str += `&${key}=${encodeURIComponent(value)}`;
}
url = url + (url.indexOf('?') > 0 ? '' : '?') + str.substr(1);
url = `${url}&${param}=${name}`;
const script = document.createElement('script');
script.src = url;
document.head.appendChild(script);
})
}
其中传递的url的格式如同下面,最后jsonp里面的函数名由客户端指定,然后服务端把数据放到函数里面,然后通过客户端实现函数的回调,拿到数据。
<script type="text/javascript"
src="http://server2.example.com/RetrieveUser?UserId=1823&jsonp=parseResponse">
</script>
服务器会在传给浏览器前将JSON数据填充到回调函数(parseResponse)中。浏览器得到的回应已不是单纯的资料叙述而是一个脚本,这样浏览器就可以调用该函数进行处理。在本例中,浏览器得到的是:
parseResponse({"Name": "小明", "Id" : 1823, "Rank": 7})
7.jsonp 为什么不是真正的 ajax?
- ajax和jsonp这两种技术在调用方式上"看起来"很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jquery和ext等框架都把jsonp作为ajax的一种形式进行了封装;
- ajax和jsonp这两种技术在调用方式上"看起来"很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jquery和ext等框架都把jsonp作为ajax的一种形式进行了封装;
- 所以说,其实ajax与jsonp的区别不在于是否跨域,ajax通过服务端代理一样可以实现跨域,jsonp本身也不排斥同域的数据的获取。
- 还有就是,jsonp是一种方式或者说非强制协议,如同ajax一样,它也不一定非要json格式来传递数据,如果你愿意,字符换也行,只不过这样不利于jsonp提供公开服务。
8.请掌握2种以上数组去重的方式
- 使用indexof()方法
- 使用lastindexof()方法 和indexof方法一样 indexof从头部开始匹配 lastindexof从尾部匹配
- ES6的set结构 set不接受重复数据
- 使用sort方法先将原数组排序,然后与相邻的比较,如果不同则存入新数组
- 使用filiter和indexof方法
- 使用ES6 的set和扩展运算符
- 使用set和Array.from()方法 array.from可以将set结构转成数组
- 用splice和双层循环
- 使用includes方法
9.深浅拷贝是什么如何实现?
- 深拷贝:指针赋值,并且内容拷贝
- 浅拷贝:只是简单的指针赋值
- 数组浅拷贝: 如果是数组,可以使用数组的一些方法实现:slice(),concat()返回一个新数组的特性实现拷贝。
- 数组深拷贝: **JSON.parse(JSON.stringify())**不仅适用于数组还适用于对象。不能拷贝函数,undefined,symbol。
10.For循环与map循环有什么区别
- For遍历对象自身的和继承可枚举的属性,也就是说会包括那些原型链上的属性。
- Map方法不会对空数组进行检测,map会返回一个新数组,不会对原数组产生影响
11.同步与异步的区别/阻塞与非阻塞区别
- 同步,咱两在一起上班,到吃饭时间了,我去喊你一起吃饭,你很忙,我就坐着等你忙完再一起去吃饭
- 异步,咱两在一起上班,到吃饭时间了,我去喊你一起吃饭,你很忙,我就先自己去吃了,你忙完了再去吃饭
- 同步(阻塞)异步(非阻塞)这两个关注的是程序在等待调用结果时的状态
12.重绘和回流是什么
? 回流就是重新构建render tree,重绘就是回流之后,重新把render tree绘制到屏幕中
- 回流:当render tree中的一部分或者全部因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,这就叫回流,每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候一定会发生回流,因为要构建render tree
- 重绘:在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,这就是重绘。
- 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,不会影响布局,就叫重绘。
13.http是什么?有什么特点?
- http叫做超文本传输协议,是互联网应用最广泛的一种网络协议
- 特点:基于请求-响应的模式 无状态保存 无连接
14.HTTP协议和HTTPS区别?
- http是超文本传输协议,信息是明文传输,https是具有安全性的ssl解密传输协议
- http和https连接方式完全不同,端口也不同,http是80,https是443
- http的连接很简单,是无状态的,https协议是由ssl+http协议构建的可进行加密传输,身份认证的网络协议,比http协议安全
15.箭头函数与普通函数的区别
- 箭头函数是匿名函数,不能作为构造函数,不能使用new
- 箭头函数不能绑定arguments,要用rest参数解决
- 箭头函数没有原型属性
- 箭头函数的this永远指向其上下文的this
- 箭头函数不能绑定this,会捕获其所在的上下文的this值,作为自己的this值
16.你如何对网站的文件和资源进行优化?
- 文件合并(目的是减少http请求)
- 文件压缩(目的是直接减少文件下载的体积)
- 使用cdn托管资源
- 使用缓存
- gizp压缩你的js和css文件
- meta标签优化(title,description,keywords)、heading标签的优化、alt优化
- 反向链接,网站外链接优化
17.请简述ajax的执行过程 以及常见的HTTP状态码
- 100:这个状态码是告诉客户端应该继续发送请求,这个临时响应是用来通知客户端的,部分的请求服务器已经接受,但是客户端应继续发送求请求的剩余部分,如果请求已经完成,就忽略这个响应,而且服务器会在请求完成后向客户发送一个最终的结果。
- 200:这个是最常见的http状态码,表示服务器已经成功接受请求,并将返回客户端所请求的最终结果
- 202:表示服务器已经接受了请求,但是还没有处理,而且这个请求最终会不会处理还不确定
- 204:服务器成功处理了请求,但没有返回任何实体内容 ,可能会返回新的头部元信息
- 301:客户端请求的网页已经永久移动到新的位置,当链接发生变化时,返回301代码告诉客户端链接的变化,客户端保存新的链接,并向新的链接发出请求,已返回请求结果
- 404:请求失败,客户端请求的资源没有找到或者是不存在
- 500:服务器遇到未知的错误,导致无法完成客户端当前的请求。
- 503:服务器由于临时的服务器过载或者是维护,无法解决当前的请求
18.预加载和懒加载的区别,预加载在什么时间加载合适
-
预加载是指在页面加载完成之前,提前将所需资源下载,之后使用的时候从缓存中调用; -
懒加载是延迟加载,按照一定的条件或者需求等到满足条件的时候再加载对应的资源 -
两者主要区别是一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
19.Jquery插入节点的方法
- append() 向每个匹配的元素内部追加内容
- appendTo() 将所有匹配的元素追加到指定元素中,实际上,使用该方法是颠倒了常规的$(A).append(B)的操作 将A追加到B中。
- prepend() 向每个匹配的元素内部前置内容。
- prependTo() 将所有匹配的元素前置到指定的元素中。实际上,使用该方法是颠倒了常规的$(A).prepend(B)的操作,即不是将B前置到A中,而是将A前置到B中。
- after() 在每个匹配的元素之后插入内容。
- insertAfter() 将所有匹配的元素插入到指定元素的后面。实际上,使用该方法是颠倒了常规的$(A).after(B)的操作,即不是讲B插入到A后面,而是将A插入到B后面。
- before() 在每个匹配的元素之前插入内容。
- insertBefore() 将所有匹配的元素插入到指定的元素的前面。实际上,使用该方法是颠倒了常规的$(A).before(B)的操作,即不是将B插入到A前面,而是将A插入到B前面。
20.Js的函数节流和函数防抖的区别
-
函数节流是指一定时间内js方法只执行一次。时间固定的模式 -
函数防抖是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次。(当连续触发的时间一直小于指定的时间就不会执行处理函数,知道指定的时间内没有连续触发) -
函数节流是 声明一个变量当标志位,记录当前代码是否在执行,如果正在执行,取消这次方法执行,直接return,如果空闲,正常触发方法执行。 -
函数防抖是需要一个延时器来辅助实现,延迟执行需要执行的代码,如果方法多次触发,把上次记录的延迟执行代码用cleartimeout清除掉,重新开始,如果计时完毕,没有方法来访问触发,则执行代码 防抖debounce代码:
// 防抖
function debounce(fn, wait) {
var timeout = null;
return function() {
if(timeout !== null) clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
// 处理函数
function handle() {
console.log(Math.random());
}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));
当持续触发scroll事件时,事件处理函数handle只在停止滚动1000毫秒之后才会调用一次,也就是说在持续触发scroll事件的过程中,事件处理函数handle一直没有执行。
var throttle = function(func, delay) {
var prev = Date.now();
return function() {
var context = this;
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
当高频事件触发时,第一次会立即执行(给scroll事件绑定函数与真正触发事件的间隔一般大于delay,如果你非要在网页加载1000毫秒以内就去滚动网页的话,我也没办法o(╥﹏╥)o),而后再怎么频繁地触发事件,也都是每delay时间才执行一次。而当最后一次事件触发完毕后,事件也不会再被执行了 (最后一次触发事件与倒数第二次触发事件的间隔小于delay,为什么小于呢?因为大于就不叫高频了呀(?▽?))。
节流throttle代码(定时器):
var throttle = function(func, delay) {
var timer = null;
return function() {
var context = this;
var args = arguments;
if (!timer) {
timer = setTimeout(function() {
func.apply(context, args);
timer = null;
}, delay);
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
var throttle = function(func, delay) {
var timer = null;
var startTime = Date.now();
return function() {
var curTime = Date.now();
var remaining = delay - (curTime - startTime);
var context = this;
var args = arguments;
clearTimeout(timer);
if (remaining <= 0) {
func.apply(context, args);
startTime = Date.now();
} else {
timer = setTimeout(func, remaining);
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
在节流函数内部使用开始时间startTime、当前时间curTime与delay来计算剩余时间remaining,当remaining<=0时表示该执行事件处理函数了(保证了第一次触发事件就能立即执行事件处理函数和每隔delay时间执行一次事件处理函数)。如果还没到时间的话就设定在remaining时间后再触发 (保证了最后一次触发事件后还能再执行一次事件处理函数)。当然在remaining这段时间中如果又一次触发事件,那么会取消当前的计时器,并重新计算一个remaining来判断当前状态。
21.Get和post不同
- Get是从服务器上获取数据,post是向服务器传送数据。
- 在客户端,get通过url提交数据,数据在url中可以看到,post方式,数据放在html header中提交
- 安全性问题
- ? Get提交数据最多只能有1024字节,post没有限制
22.什么是csrf攻击
- Csrf(跨站点请求伪造) 攻击者在用户已经登录目标网站之后,诱使用户访问一个攻击页面,利用目标网站对用户的信任,以用户身份再攻击页面对目标网站发起伪造用户操作的请求,达到攻击目的
23.深拷贝,浅拷贝
https://juejin.cn/post/6844903929705136141#heading-1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hbkW7gBE-1631108961616)(C:\Users\吕晓亮\AppData\Roaming\Typora\typora-user-images\1630995676011.png)]
-
深拷贝: 将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-46xCGsxG-1631108961618)(C:\Users\吕晓亮\AppData\Roaming\Typora\typora-user-images\1630995730446.png)]
**乞丐版:**在不使用第三方库的情况下,我们想要深拷贝一个对象,用的最多的就是下面这个方法:
JSON.parse(JSON.stringify());
这种写法非常简单,而且可以应对大部分的应用场景,但是它还是有很大缺陷的,比如拷贝其他引用类型、拷贝函数、循环引用等情况。
基础版本:
如果是浅拷贝的话,我们很容易写出下面的代码:
function clone(target){
let cloneTarget = {};
for(const key in target){
cloneTarget[key] = target[key];
}
return cloneTarget;
}
创建一个新的对象,遍历需要克隆的对象,将需要克隆的属性一次添加到新对象上,返回。
如果深拷贝的话,考虑到我们要拷贝的对象是不知道有多少层深度的,我们可以用递归来解决问题,稍微改写上面的代码:
function clone(target) {
if (typeof target === 'object') {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
考虑数组:
- 在上面的版本中,我们的初始化结果只考虑了普通的
object ,下面我们只需要把初始化代码稍微一变,就可以兼容数组了:
module.exports = function clone(target) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
循环引用:
我们执行下面这样一个测试用例:
const target = {
field1: 1,
field2: undefined,
field3: {
child: 'child'
},
field4: [2, 4, 8]
};
target.target = target;
得到结果:Maximum call stack size exceeded
很明显,因为递归进入死循环导致栈内存溢出了。
原因就是上面的对象存在循环引用的情况,即对象的属性间接或直接的引用了自身的情况:
-
解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。 -
这个存储空间,需要可以存储key-value 形式的数据,且key 可以是一个引用类型,我们可以选择Map 这种数据结构:
function clone(target, map = new Map()) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
for (const key in target) {
cloneTarget[key] = clone(target[key], map);
}
return cloneTarget;
} else {
return target;
}
};
三、Vue 相关
1.请简述你对vue的理解:
vue是一套构建用户界面的渐进式的自底向上增量开发的MVVM框架,核心是关注视图层,vue的核心是为了解决数据的绑定问题,为了开发大型单页面应用和组件化,所以vue的核心思想是数据驱动和组件化,这里也说一下MVVM思想,MVVM思想是 模型 视图 vm是v和m连接的桥梁,当模型层数据修改时,VM层会检测到,并通知视图层进行相应修改。
2.请简述vue的单向数据流
- 父级prop的更新会向下流动到子组件中,每次父组件发生更新,子组件所有的prop都会刷新为最新的值
- 数据从父组件传递给子组件,只能单向绑定,子组件内部不能直接修改父组件传递过来的数据,(可以使用data和computed解决)
3.Vue 修饰符
- .lazy 改变后触发,光标离开input输入框的时候值才会改变
- .number 将输出字符串转为number类型
- .trim 自动过滤用户输入的首尾空格
-
事件修饰符: .stop 阻止点击事件冒泡,相当于原生js中的event.stopPropagation() .prevent 防止执行预设的行为,相当于原生js中event.preventDefault() .capture 添加事件侦听器时使用事件捕获模式,就是谁有该事件修饰符,就先触发谁 .self 只会触发自己范围内的事件,不包括子元素 .once 只执行一次 -
键盘修饰符:
.enter 回车键 .tab 制表键 .esc返回键 .space 空格键
.up向上键 .down 向下键 .left向左建 .right向右键
- 系统修饰符:.ctrl .alt .shift .meta
5.v-text与{{}}与v-html区别
- {{}} 将数据解析为纯文本,不能显示输出html。
- v-html 可以渲染输出html
- v-text 将数据解析为纯文本,不能输出真正的html,与花括号的区别是在页面加载时不显示双花括号。
- v-text 指令:
- 作用:操作网页元素中的纯文本内容。{{}}是他的另外一种写法。
- v-text与{{}}区别:
- v-text与{{}}等价,{{}}叫模板插值,v-text叫指令。
- 有一点区别就是,在渲染的数据比较多的时候,可能会把大括号显示出来,俗称屏幕闪动:
6.v-on可以绑定多个方法吗?
- 可以 如果绑定多个事件,可以用键值对的形式 事件类型:事件名
- 如果绑定是多个相同事件,直接用逗号分隔就行
7.Vue循环的key作用
- Key值的存在保证了唯一性,Vue在执行时,会对节点进行检查,如果没有key值,那么vue检查到这里有dom节点,就会对内容清空并赋新值,如果有key值存在,那么会对新老节点进行对比,比较两者key是否相同,进行调换位置或删除操作。
8.什么是计算属性
-
计算属性是用来声明式的描述一个值依赖了其他的值,当它依赖的这个值发生改变时,就更新DOM。 -
当在模板中把数据绑定到一个计算属性上时,vue会在它依赖的任何值导致该计算属性改变时更新DOM -
每个计算属性都包括一个getter和setter,读取时触发getter,修改时触发setter
9.Vue单页面的优缺点
10.Vuex是什么?怎么使用?在那种场景下使用
- Vuex是一个专为vue.js应用程序开发的状态管理模式,通过创建一个集中的数据存储,方便程序中的所有组件进行访问,简单来说 vuex就是vue的状态管理工具。
- Vuex有五个属性 state getters mutations actions modules。
- State就是数据源存放地,对应一般vue对象的data,state里面存放的数据是响应式的,state数据发生改变,对应这个数据的组件也会发生改变 用this.$store.state.xxx调用
- Getters 相当于store的计算属性,主要是对state中数据的过滤,用this.$store.getters.xxx调用
- Mutations 处理数据逻辑的方法全部放在mutations中,当触发事件想改变state数据的时候使用mutations,用this.$store.commit调用,给这个方法添加一个参数,就是mutation的载荷(payload)
- Actions 异步操作数据,但是是通过mutation来操作 用this.$store.dispatch来触发,actions也支持载荷
- 使用场景:组件之间的状态,登录状态,加入购物车,音乐播放
Vuex使用流程:
- 下载vuex
- 在src文件夹下创建store以及index.js
- 引入vue和vuex, 使用vuex ,导出实例对象
- 在main.js中引入,在.vue文件中使用
方法的执行过程
- 在vue组件里面,通过dispatch来触发actions提交修改数据的操作,然后通过actions的commit触发mutations来修改数据,mutations接收到commit的请求,就会自动通过mutate来修改state,最后由store触发每一个调用它的组件的更新
Vuex怎么请求异步数据
- 首先在state中创建变量
- 然后在action中调用封装好的axios请求,异步接收数据,commit提交给mutations
- Mutations中改变state中的状态,将从action中获取到的值赋值给state
Vuex中action如何提交给mutation的
- Action函数接收一个与store实例具有相同方法和属性的context对象,可以调用context.commit提交一个mutation,或者通过context.state和context.getters获取state和getters
vuex的优势
- 优点:解决了非父子组件的通信,减少了ajax请求次数,有些可以直接从state中获取
- 缺点:刷新浏览器,vuex中的state会重新变为初始状态,解决办法是vuex-along,得配合计算属性和sessionstorage来实现
使用场景:
组件之间的状态,登录状态,加入购物车,音乐播放
11.vue中路由的跳转方式(声明式/编程式)
- Vue中路由跳转有两种,分别是声明式和编程式
- 用js方式进行跳转的叫编程式导航 this.$router.push()
- 用router-link进行跳转的叫声明式 router-view 路由出口,路由模板显示的位置
1.路由中name的属性有什么作用?
- 在router-link中使用name导航到对应路由
- 使用name导航的同时,给子路由传递参数
12.vue跨域的解决方式
1.后台更改header
2.使用jq提供jsonp
3.用http-proxy-middleware(配置代理服务器的中间件)
13.Vue的生命周期请简述
vue的生命周期就是vue实例创建到实例销毁的过程。期间会有8个钩子函数的调用。
- beforeCreate(创建实例)
- created(创建完成)、
- beforeMount(开始创建模板)
- mounted(创建完成)、
- beforeUpdate(开始更新)
- updated(更新完成)、
- beforeDestroy(开始销毁)
- destroyed(销毁完成)
Vue生命周期的作用
? 给了用户在不同阶段添加自己的代码的机会
? DOM渲染在那个生命周期阶段内完成
DOM渲染在mounted周期中就已经完成
14.vue路由的实现
15.Vue路由模式hash和history,简单讲一下
- Hash模式地址栏中有#,history没有,history模式下刷新,会出现404情况,需要后台配置
- 使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值
- 可以使用 hashchange 事件来监听 hash 值的变化
- HTML5 提供了 History API 来实现 URL 的变化。其中最主要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录。
16.Vue路由传参的两种方式,params和query方式与区别
- **动态路由也可以叫路由传参,**就是根据不同的选择在同一个组件渲染不同的内容
- 用法上:query用path引入,params用name引入**,**接收参数都是类似的,分别是this.
r
o
u
t
e
.
q
u
e
r
y
.
n
a
m
e
和
t
h
i
s
.
route.query.name和this.
route.query.name和this.route.params.name
- url展示上:params类似于post,query类似于get,也就是安全问题,params传值相对更安全点,query通过url传参,刷新页面还在,params刷新页面不在了
17.Vue数据绑定的几种方式
? 1.单向绑定 双大括号 {{}} html内字符串绑定
? 2.v-bind绑定 html属性绑定
? 3.双向绑定 v-model
? 4.一次性绑定 v-once 依赖于v-model
18.Vue注册一个全局组件
- Vue.componnet(“组件的名字”,{ template :”
组建的内容
“})
19.Vue的路由钩子函数/路由守卫有哪些
- 全局守卫:beforeEach(to,from,next)和afterEach(to,from)
- 路由独享守卫:beforeEnter
- 组件内的守卫:路由进入/更新/离开之前 beforeRouterEnter/update/leave
20.Elementui中的常用组件有哪些?请简述你经常使用的 并且他们的属性有哪些?
-
Container布局容器 外层容器 顶栏容器 侧边栏容器 主要内容容器 底栏容器 Dropdown 下拉菜单 下拉按钮 下拉菜单 下拉项 Table 表格 Tabs 标签页 Form 表单 Pagination 分页 Message 消息提示
21.Vue如何定义一个过滤器
22.对vue 中keep-alive的理解
-
概念:keep-alive是vue的内置组件,当它动态包裹组件时,会缓存不活动的组件实例,它自身不会渲染成一个DOM元素也不会出现在父组件链中 -
作用:在组件切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间以及性能消耗,提高用户体验。 -
生命周期函数:Activated在keep-alive组件激活时调用,deactivated在keep-alive组件停用时调用。
23.Vue生命周期一共几个阶段
- ? 创建 加载 更新 销毁
- 页面第一次加载会触发 beforecreate created beforemount mounted
- DOM渲染在mounted周期中就已经完成
24.Vue组件中的data为什么是函数
25.插槽
- 具名插槽和匿名插槽,作用域插槽,说白了就是在组件上的属性,可以在组件元素内使用,
- 可以在父组件中使用slot-scope从子组件获取数据
26.Watch请简述
- Watch的作用是监控一个值的变化,并调用因为变化需要执行的方法
27.计算属性与watch区别
- Computed watch 区别就是computed的**缓存功能,当无关数据数据改变时,不会重新计算,直接使用缓存中的值。**计算属性是用来声明式的描述一个值依赖了其他的值,当所依赖的值后者变量发生变化时,计算属性也跟着改变,
- Watch监听的是在data中定义的变量,当该变量变化时,会触发watch中的方法
28.Route与router区别
- router是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由包含了许多关键的对象和属性。
- route是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象,可以获取对应的name,path,params,query等
29.v-for与v-if优先级
- 首先不要把v-if与v-for用在同一个元素上,原因:v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。
30.Vue的双向绑定原理:
} } })
- 使用方法:<h1>{{表达式 | 过滤器}}</h1>
- 过滤器高级用法:可以指定参数,告诉过滤器按照参数进行数据的过滤
#### 22.对vue 中keep-alive的理解
- 概念:keep-alive是vue的内置组件,当它动态包裹组件时,会缓存不活动的组件实例,它自身不会渲染成一个DOM元素也不会出现在父组件链中
- 作用:在**组件切换过程中将状态保留在内存中,防止重复渲染DOM**,减少加载时间以及性能消耗,提高用户体验。
- 生命周期函数:Activated在keep-alive组件激活时调用,deactivated在keep-alive组件停用时调用。
#### 23.Vue生命周期一共几个阶段
- ? 创建 加载 更新 销毁
- 页面第一次加载会触发 beforecreate created beforemount mounted
- DOM渲染在mounted周期中就已经完成
#### 24.Vue组件中的data为什么是函数
- **Data**是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响
- 如果是引用类型(对象),当多个组件共用一个数据源时,一处数据改变,所有的组件数据都会改变,所以要利用函数通过return返回对象的拷贝,(返回一个新数据),让每个实例都有自己的作用域,相互不影响。
#### 25.插槽
- 具名插槽和匿名插槽,作用域插槽,说白了就是在组件上的属性,可以在组件元素内使用,
- **可以在父组件中使用slot-scope从子组件获取数据**
#### 26.Watch请简述
- Watch的作用是监控一个值的变化,并调用因为变化需要执行的方法
#### 27.计算属性与watch区别
- Computed watch 区别就是computed的**缓存功能,当无关数据数据改变时,不会重新计算,直接使用缓存中的值。**计算属性是用来声明式的描述一个值依赖了其他的值,当所依赖的值后者变量发生变化时,计算属性也跟着改变,
- Watch监听的是在data中定义的变量,当该变量变化时,会触发watch中的方法
#### 28.Route与router区别
- router是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由包含了许多关键的对象和属性。
- route是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象,可以获取对应的name,path,params,query等
#### 29.v-for与v-if优先级
- 首先不要把v-if与v-for用在同一个元素上,原因:v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。
#### 30.Vue的双向绑定原理:
|