前端面试

235 阅读42分钟

v-if / v-for 同时使用

  自:for 优先级要比if高 ,如果一起用会造成性能浪费,可以for 写if里面,或者用computed

性能优化

    精灵图合并,减少HTTP请求;压缩HTML、CSS、JavaScript文件;使用CDN托管静态文件;少用行业样式;减少dom操作;图片预加载、懒加载;开启服务器端的Gzip压缩(对文本资源非常有效);延长资源缓存时间;

web安全

  •  css攻击(跨域脚本攻击) XSS攻击的核心是将可执行的前端脚本代码(一般为JavaScript)植入到网页中,听起来比较拗口,用大白话说就是攻击者想让你的浏览器执行他写的JS代码。防御:输入内容的限制校验;编码-转意敏感字符 ;过滤-onclick等事件、style、script节点、iframe节点;httponle-防止劫取 Cookie;

  • csrf攻击:(跨站请求伪造)其核心思想在于,在打开A网站的情况下,另开Tab页面打开恶意网站B,此时在B页面的“唆使”下,浏览器发起一个对网站A的HTTP请求。防御:token 双重cookie;

  •  sql注入(让服务器执行攻击者的SQL语句)核心在于让Web服务器执行攻击者期望的SQL语句,以便得到数据库中的感兴趣的数据或对数据库进行读取、修改、删除、插入等操作 ;防御:输入校验、数据库权限分配;

参考: blog.csdn.net/Ed7zgeE9X/a…

             www.jianshu.com/p/544bb4bcc…

缓冲

   DNS缓冲

   进去页面先DNS查询,找到域名对应的服务器的IP地址,再发送请求

*DNS域名查找先在客户端进行递归查询


在任何一步找到就会结束查找流程,而整个过程客户端只发出一次查询请求

如果都没有找到,就会走DNS服务器设置的转发器,如果没设置转发模式,则向13根发起解析请求,这里就是迭代查询,如图

13根:
全球共有13个根域服务器IP地址,不是13台服务器!
因为借助任播技术,可以在全球设立这些IP的镜像站点,所以访问的不是唯一的那台主机

浏览器缓冲

浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求。那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中HTTP头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中,简单的过程如下图:

1625202449441.jpg

强缓冲

 第一次请求-  响应头返回过期时间,没有过期直接使用,不需要向服务器发请求。

缺点:过期之后不管资源是否变换都需要重新请求

-Expires-  :

expires: Wed, 22 Nov 2021 08:41:00 GMT

-Cache-Control-  : (优先级高于expires)

cache-control:max-age=300
Cache-control: private, max-age=0, no-cache

 协商缓冲

1.  响应头返回资源最后修改时间

     -last-modified(第一次请求)  VS    If-Modified-Since(再次请求) 相同返回304,不同返回200和新资源

缺点:即使打开也算修改

2  响应头返回资源文件的唯一标识,精确感知文件内容不同

   -  Etag    vs     If-None-Match     相同返回304,不同返回200和新资源

  Last-Modigied 和Etag 区别

   1.Etag 感知精准度高于last-modified

   2.last-modified 性能高于Etag,因为Etag是由服务器生成的,影响性能,不能替代只是补充加强。

querySelector 和 getElementBy

区别:querySelector 属于W3C 中 Selectors API 规范;返回的是文档节点集合;静态集合

            getElementBy 属于W3C 中 DOM 规范 ;返回HTML元素集合;动态集合;

DocumentFragment 文档碎片节点

没有父级的最小文档对象,轻量版的document。documentfragment节点不属于DOM树,变化不会起dom树变化,所以不会引起回流重绘。

使用:将documentFragment作为暂时的节点储存器,将存储的节点一次性加入DOM树,减少回流。

vue使用:双向绑定v-for/model——每个vue实例都有一个根元素Id属性是el,找到要渲染的部分,使用createDocumentgrament()创建一个documentgrament,遍历根元素的所有子元素,劫持插入documentgrament,执行vue编译,这个编译就是v-model等,然后再吧编译完成的documentgrament返回给dom根元素。

js数据类型,原始数据类型和引用数据类型的区别?

   js数据类型:number,boolean,string,null,underfined,object,symbol,bigint

   bigint(BigInt 用于当整数值大于Number数据类型支持的范围。number最大安全整数 2^53 - 1)

栈:原始数据类型(string boolean number undefined null)

堆:引用数据类型(function,array , object)

区别是存储位置不同

 基本数据类型在栈中的简单数据段,占据空间小,大小固定,属于频发使用数据;

引用数据类型在堆中的对象,占据空间大,大小不固定,在栈中存储了指针,指向堆的地址。

event loop  循环机制

js是单线程,所有同步任务在主线程执行,形成执行栈;主线程之外还有“任务队列”,执行栈执行结束后会读取“任务队列“,异步任务结束等待状态进去执行栈开始执行。

执行栈执行完同步任务后,执行栈也为空就会去执行微任务,微任务执行后执行宏任务。

 什么是闭包,闭包的应用有哪些地方,闭包优缺点

定义:有权访问另一个函数内变量的函数

闭包基本上就是⼀个函数内部返回⼀个函数

  应用:防抖,柯里化

   优点:

  • 可以读取函数内部的变量
  • 将变量始终保持在内存中
  • 可以封装对象的私有属性和私有方法

   缺点:比较耗内存,造成内存溢出

const add2 = function (){
   var count
   return function add (){ // 访问了外部变量的函数
      count += 1
   }
}()

map / weakmap 

Map本质上是一个键值对的集合。和传统对象结构相比,传统的对象只能用string为键名, map的键key 为任意类型。

  区别:1 map的key是任意类型,weakmap 只能是对象(除了null)

             2 map 是强引用;weakmap 是弱引用(可被垃圾回收)

             3 map 可遍历;weakmap 不可遍历

// 1. 通过new Map来创建dataMap容器
const dataMap = new Map();
// 2. 获取节点对象,作为测试数据
const element = document.querySelector(".node");
// 3. 通过 set 方法给 dataMap 中指定键和对应的值
dataMap.set(element,'objectData');
// 4. 通过 get 来从 dataMap 中获取键名对应的值
console.log(dataMap.get(element));
// 5. 揭开面目
console.log(dataMap);  

--------map 属性方法 set map has delete clear
// 判断传入的键是否在当前的map对象中 返回boolean
cosole.log(dataMap.has('name'))
//删除传入的键
dataMap.delete('name')
//清除所有成员
dataMap.clear()

参考:juejin.cn/post/699310…

webworker (运行在后台的js)

   webworker 是运行在后台的 JavaScript,不会影响页面的性能。

    Web Worker 是 HTML5 标准的一部分,这一规范定义了一套 API,它允许一段 JavaScript 程序运行在主线程之外的另外一个线程中。webworker给浏览器带来了后台计算的能力,把耗时的任务分配给worker线程来做,在很大程度上缓解了主线程UI渲染阻塞的问题,提升页面性能

    self 代表worker 进程自己,而不是window,

创建 worker : const worker = new Worker('https://~.js')

主线程与 worker 线程通信:    worker.postMessage({ hello: ['hello', 'world'] });

主线程关闭worker线程 :worker.terminate(); 

参考: juejin.cn/post/684490…

http 和 https

http:是客户端和服务端请求和应答的标准(tcp),是超文本传输协议。

  https: 以安全为目标的http通道,http加入SSL层加密。

 优缺点:

  • http 是超文本传输协议,信息是明文传输,HTTPS 协议要比 http 协议安全,https 是具有安全性的 ssl 加密传输协议,可防止数据在传输过程中被窃取、改变,确保数据的完整性(当然这种安全性并非绝对的,对于更深入的 Web 安全问题,此处暂且不表)。

  • http 协议的默认端口为 80,https 的默认端口为 443。

  • http 的连接很简单,是无状态的。https 握手阶段比较费时,会使页面加载时间延长 50%,增加 10%~20%的耗电。

  • https 缓存不如 http 高效,会增加数据开销。

  • Https 协议需要 ca 证书,费用较高,功能越强大的证书费用越高。

  • SSL 证书需要绑定 IP,不能再同一个 IP 上绑定多个域名,IPV4 资源支持不了这种消耗。

HTTP/1 .1和 HTTP/2区别


  • 新的二进制格式(Binary Format),HTTP1.x解析是基于文本的,基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。

  • 多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。

  • header压缩,HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。

  • 服务端推送(server push),同SPDY一样,HTTP2.0也具有server push功能。

TCP 3次握手、4次挥手

 TCP 3次握手 

  1. 建立连接,客户端向服务端发包
  2. 服务端收到包,向客户端发送自己的包
  3. 客户端向服务端发送确认包

TCP 4次挥手

  1. 客户端向服务端发送连接释放请求
  2. 服务端发送确认并同意关闭请求
  3. 服务端发送请求关闭连接
  4. 客户端确认包

TCP  UDP 

  • TCP是面向链接的,而UDP是面向无连接的。

  • TCP仅支持单播传输,UDP 提供了单播,多播,广播的功能。

  • TCP的三次握手保证了连接的可靠性; UDP是无连接的、不可靠的一种数据传输协议,首先不可靠性体现在无连接上,通信都不需要建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收。

  • UDP的头部开销比TCP的更小,数据传输速率更高实时性更好

TCP 和 UDP 应用场景

UDP的应用场景:即时通信。面向数据报方式;网络数据大多为短消息;拥有大量客户端;对数据安全性无特殊要求;网络负担重但对响应速度要求高的场景。eg: IP电话、实时视频会议等。

TCP的应用场景:对数据准确性要求高,速度可以相对较慢的。eg: 文件传输、邮件的发送与接收等。

  HTTP 请求跨域

同源策略:只要协议、域名、端口有任何一个不同,都被当作是不同的域。

解决方案:

  • JSONP 利用 script标签的src 属性没有跨域限制的漏洞,只能使用get请求

  • CORS(Cross-origin resource sharing)跨域资源共享 服务器设置对CORS的支持原理:服务器设置Access-Control-Allow-Origin HTTP响应头之后,浏览器将会允许跨域请求

  • proxy代理 目前常用方式,通过服务器设置代理

  • window.postMessage()  利用h5新特性它更多地用于窗口间的消息传递

  • Node 中间件和 Nginx 反向代理都是利用了服务器对服务器没有同源策略限制

  • Websocket (全双工通信协议)也是一种跨域的解决方案

  • document.domain, window.name, location.hash 逐渐淡出历史舞台,作为替代 PostMessage 是一种不错的方案

参考:juejin.cn/post/684490…

postMessage

postMessage :是html5引入的API,postMessage()方法允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档,多窗口,跨域消息传递.多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案.

使用场景:

  • 跨域通信(多用于窗口消息传递)
  • webworker(运行在后台的js线程,用于和主线程通信-(上述11小点))
  • Service worker 离线缓冲 用于与页面通信

参考:juejin.cn/post/684490…

  预检请求

预检请求(preflight request),是一个跨域请求,用来校验当前跨域请求能否被理解。**
**

针对CORS请求,浏览器将请求分成两个类型: 简单请求和非简单请求(预检请求)

简单请求:请求头有Origin字段,表示请求来自哪一个源;响应头有Access-Control-Allow-Origin字段, 它的值要么包含由 Origin 首部字段所指明的域名,要么是一个 "*" ,表示接受任意域名的请求。

使用以下方法之一:GET, HEAD, POST,

并且 Content-Type 的值仅限于下列三者之一:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

非简单请求:正式通信前发送预检请求,目的在于询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 动词和头信息字段,只有得到肯定答复,浏览器才会发出正式的请求,否则就报错。

  参考:juejin.cn/post/684490…

从输入URL到页面加载的全过程

  • DNS解析
  • 发起TCP连接
  • 发送HTTP请求
  • 服务器处理请求并返回HTTP报文
  • 浏览器解析渲染页面
  • 连接结束。

参考:juejin.cn/post/684490…

hash history

一. vue-router(前端路由)有两种模式,hash模式和history模式 1.hash 就是指 url 后面的 # 号以及后面的字符,history没有带#,外观上比hash 模式好看些 2.原理的区别(原理) 3. hash 能兼容到IE8, history 只能兼容到 IE10; 4.由于 hash 值变化不会导致浏览器向服务器发出请求,而且 hash 改变会触发 hashchange 事件(hashchange只能改变 # 后面的url片段);虽然hash路径出现在URL中,但是不会出现在HTTP请求中,对后端完全没有影响,因此改变hash值不会重新加载页面,基本都是使用 hash 来实现前端路由的。

二.原理: 1.hash通过监听浏览器的onhashchange()事件变化,查找对应的路由规则
2.history原理: 利用H5的 history中新增的两个API pushState() 和 replaceState() 和一个事件onpopstate监听URL变化

history模式的问题
怕刷新(如果后端没有准备的话),因为刷新是实实在在地去请求服务器的。
在hash模式下,前端路由修改的是#中的信息,而浏览器请求时不会将 # 后面的数据发送到后台,所以没有问题。但是在history下,你可以自由的修改path,当刷新时,如果服务器中没有相应的响应或者资源,则会刷新出来404页面。

computed 和 watch 区别

   computed计算属性:依赖其他属性值,并且computed 的值有缓冲,只有依赖的值发生改变,下一次获取才会重新计算。(依赖属性不改变可以直接缓冲获取computed,不需要重新计算)

watch 监听属性:更多的是观察作用,无缓冲性

多个因素影响一个显示用computed;  一个因素影响多个因素显示用watch

普通函数和箭头函数

   最大的区别在于this的指向问题(严格来说,其实箭头函数没有自己的this

  • 箭头函数不能用new来创建构造函数;普通函数可以
  • 不可以使用argumengts对象,不存在;
  • 普通函数的this是动态的, 箭头函数的this是包裹箭头函数的那个对象
  • 箭头函数不能通过bind,call,apply来改变this,但是可以用这几个方法,只是this的值不受这几个方法控制。

let const 区别

  • let 命令不存在变量提升
  • 存在const let 会形成封闭作用域
  • 不允许重复声明
  • const 声明的是常量,不能修改,但是如果定义的是对象,是可以修改对象内部的数据。

js是单线程的,但是为什么要设计成单线程?   

    这主要和js的用途有关,js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。 

 单线程:在同一时间只能做一件事
js也有多线程能力,即web worker,但有限制,无法操作DOM
因为js主要功能是操作DOM,如果有多线程,其中一个线程要删除一个div,另外一个线程要给这个div添加子元素,那到底要听那个线程的呢,这样会产生很多冲突.

 为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

JS单线程的问题及解决方法

单线程引发的问题:所有任务都需要排队,前一个任务结束,才会执行后一个任务,这样一些耗时很长的任务就会阻塞页面的操作,如果一值等待则会非常不合理
解决办法:引入异步,添加了一个消息队列, 即将一些需要等待的任务分类按顺序放在消息队列中,先执行可以直接执行的任务,之后不停的去消息队列询问是否有任务可以执行,有就将任务重新放到主线程里执行,没有就继续之前的操作,这个过程就是js的事件轮询
1.所有任务都在主线程上执行,形成一个执行栈
2.如果执行栈中的所有同步任务执行完毕,js就会读取消息队列中的异步任务,如果有可以执行的任务就把它放到执行栈中并开始执行
3.主线程不断重复上面的第二步,这样的一个循环称为事件循环

原型 原型链

  • 所有的函数都有prototype属性(原型)
  • 所有的对象都有__proto__属性
  • Javascript中,每个函数都有⼀个原型属性prototype指向⾃⾝的原型,⽽由这个函数创建的对象也有⼀个proto属性指向这个 原型,⽽函数的原型是⼀个对象,所以这个对象也会有⼀个proto指向⾃⼰的原型,这样逐层深⼊直到Object对象的原型,这样就 形成了原型链

cat.proto === Animal.prototype

cat.constructor==Animal

vue组件中data为什么必须是⼀个函数?

因为javaScript的特性所导致,在component中,data必须以函数的形式存在,不可以是对象。 组件中的data写成⼀个函数,数据以函数返回值的形式定义,这样每次复⽤组件的时候,都会返回⼀份新的data,相当于每个组件 实例都有⾃⼰私有的数据空间,他们只负责各⾃维护数据,不会造成混乱。⽽单纯的写成对象形式,就是所有组件实例共⽤了⼀个 data,这样改⼀个全部都会修改。

常见状态码

  • 1xx: 接受,继续处理

  • 200: 成功,并返回数据

  • 201: 已创建

  • 202: 已接受

  • 203: 成为,但未授权

  • 204: 成功,无内容

  • 205: 成功,重置内容

  • 206: 成功,部分内容

  • 300:(多种选择):针对请求,服务器可执行多种操作。

  • 301: 永久移动,重定向

  • 302: 临时移动,可使用原有URI

  • 304: 资源未修改,可使用缓存

  • 305: 需代理访问

  • 400: 请求语法错误

  • 401: 要求身份认证

  • 403: 拒绝请求

  • 404: 资源不存在

  • 500: 服务器错误

做过的vue性能优化

  • 对象层级不要过深,否则性能就会差。
  • 不需要响应式的数据不要放在 data 中(可以使⽤ Object.freeze() 冻结数据)
  •  v-if 和 v-show 区分使⽤场景 
  • computed 和 watch 区分场景使⽤
  •  v-for 遍历必须加 key,key最好是id值,且避免同时使⽤ v-if 
  • ⼤数据列表和表格性能优化 - 虚拟列表 / 虚拟表格 
  • 防⽌内部泄露,组件销毁后把全局变量和时间销毁
  •  图⽚懒加载 
  • 路由懒加载 
  • 异步路由 
  • 第三⽅插件的按需加载
  •  适当采⽤ keep-alive 缓存组件 
  • 防抖、节流的运⽤
  •  服务端渲染 SSR or 预渲染

 Vue.js 双向绑定的原理

Vue.js 2.0 采用数据劫持(Proxy 模式)结合发布者-订阅者模式(PubSub 模式)的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

每个组件实例都有相应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。

Vue.js 3.0, 放弃了Object.defineProperty ,使用更快的ES6原生 Proxy (访问对象拦截器, 也称代理器)

Object.defineProperty()属性: configurable:描述是否可改变;默认false enumerable:是否可枚举;默认false writable:属性的值是否可改变,默认false value:属性对应的值;默认underfined,(get,set也默认underfined)

 vue 中使用了哪些设计模式?

  • 1、工厂模式 - 传入参数即可创建实例
  • 虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode。
  • 2、单例模式 - 整个程序有且仅有一个实例
  • vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉。
  • 3、发布-订阅模式。(vue 事件机制)
  • 4、观察者模式。(响应式数据原理)
  • 5、装饰器模式(@装饰器的用法)
  • 6、策略模式,策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案 - 比如选项的合并策略。

 Vuex 页⾯刷新数据丢失怎么解决? 

  • 需要做 vuex 数据持久化,⼀般使⽤本地储存的⽅案来保存数据,可以⾃⼰设计存储⽅案,也可以使⽤第三⽅插件。
  •  推荐使⽤ vuex-persist (脯⾁赛斯特)插件,它是为 Vuex 持久化储存⽽⽣的⼀个插件。不需要你⼿动存取 storage,⽽是直接将 状态保存⾄ cookie 或者 localStorage中。

Cookie、sessionStorage、localStorage 的区别

相同点

  • 存储在客户端

不同点

  • cookie数据大小不能超过4k;sessionStorage和localStorage的存储比cookie大得多,可以达到5M+
  • cookie设置的过期时间之前一直有效;localStorage永久存储,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage数据在当前浏览器窗口关闭后自动删除
  • cookie的数据会自动的传递到服务器;sessionStorage和localStorage数据保存在本地

所谓的浏览器页面之间共享信息只能由localStorage来完成,并且前提是相同的域名、协议和端口号,对于sessionStorage来说,即使域名、协议和端口相同,那么sessionStorage的信息也是无法共享的

浏览器重绘与重排的区别?

  • 重排/回流(Reflow):当DOM的变化影响了元素的几何信息,浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。表现为重新生成布局,重新排列元素。
  • 重绘(Repaint): 当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。表现为某些元素的外观被改变

     『重绘』不一定会出现『重排』,『重排』必然会出现『重绘』。

js 内存泄漏

一般是:定时器 闭包 全局变量 dom元素背引用

settimeout promise async/away区别

1 settimeout 的回调函数放到宏任务队列里,等执行栈清空后再执行。

2 promise

本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行。

3 async/await

async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。

Async/Await 如何通过同步的方式实现异步

Async/Await就是一个自执行的generate函数。利用generate函数的特性把异步的代码写成“同步”的形式,第一个请求的返回值作为后面一个请求的参数,其中每一个参数都是一个promise对象.

介绍节流防抖原理、区别以及应用

节流:在规定的时间只能触发一次,且是最先被调用的那次。使用:高频点击、滚动条加载、 防抖:多次触发只有在操作结束才会去执行。使用:搜索框输入

简述MVVM

视图模型双向绑定,是Model-View-ViewModel的缩写,也就是把MVC中的Controller演变成ViewModel。Model层代表数据模型,View代表UI组件,ViewModelViewModel层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。以前是操作DOM结构更新视图,现在是数据驱动视图

Vue底层实现原理

vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调
Vue是一个典型的MVVM框架,模型(Model)只是普通的javascript对象,修改它则试图(View)会自动更新。这种设计让状态管理变得非常简单而直观

Observer(数据监听器) : Observer的核心是通过Object.defineProprtty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher

Watcher(订阅者) : Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:

  1. 在自身实例化时往属性订阅器(dep)里面添加自己
  2. 自身必须有一个update()方法
  3. 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调

Compile(指令解析器) : Compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新试图

vue组件的通信方式

  • props/$emit 父子组件通信

    父->子props,子->父 $on、$emit 获取父子组件实例 parent、children Ref 获取实例的方式调用组件的属性或者方法 父->子孙 Provide、inject 官方不推荐使用,但是写组件库时很常用

  • $emit/$on 自定义事件 兄弟组件通信

    Event Bus 实现跨组件通信 Vue.prototype.$bus = new Vue() 自定义事件

  • vuex 跨级组件通信

    Vuex、$attrs、$listeners Provide、inject

nextTick的实现原理是什么?

在下次 DOM 更新循环结束之后 执行延迟回调,在修改数据之后立即使用 nextTick 来获取更新后的 DOM。 nextTick主要使用了宏任务和微任务。 根据执行环境分别尝试采用Promise,如果不行则采用setTimeout定义了一个异步方法,多次调用nextTick会将方法存入**队列中,通过这个异步方法清空当前队列。

  • nextTick与setTimeout区别**
  1. 都是异步函数 不同的是nextTick比setTimeout优先执行
  2. $nextTick是Vue封装的方法 源码实际上优先用的Promise 然后 setInterval 两者都不支持的情况下使用setTimeout。
  3. promise是微任务,settimout是微任务,先执行微任务后宏任务,

使用过插槽么?用的是具名插槽还是匿名插槽或作用域插槽

vue中的插槽是一个非常好用的东西slot说白了就是一个占位的 在vue当中插槽包含三种一种是 默认插槽(匿名)一种是具名插槽 还有一种就是作用域插槽 slot-scope="scope" 父组件获取子组件的数据

keep-alive的实现

作用:实现组件缓存,保持这些组件的状态,以避免反复渲染导致的性能问题。 需要缓存组件 频繁切换,不需要重复渲染

场景:tabs标签页 后台导航,vue性能优化

原理:Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。

属性:

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
  • max - 数字。最多可以缓存多少组件实例。

activated 激活可调用

mixin

mixin 项目变得复杂的时候,多个组件间有重复的逻辑就会用到mixin
多个组件有相同的逻辑,抽离出来
mixin是逻辑抽离
mixin并不是完美的解决方案,会有一些问题
vue3提出的Composition API旨在解决这些问题【追求完美是要消耗一定的成本的,如开发成本】
场景:PC端新闻列表和详情页一样的右侧栏目,可以使用mixin进行混合
劣势:1.变量来源不明确,不利于阅读
2.多mixin可能会造成命名冲突

javascript异步

  • 回调函数
  • promise 即,通过then来实现多级嵌套(链式调用)
  • Generator Generator 就是一个返回值为iterator(迭代器)对象的函数。 ES6提供的。
function* createIterator() {
  yield 1;
  yield 2;
  yield 3;
}
// generators可以像正常函数一样被调用,不同的是会返回一个 iterator
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

generator 特性:

  1. function关键字与函数名之间有一个星号
  2. 函数体内部使用yield语句,定义不同的内部状态
  • async/await 总结

  • promise让异步执行看起来更清晰明了,通过then让异步执行结果分离出来。

  • async/await其实是基于Promise的。async函数其实是把promise包装了一下。使用async函数可以让代码简洁很多,不需要promise一样需要些then,不需要写匿名函数处理promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码。

  • async函数是Generator函数的语法糖。async函数的返回值是 promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。同时,我们还可以用await来替代then方法指定下一步的操作。

  • 感觉Promise+async的操作最为常见。因为Generator的常用功能可以直接由async来体现呀~

CommonJs和es6的Module的区别

  1. 两者的模块导入导出语法不同,commonjs是module.exports,exports导出,require导入;ES6则是export导出,import导入。
  2. commonjs是运行时加载模块,ES6是在静态编译期间就确定模块的依赖。
  3. ES6在编译期间会将所有import提升到顶部,commonjs不会提升require。
  4. commonjs导出的是一个值拷贝,会对加载结果进行缓存,一旦内部再修改这个值,则不会同步到外部。ES6是导出的一个引用,内部修改可以同步到外部。
  5. 两者的循环导入的实现原理不同,commonjs是当模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。ES6 模块是动态引用,如果使用import从一个模块加载变量(即import foo from 'foo'),那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
  6. commonjs中顶层的this指向这个模块本身,而ES6中顶层this指向undefined。

vue 大量数据渲染

虚拟列表:虚拟列表是一种根据滚动容器元素的可视区域来渲染长列表数据中某一个部分数据的技术
1 vue-virtual-scroller 插件
2

  • 实现虚拟列表的背后原理,最外层给定一个固定的高度,然后设置纵向Y轴滚动,然后每个元素的父级设置相对定位,设置真实展示数据的高度,根据item固定高度(rowHeight),根据可视区域和rowHeight计算可显示的limit数目。

  • 当滚动条上滑时,计算出滚动的距离scrollTop,通过currentIndex = Math.floor(scrollTop/rowHeight)计算出当前起始索引

  • 根据endIndex = Math.min(currentIndex+limit, total-1)计算出最后可显示的索引

  • 最后根据startIndex与结束位置endIndex,根据startIndexendIndex渲染可视区域

继承

1、原型链继承

2、构造函数继承

核心代码是SuperType.call(this),创建子类实例时调用SuperType构造函数,于是SubType的每个实例都会将SuperType中的属性复制一份。

3、组合继承

4、es6继承 extends

extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类\

ES5继承和ES6继承的区别

  • ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.call(this)).
  • ES6的继承有所不同,实质上是先创建父类的实例对象this,然后再用子类的构造函数修改this。因为子类没有自己的this对象,所以必须先调用父类的super()方法,否则新建实例报错。

this

定义:this就是指针, 指向我们调用函数的对象; this是JavaScript中的一个关键字,它是函数运行时,在函数体内自动生成的一个对象,只能在函数体内部使用。

this规则

  1. 全局环境中this指向全局变量(window)
  2. this一旦绑定,就不会被任何代码更改。
  3. 对this来说,new的优先级是最高的,如果let c=new foo(),那么foo()里的this就都会绑定在c上。
  4. call、apply、bind改变this,优先级仅次于new。
  5. 箭头函数的this取决于它外面第一个不是箭头函数的函数的this。
  6. 函数中的this,由调用函数的方式来决定(1)如果函数是独立调用的,在严格模式下(use strict)是指向undefined的,在非严格模式下,指向window(2)如果这个函数是被某一个对象调用,那么this指向被调用的对象;

vue 渐进式框架理解

渐进式的含义:没有多做职责之外的事,只做了自己该做的事,没有做不该做的事,仅此而已。

webpack

webpack的核心概念

  • Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。告诉webpack要使用哪个模块作为构建项目的起点,默认为./src/index.js
  • output :出口,告诉webpack在哪里输出它打包好的代码以及如何命名,默认为./dist
  • Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
  • Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
  • Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
  • Plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。 有哪些常见的Loader
  • source-map-loader:加载额外的 Source Map 文件,以方便断点调试
  • babel-loader:把 ES6 转换成 ES5
  • sass-loader:将SCSS/SASS代码转换成CSS
  • image-loader:加载并且压缩图片文件
  • eslint-loader:通过 ESLint 检查 JavaScript 代码 有哪些常见的Plugin
  • ignore-plugin:忽略部分文件
  • uglifyjs-webpack-plugin:不支持 ES6 压缩 (Webpack4 以前)
  • webpack-parallel-uglify-plugin: 多进程执行代码压缩,提升构建速度
  • mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-webpack-plugin) Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。 因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。

Plugin 就是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。 Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。

Plugin 在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。 source map是什么?

source map 是将编译、打包、压缩后的代码映射回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要 soucre map。

** Webpack 的热更新原理**

Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。

HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上 WDS 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。

后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loadervue-loader 都是借助这些 API 实现 HMR。

参考Webpack HMR 原理解析

优化解析时间 - 开启多进程打包

1. thread-loader(webpack4 官方推荐)

2. HappyPack

你是如何提高webpack构件速度的?

  • 利用DllPlugin和DllReferencePlugin预编译资源模块.通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来。

  • 使用Happypack 实现多线程加速编译

  • 使用webpack-uglify-paralle来提升uglifyPlugin的压缩速度。

    原理上webpack-uglify-parallel采用了多核并行压缩来提升压缩速度使用Tree-shaking和Scope Hoisting来剔除多余代码

长连接

         *websocket 是双向通信   sse 是单向通信*
  • webSocket
    WebSocket 是 Html5 定义的一个新协议,与传统的 http 协议不同,Websocket 是一个持久化的协议,该协议允许由服务器主动的向客户端推送信息。
    使用 WebSocket 协议的缺点是在服务器端的配置比较复杂。WebSocket 是一个全双工的协议,也就是通信双方是平等的,可以相互发送消息。

websocket特点

  • 支持双向通信,实时性更强;
  • 可以发送文本,也可以二进制文件;
  • 协议标识符是 ws,加密后是 wss
  • 较少的控制开销。连接创建后,ws客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有2~10字节(取决于数据包长度),客户端到服务端的的话,需要加上额外的4字节的掩码。而HTTP协议每次通信都需要携带完整的头部;
  • 支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)
  • 无跨域问题。

websocket 最大的特点就是可以双向通信。 通信双方都可以主动发送信息。

    var ws = new WebSocket('wss://echo.websocket.org');
    //发送请求
    ws.onopen = function (evt) {
        console.log('Connection open ...');
        ws.send('Hello WebSockets!');
    };
    //接收数据
    ws.onmessage = function (evt) {
        console.log('Received Message: ', evt.data);
        ws.close();
    };
    //关闭连接
    ws.onclose = function (evt) {
        console.log('Connection closed.');
    };

  • sse SSE是HTML5新增的功能,SSE(sever-sent events)服务器端推送事件,是指服务器推送数据给客户端,而不是传统的请求响应模式。

EventSource 是服务器推送的一个网络事件接口。 会一直保持开启直到被要求关闭。

与WebSocket不同的是,SSE是服务端单向推送数据到客户端。数据信息被单向从服务端到客户端分发,当不需要以消息形式将数据从客户端发送到服务器时,这使它们成为绝佳的选择。例如,对于处理社交媒体状态更新,新闻提要或将数据传递到客户端存储机制(如IndexedDB或Web存储)之类 EventSource无疑是一个有效方案\

优缺点👇

  • 优点:不用每次建立新连接,延迟较低;SSE和WebSocket相比,最大的优势是便利,服务端不需要其他的类库,开发难度较低。
  • 缺点:如果客户端有很多,那就要保持很多长连接浏览器一直转圈,这会占用服务器大量内存和连接数。

vue 父子组件生命周期执行顺序

挂载 执行顺序为:
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted

自定义组件 -directive

注册指令分为全局注册局部注册

Vue.directive('focus', {/** */}) 全局

局部
const vm = new Vue({
  el: '#app',
  directives: {
    focus: {/** */}
  }
})

自定义指令生命周期钩子函数

  • bind:只调用一次,指令第一次绑定到元素时调用。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

判断空对象

  1. 利用 for...in 判断
function isEmptyObject(object) {
	for (let key in object) {
		return false;	// 能遍历,对象不为空
	}
	return true;
}

思路: 利用for in 循环遍历对象和对象原型上的可枚举属性;
缺点: 只能遍历可枚举属性,若一个对象上只有不可枚举属性的话,会判断错误;

  1. 利用 Object.keys()  判断
function isEmptyObject(object) {
	return Object.keys(object).length === 0;
}

思路: Object.keys 能返回对象自身上所有可枚举属性的名称所构成的数组,若数组长度为0,那就是一个空对象;
缺点: 如 for...in 判断一样,Object.keys 方法也只返回可枚举属性;

  1. 将对象转化为json字符串
function isEmptyObject(object) {
	return JSON.stringify(object) === "{}";
}
  1. 利用 Object.getOwnPropertyNames()  判断
function isEmptyObject(object) {
	return Object.getOwnPropertyNames(object).length === 0;
}

思路:  Object.getOwnPropertyNames 方法获取到对象中的属性名,存到一个数组中,返回数组对象,若数组长度为0,则是空对象;
缺点:  Object.getOwnPropertyNames 方法是 Object.keys 的改进,可获取到不可枚举属性,但该方法无法获取 Symbol 属性;

  1. 利用 Reflect.ownKeys()  判断
function isEmptyObject(object) {
	return Reflect.ownKeys(object).length === 0;
}

思路:  Reflect.ownKeys 也可以返回对象自身属性名所构成的数组,该方法可以返回不可枚举属性以及 Symbol 属性;

数组交集

  1. 最简单filter加includes
var arr2=['a','b',1,2]
var arr3=arr1.filter(item=>{
    return arr2.includes(item)
})
console.log(arr3);//[ 1, 2, 'a', 'b' ]

Vue 事件修饰符

名称可用版本可用事件说明
.stop所有任意当事件触发时,阻止事件冒泡
.prevent所有任意当事件触发时,阻止元素默认行为
.capture所有任意当事件触发时,阻止事件捕获
.self所有任意限制事件仅作用于节点自身
.once2.1.4以上任意事件被触发一次后即解除监听
.passive2.3.0以上滚动移动端,限制事件用不调用preventDefault()方法

webpack 和 vite

底层语言

  • 从底层原理上来说,Vite是基于esbuild预构建依赖。而esbuild是采用go语言编写,因为go语言的操作是纳秒级别,而js是以毫秒计数,所以vite比用js编写的打包器快10-100倍。 原理

webpack: 分析依赖=> 编译打包=> 交给本地服务器进行渲染。首先分析各个模块之间的依赖,然后进行打包,在启动webpack-dev-server,请求服务器时,直接显示打包结果。webpack打包之后存在的问题:随着模块的增多,会造成打出的 bundle 体积过大,进而会造成热更新速度明显拖慢。

vite: 启动服务器=> 请求模块时按需动态编译显示。是先启动开发服务器,请求某个模块时再对该模块进行实时编译,因为现代游览器本身支持ES-Module,所以会自动向依赖的Module发出请求。所以vite就将开发环境下的模块文件作为浏览器的执行文件,而不是像webpack进行打包后交给本地服务器。

webpack会先打包依赖,然后再服务器渲染所以会很慢 vite按需实时编译

  • vite 它在启动的时候不需要打包,所以不用分析模块与模块之间的依赖关系,不用进行编译。当浏览器请求某个模块时,再根据需要对模块内容进行编译。按需动态编译可以缩减编译时间,当项目越复杂,模块越多的情况下,vite明显优于webpack.
  • 热更新方面,效率更高。当改动了某个模块的时候,也只用让浏览器重新请求该模块,不需要像webpack那样将模块以及模块依赖的模块全部编译一次。

浏览器兼容

  • webpack
    1. 通过browserlist来配置需要兼容的浏览器版本并告诉webpack中的其他插件去做对应的兼容。
    2. CSS的兼容:具体是给我们的CSS自动添加前缀实现,在这里就需要用到postcss包来实现
    3. JS的兼容:JS的兼容是通过babel来实现将ES6的语法向之前版本做转换以兼容浏览器,但是仅通过babel还不够,对于类似Promise这个高级语法我们还需要求助于polyfill- 下的 core-js 和 regenerator-runtime

项目遇到的问题

  1. 权限划分
  • 一种方法是通过动态添加路由和菜单来做控制,后台返回路由。
    
    利用 vue-router 的 addRoutes 方法可以动态添加路由
  • 另一种办法就是所有的页面都在路由表里,访问判断权限。没有权限404。
    

HTML&CSS

BFC(块级格式上下文)

BFC的概念

BFC 是 Block Formatting Context 的缩写,即块级格式化上下文。BFC是CSS布局的一个概念,是一个独立的渲染区域,规定了内部box如何布局, 并且这个区域的子元素不会影响到外面的元素,其中比较重要的布局规则有内部 box 垂直放置,计算 BFC 的高度的时候,浮动元素也参与计算。

BFC的原理布局规则

  • 内部的Box会在垂直方向,一个接一个地放置
  • Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠
  • 每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反
  • BFC的区域不会与float box重叠
  • BFC是一个独立容器,容器里面的子元素不会影响到外面的元素
  • 计算BFC的高度时,浮动元素也参与计算高度
  • 元素的类型和display属性,决定了这个Box的类型。不同类型的Box会参与不同的Formatting Context

如何创建BFC?

  • 根元素,即HTML元素
  • float的值不为none
  • position为absolute或fixed
  • display的值为inline-block、table-cell、table-caption
  • overflow的值不为visible

BFC的使用场景

  • 去除边距重叠现象
  • 清除浮动(让父元素的高度包含子浮动元素)
  • 避免某元素被浮动元素覆盖
  • 避免多列布局由于宽度计算四舍五入而自动换行

AST语法树

tree shacking

原理esm,tree shaking 就是通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如 import 和 export。

0.1+0.2!==0.3

console.log(parseFloat((0.1 + 0.2).toFixed(10)) === 0.3); // true