1. 跨域是什么?如何解决跨域?
由于浏览器的同源策略导致,前端请求后台接口时,如果协议、域名、端口三者有一个不同则会产生跨域问题。
JSONP(前端)
借助于script的src没有跨域问题,利用src引入对应的函数的调用,函数的实参就是对应的数据。
CORS(后台)
后台可以去设置请求头,可以允许某个域名下的某种请求方式跨域请求。 一般情况下,在开发环境中,开启所有域名和请求方式,生产环境根据需求指定特定的域名。
代理
开发环境代理
基于vue的开发环境,我们可以去配置devServer里的proxy。根据我们后端提供的结构,进行对应的代理操作 vue.config.js
module.exports = {
devServer: {
proxy: '要代理的后台地址'
}
}
原接口 | 现地址 |
---|---|
http://localhost:3000/users | http://localhost:8080/users |
有些时候我们需要对特定前缀的接口进行代理
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
ws: true,
changeOrigin: true
},
'/foo': {
target: 'http://localhost:4000'
}
}
}
}
原接口 | 现地址 |
---|---|
http://localhost:3000/api/users | http://localhost:8080/api/users |
http://localhost:4000/foo/xxx | http://localhost:8080/foo/xxx |
如果后台在开发接口的过程中没有给我们添加公共前缀
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
ws: true,
changeOrigin: true,
pathRewrite: {
'^/api': '',
}
},
'/foo': {
target: 'http://localhost:4000',
ws: true,
changeOrigin: true,
pathRewrite: {
'^/foo': '/aaa',
}
}
}
}
}
原接口 | 现地址 |
---|---|
http://localhost:3000/users | http://localhost:8080/api/users |
http://localhost:4000/aaa/xxx | http://localhost:8080/foo/xxx |
生产环境
当开发完成后,没有服务了,就不能靠devServer的代理,我们可以使用nginx代理
server {
listen 90;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
// 代理配置 要让什么样的前缀代理到哪个地址
location /api {
proxy_pass http://localhost:3000;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
2. 异步的解决
回调函数
Promise
Generator
async/await 是上面的语法糖
基于Promise
3. Promise.all的作用是什么
可以在同时多个异步操作时,同时发生,等待最后一个结束才结束。只要有一个出错,则都获取不到
const request1 => () => axios.get()
const request2 => () => axios.get()
const request3 => () => axios.get()
const request4 => () => axios.get()
Promise.all([request1(), request2(), request3(), request4()]).then(res => {
// res中就包含了四个请求得到的结果
})
4. Promise.race()的作用是什么
可以在同时多个异步操作时,同时发生,第一个结束就结束
在一些异步处理中,我们想要设置超时时间的话,xhr对象可以调用xhr.abort()让请求结束,但是其他的没有
const asyncFn = () => new Promise(() => {
// 代码
})
Promise.race([asyncFn(), new Promise((resolve) => {
setTimeout(() => {
resolve()
}, 5000)
})])
5. 如果后台直接返回10万条数据,前端怎么优化
在项目中这个操作是不允许,一般我们是要求后台做分页的
前端在请求的过程中,如果后台没有提供对应的参数我们没有办法优化请求过程,前端能做的优化是前端渲染部分,不能直接把10w条数据直接渲染。可以每次只取其中的n条,渲染在页面上,做分页加载,或者滚动加载
在前端定义两个参数,一个page,一个是limit
根据这两个参数从10w条数据中,去得到对应的列表
for(let i = (page - 1) * limit; i < page * limit - 1; i++) {
10w条数据[i]
}
6. vue2数据响应式原理
(不兼容IE8)
响应式:当数据改变,页面自动渲染。
想要实现这个功能就要监听数据的改变
在Vue2里利用Object.defineProperty
可以给对象中的每个属性添加getter和setter方法。所谓的setter的目的,就是我们在设置数据时可以监听到数据的改变。监听到数据的改变后,就可以通知对应的watcher,watcher去调用页面的render函数,触发页面的渲染
Object.defineProperty
let name = "张三"
const stu = {
name
}
// 这样写不能监听数据的变化
// 可以利用Object.defineProperty
let name = "张三"
const stu = {}
Object.definProperty(stu, "name", {
set
})
7. Vue3的数据响应式原理
(Vue3不兼容IE)
Vue3中利用的proxy,给数据添加拦截,当我们要修改数据时,可以触发对应的set函数,所谓的setter的目的,就是我们在设置数据时可以监听到数据的改变。监听到数据的改变后,就可以通知对应的watcher,watcher去调用页面的render函数,触发页面的渲染
8. 动画如何强制开启GPU渲染
tansform中的属性是3D,随便一个属性用,一般情况下,使用这个
div {
transform: translateZ(0);
}
9. 深浅拷贝
{
obj: {
a: 1
}
}
//浅拷贝:是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象;浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。
//深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象;深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。
深拷贝、浅拷贝、赋值的区别
// 浅拷贝
let obj1 = {
name : '浪里行舟',
arr : [1,[2,3],4],
};
let obj3=shallowClone(obj1)
obj3.name = "阿浪";
obj3.arr[1] = [5,6,7] ; // 新旧对象还是共享同一块内存
// 这是个浅拷贝的方法
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
console.log('obj1',obj1) // obj1 { name: '浪里行舟', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj3',obj3) // obj3 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
// 对象赋值
let obj1 = {
name : '浪里行舟',
arr : [1,[2,3],4],
};
let obj2 = obj1;
obj2.name = "阿浪";
obj2.arr[1] =[5,6,7] ;
console.log('obj1',obj1) // obj1 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj2',obj2) // obj2 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
// 深拷贝
let obj1 = {
name : '浪里行舟',
arr : [1,[2,3],4],
};
let obj4=deepClone(obj1)
obj4.name = "阿浪";
obj4.arr[1] = [5,6,7] ; // 新对象跟原对象不共享内存
// 这是个深拷贝的方法
function deepClone(obj) {
if (obj === null) return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== "object") return obj;
let cloneObj = new obj.constructor();
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
console.log('obj1',obj1) // obj1 { name: '浪里行舟', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4',obj4) // obj4 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
深拷贝浅拷贝实现方法
浅拷贝
- 1.
Object.assign()
- 2.
函数库lodash的_.clone方法
- 3.展开运算符...
深拷贝
-
1.函数库
lodash
的_.cloneDeep
方法 -
2.
jQuery.extend()
方法$.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝 var $ = require('jquery'); var obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] }; var obj2 = $.extend(true, {}, obj1); console.log(obj1.b.f === obj2.b.f); // false
-
3.手写递归方法
递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
深拷贝
var obj = {
name: "test",
desc: "origin",
sendobj: {
name: "test2",
desc: "origin2"
}
}
function copy(obj) {
let newobj = null // 接受拷贝的新对象
if (typeof (obj) == 'object' && typeof (obj) !== null) { // 判断是否是引用类型
newobj = obj instanceof Array ? [] : {} // 判断是数组还是对象
for (var i in obj) {
newobj[i] = copy(obj[i]) // 判断下一级是否还是引用类型
}
} else {
newobj = obj
}
return newobj
}
var obj1 = copy(obj)
obj1.sendobj.name = "change"
console.log(obj1);
console.log(obj);
console.log(obj1.sendobj.name)//change
console.log(obj.sendobj.name);//test2
10 排序算法
-
冒泡排序
依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
//从小到大排序
const arr = [5, 2, 7, 8, 34, 7, 39, 12, 56, 9, 1]
function Sort(arr) {
// 外层循环i控制比较的轮数
for (let i = 0; i < arr.length; i++) {
// 里层循环控制每一轮比较的次数j,arr[i] 只用跟其余的len - i个元素比较
for (let j = 1; j < arr.length - i; j++) {
// 若前一个元素"大于"后一个元素,则两者交换位置
if (arr[j - 1] > arr[j]) {
[arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
}
}
}
return arr
}
console.log(Sort(arr)) // [1, 2, 5, 7, 7, 8, 9, 12, 34, 39, 56]
-
插入排序
插入排序是指在待排序的元素中,假设前面n-1(其中n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序。
//将arr[]按升序排列,插入排序法
function insertSort(arr) {
for (let i = 1; i < arr.length; i++) {
//将arr[i]插入到arr[i-1],arr[i-2],arr[i-3]……之中
for (let j = i; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
[arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
}
}
}
return arr
}
11 查找算法
- 顺序查找
- 二分查找
12 什么是函数防抖和节流
函数防抖:在规定时间内多次执行代码,只执行最后一次(按钮频繁点击时,只让最后一次生效) 函数节流:定义一个执行频率时间,在执行的过程每隔对应的频率时间执行一次(表单验证中输入内容时、滚动条事件)
平时项目中我们会直接使用lodash
库,来解决对应的问题
// 防抖
function debounce(func, wait) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args)
}, wait);
}
}
// 节流函数
function throttle(fn, delay) {
// 记录上一次函数触发的时间
var lastTime = 0;
return function() {
// 记录当前函数触发的时间
var nowTime = Date.now();
if (nowTime - lastTime > delay) {
// 修正this指向问题
fn.call(this);
// 同步时间
lastTime = nowTime;
}
}
}
document.onscroll = throttle(function() { console.log('scroll事件触发' + Date.now()) }, 200)
13 你在项目中常用的ES6的功能有哪些
-
扩展运算 ...
-
let const
-
箭头函数
-
解构
-
Promise async/await
-
数组的一些方法
-
对象新写法
结合上文的解构赋值,这里的代码会其实是声明了x,y,z变量,因为bar函数会返回一个对象,这个对象有x,y,z这3个属性,解构赋值会寻找等号右边表达式的x,y,z属性,找到后赋值给声明的x,y,z变量
- 模板字符串 ``
14 箭头函数和普通函数有什么区别
写法不同,可以更精简
this指向不同,箭头函数的this指向,指向箭头函数创建时所在的环境的this
btn.onclick = () => {
// this -> window
}
事件函数和对象中的方法不建议使用箭头函数
15 虚拟DOM
虚拟DOM就是用一个JS对象来模拟一个DOM对象的操作,最终虚拟DOM还是要被转换成真实DOM
虚拟DOM可以提升页面中列表修改时的DOM渲染性能,主要带来的好处是可以通过虚拟DOM渲染成所有的其他的颜色的UI
组件比如安卓 IOS
,赋予了js
开发原生APP
的能力。
16 Vuex是什么,你们在项目中如何使用vuex
Vuex
用来简化数据通信,是一个集中式的状态管理,用于存储我们页面中的数据
我们在使用Vuex时,基于路由组件为模块,我们在对应的模块中,去管理我们对应的页面的所有数据,数据存放在state,修改数据使用mutation,获取数据使用action
VueX的五个核心
state 存储对应的数据
mutations 修改state的数据
actions 异步获取数据然后commit给mutation
getters 从state派生出新的数据
modules 用于模块的划分
17 axios请求相关
所有请求都携带token怎么做
使用axios
的请求拦截器,在config中设置对应的headers.token为我们登录后拿到的token。
// 封装axios
import axios from "axios";
import { Message } from "element-ui";
import router from "../router";
// 通过create方法,创建一个新的axios对象
const instance = axios.create({
baseURL: "http://localhost:3000/", // 未来公共接口地址是什么,就写什么
timeout: 5000, // 设置超时时间
});
// 请求拦截器
instance.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers.authorization = token;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
instance.interceptors.response.use(
(response) => {
// 想这么写一定要和后台进行沟通
if (response.data.code === 200) {
// 请求成功 方便我们获取请求到的数据 不需要再res.data.data
if (response.headers["x-total-count"]) {
return {
data: response.data.data,
total: +response.headers["x-total-count"],
};
} else {
return response.data.data;
}
} else {
// 当接口中有错误时,直接显示错误信息,不需要在每次请求的时候都去判断错误,然后显示错误信息
Message.error(response.data.msg);
return Promise.reject(response.data.msg);
}
// return response
},
(error) => {
console.log([error]);
// 判断错误代码是不是401
if (error.response.status === 401) {
Message.error("token失效");
// 跳转到登录
router.push("/login");
}
return Promise.reject(error);
}
);
export default instance;
token放在localstorage里安全么?
不安全,但是后台也会对前端传递的token进行验证,不通过返回401
如何做统一的错误处理
利用响应拦截器,判断对应的错误代码,做出对应的相应错误判断
axios请求后数据会被放在res.data怎么快速访问data
-
解构 .then(({data}) => {})
-
在响应拦截器
axios.interceptors.response.use((response) => {
return response.data//这里
}, (err) => {
return Promise.reject(err)
})
18 JS事件循环机制EventLoop
或者js的执行机制是怎么样的?
一次弄懂Event Loop
EventLoop
,就像是一个银行叫号系统,负责去找到对应任务队列中的函数,然后放入执行栈中进行调用,任务队列分为宏任务和微任务。在执行过程中,某个宏任务执行结束后,然后查看是否有微任务,如果没有,则执行下一个宏任务,以此类推直到全部执行结束。
js是一个单线程、异步、非阻塞I/O模型、 event loop事件循环的执行机制
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。 同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。异步 任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程, 某个异步任务可以执行了,该任务才会进入主线程执行。
宏任务包含
script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)
微任务
Promise.then
Object.observe
MutationObserver
process.nextTick(Node.js 环境)
请写出下段代码的输出结果(js事件循环机制)
new Promise(resolve => {
console.log(1);
setTimeout(() => console.log(2), 0)
Promise.resolve().then(() => console.log(3))
resolve();
}).then(() => console.log(4))
console.log(5)
1 5 3 4 2
19 React 类组件和函数组件的区别
- 类组件有状态和生命周期, 函数式组件没有(现在可以利用hook来解决对应的问题)
- this指向的问题,所有的状态及函数的使用,都需要使用this.事件绑定中,this指向有问题,需要用.bind 函数式组件没有
- 类组件是个class 函数式组件是一个function
- 函数式组件渲染的速度更快
20 为什么vue和react在渲染列表都需要添加key
添加key是为了提高对列表操作的性能。
key的作用是用来优化虚拟DOM的diff算法的。在修改列表中某个值时,没有key的话,虚拟DOM需要把新的数据重新的生成虚拟DOM结构,然后替换到原先列表的位置。如果有key可以直接找到对比后不一样的虚拟节点进行修改。
21 http状态码
2XX 成功
· 200 OK,表示从客户端发来的请求在服务器端被正确处理
· 204 No content,表示请求成功,但响应报文不含实体的主体部分
· 206 Partial Content,进行范围请求
3XX 重定向
· 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL
· 302 found,临时性重定向,表示资源临时被分配了新的 URL
· 303 see other,表示资源存在着另一个 URL,应使用 GET 方法丁香获取资源
· 304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况
· 307 temporary redirect,临时重定向,和302含义相同
4XX 客户端错误
· 400 bad request,请求报文存在语法错误
· 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息
· 403 forbidden,表示对请求资源的访问被服务器拒绝
· 404 not found,表示在服务器上没有找到请求的资源
5XX 服务器错误
· 500 internal sever error,表示服务器端在执行请求时发生了错误
· 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求
22 为啥有的请求之前会自动有一个options请求
当我们的后台通过cors解决跨域时,发POST请求会遇到options请求。
先发一个options请求目的是为了确保接口可以正常请求,只有options请求成功了,才有继续发送post请求
23 什么是三次握手
TCP建立连接要经历三次握手
第一次
第一次握手:建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次
第二次握手:服务器收到syn包,必须确认客户端的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
第三次
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。 ****
24 什么是四次挥手
对于一个已经建立的连接,TCP使用改进的四次挥手来释放连接(使用一个带有FIN附加标记的报文段)。TCP关闭连接的步骤如下:
第一步,当主机A的应用程序通知TCP数据已经发送完毕时,TCP向主机B发送一个带有FIN附加标记的报文段(FIN表示英文finish)。
第二步,主机B收到这个FIN报文段之后,并不立即用FIN报文段回复主机A,而是先向主机A发送一个确认序号ACK,同时通知自己相应的应用程序:对方要求关闭连接(先发送ACK的目的是为了防止在这段时间内,对方重传FIN报文段)。
第三步,主机B的应用程序告诉TCP:我要彻底的关闭连接,TCP向主机A送一个FIN报文段。
第四步,主机A收到这个FIN报文段后,向主机B发送一个ACK表示连接彻底释放。
25 Vue和React路由的原理
都有两种模式 hash模式和history模式,让url改变,不改变访问的页面,通过url中内容变化,让js渲染不同的内容到页面上
hash模式就是改变#后面的内容,js检测到url的改变后,渲染不同的内容到页面上
history模式利用html5的hisotry.pushState让url改变,但是访问的页面不变,原理同上
26、例举出你所知的在VUE里动态绑定单个及多个class样式的方法?
<div :class="className"></div>
<div :class="{className: Boolean}"></div>
<div :class="{className: Boolean, className2: Boolean}"></div>
<div :class="['box', {className: Boolean}]"></div>
27、基本数据类型和引用数据类型的区别
Number String Boolean Null Undefined Symbol
原始类型存储在栈内存中,修改对应的值,值会被覆盖。
引用类型存储在堆内存中,在栈内存中只是一个地址。
28、typeof和instanceof的区别
前者检测类型,后者用于判断对应的构造函数是什么
typeof 在判断null 数组不能得到精确的答案,instanceOf可以获取到对应的结果。
Object.prototype.toString.call([])//tostring可以返回一个精确的类型
29、什么情况会造成修改vue里 数组或对象而页面没有更新 你是怎么解决的
对象中,如果我们要对对象进行遍历,显示到页面,这个时候对象中新增的属性,不会获取getter和setter方法,需要使用this.$set() 或者 Vue.set()方法来解决
30、不同的页面有不同的 title 用单页面应用可以解决吗? 怎么解决!
我们在路由守卫中,获取到当前的路由信息,从路由信息中获取对应的页面的标题,使用document.title = 标题
解决问题。需要我们手动的给每一个路由配置添加title。
31、组件中的data
为什么是个函数,而不是一个对象?
每个Vue组件都是一个实例,共享data的属性,当data中是引用数据类型时,修改一个data就会影响所有的数据变化。
32、cookie 、localstorage 、 sessionstrorage 之间有什么区别?
-
相同点:存储在客户端
-
不同点
-
与服务器交互: cookie 是网站为了标示用户身份而储存在用户本地终端上的数据(通常经过加密)
cookie 始终会在同源 http 请求头中携带(即使不需要),在浏览器和服务器间来回传递 sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存
-
存储大小: cookie 数据根据不同浏览器限制,大小一般不能超过 4k ,sessionStorage 和 localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或 更大
-
有效期时间: localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据 sessionStorage 数据在当前浏览器窗口关闭后自动删除 cookie 设置的cookie过期时间之前一直有效,与浏览器是否关闭无关
33、数组去重方法(filter、Arr.from+set、for循环+splice)
-
filter 遍历数组,过滤出一个符合条件的新数组
let newArr = arr.filter((item,index)=>{return item>4})//筛选出原数组中值大于4的元素 console.log(newArr);
-
Set是es6中提供的一种数据结构,它类似数组但与数组不同的是,它的值都是唯一的没有重复值.
- Set本质也是一个构造函数,因此在使用时需要new,同时Set可以接收一个数组(或者具有 iterable 接口的其他数据结构)作为参数用来初始化Set
- Set中的值可以是任意类型的,但必须不能重复
- Set的最大特点就是,里面的值都是唯一的,因此可以用来进行数组去重使用
- Set中认为NaN和NaN是同一个值,因此Set中只能有一个NaN值(但我们知道事实上NaN和NaN用于是不相等的)
- Set中两个对象永远是不相等的,即使键和值都是一样的
- Set也可以为字符串去重
- 在向Set添加值的时候不会发生类型转换
- Set 是可遍历的
let arr = [1,1,1,2,3,4,4,5,6,7,8,8,8] arr = Array.from(new Set(arr)) //arr = [...new Set(arr)] //输出结果:[1,2,3,4,5,6,7,8]
34、网页首屏有图片,网页加载出现长时间空白
- 压缩优化图片,减少大小
- 占位图
35、在登录注册密码加密操作
我们使用的md5加密,前端传递过去的内容是经过md5加密的,后台拿到数据库中也是加密的密码和我的密码进行比对,如果一样则登录成功
36、图片懒加载是如何实现的?
在图片img标签上添加data-set属性(名字自拟比如:data-src),然后在图片的默认src上存放一张占位图(可以是低像素的原图),然后当浏览器窗口滚动到视图范围判断一下,如果是的话就将src的地址换成data-set的地址,这样就实现图片的懒加载。
小优化:由于浏览器的滚动条滚动事件会频繁触发,我们可以使用节流函数来解决
总结:
- 拿到所有的图片 dom
- 遍历每个图片判断当前图片是否到了可视区范围内。
- 如果到了就设置图片的 src 属性。
- 绑定 window 的
scroll
事件,对其进行事件监听。
37、Vue中Computed和Watch的区别
- computed用来监控自己定义的变量,该变量不在data里面声明,直接在computed里面定义,然后就 可以在页面上进行双向数据绑定展示出结果或者用作其他处理,具有缓存特性(值如果不变化会复用)
- watch主要用于监控vue实例的变化,它监控的变量必须在data里面声明才可以,它可以监控一个 变量,也可以是一个对象,一般用于监控路由、input输入框的值特殊处理等等,它比较适合的场景是 一个数据影响多个数据,它不具有缓存性
- 计算属性不能执行异步任务,计算属性必须同步执行。
38、父子-子父-非父子通信方式
父->子:propos
子->父:$emit
非父子:事件总线 emit(用于创建发出的事件)
39、promise是什么?他有哪些作用?
是异步编程解决的一种方案,可以浅显的认为他就是个容器,里面存放着未来才会结束的事情的结果。同时Promise也是一个对象,可以从该对象获取异步操作的消息。可以解决回调层层嵌套的问题。
40、pc端登陆注册流程
41、BFC
官方:块级格式上下文,是Web页面的可视CSS
渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。浏览器对BFC
的限制规则是:
- 生成
BFC
元素的子元素会一个接一个的放置。 - 垂直方向上他们的起点是一个包含块的顶部,两个相邻子元素之间的垂直距离取决于元素的margin特性。在
BFC
-- 中相邻的块级元素的外边距会折叠(Mastering margin collapsing)。 - 生成
BFC
元素的子元素中,每一个子元素左外边距与包含块的左边界相接触(对于从右到左的格式化,右外边距接触右边界),即使浮动元素也是如此(尽管子元素的内容区域会由于浮动而压缩),除非这个子元素也创建了一个新的BFC
(如它自身也是一个浮动元素)。
触发条件:
- 根元素,即
HTML
标签 - 浮动元素:
float
值为left、right
overflow
值不为visible
,为auto
、scroll
、hidden
display
值为inline-block
、table-cell
、table-caption
、table
、inline-table
、flex
、inline-flex
、grid
、inline-grid
- 定位元素:
position
值为absolute、fixed
我的理解:
内部的盒子会在垂直方向上一个接一个放置;垂直方向上的盒子间距margin
决定,但是同属于一个BFC
的两个盒子会发生margin
重叠;每个盒子左右外边距不会超出包含他的块;BFC
的区域不会与float
的元素区域重叠;计算高度时浮动元素也要计算在内;
- 解决
margin
重叠问题:给其中一个盒子添加float
利用规则(BFC
的区域不会与float
的元素区域重叠) - 利用
BFC
可以清除浮动:计算高度时浮动元素也要计算在内所以可以利用这一点清除浮动
42、一个页面从输入 URL 到页面加载显示完成,这个过程中都发生 了什么?
- 浏览器地址栏输入url
- 浏览器会先查看浏览器缓存--系统缓存--路由缓存,如有存在缓存,就直接显示。如果没有,接着第三步
- 域名解析(DNS)获取相应的ip
- 浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手
- 握手成功,浏览器向服务器发送http请求,请求数据包
- 服务器请求数据,将数据返回到浏览器
- 浏览器接收响应,读取页面内容,解析html源码,生成DOM树
- 解析css样式、浏览器渲染,js交互绑定多个域名,数量不限;
43、js模块化和组件是啥?
js模块化:具备特定功能的js文件,需要哪些功能就去拆分他并引入
组件:具备特定功能效果的代码集合
47、router 的区别是什么?
$route 是“路由信息对象”,包括 path,params
,hash,query,fullPath
,matched, name 等路由信息参数
router.go `方法
48、Vue路由传值的方式有哪几种?
Vue-router
传参可以分为两大类,分别是编程式的导航 router.push
和声明式的导航
1、router.push
1.1)字符串:直接传递路由地址,但是不能传递参数 this.$router.push("home")
对象:
1.2)命名路由 :这种方式传递参数,目标页面刷新会报错 this.$router.push({name:"news",params:{userId:123})
1.3)查询参数 :和 name 配对的式 params,path
配对的是 query this.$router.push({path:"/news',query:{uersId:123})
1.4)接收参数this.$route.query
2、声明式导航
2.1)字符串 <router-link to:"news"></router-link>
2.2) 命名路由<router-link to:"{name:'news',params:{userid:1111}}"></router-link>
2.3)查询参数<router-link to:"{name:'/news',query:{userid:1111}}"></router-link>
49、Vue 的 nextTick 的原理是什么?
Vue nextTick使用场景及实现原理 - 掘金 (juejin.cn)
1、为什么需要 nextTick
主要是处理我们再变更完数据以后,无法立刻拿到最新的DOM节点对象的问题。我们可以这样理解:vue执行完渲染后会执行this.nextTick()里面的callback函数。
2、理解原理前的准备 首先需要知道事件循环中宏任务和微任务这两个概念
2,1)常见的宏任务有:script, setTimeout, setInterval, setImmediate, I/O, UI rendering
2,2)常见的微任务有:process.nextTick(nodejs)
,Promise.then()
, MutationObserver
50、原型&原型链
原型: 在 JS
中,每当定义一个对象(函数)时,对象中都会包含一些预定义的属性。其中每个函数对象
都有一个prototype
属性,这个属性指向函数的原型对象
特点: JavaScript
对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
原型链:
51、导航解析流程
完整的导航解析流程:
- 导航被触发(/index=>/about)
- 在失活的组件(index)中调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在复用的组件中调用
beforeRouteUpdate
(如果有复用的话) - 调用路由独享守卫
beforeEnter
- 解析异步路由组件
- 在被激活的组件中调用
beforeRouteEnter
- 调用全局的
beforeResolve
守卫(全局解析守卫) - 导航被确认
- 调用全局后置守卫
afterEach
守卫 - 触发DOM更新
- 用创建好的组件实例调用
beforeRouteEnter
守卫中传给next的回调函数。
52、this指向
- 全局函数中,this指向window
- 作为对象的方法调用 this 指向调用对象
- 自定义构造函数中,this指向新的实例化对象(new 会改变 this 的指向)
- 事件绑定中this指向事件源
- 定时器函数中this指向window
53、Vue
生命周期共分为几个阶段?
Vue
实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载 Dom
→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue
的生命周期
54 路由守卫
路由守卫分为三大类:
1. 全局守卫:前置守卫:beforeEach
后置钩子:afterEach
2. 单个路由守卫:独享守卫:beforeEnter
3. 组件内部守卫:beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
所有的路由守卫都是三个参数:
to
: 要进入的目标路由(去哪儿)
from
: 要离开的路由(从哪来)
next
: 是否进行下一步(要不要继续)