在Promise内部,有一个状态管理器的存在,有三种状态: pending、fulfilled、rejected
(1) promise初始化状态为pending
(2) 当前调用resolve(成功), 会由pending => fulfilled
(3) 当调用reject(失败), 会由pending => rejected
协议、端口和域名不一致导致的跨域 跨域是因为浏览器需要遵守同源策略,发出的请求即使相应成功,也被浏览器拦截下来
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互、这是一个用于隔离潜在恶意文件的重要安全机制、
如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。相关链接
1、 防御 XSS 攻击
XSS,即 Cross Site Script,中译是跨站脚本攻击。HttpOnly 防止劫取 Cookie用户的输入检查服务端的输出检查2、防御 CSRF 攻击
CSRF,即 Cross Site Request Forgery,中译是跨站请求伪造,是一种劫持受信任用户向服务器发送非预期请求的攻击方式。验证码Referer CheckToken验证1、通过jsonp跨域 2、document.domain + iframe跨域 3、location.hash + iframe 4、window.name + iframe跨域 5、postMessage跨域 6、跨域资源共享(CORS) 7、nginx代{过}{滤}理跨域 8、nodejs中间代{过}{滤}理跨域 9、WebSocket协议跨域
jsonp的核心则是动态添加 script 标签调用服务器提供的js脚本,允许用户传递一个callback参数给服务器,然后服务器返回数据时会将这个callback参数作为函数名老包裹JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了
仅支持GET方法1、Content方面
减少HTTP请求:合并文件、CSS精灵、inline image减少DNS查询: DNS查询完之前浏览器不能从这个主机下载任何文件、方法:DNS缓存、讲资源分布到恰当的数量的主机名,平衡并行下载和DNS查询避免重定向 : 多余的中间访问使用AJAX缓存,真相跟 HTTP缓存没有什么区别。相关链接非必须组件延迟加载未来所需组件预加载减少DOM元素数量将资源放到不同的域下面:浏览器同时从一个域下载资源的数目有限,增加域可以提高并行下载量减少iframe数量不要4042、Server方面
使用CDN添加Expires或者Cache-Control: 当Cache-Control和Expires同时存在时,Cache-Control会覆盖Expires。相关链接使用Gzip压缩配置EtagFlush Buffer EarlyAjax使用GET进行请求避免空src的img标签3、Cookie方面
减小Cookie引入资源的域名不要包含cookie4、CSS方面
将样式表放到顶部不要使用CSS表达式不使用@import不使用IE的Filter5、JavaScript
将脚本放到页面的底部将JavaScript和CSS从外部引入压缩JavaScript和CSS删除不需要的脚本减少DOM的查询合理设计事件监听器6、图片方面
优化图片: 根据实际颜色需要选择色深、压缩优化CSS精灵不要在HTML中拉伸图片保证favicon、ico小并且可缓存7、移动方面
保证组件小于25KPack Components into a Multipart Document大概流程
URL输入DNS解析TCP连接发送HTTP请求服务器处理请求服务器响应请求浏览器解析渲染页面连接结束1、在浏览器数地址栏输入URL
2、浏览器查看缓存,如果请求资源在缓存中并且新鲜(即是有没有过期的意思),跳转到转码步骤
如果资源未缓存,发起新请求如果已缓存,检验是否足够新鲜,足够新鲜直接提供给客户端,否则与服务器进行验证。检验新鲜通常有两个HTTP头进行控制 Expires 和 Cache-Control HTTP1.0提供Expires,值为一个绝对值表示HTTP1.1增加了Cache-Control : max-age=,值为以秒为单位的最大新鲜时间3、浏览器解析URL获取协议,主机,端口,path
4、浏览器组装一个HTTP(GET)请求报文
5、浏览器获取主机ip地址,过程如下:
浏览器缓存本机缓存hosts文件路由器缓存ISP DNS缓存DNS递归查询(可能存在负载均衡导致每次IP不一样)6、打开一个sokcet与目标地址端口建立TCP链接, 三次握手如下:
客户端发送一个TCP的SYN=1,Seq=X的包到服务器端口服务器发送SYN=1,ACK=X+1,Seq=Y的响应包客户端发送ACK=Y+1,Seq=Z7、TCP链接建立后发送HTTP请求
8、服务器接受请求并解析,将请求转发到服务程序,如虚拟机使用HTTP Host头部判断请求的服务程序
9、服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码
10、处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作
11、服务器将响应报文通过TCP链接发送回浏览器
12、浏览器接受HTTP响应,然后根据情况选择关闭TCP连接或者保留重用,关闭TCP连接的四次握手如下:
主动发送Fin=1,Ack=Z,Seq=X报文被动发送ACK=X+1,Seq=Z报文被动发送Fin=1,ACK=X,Seq=Y报文主动发送ACK=Y,Seq=X报文13、浏览器检查响应状态码:是否为1XX、3XX、4XX、5XX,这些情况处理与2XX不同
14、如果资源可缓存,进行缓存
15、对响应进行解码(例如gzip压缩)
16、根据资源类型决定如何处理(假设资源为HTML文档)
17、解析HTML文档、构件DOM树,下载资源,构造CSSOM树,执行js脚本,这些操作没有严格的先后顺序,以下分别解释
18、构建DOM树:
Tokenizing: 根据HTML规范将字符流解析为标记Lexing:词法分析将标记转换为对象并定义属性和规则DOM construction: 根据HTML标记关系将对象组成DOM树19、解析过程中遇到图片、样式表、js文件,启动下载
20、构建CSSOM树
Tokenizing: 字符流转换为标记流Node:根据标记创建节点CSSOM:节点创建CSSOM树21、根据DOM树和CSSOM树构建渲染树:
从DOM树的根节点遍历所有可见节点,不可见节点包括:1)script,meta这样本身不可见的标签。2)被CSS隐藏的节点,入display:none对每一个可节点,找到恰当的CSSOM规则并应用发布可视节点的内容和计算样式22、js解析如下
浏览器创建Document对象并解析HTML,将解析到的元素和文本节点添加到文档中,此时document.readystate为loadingHTML解析器遇到没有async和defer的script时,将他们添加到文档中,然后执行行内或者外部脚本。这些脚本同步执行,并且在脚本下载和执行时解析器会暂停。这样就可以用document.write()把文本插入到输入流中。同步脚本经常定义为函数和注册事件处理事件,他们可以遍历和操作script和他们之前的文档内容。当解析器遇到设置了async属性的script时,开始下载脚本并继续解析文档。脚本在它下载完成后尽快执行,但是解析器不会停下来等它下载。异步脚本禁止使用document.write(),它们可以访问自己script和之前的文档元素所有deter脚本会按照在文档上出现的顺序执行,延迟脚本能访问完整文档时,禁止使用document.write()浏览器在Document对象上触发DOMContentLoaded事件此时文档完成解析完成,浏览器可能还在等待如图片等内容加载,等这些内容完成载入并且所有异步脚本完成和执行,document.readState变为complete,window触发load事件23、显示页面(HTML解析过程中会逐步显示页面)
1、通过meta标签设置viewport,移动端的理想适口。
<meta name="viewport" content="width=width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">2、设置rem单位来进行适配、加上Flex布局、百分比布局
3、其它方案,响应式适配、vw+rem
与我们常见的很多语言不同,JavaScript 函数中的 this 指向并不是在函数定义的时候确定的,而是在调用的时候确定的。换句话说,函数的调用方式决定了 this 指向。
直接调用 直接调用,就是通过 函数名(…) 这种方式调用方法调用 方法调用是指通过对象来调用其方法函数,它是 对象.方法函数(…) 这样的调用形式new关键字调用通过 bind() 将函数绑定到对象之后再进行调用通过 call()、apply() 进行调用官方解释:箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或 new.target。
引用箭头函数有两个方面的作用:更简短函数和并且不绑定this
箭头函数不会创建this,它只会从自己的作用域链上一层继承this。
简而言之,箭头函数,永远指向当前调用的对象
BFC就是"块级格式化上下文"的意思,创建了BFC的元素就是一个独立的盒子,不过只有Block-level Box 可以参与创建BFC,它规定了内部的Block-level Box如何布局,并且与这个独立盒子里的布局不受外部影响,当然它不会影响到外面的元素。
利用发布/订阅模式,发布/订阅模式由一个发布者、多个订阅者以及一个调度中心所组成。订阅者们先在调度中心订阅某一事件并注册相应的回调函数,当某一个时刻发布者发布了一个事件,调度中心取出订阅了该事件的订阅者们所注册的回调函数来执行。
在发布/订阅模式中,订阅者和发布者并不需要关心对方的状态,订阅者只管订阅事件并注册回调、发布者只管发布事件,其余一切交给调度中心来调度,从而实现解耦。
1.父组件与子组件传值 父组件传给子组件:子组件通过props方法接受数据; 子组件传给父组件:$emit方法传递参数
2.非父子组件间的数据传递,兄弟组件传值
Vue是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发响应的监听回调。
具体步骤:
第一步:需要 Observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到数据变化。
第二步:Compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
第三步:Watcher 订阅者是 Observe 和 Compile 之间通信的桥梁,主要的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()
3、待属性变动dep.notify()通知时,调用自身的 update() 方法,并触发 Compile 中绑定回调,则功成身退。
第四步:MVVM作为数据绑定的入口,整合 Observe、Compile 和 Watcher 三者,通过 Observe 来监听自己的 Model 数据变化。 通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observe 和 Compile 之间的通信桥梁; 达到数据变化 -> 视图更新; 视图交互(input) -> 数据 Model 变更的双向绑定效果。
实现原理:遍历computed对象的key,调用defineComputed函数,给对应的key添加getter和setter。同时给每个对象添加一个watcher,通过这个watch来进行派发通知,更新视图
缓存原理:缓存就是在获取 getter 数据的,判断是否值相等,相等的话就直接返回,不再进行更新视图
watch 主要监控数据的变化,根据变化自定义执行相应的操作 computed 在计算获得数据,在getter之后会进行缓存,只有依赖的属性值变化后,才会发生变化,否则从缓存获取数据
MVVM分为Model、View、ViewModel三者
Model 代表数据模型,数据和业务逻辑都在Model层中定义View 代表UI视图,负责数据展示ViewModel 负责监听 Model 中数据的改变并且控制视图更新,处理用户交互操作:Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的, Model 和 ViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步
区别:这种模式实现了 Model 和 View的数据自动同步,因此开发时这需要要专注对数据的维护操作即可,而不需要自己操作dom 场景:数据操作比较多的场景,更加便捷
面试回答: jQuery主要简化了DOM的操作,是一个JS函数库。Vue,主要实现了MVVM的模式框架,通过数据驱动视图的变化,不需要自己操作DOM,增加了开发效率,并实现了组件化的思想,增加项目的可维护性。
主要思路合并对象,遍历循环mixin对象,然后找到对应的钩子函数进行合并。
概念补充: JS,是单线程的,利用JS的事件循环
事件循环大致分为以下几个步骤:
(1) 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)
(2) 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3) 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。哪些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4) 主线程不断重复上面的第三步
先执行宏观任务,再执行微观任务
宏观任务:setTimeout、MessageChannel、postMessage、setImmediate…微观任务:MutationObsever、Promise.then for (macroTask of macroTaskQueue) { // 1. Handle current MACRO-TASK handleMacroTask(); // 2. Handle all MICRO-TASK for (microTask of microTaskQueue) { handleMicroTask(microTask); } }nextTick原理:
会有一个callbacks数组,接受nextTick的回调函数,push进去
首先判断是否支持Promise,支持则利用的Promise.then进行调用遍历调用callbacks数组
判断是否支持 MutationObserver,支持则利用 MutationObserver 遍历调用callbacks数组
判断是否支持 setImmediate,支持则利用 setImmediate 遍历调用callbacks数组
都不支持,则利用setTimeout进行遍历调用 callbacks数组
面试回答: 把传入的回调函数压入 一个 callbacks 数组,判断当前浏览器支持宏任务还是微任务函数,在对应的函数里面进行回调 遍历callbacks 数组,然后执行相应的函数。
VNode是对真实 DOM 的一种抽象描述,它的核心定义无非就几个关键属性,标签名、数据、子节点、键值等,其它属性都是用来扩展VNode的灵活性以及实现一些特殊 feature的。
Virtual DOM 除了它的数据结构的定义,映射到真实的 DOM 实际上要经历 VNode的 create、diff、 patch等过程。
父子组件通信,props、emit、ref调用函数
兄弟组件通信,vuex、eventBus
vuex具有五种属性: state、getter、mutation、action、module
vuex就是一个仓库,仓库里面放很多对象。state就是数据存放地,对应于一般vue对象里面的data
state里面存放的数据是响应式的
getters可以对state进行计算操作
可以在多组件之间复用
action类似于mutation
action提价的是mutation,而是不是直接变更状态
action可以包含任何异步操作
可维护性会下降,你要想修改数据,你得维护三个地方
可读性下降,因为一个组件里的数据,你根本看不出来是从哪来的
增加耦合,大量的上传派发,会让耦合性大大的增加,本来Vue用Component就是为了减少耦合,现在这么用,和组件化的初衷相背。
总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后
创建前/后: 在beforeCreated阶段,vue实例的挂载元素 e l 和 数 据 对 象 d a t a 都 为 u n d e f i n e d , 还 未 初 始 化 。 在 c r e a t e d 阶 段 , v u e 实 例 的 数 据 对 象 d a t a 有 了 , el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了, el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,el还没有。
载入前/后: 在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
更新前/后: 当data变化时,会触发beforeUpdate和updated方法。
销毁前/后: 在执行destroy方法后,对data的改变不会触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在
首先,组件可以提升整个项目的开发效率。能够把页面抽象成多个相对独立的模快,解决了我们传统项目开发:效率低、难维护、复用性等问题。
然后,使用Vue.extend方法创建一个组件,然后使用Vue.component方法注册组件。子组件需要数据,可以在props中接受定义。而子组件修改好数据后,想把数据递给父组件。可以采用emit方法。
keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现在父组件链中;使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁他们。
获取keep-alive包裹的第一个子组件对象以其组件名
根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode)
根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该 key 在 this.keys 中的位置(更新key的位置是实现LRU置换策略的关键),否则执行第四步
在 this.cache 对象中存储该组件实例并保存 key 值,之后检查缓存的实例数量是否超过 max 的设置值,超过则根据LRU置换策略删除最近最久未使用的实例(即是下标为0的那个key)。
最后并且很重要,将该组件实例的 keepAlive 属性值设置为 true
面试回答: 根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode),如果匹配的话,存储到一个对象里面,并根据组件ID和生成缓存key,并且标识该组件已缓存。然后如果下次再匹配的话,就会通过对应的缓存Key从内存里面获取对应的Vue实例。
Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是 **Object.defineProperty()**不具备的
Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改
Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利
当然,Proxy的劣势就是兼容性问题,而且无法用polyfill磨平,因此Vue的作者才声明需要等到下个大版本(3.0)才能用Proxy重写。
数据存储方案:
CookieWeb存储(localStorage和sessionStorage)IndexedDB大概说一下Cookie和localStorage、sessionStorage的功能特性。问到的话,Cookie的缺点就是,存储量少、数据大影响性能、只能储存字符串、安全性问题、需要检查Cookie能否使用
如果是一个数组,就声明一个数据组,然后循环遍历,递归赋值。 如果是一个对象,就声明一个对象,然后判断是否子元素,递归赋值
除了递归,我们还可以借用JSON对象的parse和stringify
function deepClone(obj){ let _obj = JSON.stringify(obj), objClone = JSON.parse(_obj); return objClone }权限控制的主体思路,前端会有一份路由表,它表示了每个路由的访问权限。当用户登录之后,通过token获取用户的role,动态根据用户的role算出其对应有的权限的路由,通过router.addRoutes动态挂载路由。
如果是对象的话,每一个vue组件都是一个vue实例,通过new Vue()实例化,引用同一个对象,如果data直接是一个对象的话,那么一旦修改其中一个组件的数据,其他组件相同数据就会被改变。 而data是函数的话,每个vue组件的data都因为函数有了自己的作用域,互不干扰。
WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。
因为通过webpack把一些繁琐的操作的,比如CSS添加对应的前缀,通过引入模快的方式来进行操作。