2022前端重点面试题集锦【浓缩版】

604 阅读8分钟

1. 跨域是什么?如何解决跨域?

由于浏览器的同源策略导致,前端请求后台接口时,如果协议、域名、端口三者有一个不同则会产生跨域问题。

JSONP(前端)

借助于script的src没有跨域问题,利用src引入对应的函数的调用,函数的实参就是对应的数据。

CORS(后台)

后台可以去设置请求头,可以允许某个域名下的某种请求方式跨域请求。 一般情况下,在开发环境中,开启所有域名和请求方式,生产环境根据需求指定特定的域名。

代理

开发环境代理

基于vue的开发环境,我们可以去配置devServer里的proxy。根据我们后端提供的结构,进行对应的代理操作 vue.config.js

module.exports = {
  devServer: {
    proxy: '要代理的后台地址'
  }
}
原接口现地址
http://localhost:3000/usershttp://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/usershttp://localhost:8080/api/users
http://localhost:4000/foo/xxxhttp://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/usershttp://localhost:8080/api/users
http://localhost:4000/aaa/xxxhttp://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函数,触发页面的渲染

data

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的功能有哪些

近一万字的ES6语法知识点补充

  • 扩展运算 ...

  • let const

  • 箭头函数

  • 解构

  • Promise async/await

  • 数组的一些方法

  • 对象新写法

img

结合上文的解构赋值,这里的代码会其实是声明了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 环境)

img

请写出下段代码的输出结果(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可以返回一个精确的类型

image-20220303200944632

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的地址,这样就实现图片的懒加载。

小优化:由于浏览器的滚动条滚动事件会频繁触发,我们可以使用节流函数来解决

总结:

  1. 拿到所有的图片 dom
  2. 遍历每个图片判断当前图片是否到了可视区范围内。
  3. 如果到了就设置图片的 src 属性。
  4. 绑定 window 的 scroll 事件,对其进行事件监听。

37、Vue中Computed和Watch的区别

  • computed用来监控自己定义的变量,该变量不在data里面声明,直接在computed里面定义,然后就 可以在页面上进行双向数据绑定展示出结果或者用作其他处理,具有缓存特性(值如果不变化会复用)
  • watch主要用于监控vue实例的变化,它监控的变量必须在data里面声明才可以,它可以监控一个 变量,也可以是一个对象,一般用于监控路由、input输入框的值特殊处理等等,它比较适合的场景是 一个数据影响多个数据,它不具有缓存性
  • 计算属性不能执行异步任务,计算属性必须同步执行。

38、父子-子父-非父子通信方式

父->子:propos

子->父:$emit

非父子:事件总线 on(用于订阅)on(用于订阅 ) 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,为 autoscrollhidden
  • display值为 inline-blocktable-celltable-captiontableinline-tableflexinline-flexgridinline-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、routeroute 和router 的区别是什么?

$route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched, name 等路由信息参数

routerVueRouter的实例,相当于一个全局的路由器对象,里面含有很多属性和子对象,例如history对象,经常用的跳转链接就可以用this.router.push会往history栈中添加一个新的记录。返回上一个history也是使用router 为 `VueRouter` 的实例,相当于一个全局的路由器对象,里面含有很多属性和子对 象,例如 history 对象,经常用的跳转链接就可以用 `this.router.push` 会往 history 栈中添加一个 新的记录。返回上一个 history 也是使用`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对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

原型链

原型对象与实例关系.png

51、导航解析流程

完整的导航解析流程:

  1. 导航被触发(/index=>/about)
  2. 在失活的组件(index)中调用beforeRouteLeave守卫。
  3. 调用全局的beforeEach守卫。
  4. 在复用的组件中调用beforeRouteUpdate(如果有复用的话)
  5. 调用路由独享守卫beforeEnter
  6. 解析异步路由组件
  7. 在被激活的组件中调用beforeRouteEnter
  8. 调用全局的beforeResolve守卫(全局解析守卫)
  9. 导航被确认
  10. 调用全局后置守卫 afterEach守卫
  11. 触发DOM更新
  12. 用创建好的组件实例调用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: 是否进行下一步(要不要继续)