前端面试官~你准备好了吗

前端面试~必过大全(含答案)-- 持续更新

背的最少、说的最好。你的面试我帮你,本博客持续优化改进,请关注收藏

一、HTML 相关

1.简述一下你对 HTML 语义化的理解?

用正确的标签做正确的事情。
html 语义化让页面的内容结构化,结构更清晰,便于对浏览器、搜索引擎解析;
即使在没有样式 CSS 情况下也以一种文档格式显示,并且是容易阅读的;
搜索引擎的爬虫也依赖于 HTML 标记来确定上下文和各个关键字的权重,利于 SEO;
使阅读源代码的人对网站更容易将网站分块,便于阅读维护理解。

2.标签上 title 与 alt 属性的区别是什么?

!important > 行内样式(比重1000)> ID 选择器(比重100) > 类选择器(比重10) 
> 标签(比重1) > 通配符 > 继承 > 浏览器默认属性

3.href 与 src?

href (Hypertext Reference)指定网络资源的位置,从而在当前元素或者当前文档和由当前属性定义的需要的锚点或
资源之间定义一个链接或者关系。(目的不是为了引用资源,而是为了建立联系,让当前标签能够链接到目标地址。)
src source(缩写),指向外部资源的位置,指向的内容将会应用到文档中当前标签所在位置。
href与src的区别
1、请求资源类型不同:href 指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的联系。
在请求 src 资源时会将其指向的资源下载并应用到文档中,比如 JavaScript 脚本,img 图片;
2、作用结果不同:href 用于在当前文档和引用资源之间确立联系;src 用于替换当前内容;
3、浏览器解析方式不同:当浏览器解析到src ,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,
图片和框架等也如此,类似于将所指向资源应用到当前内容。这也是为什么建议把 js 脚本放在底部而不是头部的原因。

4. iframe的优缺点?

优点:

解决加载缓慢的第三方内容如图标和广告等的加载问题
Security sandbox
并行加载脚本
缺点:

iframe会阻塞主页面的Onload事件
即时内容为空,加载也需要时间
没有语意

二、CSS 相关

1. 介绍一下 CSS 的盒子模型?

有两种, IE 盒子模型、W3C 盒子模型;
盒模型: 内容(content)、填充(padding)、边界(margin)、 边框(border);
区 别: IE 的 content 部分把 border 和 padding 计算了进去;

2. css 选择器优先级?

!important > 行内样式(比重1000)> ID 选择器(比重100) > 类选择器(比重10) > 标签(比重1)
 > 通配符 > 继承 > 浏览器默认属性

3. 垂直居中几种方式?

单行文本: line-height = height
图片: vertical-align: middle;
absolute 定位: top: 50%;left: 50%;transform: translate(-50%, -50%);
flex: display:flex;margin:auto

4. 简明说一下 CSS link 与 @import 的区别和用法?

link 是 XHTML 标签,除了加载CSS外,还可以定义 RSS 等其他事务;@import 属于 CSS 范畴,只能加载 CSS。
link 引用 CSS 时,在页面载入时同时加载;@import 需要页面网页完全载入以后加载。
link 是 XHTML 标签,无兼容问题;@import 是在 CSS2.1 提出的,低版本的浏览器不支持。
link 支持使用 Javascript 控制 DOM 去改变样式;而@import不支持。

5. rgba和opacity的透明效果有什么不同?

opacity 会继承父元素的 opacity 属性,而 RGBA 设置的元素的后代元素不会继承不透明属性。

6. display:none和visibility:hidden的区别?

display:none 隐藏对应的元素,在文档布局中不再给它分配空间,它各边的元素会合拢,就当他从来不存在。
visibility:hidden 隐藏对应的元素,但是在文档布局中仍保留原来的空间。

7. position的值, relative和absolute分别是相对于谁进行定位的?

relative:相对定位,相对于自己本身在正常文档流中的位置进行定位。
absolute:生成绝对定位,相对于最近一级定位不为static的父元素进行定位。
fixed: (老版本IE不支持)生成绝对定位,相对于浏览器窗口或者frame进行定位。
static:默认值,没有定位,元素出现在正常的文档流中。
sticky:生成粘性定位的元素,容器的位置根据正常文档流计算得出。

8. 画一条0.5px的直线?

考查的是css3的transform
height: 1px;
transform: scale(0.5);

9. calc, support, media各自的含义及用法?

@support 主要是用于检测浏览器是否支持CSS的某个属性,其实就是条件判断,如果支持某个属性,你可以写一套样式,
如果不支持某个属性,你也可以提供另外一套样式作为替补。
calc() 函数用于动态计算长度值。 calc()函数支持 “+”, “-”, “*”, “/” 运算;
@media 查询,你可以针对不同的媒体类型定义不同的样式。

10. 1rem、1em、1vh、1px各自代表的含义?

rem
rem是全部的长度都相对于根元素元素。通常做法是给html元素设置一个字体大小,然后其他元素的长度单位就为rem。
em
子元素字体大小的em是相对于父元素字体大小
元素的width/height/padding/margin用em的话是相对于该元素的font-size
vw/vh
全称是 Viewport Width 和 Viewport Height,视窗的宽度和高度,相当于 屏幕宽度和高度的 1%,不过,
处理宽度的时候%单位更合适,处理高度的 话 vh 单位更好。
px
px像素(Pixel)。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。
一般电脑的分辨率有{19201024}等不同的分辨率
19201024 前者是屏幕宽度总共有1920个像素,后者则是高度为1024个像素

11. 画一个三角形?

这属于简单的css考查,平时在用组件库的同时,也别忘了原生的css
.a {
    width: 0;
    height: 0;
    border-width: 100px;
    border-style: solid;
    border-color: transparent #0099CC transparent transparent;
    transform: rotate(90deg); /*顺时针旋转90°*/
}
<div class="a"></div>

三、JS 相关

1. JS 数据类型 ?

数据类型主要包括两部分:

基本数据类型: Undefined、Null、Boolean、Number 和 String
引用数据类型: Object (包括 Object 、Array 、Function)
ECMAScript 2015 新增:Symbol(创建后独一无二且不可变的数据类型 )

2. 判断一个值是什么类型有哪些方法?

typeof 运算符
instanceof 运算符
Object.prototype.toString 方法

3. null 和 undefined 的区别?

null 表示一个对象被定义了,值为“空值”;
undefined 表示不存在这个值。
(1)变量被声明了,但没有赋值时,就等于undefined。 (2) 调用函数时,应该提供的参数没有提供,
该参数等于undefined。 (3)对象没有赋值的属性,该属性的值为undefined。 (4)函数没有返回值时,默认返回undefined。

4. 怎么判断一个变量arr的话是否为数组(此题用 typeof 不行)?

arr instanceof Array
arr.constructor == Array
Object.prototype.toString.call(arr) == ‘[Object Array]’

5. “ ===”、“ ==”的区别?

==,当且仅当两个运算数相等时,它返回 true,即不检查数据类型
===,只有在无需类型转换运算数就相等的情况下,才返回 true,需要检查数据类型

6. “eval是做什么的?

它的功能是把对应的字符串解析成 JS 代码并运行;
应该避免使用 eval,不安全,非常耗性能(2次,一次解析成 js 语句,一次执行)。

7. 箭头函数有哪些特点?

不需要function关键字来创建函数
省略return关键字
改变this指向

8. var、let、const 区别?

var 存在变量提升。
let 只能在块级作用域内访问。
const 用来定义常量,必须初始化,不能修改(对象特殊)

9. new操作符具体干了什么呢?

1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
2、属性和方法被加入到 this 引用的对象中。
3、新创建的对象由 this 所引用,并且最后隐式的返回 this 。

10. JSON 的了解?

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小
{‘age’:‘12’, ‘name’:‘back’}

11. document.write 和 innerHTML 的区别?

document.write 只能重绘整个页面
innerHTML 可以重绘页面的一部分

12. ajax过程?

(1)创建XMLHttpRequest对象,也就是创建一个异步调用对象.
(2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.
(3)设置响应HTTP请求状态变化的函数.
(4)发送HTTP请求.
(5)获取异步调用返回的数据.
(6)使用JavaScript和DOM实现局部刷新.

13. 请解释一下 JavaScript 的同源策略?

概念:同源策略是客户端脚本(尤其是Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。
这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议。
指一段脚本只能读取来自同一来源的窗口和文档的属性。

14. 介绍一下闭包和闭包常用场景?

闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包常见方式,就是在一个函数的内部创建另一个函数
使用闭包主要为了设计私有的方法和变量,闭包的优点是可以避免变量的污染,缺点是闭包会常驻内存,会增大内存使用量,
使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念。
闭包有三个特性:
函数嵌套函数
函数内部可以引用外部的参数和变量
参数和变量不会被垃圾回收机制回收
应用场景,设置私有变量的方法
不适用场景:返回闭包的函数是个非常大的函数
闭包的缺点就是常驻内存,会增大内存使用量,使用不当会造成内存泄漏

15. javascript的内存(垃圾)回收机制?

垃圾回收器会每隔一段时间找出那些不再使用的内存,然后为其释放内存
一般使用标记清除方法(mark and sweep), 当变量进入环境标记为进入环境,离开环境标记为离开环境
垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),
在这些完成之后仍存在标记的就是要删除的变量了
还有引用计数方法(reference counting), 在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。
引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,
如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,
这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的, 
也就是说只要涉及BOM及DOM就会出现循环引用问题。

16. JavaScript原型,原型链 ? 有什么特点?

17. 用js递归的方式写1到100求和?

function add(num1, num2) {
	const num = num1 + num2;
    if(num2 === 100) {
        return num;
	} else {
	    return add(num, num2 + 1)
    }
}
var sum = add(1, 2);    

18. 事件队列(宏任务微任务)

可以分为微任务(micro task)队列和宏任务(macro task)队列。

微任务一般比宏任务先执行,并且微任务队列只有一个,宏任务队列可能有多个。另外我们常见的点击和键盘等事件也属于宏任务。

下面我们看一下常见宏任务和常见微任务。

常见宏任务:

setTimeout()
setInterval()
setImmediate()
常见微任务:

promise.then()、promise.catch()
new MutaionObserver()
process.nextTick()
微任务和宏任务的本质区别。

宏任务特征:有明确的异步任务需要执行和回调;需要其他异步线程支持。
微任务特征:没有明确的异步任务需要执行,只有回调;不需要其他异步线程支持。
setTimeout(function () {
    console.log("1");
}, 0);
async function async1() {
    console.log("2");
    const data = await async2();
    console.log("3");
    return data;
}
async function async2() {
    return new Promise((resolve) => {
        console.log("4");
        resolve("async2的结果");
    }).then((data) => {
        console.log("5");
        return data;
    });
}
async1().then((data) => {
    console.log("6");
    console.log(data);
});
new Promise(function (resolve) {
    console.log("7");
  resolve()
}).then(function () {
    console.log("8");
});

// 2 4 7 5 8 3 6 async2的结果 1

19. async/await

async 是一个通过异步执行并隐式返回 Promise 作为结果的函数。是Generator函数的语法糖,并对Generator函数进行了改进。
改进:

内置执行器,无需手动执行 next() 方法。
更好的语义
更广的适用性:co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,
可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
返回值是 Promise,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用。
async 隐式返回 Promise 作为结果的函数,那么可以简单理解为,await后面的函数执行完毕时,
await会产生一个微任务(Promise.then是微任务)。

20. JavaScript 是单线程的,浏览器是多进程的

每打开一个新网页就会创建一个渲染进程
渲染进程是多线程的
负责页面渲染的 GUI 渲染线程
负责JavaScript的执行的 JavaScript 引擎线程,
负责浏览器事件循环的事件触发线程,注意这不归 JavaScript 引擎线程管
负责定时器的定时触发器线程,setTimeout 中低于 4ms 的时间间隔算为4ms
负责XMLHttpRequest的异步 http 请求线程
GUI 渲染线程与 JavaScript 引擎线程是互斥的
单线程JavaScript是因为避免 DOM 渲染的冲突,web worker 支持多线程,但是 web worker 不能访问 window 对象,document 对象等。

四、HTML5&CSS3相关

1. HTML5、CSS3 里面都新增了那些新特性?

HTML5

新的语义标签
article 独立的内容。
aside 侧边栏。
header 头部。
nav 导航。
section 文档中的节。
footer 页脚。
画布(Canvas) API
地理(Geolocation) API
本地离线存储 localStorage 长期存储数据,浏览器关闭后数据不丢失;
sessionStorage 的数据在浏览器关闭后自动删除
新的技术webworker, websocket, Geolocation
拖拽释放(Drag and drop) API
音频、视频API(audio,video)
表单控件,calendar、date、time、email、url、searc

CSS3

2d,3d变换
Transition, animation
媒体查询
新的单位(rem, vw,vh 等)
圆角(border-radius),阴影(box-shadow),对文字加特效(text-shadow),线性渐变(gradient)
,旋转(transform)transform:rotate(9deg) scale(0.85,0.90) translate(0px,-30px) skew(-9deg,0deg);
//旋转,缩放,定位,倾斜
rgba

2. BFC 是什么?

BFC 即 Block Formatting Contexts (块级格式化上下文),它属于普通流,
即:元素按照其在 HTML 中的先后位置至上而下布局,在这个过程中,
行内元素水平排列,直到当行被占满然后换行,块级元素则会被渲染为完整的一个新行,
除非另外指定,否则所有元素默认都是普通流定位,也可以说,普通流中元素的位置由该元素在 HTML 文档中的位置决定。
可以把 BFC 理解为一个封闭的大箱子,箱子内部的元素无论如何翻江倒海,都不会影响到外部。
只要元素满足下面任一条件即可触发 BFC 特性

body 根元素
浮动元素:float 除 none 以外的值
绝对定位元素:position (absolute、fixed)
display 为 inline-block、table-cells、flex
overflow 除了 visible 以外的值 (hidden、auto、scroll)

3. 常见兼容性问题?

浏览器默认的margin和padding不同。解决方案是加一个全局的*{margin:0;padding:0;}来统一。
Chrome 中文界面下默认会将小于 12px 的文本强制按照 12px 显示,
可通过加入 CSS 属性 -webkit-text-size-adjust: none; 解决.

五、Vue 相关

1. 谈谈你对MVVM开发模式的理解?

MVVM分为Model、View、ViewModel三者。
Model 代表数据模型,数据和业务逻辑都在Model层中定义;
View 代表UI视图,负责数据的展示;
ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;
Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。
因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。
这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom。

2. v-if 和 v-show 有什么区别?

v-if 是真正的条件渲染,会控制这个 DOM 节点的存在与否。因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。
当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。

3. 你使用过 Vuex 吗?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,
它包含着你的应用中大部分的状态 ( state )。

(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,
那么相应的组件也会相应地得到高效更新。
(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
主要包括以下几个模块:

State => 基本数据,定义了应用状态的数据结构,可以在这里设置默认的初始状态。
Getter => 从基本数据派生的数据,允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
Mutation => 是唯一更改 store 中状态的方法,且必须是同步函数。
Action => 像一个装饰器,包裹mutations,使之可以异步。用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
Module => 模块化Vuex,允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

4. 说说你对 SPA 单页面的理解,它的优缺点分别是什么?

SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页> 面加载完成,
SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 > HTML 内容的变换,UI 与用户的交互,
避免页面的重新加载。

优点:
用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
基于上面一点,SPA 相对对服务器压力小;
前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
缺点:
初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一> 加载,部分页面按需加载;
前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,
所> 有的页面切换需要自己建立堆栈管理;
SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

5. Class 与 Style 如何动态绑定?

Class 可以通过对象语法和数组语法进行动态绑定:

  • 对象语法:
<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>
data: {
	isActive: true,
  	hasError: false
}
  • 数组语法:
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}
  • 对象语法:
 <div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
 activeColor: 'red',
 fontSize: 30
}
  • 数组语法:
<div v-bind:style="[styleColor, styleSize]"></div>
data: {
 styleColor: {
    color: 'red'
  },
 styleSize:{
    fontSize:'23px'
 }
}

6. 怎样理解 Vue 的单向数据流?

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。
这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。

7. computed 和 watch 的区别和运用的场景?

computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,
下一次获取 computed 的值时才会重新计算 computed 的值;
watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
运用场景:
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性
,避免每次获取值时,都要重新计算;
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),
限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

8. 直接给一个数组项赋值,Vue 能检测到变化吗?

由于 JavaScript 的限制,Vue 不能检测到以下数组的变动:

当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:vm.items.length = newLength
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

9. 谈谈你对 Vue 生命周期的理解?

Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,
我们称这是 Vue 的生命周期。

10. Vue 的父组件和子组件生命周期钩子函数执行顺序?

Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:

加载渲染过程 :
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 
子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
子组件更新过程 :
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
父组件更新过程 :
父 beforeUpdate -> 父 updated
销毁过程 :
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

11. 父组件可以监听到子组件的生命周期吗?

比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:
// Parent.vue
<Child @mounted="doSomething"/>

// Child.vue
mounted() {
  this.$emit("mounted");
}
以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:
//  Parent.vue
<Child @hook:mounted="doSomething" ></Child>

doSomething() {
   console.log('父组件监听到 mounted 钩子函数 ...');
},

//  Child.vue
mounted(){
   console.log('子组件触发 mounted 钩子函数 ...');
},    

// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...

12. 谈谈你对 keep-alive 的了解?

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:

一般结合路由和动态组件一起使用,用于缓存组件;
提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,
exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,
触发钩子函数 deactivated。

13. 组件中 data 为什么是一个函数?

  • 为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?
因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,
子组件中的 data 属性值会相互影响,
如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响
;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。

14. v-model 的原理?

我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,
我们知道 v-model 本质上不过是语法糖,
v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

text 和 textarea 元素使用 value 属性和 input 事件;
checkbox 和 radio 使用 checked 属性和 change 事件;
select 字段将 value 作为 prop 并将 change 作为事件。
父组件:
<ModelChild v-model="message"></ModelChild>

子组件:
<div>{{value}}</div>

props:{
    value: String
},
methods: {
  test1(){
     this.$emit('input', '小红')
  },
},

15. Vue 组件间通信有哪几种方式?

Vue 组件间通信是面试常考的知识点之一,这题有点类似于开放题,你回答出越多方法当然越加分,
表明你对 Vue 掌握的越熟练。

Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,下面我们分别介绍每种通信方式且
会说明此种方法可适用于哪类组件间通信。
(1)props / $emit 适用 父子组件通信

这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。
(2)ref 与 $parent / $children适用 父子组件通信

ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
$parent / $children:访问父 / 子实例
(3)EventBus ($emit / $on)适用于 父子、隔代、兄弟组件通信

这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,
包括父子、隔代、兄弟组件。
(4)$attrs/$listeners适用于 隔代组件通信

$attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,
这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
(5)provide / inject适用于 隔代组件通信

祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题
,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
(6)Vuex适用于 父子、隔代、兄弟组件通信

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器
,它包含着你的应用中大部分的状态 ( state )。
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

16. 使用过 Vue SSR 吗?说说 SSR?

Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,
也可以将同一个组件渲染为服务端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
即:SSR大致的意思就是vue在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的html 片段直接返回
给客户端这个过程就叫做服务端渲染。
服务端渲染 SSR 的优缺点如下:

(1)服务端渲染的优点:
更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,
所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中)
,所以搜索引擎爬取工具可以抓取渲染好的页面;
更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,
文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,
所以 SSR 有更快的内容到达时间;
(2) 服务端渲染的缺点:
更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,
才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,
服务端渲染应用程序,需要处于 Node.js server 运行环境;
更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU
 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,
 并明智地采用缓存策略。

17. vue-router 路由模式有几种?

vue-router 有 3 种路由模式:hash、history、abstract,对应的源码如下所示:
switch (mode) {
  case 'history':
    this.history = new HTML5History(this, options.base)
    break
  case 'hash':
    this.history = new HashHistory(this, options.base, this.fallback)
    break
  case 'abstract':
    this.history = new AbstractHistory(this, options.base)
    break
  default:
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `invalid mode: ${mode}`)
    }
}
其中,3 种路由模式的说明如下:

hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;
history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;
abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.

18. 能说下 vue-router 中常用的 hash 和 history 路由模式实现原理吗?

(1)hash 模式的实现原理
早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,
它的 location.hash 的值为 ‘#search’:
https://www.word.com#search
hash 路由模式的实现主要是基于下面几个特性:

URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,
改变 URL 的 hash 值;
我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。
(2)history 模式的实现原理
HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState() 和 history.repalceState()。
这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。
唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
history 路由模式的实现主要基于存在下面几个特性:

pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。

六、React 相关

1.React.memo() 和 useMemo() 的用法是什么,有哪些区别?

都是react中性能优化的工具,用于避免不必要的组件渲染
React.memo()是一个高阶组件,用于包装一个组件,当组件中的props没有变化时,会使用之前的渲染结果,避免重新渲染
useMemo()是一个Hook,用于缓存计算结果,避免重复计算
区别:react.memo()用于组件渲染结果,useMemo()用于计算结果的缓存;
React.memo()可以避免不必要的组件渲染,useMemo()可以避免重复计算,提高计算效率

2.受控组件和非受控组件的区别?

受控组件:由react表单所控制的其值改变的方式称为受控组件
比如给input添加一个onChange事件,当input状态发生变化时候会触发onChange事件,从而达到更新组件,适用于修改
非受控组件:由原生的dom所控制,不受setState控制,和原生的html表单相似,既显示最新值,适用于添加,ref可以获取表单值

3.React组件通讯的方式有哪些说出最少三种?

父传子:在父组件中子组件标签上定义一个自定义属性,挂载要传递的数据,在子组件通过this.props接收这个数据,直接使用即可
子传父:在父组件的子组件标签上定义一个自定义方法,在子组件中通过this.props接收,传递相应的数据即可
非父子组件通信:context状态树
发布订阅者:在订阅者创建一个回调函数添加到数组中,发布者调用这个回调函数,传递相应的数据

4.什么是JSX,详细说明jsx语法?

Jsx是JavaScript的一种扩展语法,用于react架构中,格式类似于模板语言,完全由JavaScript内部实现
JavaScript语法要求最外层只能有一个根节点;单标签必须闭合;<img/>标签一定要有alt属性,否则会警告;
class必须写成className,label标签中的for必须写成htmlFor

5.常用的hook都有哪些,如何使用?

useState让函数组件拥有状态,函数组件中用来管理数据的     
useEffect取代了生命周期,用来监听数据变化的
useContext跨组件共享数据
useMemo用来对复杂的结果进行缓存的
UseCallback性能优化,用来对方法进行缓存
useRef返回一个元素索引

6.React有哪些性能优化的方法?

使用懒加载组件,使用react fragment避免重复渲染,绑定事件要用bind指定函数,尽量不要使用内联样式,
ShouldComponentUpdate优化不必要的更新,图片懒加载,路由懒加载

7.React中的类组件和函数组件之间有什么区别,说出四点不同?

···
类组件有生命周期,函数组件没有生命周期
类组件有状态state,函数组件没有状态
类组件有this指向,函数组件没有this指向
类组件实例化后需要render方法返回出去,函数组件直接返回
This.bind只能在类组件中使用
···

8.如何让 useEffect 支持 async/await?

可以将async和await放在一个函数中,在useEffect中调用,这个函数使用自动执行函数,,在useEffect得回调函数内部定义一个async函数,
在useEffect得回调函数参数外定义一个await函数,在调用函数中执行
还也可以在useEffect中定义一个方法,在方法中使用async/await

9.Context状态树的执行流程?

1在根组件通过createContext创建一个状态树数据
2,在根组件中通过provider包裹render内的标签或者函数组件return中的标签内容,在标签上加上一个value属性,给其赋值
3,在子组件通过customer进行包裹标签内容,通过return返回对应根组件定义的参数信息,或者通过static context定义一个静态的状态树信息
,然后通过customer获取父组件传递过来的数据给静态状态树赋值,并进行使用

10.React路由传参的方式及具体操作?

1通过history.push的方法传递参数,通过路由路径上/?Id=的方法静态路由传递参数,需要获取location的match参数来获取静态参数
2,动态参数,push方法的路由/1,在route路由表定义的时候,在路由后面加/id的形式,通过location的params中可以获取到动态参数信息
3,通过push方法的第二个参数,进行传递参数,传递一个对象和数组都是可以的,
是通过location的state中获取参数

11.说说你对Redux中间件的理解,常用的中间件有哪些?

Redux中间件是为了解决redux只能进行同步操作,不能进行异步操作,请求的信息不能从后端获取并使用等问题而出现的,
常用的中间件有 redux-thunk,redux-logger,redux-mome

12.为什么不能用数组下标来作为react组件中的key?

Key是react中虚拟dom进行更新的唯一标识符,如果key使用index,那么数组进行逆序删除,逆序添加的数据操作时
,数据请求将变得混乱,key也是为了数组的diff算法进行同级比较,如果key使用index,必然会出现数据顺序问题出错,
也就做不到降低数据复用率了

13.js内存泄漏的几种情况?

①意外的全局变量
②定时器没有及时清除
③闭包
④dom元素没有及时清理

14.react-redux和redux的区别?

17.①redux是直接通过暴露store到组件直接使用,react-redux需要引入Provider,使Provider里面的组件共享store的数据
②react-redux通过connect(mapxxx,mapxxx)(counterUI)将UI组件变成容器组件,
Redux直接用
③redux通过store.getState来获取数据,react-redux通过mapStateFromProps来获取
④redux通过store.dispatch({type:”xxx”,payload:{}})来派发事件,react-redux通过mapDispatchFromProps来直接使用

15.connect方法的作用?

①将UI组件变成容器组件
②连接react组件和store
③通过mapStateFromProps来获取数据
④mapDispatchFromProps来直接使用方法
⑤简化了redux的步骤使其在一个子组件里

16.react单页面应用程序的优缺点?

19.优点:
	①良好的优化体验
	②页面数据传递便捷	
	③刷新页面快,是局部刷新
缺点:
①首屏加载慢
②不利于SEO搜索引擎优化
③渲染慢,需要在ssr中定义好页面结构直接用

17.React 路由组件和组件的区别?

路由组件是拥有一个a标签的超链接属性的组件内容,也需要一个对应的path信息来确认路由是否正确,路由组件需要有一个
对应navlink标签进行跳转功能,路由组件有普通组件不具备的history,match,location三个获取页面url信息的方法
组件就是react中必备的,react中都是由组件构成的,组件名是需要大驼峰命名的,
组件可以传递参数,通过组件通信进行传递,组件可以通过props和state分别渲染不同的数据信息

18.React中路由模式BrowserRoute和HashRouter的区别,最少说出三点不同?

区别:1,BrowserRoute是历史路由模式,在路由上面不需要使用#来匹配路由内容
2,hashRouter是哈希路由模式,当你使用时,对于历史路由的方法,push,go,goback,replace,是不能直接使用的
3,在传递参数时,也有不同,BrowserRoute是可以通过/动态params传参,也可以通过state参数传递参数,
或者通过?Id=的方法静态传递参数,哈希路由模式不可以方便的传递参数,每一个路由都需要#

19.withRouter的作用是什么?

withRouter是一个高阶组件,就是函数嵌套函数的方式形式的,这是一个为了解决组件没有继承到父组件的路由方法而使用高阶组件,
使用方法就是,先引入,在子组件导出位置进行外层包裹,这样,子组件也可以获取到路由的相关信息,调用方法了

20.React中state、props、ref三大核心属性的特点和使用方式?

State是组件内所拥有的,可以通过setState进行修改内容,也可以通过给子组件传递数据,传递过去的数据就是props了,
因为是受控组件,所以比较适合使用在修改的场景
Props是通过父组件传递过来的数据信息,有函数方法也有数据,不可以进行修改,
需要通过调用props中的函数方法进行传递参数,让父组件执行修改命令等
Ref是获取一个表单对象,在需要绑定的标签内使用,获取对应事件对象的具体内容信息,可以用来对信息的添加

21.虚拟dom一定比真实dom快吗,为什么?

不一定,虚拟dom的首屏加载速度是比较缓慢的,
在react挂载阶段时就渲染了真实dom,真实dom的速度不慢,
但是真实dom在加载大量数据信息的时候,是不如虚拟dom的加载速度的,
虚拟dom需要更新部分dom信息,而不是全部更新,
在更新全部信息时,真实dom会比虚拟dom快
在更新局部dom时,虚拟dom会比真实dom快

七、手写题(数组相关)

1.手写push方法

Array.prototype.push = Array.prototype.push || function() {
	for(var i = 0; i < arguments.length; i++) {
	  // 将传递的参数每次都追加到数组的最后面
	  this[this.length] = arguments[i]
	}
	// 返回数组长度
	return this.length;
}

2.手写pop方法

Array.prototype.pop = Array.prototype.pop || function() {
	// 如果数组为空,直接return;
	if(this.length === 0) return;
	// 先保存最后一个元素
	var lastItem = this[this.length - 1];
	// 通过设置数组的length属性来实现删除元素的效果
	this.length = this.length - 1;
	return lastItem;
}

3.手写unshift方法

Array.prototype.unshift = Array.prototype.unshift || function() {
	var argLen = arguments.length; // 获取实参列表的长度并保存
	var len = this.length + argLen; // unift完以后数组的长度 = 原数组的长度+实参列表的长度
	// 在这里采用倒着循环,目的是空出原数组的位置,然后将原数组中的元素后移
	for(var i = len - 1; i >= 0; i--) {
	  this[i] = this[i - argLen] // 将原数组元素后移
	  // 举例说明:假设原数组为[1, 2], 要插入的元素为a, b。经此操作原数组变成:
	  [undefined, undefined, 1, 2]
	}
	// 循环实参列表,将实参列表中的元素依次填入原数组的空白位置
	for(var i = 0; i < argLen; i++) {
	  this[i] = arguments[i]
	}
	// 返回数组长度
	return len;
}

4.手写shift方法

Array.prototype.shift = Array.prototype.shift || function() {
	var res = this[0]; // 保存数组的第0项的值
	var len = this.length; // 获取并保存数组的长度
	for(var i = 0; i < len; i++) {
	  if(i >= len - 1) { // 当下标是数组的长度-1时,直接退出循环。因为最后一项没必要赋值
	    break;
	  } else {
	    // 将数组的后一项赋值给前一项
	    this[i] = this[i + 1];
	  }
	}
	// 删除数组的最后一项
	this.length = len - 1;
	
	return res; // 返回数组的第0项
}

5.手写splice方法

Array.prototype.splice = Array.prototype.splice || function() {
	// 没有传递参数的时候,直接返回一个空数组
	if(arguments.length === 0) return [];
	
	var startIndex = arguments[0]; // 起始下标
	var delArr = []; // 删除的元素
	// 传递一个参数的时候
	if(arguments.length === 1) {
	 if(startIndex >= 0) { // 起始下标是正数的情况
	   // 保存删除的元素
	   for(var i = startIndex; i < this.length; i++) {
	     delArr[i - startIndex] = this[i];
	   }
	   this.length = startIndex;
	 } else { // 如果起始下标是负数的话,应该是从后面开始
	   for(var i = 0; i < Math.abs(startIndex); i++) {
	     delArr[i] = this[this.length + startIndex + i]
	   }
	   // 删除对应的元素
	   this.length = this.length + startIndex;
	 }
	}
	// 当传递两个以上参数的时候
	if(arguments.length >= 2) {
	 var num = arguments[1]; // 替换的元素个数
	 var argLen = arguments.length - 2; // 待替换的元素的个数
	 // 如果splice的第二个参数是负数,则返回空数组,不做任何处理
	 if(num < 0) return [];
	
	 for(var i = startIndex; i < startIndex + num; i++) {
	   // 先保存代替换的元素
	   delArr[i - startIndex] = this[i];
	   // 替换对应的元素
	   this[i] = arguments[i - startIndex + 2];
	 }
	 // 处理待替换元素个数大于替换元素个数的情况
	 if(argLen > num) {
	   // 提前保存可能会被替换的元素但是不应该被替换的元素。
	   // 例如:['a', 'b', 'c', 'd'],假设被替换的元素应该是a,b,但是实际传递了1, 2, 3 这三个元素。那么c, d就不应该被替换
	   var saveArr = [];
	   // 计算可能会被替换的元素的个数
	   var count = this.length - num - startIndex;
	   // 保存会被替换的元素的个数
	   for(var i = 0; i < count; i++) {
	     saveArr[i] = this[this.length - count + i];
	   }
	   // console.log(saveArr)
	   // 将数组中对应的元素个数进行替换
	   for(var i = startIndex; i < startIndex + argLen; i++) {
	     // 替换对应的元素
	     this[i] = arguments[i - startIndex + 2];
	   }
	   for(var i = 0; i < saveArr.length; i++) {
	     this[this.length] = saveArr[i];
	   }
	 }
	 // 处理待替换的元素个数小于替换的元素个数的情况
	 if(argLen < num) {
	   for(var i = 0; i < num - argLen; i++) {
	     // 循环数组中的元素
	     for(var j = 0; j < this.length; j++) {
	       // 将不是undefined的元素前置,是undefined的元素后置
	       this[j] == undefined && (this[j] = this[j + 1]);
	     }
	   }
	   // 删除是undefined的元素
	   this.length = this.length - (num - argLen);
	 }
	}
	return delArr;
}

6.手写indexOf方法

Array.prototype.indexOf = Array.prototype.indexOf || function(el, startIndex) {
	// 设置参数的默认值
	startIndex = startIndex || 0;
	var index = -1;
	if(startIndex >= 0) {
	  for(var i = startIndex; i < this.length; i++) {
	    this[i] === el && (index = i);
	  }
	}
	return index;
}

7.手写concat方法

Array.prototype.concat = Array.prototype.concat || function() {
	// 声明一个新数组
	var arr = [];
	// 将原数组的值拷贝到新数组
	for(var i = 0; i < this.length; i++) {
	  arr[i] = this[i];
	}
	if(arguments.length > 0) {
	  // 将传递的新数组依次拷贝到 arr 中
	  for(var i = 0; i < arguments.length; i++) {
	    // 判断传入的参数是否是一个数组
	    if(Object.prototype.toString.call(arguments[i]) === '[object Array]') {
	      for(var j = 0; j < arguments[i].length; j++) {
	        arr[arr.length] = arguments[i][j]
	      }
	    } else {
	      arr[arr.length] = arguments[i];
	    }
	  }
	}
	// 返回 arr 这个新数组
	return arr;
}

8.手写reverse方法

Array.prototype.reverse = function() {
	var result = []; // 声明一个新数组
	// 将原数组倒序放入新数组
	for(var i = this.length - 1; i >= 0; i--) {
	  result[result.length] = this[i];
	}
	// 修改原数组中的值
	for(var i = 0; i < result.length; i++) {
	  this[i] = result[i];
	}
	return result;
}

9.手写forEach方法

Array.prototype.forEach = Array.prototype.forEach || function(fn) {
	// this指向调用forEach方法的数组实例	
	for(var i = 0; i < this.length; i++) {
	  fn(this[i], i, this)
	}
}

10.手写map方法

Array.prototype.map = Array.prototype.map || function(fn) {
	// 声明一个新数组
	var arr = []
	for(var i = 0; i < this.length; i++) {
	  // fn 函数的执行结果即为调用map方法时传递的函数的返回值(即判断条件)
	  arr.push(fn(this[i], i, this))
	}
	return arr;
}

11.手写filter方法

Array.prototype.filter = Array.prototype.filter || function(fn) {
	var arr = [];
	for(var i = 0; i < this.length; i++) {
	  // fn 的执行结果即为调用filter方法时传递的函数的返回值(即为过滤条件)
	  fn(this[i], i, this) && arr.push(this[i])
	}
	return arr;
}

12.手写every方法

Array.prototype.every = Array.prototype.every || function(fn) {
	var flag = true;
	for(var i = 0; i < this.length; i++) {
	  // 思路:检查数组中的元素是否都满足条件,只要有一个不满足条件就将 flag 设置为false同时退出循环
	  if(!fn(this[i], i, this)) {
	    flag = false;
	    break;
	  }
	}
	// 返回flag,flag的最终结果即为every方法的最终执行结果
	return flag;
}

13.手写some方法

Array.prototype.some = Array.prototype.some || function(fn) {
	var flag = false;
	for(var i = 0; i < this.length; i++) {
	  if(fn(this[i], i, this)) {
	    flag = true;
	    break;
	  }
	}
	return flag;
}

14.手写reduce方法

Array.prototype.reduce = Array.prototype.reduce || function(fn, initialValue) {
	// 判断初始化的值是否存在,如果存在下标从0开始,不存在下标从1开始
	var startIndex = initialValue ? 0 : 1;
	// 如果初始化的值存在,回调函数的第一个参数为初始化的值,不存在初始化的值为数组的第一项
	var type = initialValue ? initialValue : this[0];
	for(var i = startIndex; i < this.length; i++) {
	  // 将返回值赋给回调函数的第一个参数
	  type = fn(type, this[i], i, this)
	}
	return type;
}

15.手写findIndex方法

Array.prototype.findIndex = function(fn) {
	var index = -1;
	for(var i = 0; i < this.length; i++) {
	  // 如果满足条件
	  if(fn(this[i], i, this)) {
	    // 将 i 赋值给 index
	    index = i;
	    // 退出循环
	    break;
	  };
	}
	return index;
}

16.手写flat方法

// 参数deep: 代表递归的层级
Array.prototype.flat = Array.prototype.flat || function(deep) {
	// 给deep设置默认值,如果传递deep择取传递的deep,如果没有传递则默认是1
	deep = deep == undefined ? 1 : deep;
	var arr = [];
	for(var i = 0; i < this.length; i++) {
	  // 判断数组中的元素是否是一个数组并且 deep 是否大于0
	  if(Object.prototype.toString.call(this[i]) === '[object Array]' && deep > 0) {
	    arr = arr.concat(this[i].flat(deep - 1))
	  } else {
	    // 如果不是数组则将其添加到 arr 中
	    arr.push(this[i])
	  }
	}
	return arr;
}

八、数组去重方法总结

1.不使用任何数组方法

function deleteDuplicates(arr) {
  // 声明一个新数组
  var result = [];
  // 外层循环负责遍历数组中的元素
  for(var i = 0; i < arr.length; i++) {
    // 内存循环负责两两比较
    for(var j = i + 1; j < arr.length; j++) {
      // 判断是否有相同的元素
      if(arr[i] === arr[j]) {
        // 如果有相同的元素则跳过当前元素
        i++;
        j = i + 1;
      }
    }
    // 如果没有相同的元素则加入新数组
    result[result.length] = arr[i]
  }
  // 改变原来的数组
  for(var i = 0; i < result.length; i++) {
    arr[i] = result[i];
  }
  // 删除无用元素
  arr.length = result.length;
}

2.sort 结合 splice方法进行数组去重

function deleteDuplicates(arr) {
  // 先调用sort方法对数组进行排序
  arr.sort();
  // 循环数组
  for (var i = 0; i < arr.length; i++) {
    // 判断排序后的数组前后两项是否相等,如果相等就进行递归删除
    if(arr[i] === arr[i + 1]) {
      arr.splice(i, 1);
      // 递归删除
      deleteDuplicates(arr)
    }
  }
}

3.利用indexOf或者include进行数组去重

function deleteDuplicates(arr) { 
  var result = [];
  for (var i = 0; i < arr.length; i++) {
    if (result.indexOf(arr[i]) == -1) {
      result.push(arr[i]);
    }
  }
  for (var i = 0; i < result.length; i++) {
    arr[i] = result[i];
  }
  arr.length = result.length;
}

4.利用filter结合indexOf方法进行数组去重

function deleteDuplicate(arr) {
  // 判断数组中的元素第一次出现的位置是否为index
  return arr.filter((item, index) => arr.indexOf(item) === index);
}

5.利用ES6新增的Set结构进行数组去重

function deleteDuplicates(arr) {
  return [...new Set(arr)];
}

6.利用ES6新增的Map结构并结合filter进行数组去重

function deleteDuplicates(arr) {
  const map = new Map();
  return arr.filter(item => !map.has(item) && map.set(item, true))
}

该方法传递第二参数key,可以修改成根据key对对象进行数组去重

function deleteDuplicates(arr, key) {
  const map = new Map();
  return arr.filter(item => !map.has(item[key]) && map.set(item[key], true))
}

注: 以上方法均没有考虑到数组元素是对象的情况,最后一种方法的第二种也不能完美实现对象数组去重

7.数组去重通用方法

// 封装数组去重方法
function deleteDuplicates(arr) {
  for(var i = 0; i < arr.length; i++) {
    for(var j = i + 1; j < arr.length; j++) {
      // 判断两个元素是否相等,如果相等则删除一个
      if(equals(arr[i], arr[j])) {
        arr.splice(j, 1);
        j--;
      }
    }
  }
}
// 封装判断元素是否是一个对象的方法
function isObject(obj) {
  return typeof obj === 'object' && obj!== null;
}
// 封装对两个元素进行深度比较的方法
function equals(val, val2) {
  // 如果传递的不是对象,直接判断
  if(!isObject(val) || !isObject(val2)) return val === val2;
  var arrKeys = [];
  var arrKeys2 = [];
  // 循环对象,将对象的key值添加到数组中
  for(var key in val) {
    val.hasOwnProperty(key) && arrKeys.push(key);
  }
  for(var key in val2) {
    val2.hasOwnProperty(key) && arrKeys2.push(key);
  }
  // 如果传递的是对象,先比较其长度是否相等
  if(arrKeys.length!== arrKeys2.length) return false;
  // 循环数组中的元素,递归判断两个元素是否相等
  for(var i = 0; i < arrKeys.length; i++) {
    var key = arrKeys[i];
    // 递归判断两个元素是否相等,如果不相等则返回false
    if(!equals(val[key], val2[key])) return false;
  }
  return true;
}

九、uniapp相关

1. 发布消息,如何实现消息的实时推送,uniapp中websocket,后端实现方式?

十、数组排序基本算法

1.冒泡排序

function bubblingSort(arr) {
  // 外层循环负责循环的次数。由于最后一个一定是最大的数,所以最后一个可以不用排。因此循环的次数是length - 1
  for(var i = 0; i < arr.length - 1; i++) {
    // 内层循环负责两两比较
    for(var j = 0; j < arr.length - i - 1; j++) {
      // 如果发现前一个数比后一个数大,则交换位置
      if(arr[j] > arr[j + 1]) {
        var temp = arr[j]
        arr[j] = arr[j + 1]
        arr[j + 1] = temp
      }
    }
  }
  // 返回排好序的数组
  return arr;
}

2.选择排序

function selectSort(arr) {
  for(var i = 0; i < arr.length - 1; i++) {
    // 先假设第i个位置的元素是最小元素,然后保存其下标
    var minIdx = i;
    // 内层循环负责两两比较
    for(var j = i + 1; j < arr.length; j++) {
      // 如果发现有比假设的最小元素小的元素
      if(arr[j] < arr[minIdx]) {
        // 则立即更新最小位置的下标
        minIdx = j;
      }
    }
    // 交换位置
    var temp = arr[i];
    arr[i] = arr[minIdx];
    arr[minIdx] = temp;
  }
  return arr;
}

3.快速排序

实现原理:先从数组中随便选择一个元素(通常是数组的第0项),作为基准元素。然后循环数组,把比基准元素小的放左边,比基准元素大的放右边

function fastSort(arr) {
  // 如果只有一个元素直接返回
  if(arr.length <= 1) return arr;
  // 假设第0个元素就是标杆
  var pivot = arr[0];
  // 声明两个数组比标杆元素小的放左边,比标杆元素大的放右边
  var left = [];
  var right = [];
  // 循环数组,比标杆元素小的放左边,比标杆元素大的放右边
  for(var i = 1; i < arr.length; i++) {
    if(arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  // 递归调用,把比标杆元素小的放左边,比标杆元素大的放右边
  var arr2 = fastSort(left).concat(pivot, fastSort(right));
  // 改变原有数组中的元素
  for(var i = 0; i < arr2.length; i++) {
    arr[i] = arr2[i];
  }
  return arr;
}

4.插入排序

实现原理:依次从数组中取出一个元素,然后从右向左依次查找,直到找到比它小的元素,将其插入到对应的位置

function insertSort(arr) {
  for (var i = 1; i < arr.length; i++) {
    // 依次从数组中取出一个元素
    var curValue = arr[i];
    // 指针依次向左移动一格(即从右向左依次查找)
    var j = i - 1;
    while(j >= 0 && curValue < arr[j]) {
      // 将当前找到的元素右移一格,空出当前的位置
      arr[j + 1] = arr[j];
      j--;
    }
    // 将当前找到的元素插入到正确的位置
    arr[j + 1] = curValue;
  }
  return arr;
}

4.归并排序

实现原理:先对数组进行拆分,拆分到每个数组中只有一个元素则停止拆分。然后在对拆分后数组按顺序进行合并

function mergeSort(arr) {
  // 如果数组的长度小于等于1,则直接返回
  if(arr.length <= 1) return arr;
  
  // 将数组从中间进行拆分
  var mid = Math.floor(arr.length / 2);
  // 递归拆分左边数组
  var left = mergeSort(arr.slice(0, mid));
  // 递归拆分右边数组
  var right = mergeSort(arr.slice(mid));

  // 递归进行拆分合并
  var arr2 = merge(mergeSort(left), mergeSort(right));
  // 修改原数组
  for(var i = 0; i < arr2.length; i++) {
    arr[i] = arr2[i];
  }
  return arr;
}

// 合并数组方法
function merge(left, right) {
  // 先声明一个空数组
  var result = [];
  // 不断的从拆分的数组中取元素进行比较,直到其中一边没有元素为止
  while(left.length > 0 && right.length > 0) {
    // 对拆分后的数组元素进行比较,小的放左边,大的放右边
    if(left[0] <= right[0]) {
      result.push(left.shift());
    } else {
      result.push(right.shift());
    }
  }
  // 将剩余的数组元素添加到结果数组(因为比较完以后有可能左边会有剩余元素或者右边会有剩余元素)
  return result.concat(left, right);
}

十一、手写节流和防抖函数

1.手写节流函数

  • 原理:不管事件触发频率多⾼,只在单位时间内执⾏㇐次。(频繁触发,还是按照时间间隔执⾏)
  • 应用场景:
  1. ⿏标不断点击触发,mousedown(单位时间内只触发㇐次)
  2. 监听滚动事件,⽐如是否滑到底部⾃动加载更多,⽤throttle 来判断
  • 节流实现:
    定时器版:第一次不执行,最后一次执行
    时间戳版:第一次执行,最后一次不执行
// 定时器版
function throttle(fn, delay) {
  delay = delay || 500;
  var timer = null;
  return function() {
    var args = [];
    var self = this;
    if(!timer) {
      timer = setTimeout(function() {
        fn.apply(self, args);
        timer = null;
      }, delay);
    }
  }
}
function throttle(fn, delay) {
  delay = delay || 500;
  var startTime = 0;
  return function() {
    var self = this;
    var args = arguments;
    if(Date.now() - startTime > delay) {
      startTime = Date.now();
      fn.apply(self, args);
    }
  }
}

2.手写防抖函数

  • 原理:不管事件触发频率多⾼,㇐定在事件触发 n 秒后才执⾏,如果你在㇐个事件触发的 n 秒内⼜触发了这个事件,就以新的事件的时间为准, n 秒后才执⾏,总之,触发完事件 n 秒内不再触发事件, n 秒后再执⾏
  • 应用场景:
  1. 窗⼝⼤⼩变化,调整样式
  2. 搜索框,输⼊后 1000 毫秒搜索
  3. 表单验证,输⼊1000 毫秒后验证
  4. 频繁点击按钮,使⽤防抖避免重复提交请求
  • 防抖实现:
function debounce(fn, delay) {
  var timer = null;
  // 设置默认的延迟时间为500ms
  delay = delay || 500;
  return function() {
    // 这里的this指向为防抖函数执行时返回的函数的调用者。在这里要保存当前的this指向,因为在setTimeout中this会改变
    var self = this;
    var args = [];
    // 接收函数执行时传递的参数并保存到args数组中
    args = arguments;
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(self, args);
    }, delay);
  }
}

十二、高频面试题相关

1. 微信小程序的支付流程?

2. 一键登录具体做了那些事?

3. 第三方登录流程,如何拉取第三方登录,服务器如何做?

4. 滑块登录的原生实现方式?

5. 音频大文件如何处理包括上传和下载,切片还是断点?

6. 人脸识别,如何调起浏览器的api?BOM对象

7. 后台管理系统的增删改查模块较多?考虑封装?

8. 大屏的数据是如何实时更新的?websocket? h5以前没有websocket如何处理?

9. 前端项目上线后为什么有404?

10. 场景有后台返回首页1000条数据,如何处理优化?

11. 短信的种类?

12. 高德地图关系判断?

13. 用户安全问题?

14. 登录是否返回token,怎么存储的?为什么选择这个方式?

15. 长时间不操作页面,重定向到登录页面怎么实现?

16. 对称加密和非对称加密的区别?

17. 直播的推流与拉流如何实现的?

18. wensocket的心跳检测机制?

19. 项目的优化

20. 视频分片上传以及断点续传

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 作为一个前端开发者,我认为自己具备良好的前端技术。我熟悉HTML、CSS、JavaScript等基础技术,并且掌握了常用的前端框架和工具,如React、Vue、Webpack等。我对前端开发的趋势和新技术保持着持续的学习和关注,以便更好地适应市场的需求。同时,我也注重代码的可维护性和可扩展性,采用模块化的开发方式来提高代码的复用性和可读性。总的来说,我相信我具备良好的前端技术能力,并且能够胜任相关的工作。 ### 回答2: 如果面试问我:“你觉得你前端技术怎么样?” 我会这样回答: 首先,我对我的前端技术非常自信。我有坚实的HTML、CSS和JavaScript基础,并且对前端开发的各种技术和工具都有一定的了解和应用经验。 我注重学习和保持对最新前端技术的敏感性,积极关注业界的发展趋势和新兴技术。我经常阅读相关的博客、文章和书籍,不断提升自己的技术水平。 我还熟悉常用的前端框架,例如React和Vue,能够灵活地运用它们来构建用户友好的界面和交互体验。 在项目开发中,我能够编写结构清晰、可维护和可扩展的代码。我了解前端性能优化的重要性,注重页面加载速度和响应性能方面的优化。 我对团队合作非常热衷,能够与设计师和后端开发人员紧密合作,高效地完成项目。我也有良好的沟通技巧,能够与非技术背景的人员进行有效的沟通。 虽然我的前端技术已经达到了一定的水平,但我相信学习永无止境。我会不断学习新的技术和提升自己的技能,以满足日益变化的前端行业需求。 总结起来,我认为我的前端技术扎实且有潜力,我准备在工作中充分发挥我的技术能力,与团队共同努力,创造出优秀的产品和用户体验。 ### 回答3: 面试问:你觉得你前端技术怎么样。 回答:非常感谢您的提问。我对自己的前端技术有一定的自信。在过去的工作经验中,我参与并负责了多个前端项目的开发,积累了一定的技术经验和实践经验。我熟悉HTML5、CSS3、JavaScript前端技术,并能熟练运用各类前端框架和工具,如Vue.js和React等。 我注重用户体验和界面设计,擅长将设计稿转化为高质量的页面代码,并能够根据产品需求进行页面优化和响应式开发。同时,我对前端性能优化也有一定的了解,能够通过优化网页加载速度和资源的使用来提升用户访问体验。 我还具备良好的团队合作能力和沟通能力。在团队中,我能够与设计师、后端工程师和产品经理紧密配合,理解并满足他们的需求。我也积极参加技术交流活动,不断学习新知识和掌握新技术,以保持对行业的敏感度和追求卓越的态度。 当然,前端技术是一个不断发展和涵盖多个领域的领域,我还有继续提升的空间。我会持续关注前端技术的最新动态,并愿意学习和应用新的技术来解决实际问题。我相信,通过我的努力和坚持,我能够在前端领域取得更好的成长和发展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值