面试题汇总

js

1.闭包

1.什么是闭包

在函数中引用父级函数中的变量或者全局变量中的变量,叫闭包

2.闭包的优点

1.延长变量声明周期(因为函数被调用,所以变量不会被垃圾机制回收)
2.防止全局变量污染

3. 闭包的缺点

会引发内存泄漏(因为变量一直不能被回收)

2.原型链,终点为什么

构造函数的Prototype属性为一个对象,指构造函数生产的实例的原型
构造函数生产的实例会有一个__proto__ 属性,这个属性也指向该实例的原型
当读取实例的属性时,如果实例中不存在该属性,就会在实例的__proto__ 中去读取该属性
如果将实例A的原型指向另一个构造函数生产的实例B,那么实例A的__proto__ 属性指向的就是实例B,访问A中不存在的属性时,便会找A的__proto__ 中是否有该属性,也就是实例B中是否存在该属性,如果按着这种方法将实例B的构造函数的原型指向实例C,… 便形成一个原型链

所有对象的原型链最终指向为Object.prototype对象,Object.prototype.__proto__为null

3.var,let,const 区别

1.var有变量声明提升,let,const没有
2.let const有块级作用域,var没有
3.const声明常量,不能修改,let,var可以
4.全局作用域下,var声明的变量会作为window的一个属性,let,const不会

4.数组去重方法

1.借助新数组,判断新数组中没有该元素,就将这个元素添加到新数组中
2.借助对象的key值不能重复
3.filter方法
4.使用new Set(),Set中的元素不能重复

5.map,set,WeakMap,WeakSet

1.map中的key可以为任意数据类型,WeakMap中的key只能为对象,是对象的弱引用,在该对象的其他引用被删除,垃圾回收机制就会释放该对象占用的内存,WeakMap中会自动清除该key对应的value
2.由于weakMap成员不稳定,所以没有size属性,没有clear()方法,也不能遍历

1.set中的元素可以为任意类型,但WeakSet中的元素只能为对象,同理weakMap,是对象的弱引用,在该对象的其他引用被删除,垃圾回收机制就会释放该对象占用的内存,WeakSet中会自动清除元素
2.由于WeakSet成员不稳定,所以没有size属性,没有clear()方法,也不能遍历

6.深度遍历.广度遍历

深度遍历是该元素有后代的时候,先遍历后代
广度遍历是该元素的同级遍历完后再依次遍历后代

// 深度遍历 
// 递归
const depthTraverse = (root) => {
	let stack = [root.name]
	if(!root.children || !root.children.length){
		return stack
	}
	root.children.forEach(item => {
		stack = stack.concat(depthTraverse(item))
	})
	return stack
}

// while
const depthTraverse = (root) => {
	let stack = []
	let arr = [root]
	while(arr.length){
		const item = arr.shift()
		stack.push(item.name)
		if(item.children && item.children.length){
			// 先遍历第一个child,因为用的是unshift,所以需要保证第一个子元素在arr的第一个
			item.children.reverse().forEach(child => {
				arr.unshift(child)
			})
		}
	}
	return stack
}

// 广度遍历
const breadthTraverse = (root) => {
	let stack = []
	let arr = [root]
	while(arr.length){
		const item = arr.shift()
		if(item.children && item.children.length){
			// 先遍历同级,所以需要把child按顺序push到arr里面
			item.children.forEach(child => {
				arr.push(child)
			})
		}
	}
}

7.事件循环.

1.js是单线程语言,所以当执行全局js的过程中,遇到异步的任务就会把这个异步任务交给其他线程处理,处理完成后会将回调放到任务队列中,主线程执行完后会从任务队列中提取任务来执行
2.任务队列中又分为宏任务队列和微任务队列,宏任务指浏览器自带的一些异步任务(settimeout,setInterval,requestAnimationFrame),微任务有process.nextTick(),promise.then()等
3.异步任务队列执行的时候微任务的优先级高于宏任务

8.为什么微任务优先级高于宏任务

因为全局js本身就属于宏任务,所以当执行完全局js后,会优先提取微任务队列中的任务,
宏任务通常为浏览器的一些绘制工作,微任务是更小的任务,包含更新应用程序的状态等,所以要在浏览器进行重新渲染ui之前先改变状态,避免重绘重制

9.垃圾回收机制,如何触发

js的垃圾回收机制是为了防止内存泄漏(需要释放的内存得不到释放),垃圾回收机制就是不停的寻找不再使用的变量,并且释放其所占内存

变量声明周期:

当一个变量的生命周期结束之后,其所指向的内存会得到释放,js中有两种变量,全局变量和局部变量,全局变量会一直存在,直到浏览器关闭,局部变量的生命周期为该函数内,当函数结束调用,该变量的内存会被释放

垃圾回收方式

1.标记清除. 大部分浏览器使用的,当变量进入执行环境的时候,垃圾回收对其进行标记,当该变量离开执行环境后,垃圾回收再次对其进行标记,并进行回收

2.引用计数.

10.async,defer的区别

如果没有async或者defer,在解析html时遇到<script></script>会立即加载js文件,不会等待后续文档元素加载完成.

相同点:

加载文件时不阻塞页面渲染。

对于inline的script无效。

使用这两个属性的脚本中不能调用document.write方法。

有脚本的onload的事件回调

不同点

async会在加载完js后不论dom元素有没有加载完,都会执行js.如果要加载的js数量多的话,不保证js执行的先后顺序
defer会在加载完js后,如果dom元素没有加载完成,会先继续加载dom元素,直到加载完dom后才会执行js,如果有多个js加载完成,会严格按照书写顺序进行执行,可以在js中用DOMContentLoaded 方法

11.promise实现拦截器的原理

使用了promise的链式调用

12.ajax.axios.fetch的区别

Ajax

Ajax是对XMLHttpRequest的封装,现在演变为一种思想,指页面局部刷新,无需重载整个页面

Fetch

fetch是一个原生的api,和XMLHttpRequest是一样的,但是是基于promise的

Axios

axios是利用了xhr并且基于promise进行了二次封装的请求库,xhr只是axios的一个请求适配器,在node中还有一个http的适配器

1.ajax以及axios都是根据xhr封装的,fetch是原生api
2.ajax包含在jQuery中,引用代价比较大,axios是一个单独的请求库,相对轻量,fetch不需要引入
3.fetch只对网络请求报错,对400,500等需要额外处理
4.fetch 默认不会带cookie,需要添加配置项
5.fetch不支持请求中止,不支持超时控制,不能检测请求的进度,axios支持
6.fetch不能检测请求的进度,xhr可以
7.axios支持自动转换json,fetch需要多一步 res.json()
8.axios支持对request和response的拦截,fetch不支持
9.axios内置支持CSRF保护,fetch不支持

13.数据类型有哪些

基本数据类型:Boolean number string
引用数据类型:Array Object Map Set symbol
null undefined

14.typeof 和instanceof的区别

1.typeof 会返回一个变量的基本类型,instanceof返回的是布尔值,判断的是是否为某个构造函数的实例
2.instanceof会准确判断复杂引用数据类型,但是不能正确判断简单数据类型,除非使用构造函数生成的简单数据类型 const str = new String(‘123’)
3.typeof虽然可以判断基本数据类型(null 除外)但是引用数据类型中,除了Function类型以外,其他的无法判断

可以采用Object.prototype.toString方法来判断格式,统一返回“[object Xxx]”的字符串

15.单例模式

单例模式保证实例只有一个

function Constructor(){
	.....	
}
Constructor.instance = null
Constructor.getInstance = function(){
	if(!this.instance){
		this.instance = new Constructor()
	}
	return this.instance
}
class Constructor {
	static instance = null
	static getInstance(){
		if(!this.instance){
			this.instance = new Constructor()
		}
		return this.instance
	}
}

16.发布订阅模式与观察者模式

17.什么是迭代器

迭代器对象本质上,就是一个指针对象。通过指针对象的next(), 用来移动指针

class MakeInterator {
  nextIndex = 0;
  constructor(data) {
    this.data = data;
  }
  next() {
    let done = false;
    let value = this.data[this.nextIndex];
    this.nextIndex++;
    if (this.nextIndex === this.data.length) {
      done = true;
    }
    return {
      value,
      done,
    };
  }
}

const it = new MakeInterator([1, 2, 3, 4, 5]);

18. for…in 与for…of的区别

  1. 循环数组 for in 输出的是index,for of输出的是value
  2. 循环对象 for in 可以循环对象,输出的是key, for of 不能循环对象
  3. for of 不能遍历对象,只能遍历带有iterator接口的,例如Set,Map,String,Array

19.前端路由实现原理

  1. hash路由 hash改变不会触发浏览器发请求,使用hashChange事件监听
  2. history路由 浏览器history对象的pushState和replaceState两个API可以在不进行刷新的情况下,操作浏览器的历史纪录
    使用popstate 监听历史记录的变化

20.富文本编辑器原理

21.异步处理方法

22.有哪些微任务和宏任务

微任务:process.nextTick(),promise.then()
宏任务:settimeout,setInterval,requestAnimationFrame

23.防抖和节流

  1. 防抖会触发多次事件,节流只会触发一次事件
  2. 防抖在单位时间内频繁触发事件,只有最后一次生效
  3. 节流在单位时间内频繁触发事件,只生效一次(也就是只有第一次生效)
	
const debounce = (fn, time) => {
  let timer;

  return function () {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, time);
  };
};

const throttle = (fn, time) => {
  let timer;
  return function () {
    if (timer) {
      return;
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, time);
  };
};
	

24.JSON.stringify进行深拷贝的弊端

  1. 日期格式的会转变为字符串
  2. RegExp Error对象会变为null
  3. Function会丢失
  4. 如果对象中存在循环引用的情况也无法正确实现深拷贝,并且会导致死循环,最后抛出异常
  5. 只能序列化可枚举属性

25. bind call apply的区别

26. 数组splice slice区别

1.splice会修改原数组,slice不会
2.参数不同,splice(startIndex, length, insert), slice(startIndex, endIndex)

27. 数组push,shift,pop,unshift

push,数组末尾加元素, 返回加完元素的数组length
unshift 数组前加元素 返回新数组的length
pop 数组末尾删元素 返回被pop的元素
shift 数组前删元素,返回被删除的元素
这些方法没有参数并且会修改原数组

sort 数组排序,会修改原数组
concat 数组拼接,参数可以是多个元素,或者一个数组,返回新数组

28. 字符串substr substring slice方法

三个方法都返回截取出来的字符串
substring(startIndex, endIndex) startIndex不支持负数
substr(startIndex,length) startIndex 支持负数,-4 即代表后四位
slice(startIndex, endIndex) startIndex支持负数

29. Web Worker

js最初设计的时候防止多个线程同时渲染Dom会带来渲染冲突,所以js被设计为单线程。但是随着发展,js的能力远不止如此,当我们遇到需要大量计算的场景(视频解码,图像处理等)的时候,js线程往往会被长时间阻塞,甚至造成页面卡顿,影响用户体验。
web work 为js提供可以操作多线程的能力。
因为是独立的线程,Worker 线程与 js 主线程能够同时运行,互不阻塞。所以,在我们有大量运算任务时,可以把运算任务交给 Worker 线程去处理,当 Worker 线程计算完成,再把结果返回给 js 主线程。这样,js 主线程只用专注处理业务逻辑,不用耗费过多时间去处理大量复杂计算,从而减少了阻塞时间,也提高了运行效率,页面流畅度和用户体验自然而然也提高了。

css

1.BFC

BFC(Block Formatting Context)格式化上下文,是Web页面中盒模型布局的CSS渲染模式,指一个独立的渲染区域或者说是一个隔离的独立容器。

2. 水平垂直居中

浏览器

1.输入url到渲染页面的过程

1、浏览器的地址栏输入URL并按下回车。

2、浏览器查找当前URL的DNS缓存记录。

3、DNS解析URL对应的IP。

5、HTTP发起请求。

6、服务器处理请求,浏览器接收HTTP响应。

8、渲染页面,构建DOM树。
	1. 解析HTML,构建DOM树,同时加载css文件
	2. 解析css文件,与DOM树合并为renderObject树
	3. 布局,计算尺寸位置
	4. 绘制renderObject树

2.浏览器缓存有哪些

  1. localstorage
  2. sessionStorage
  3. cookie

3.http缓存

  1. 强制缓存
    第一次请求的时候,服务器可以设置Expire 或Cache-control响应头来表示这个文件是强缓存,之后如果再发起一次同样的请求,就会优先从disk和memory中获取资源,如果命中,则状态码为200(from disk cache) 或200(from memory cache),其中css html会被存在disk中,因为改动少,js,图片等会存在memory中
  2. 协商缓存
    协商缓存由Last-Modified/If-Modified-Since和ETag/If-None-Match这两对 Header 来控制
    在服务器在响应请求时,会通过Last-Modified告诉浏览器资源的最后修改时间。
    浏览器再次请求服务器的时候,请求头会包含Last-Modified字段,后面跟着在缓存中获得的最后修改时间。
    服务端收到此请求头发现有if-Modified-Since,则与被请求资源的最后修改时间进行对比,如果一致则返回 304 和响应报文头,浏览器只需要从缓存中获取信息即可。如果已经修改,那么开始传输响应一个整体,服务器返回:200 OK
    但是在服务器上经常会出现这种情况,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。为了解决这个问题,HTTP/1.1 推出了Etag。Etag 优先级高与Last-Modified。

4.http,https,http2的区别

https://www.jianshu.com/p/d3edc375467e
HTTP

  • http是超文本传输协议,使用TCP协议作为传输层的协议,保障数据传输的可靠性,定义了客户端和服务端交换报文的格式和方式。
  • 特点
    1.无状态协议
    1. 传输的数据都是明文
    2. 默认端口为80
  • 连接模式
    非持续的连接 是服务器必须为每一个请求的对象建立和维护 一个全新的连接,HTTP/1.0 以前使用的非持续的连接,但是可以在请求时,加上 Connection: keep-alive 来要求服务器不要关闭 TCP 连接
    持续连接 TCP 连接默认不关闭,可以被多个请求复用。采用持续连接的好处是可以避免每次建立 TCP 连接三次握手时所花费的时间。HTTP1.1 以后默认采用的是持续的连接。目前对于同一个域,大多数浏览器支持 同时建立 6 个持久连接。
  • 缺点
    HTTP1.1默认采用持续连接,也就是多个请求可以共用同一个TCP连接,但是TCP连接中请求的次序是固定的,服务器只有处理完一个请求的响应后才会进行下一个请求的处理。如果前面请求的响应特别慢的时候,就会造成许多请求排队等待的情况,这种情况被称为队头堵塞。

HTTP2

  • 二进制协议
    HTTP2是一个二进制协议,在HTTP1中,报文的头信息是文本,数据体可以是文本或者二进制,HTTP2则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为帧
  • 多路复用
  • HTTP2实现了多路复用,仍然使用TCP连接,但是在一个连接里可以发送多个请求或回应,而且不用按照顺序依次发送,这样就避免了队头堵塞的问题
  • 数据流
    HTTP2采用了数据流的概念,因为HTTP2的数据包是不按顺序来发送的,同一个连接里连续的数据包可能属于不同的请求,因此必须对数据包进行标记,来指示属于哪个请求,HTTP2将每个请求或回应的所有数据包称为一个数据流,每个数据流都有独一无二的编号,数据包发送的时候,都必须标记数据流ID,用来区分是属于哪个数据流的
  • 服务器推送
    HTTP2允许服务器未经请求,直接向客户端发送资源,
    HTTP2下服务器推送的是静态资源,和websocket以及使用SSE等方式向客户端发送即时数据的推送是不同的
  • 头信息压缩
    由于HTTP1协议是无状态的,所以每次请求都要附上所有信息,所以很多字段都是重复的,如Cookie和User Agent,一模一样的内容,每次请求都必须附带,很影响带宽和速度
    HTTP2对这一点做了优化,引入了头部信息压缩,一方面,头信息可以进行压缩后再发送,另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引,以后就不用再发送同样字段了,只发送索引

HTTPS

  • HTTPS 超文本传输安全协议,是基于HTTP协议的,不过会使用TSL/SSL来对数据进行加密,使用TSL/SSL协议,所有信息都是加密的,第三方无法窃听,并且还提供了一种校验机制,信息一旦被篡改,通信的双方会立刻发现,还配备了身份证书,防止身份被冒充
  • 特点
    HTTPS 协议是 HTTP + SSL 协议构建成的,安全性高于HTTP
    默认使用443端口
  • HTTPS 协议的工作原理
  1. 客户端使用 HTTPS 的 url 访问 web 服务器,要求与服务器建立 SLL 连接
  2. web 服务器收到客户端请求后,会将网站的证书(包含公钥)传送一份给客户端
  3. 客户端收到网站证书后会检查证书的颁发机构以及过期时间,如果没有问题就随机产生一个秘钥
  4. 客户端利用公钥将会话秘钥加密,并传送给服务端,服务端利用自己的私钥解密出会话秘钥
  5. 之后服务器与客户端使用秘钥加密传输
  • 优点
    传输稳定
    传输完整
    传输安全
    利于SEO
  • 缺点
  1. 费时,HTTPS 握手阶段比较费时,会使页面加载时间延长 50%,增加 10%~20%的耗电
  2. 费性能,HTTPS 缓存不如 HTTP 高效,会增加数据开销
  3. 费钱,SSL 证书( ca 证书)也需要钱,功能越强大的证书费用越高
  4. SSL 证书需要绑定 IP,不能在同一个 IP 上绑定多个域名,IPV4 资源支持不了这种消耗

5.get,post的区别

  1. 功能不同
    get是从服务器上获取数据。
    post是向服务器传送数据。
  2. 参数传递方式不同
    get是把参数数据队列加到URL中,值和表单内各个字段一一对应,在URL中可以看到
    post是参数在payload中的
  3. 后端取值方式不同
  4. 传参数据量大小不同
    get传送的数据量较小,不能大于2KB
    post传送的数据量较大,一般被默认为不受限制
  5. 安全性不同
    GET 安全性较低
    POST 安全性较高

6.cookie与session的区别

  1. Cookie是客户端(浏览器)存储数据的一种机制,键值对结构,可以存储身份信息,也是可以存储关键的信息,都是程序员自定义
  2. Session是服务器存储数据的一种机制,键值对结构的,主要用来存储身份相关的信息
  3. Cookie和Session经常在一起配合使用,但也不是必须配合
  4. cookie存放数据量小,也不安全,session存放数据量大,相对安全

7.XSS攻击

恶意脚本注入
攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全

防范:表单输入校验和转义,避免eval,new Function等方法使用,cookie配置http only,表示不允许js读写,secure设为true,表示仅在https下使用

8.CSRF攻击以及防范

利用用户身份伪造请求
利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,冒充用户对被攻击的网站发送执行某项操作的请求

防范:token过期机制,http请求检查referer是否同域名,重要的请求加强验证

9.cookie中哪些字段

Name
Value
Domain
Path
Expire
Size
HttpOnly
Secure
SameSite
Partition Key
Priority Low/Medium/High 当cookie数量超出时,低优先级的cookie会被优先清除

10.http三次握手四次挥手

https://blog.csdn.net/m0_38106923/article/details/108292454

11. websocket

WebSocket是一种在单个TCP连接上进行全双工通信的协议

12.前后端鉴权

13.什么是跨域以及如何解决跨域

跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制

协议,域名,端口号任一不同,则为跨域

解决方案:

  1. 使用JSONP格式 只能发get请求
  2. CORS
    分为简单请求和非简单请求,
    只要同时满足以下条件,就属于简单请求
    1.请求方法为HEAD GET POST
    2.请求头里不超过以下字段
    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-type 只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
所有的请求头中都有 Origin:xxxxx

//简单请求的response header
Access-Control-Allow-Origin   //必选  可以是*或者requestHeader中Origin的值

Access-Control-Expose-Headers 
// 可选,CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:
//Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。
//如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

Access-Control-Allow-Credentials:true
// 可选,CORS请求,默认是不携带Cookie的,设置为true,可以在请求是携带Cookie
// 如果设置为true,则前端也需要开启
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

// 如果设置为true,则Access-Control-Allow-Origin必须设置为具体的某一个地址,不能设置为*


// 非简单请求的response header
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)

Access-Control-Allow-Methods
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
​
Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
​
Access-Control-Allow-Credentials
该字段与简单请求时的含义相同。
​
Access-Control-Max-Age:1728000
该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。


  1. postMessage
  2. Nginx反向代理服务器

14.HTTP常见状态码

200
302 重定向
304 文件未变化
400 bad request
401 Unauthorized
403 Forbidden
404 Not Found
405 Method Not Allowed
408 Request Timeout
500 Internal Server Error

Vue

1.vue生命周期以及特点

beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeDestory
destoryed

2.vue如何实现双向绑定

  1. 首先我们为每个vue属性用Object.defineProperty()实现数据劫持,为每个属性分配一个订阅者集合的管理数组dep;
  2. 然后在编译的时候在该属性的数组dep中添加订阅者,v-model会添加一个订阅者,{{}}也会,v-bind也会,只要用到该属性的指令理论上都会;
  3. 接着为input会添加监听事件,修改值就等于为该属性赋值,则会触发该属性的set方法,在set方法内通知订阅者数组dep,订阅者数组循环调用各订阅者的update方法更新视图。

3.v-if 与 v-show的区别

v-if控制元素的是否渲染
v-show通过控制visible:hidden来控制元素的隐藏

频繁的操作用v-show

4.组件之间如何通信

5.nextTick

6.Vue3解决了什么问题

7.Vuex的组成以及各部分的作用

8.vuex辅助函数有哪些,什么原理

9.路由守卫有哪些

10.vue与react的区别

  1. 核心思想不同
    vue:Vue的核心思想是尽可能的降低前端开发的门槛,是一个灵活易用的渐进式双向绑定的MVVM框架。

    react:React的核心思想是声明式渲染和组件化、单向数据流,React既不属于MVC也不属于MVVM架构

  2. 数据流不同
    vue 数据双向绑定
    react 单向数据

  3. 响应式原理不同
    vue:vue会遍历data数据对象,使用Object.definedProperty()将每个属性都转换为getter和setter,每个Vue组件实例都有一个对应的watcher实例,在组件初次渲染的时候会记录组件用到了那些数据,当数据发生改变的时候,会触发setter方法,并通知所有依赖这个数据的watcher实例调用update方法去触发组件的compile渲染方法,进行渲染数据。

    react:React主要是通过setState()方法来更新状态,状态更新之后,组件也会重新渲染。

  4. 监听数据变化原理不同
    vue:Vue通过 getter/setter以及一些函数的劫持,能精确知道数据变化。

    react:React默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染。为什么React不精确监听数据变化呢?这是因为Vue和React设计理念上的区别,Vue使用的是可变数据,而React更强调数据的不可变,两者没有好坏之分,Vue更加简单,而React构建大型应用的时候更加棒。

  5. diff算法不同
    vue和react的diff算法都是进行同层次的比较,主要有以下两点不同:

    vue对比节点,如果节点元素类型相同,但是className不同,认为是不同类型的元素,会进行删除重建,但是react则会认为是同类型的节点,只会修改节点属性。
    vue的列表比对采用的是首尾指针法,而react采用的是从左到右依次比对的方式,当一个集合只是把最后一个节点移动到了第一个,react会把前面的节点依次移动,而vue只会把最后一个节点移动到最后一个,从这点上来说vue的对比方式更加高效。

React

1.react的生命周期

渲染
static getDerivedStateFromProps
render
componentDidMount
更新
static getDerivedStateFromProps
shouldComponentUpdate
getSnapshotBeforeUpdate
componentDidUpdate
卸载
componentDidUnMount

2.react hook解决了什么问题

  1. 难以重用和共享组件中的与状态相关的逻辑
  2. 逻辑复杂的组件难以开发与维护,当我们的组件需要处理多个互不相关的 local state时,每个生命周期函数中可能会包含着各种互不相关的逻辑在里面
  3. 类组件中的this增加学习成本,类组件在基于现有工具的优化上存在些许问题
  4. 由于业务变动,函数组件不得不改为类组件等等

3.react fiber

React Fiber是react执行渲染时的一种新的调度策略,JavaScript是单线程的,一旦组件开始更新,主线程就一直被React控制,这个时候如果再次执行交互操作,就会卡顿。
React Fiber重构这种方式,渲染过程采用切片的方式,每执行一会儿,就歇一会儿。如果有优先级更高的任务到来以后呢,就会先去执行,降低页面发生卡顿的可能性,使得React对动画等实时性要求较高的场景体验更好。

4. react fiber 为什么舍弃三个will的生命周期

filber的机制下,生命周期分为render和commit的阶段,render的时候是可以被打断的,重新渲染,终止的
当一个任务执行到一半被打断后,下一次渲染线程抢回主动权时,这个任务被重启的形式是“重复执行一遍整个任务”而非“接着上次执行到的那行代码往下走”。这就导致 render 阶段的生命周期都是有可能被重复执行的

4.为什么hook不能放到条件内

react每个组件都会有一个列表记录第一次渲染的时候的hook以及状态,每次 Hook 的调用都对应一个全局的 index 索引,通过这个索引去当前运行组件 currentComponent 上的 _hooks 数组中查找保存的值,也就是 Hook 返回的 [state, useState]

如果在条件中写hook,会引起之后的hook的全局索引的混乱

5.redux和context的区别

6.redux的特点

1.数据来源的唯一性

在redux中所有的数据都是存放在你的store中,这样做的目的就是保证数据来源的唯一性。那么为什么要这样做呢?使得创建通用应用程序变得很容易,因为服务器的状态可以序列化并水合到客户机中,而不需要额外的编码工作。单个状态树也使调试或检查应用程序变得更容易;它还使您能够在开发中持久保存应用程序的状态,以获得更快的开发周期。

2.state只能是只读的状态

state只能是只读的,在你的action中不能你可以去取它的值,但是不能够去改变它,这个时候采取的方式通常是深度拷贝state,并且将其返回给一个变量,然后改变这个变量,最后将值返回出去。而且要去改变数据你只能够在的reducer中,reducer是一个描述了对象发生了一个什么样过程的函数过程。 只读状态的好处,确保视图和网络回调都不会直接写入状态。

3.使用纯函数进行改变

reducer的实质其实就是一个纯函数,那么什么是纯函数呢?函数中参数的调用的是什么,返回的就是什么。在action中其接受两个参数,第一个是state,第二个参数就是action ,其表述的方式是下面这种形式,state中通常默认的是一个对象,但是也可以是其他的值。

7.redux中间件

扩展redux的功能,创建一个函数,包装 store.dispatch,使用新创建的函数作为新的 dispatch
redux-logger中间件记录日志
redux-saga

8.错误边界

static getDerivedStateFromError
componentDidCatch

任何写了这两个方法的组件都可以捕获后代组件中抛出来的异常

static getDerivedStateFromError(error: Error) {
       // 更新 state 使下一次渲染能够显示降级后的 UI
       return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
        // 你同样可以将错误日志上报给服务器
        console.group();
        console.log('ErrorBoundary catch a error:');
        console.info('error', error);
        console.info('error info', errorInfo);
        console.groupEnd()
    }


=

9.为什么要用jsx语法

jsx 语法其实
JSX的引入使得在React中编写组件变得更直观、易读和易写。它提供了一种类似HTML的语法扩展,使得开发者可以更方便地描述组件的结构、属性和内容,提高了开发效率和代码可读性

10.react16.3新增哪些方法,删除哪些方法

新增 static getDeviredStateFromProps() getSnapshotBeforeUpdate()
删除 三个will

11.常用的Hook

12. conncet如何实现

13. minxin和hoc的区别

Mixins里也可以编写组件生命周期的方法,需要注意的是:Mixins里的方法并不会覆盖组件的生命周期方法,会在先于组件生命周期方法执行。
组件也可以使用多个Mixin 数组引入的顺序,决定了Mxins里生命周期方法的执行顺序。 // 不允许重复 //
除了生命周期方法可以重复以外,其他的方法都不可以重复,否则会报错**

Minxin只适用于React.createClass()这种方式创建组件

Minxin的缺陷
破坏了原有组件的封装:可能会带来新的state和props,意味着会有些“不可见”的状态需维护。
命名冲突:不同mixin中的命名不可知,故非常容易发生冲突,需要花一定成本解决。
增加了复杂性,难以维护。

Hoc
属性代理:通过被包裹组件的props来进行相关操作。主要进行组件的复用。
反向继承:继承被包裹的组件。主要进行渲染的劫持。

14. renderProps和childrenProps的区别

  1. 两者都可以在父组件中展示传入的子组件,renderProps是通过props传递一个渲染方法,childrenProps是直接在标签内写
  2. renderProps可以使子组件内拥有父组件的属性和方法,childrenProps只能传递父组件的父组件的方法和属性

性能优化

1.如何性能优化

2.首屏优化

  1. 路由懒加载
  2. 使用CDN加速,将通用库从vendor进行抽离
  3. 图片懒加载
  4. 使用异步组件
  5. 首屏使用骨架屏
  6. SSR渲染
  7. 按需加载UI库
  8. webpack开启gzip
  9. Nginx开启Gzip功能 使html,css,js在传输的时候进行压缩

Promise

1. Promise.all(), Promise.race(),Promise.allSettled()的区别

Promise.all 会等所有promise完成,再返回一个结果集,如果中途有一个失败,则直接返回reject
Promise.race 只要有一个完成(不管成功失败)都会返回结果
Promise.allSettled 会等所有的promise完成,哪怕中途有失败,也不会返回,等所有的完成后,返回结果集,结果集里是每一个的状态和数据或原因

// 异步操作成功时
{status: 'fulfilled', value: value}
 
// 异步操作失败时
{status: 'rejected', reason: reason}

2. 执行顺序

promise传入的函数会立即执行, then传入的会放在异步任务队列
如果转为async,await,那么await后面跟着的代码会立即执行,await下面行的代码会放异步任务队列

webpack

1.webpack打包过程

在这里插入图片描述

2.loader与plugin的区别

loader它只专注于转化文件(transform)这一个领域,完成压缩,打包,语言翻译;
plugin不仅只局限在打包,资源的加载上,还可以打包优化和压缩,重新定义环境变量,丰富webpack的功能等

3.loader原理

4.常用哪些loader与plugin

ModuleScopePlugin 防止引入src以外的文件
CaseSensitivePathsPlugin 强制要求文件路径大小写一致
InlineChunkHtmlPlugin 将js内联到html
MinCssExtractPlugin 将css提取打包一个单独的文件
OptimizeCssAssetsPlugin 压缩css文件
ManifestPlugin 控制版本
RuntimePublicPathPlugin 网页运行时覆盖webpack模块的公共文件
InterpolateHtmlPlugin html中使用插值

style-loader css-loader less-loader

其他

1.iframe 怎么传递数据

获取iframe窗口

// iframe id
document.getElementById('menuIframe').contentWindow
 
// iframe name
window.frames['menuIframe'].window
 
// iframe index 当前窗体的第几个 iframe
window.frames[1].window

iframe获取父级窗口

window.parent

传数据

//接收方
window.addEventListener('message',(e)=>{
	console.log(e.data)
)

// 发送方
目标窗口.postMessage(data, *)

2. 常见的前端漏洞

1.XSS攻击
2.CSRF攻击
3. HTTP劫持
4. 界面操作劫持
5. 不安全的第三方依赖
6. 文件上传漏洞
7. 文件下载漏洞
8. 本地存储数据泄露

3. 函数柯里化

柯里化(currying)又称部分求值。一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

实现一个 add 方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存 _args 并收集所有的参数值
    var _adder = function () {
        _args.push(...arguments);
        return _adder;
    };

    // 这个是最后输出的时候被调用的,return 后面如果是函数体,
    // 为了输出函数体字符串会自动调用 toString 方法
    // 利用 toString 隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }

    // 这个 return 是第一次调用的时候返回上面的函数体,
    // 这样后面所有的括号再执行的时候就是执行 _adder 函数体
    return _adder;
}

敲代码

1.输出目标的路径,如输入8,输出[1,2,4,8]

2.输出[1,2,3,4,5,6,7,8,9,10]

const root = {
  val: 1,
  left: {
    val: 2,
    left: {
      val: 4,
      right: {
        val: 8
      }
    },
    right: {
      val: 5
    }
  },
  right: {
    val: 3,
    left: {
      val: 6,
      left: {
        val: 9
      },
      right: {
        val: 10
      }
    },
    right: {
      val: 7
    }
  }
};

// 深度遍历
function map1(root, target) {
  let arr = [];
  if (!root) return []
  if (root.val === target) {
    arr.push(root.val);
    return arr
  }
  if (map1(root.right, target).length > 0) {
    arr = arr.concat([root.val, ...map1(root.right, target)]);
    return arr
  }
  if (map1(root.left, target).length > 0) {
    arr = arr.concat([root.val, ...map1(root.left, target)]);
    return arr
  }
  return arr
};

// 广度遍历
function map2(root){
	let arr = [];
	let stack = [];
	stack.push(root);
	while(stack.length){
		const item = stack.shift();
		arr.push(item.val);
		if(item.left){
			stack.push(item.left);
		};
		if(item.right){
			stack.push(item.right);
		}
	}
}

3.递归求和

// 50-100
function getSum(n1, n2){
 let sum = n1;
 if(n1==n2){
	return n2;
 };
 return sum + getSum(n1+1, n2)
}

// 1-50
function getSum(n){
	let sum = n;
	if(n==1){
		return 1;
	};
	return sum + getSum(n-1);
}

4.实现一个EventBus

5.简单版promise

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
    var _this = this
    this.state = PENDING; //状态
    this.value = undefined; //成功结果
    this.reason = undefined; //失败原因

    this.onFulfilled = [];//成功的回调
    this.onRejected = []; //失败的回调
    function resolve(value) {
        if(_this.state === PENDING){
            _this.state = FULFILLED
            _this.value = value
            _this.onFulfilled.forEach(fn => fn(value))
        }
    }
    function reject(reason) {
        if(_this.state === PENDING){
            _this.state = REJECTED
            _this.reason = reason
            _this.onRejected.forEach(fn => fn(reason))
        }
    }
    try {
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}
Promise.prototype.then = function (onFulfilled, onRejected) {
    if(this.state === FULFILLED){
        typeof onFulfilled === 'function' && onFulfilled(this.value)
    }
    if(this.state === REJECTED){
        typeof onRejected === 'function' && onRejected(this.reason)
    }
    if(this.state === PENDING){
        typeof onFulfilled === 'function' && this.onFulfilled.push(onFulfilled)
        typeof onRejected === 'function' && this.onRejected.push(onRejected)
    }
};

6.对象深拷贝

function deepclone(obj) {
  let result;
  if (typeof obj === "object") {
    if (Array.isArray(obj)) {
      result = [];
      for (let k in obj) {
        result.push(deepclone(obj[k]));
      }
    } else if (obj === null) {
      result = null;
    } else if (obj instanceof RegExp) {
      result = obj;
    } else {
      result = {};
      for (let k in obj) {
        result[k] = deepclone(obj[k]);
      }
    }
  } else {
    result = obj;
  }
  return result;
}

7.实现promise.all

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值