在Promise内部,有一个状态管理器的存在,有三种状态: pending、fulfilled、rejected
(1) promise初始化状态为pending
(2) 当前调用resolve(成功), 会由pending => fulfilled
(3) 当调用reject(失败), 会由pending => rejected
协议、端口和域名不一致导致的跨域
跨域是因为浏览器需要遵守同源策略,发出的请求即使相应成功,也被浏览器拦截下来
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互、这是一个用于隔离潜在恶意文件的重要安全机制、
如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。相关链接
1、 防御 XSS 攻击
2、防御 CSRF 攻击
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数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了
1、Content方面
2、Server方面
3、Cookie方面
4、CSS方面
5、JavaScript
6、图片方面
7、移动方面
大概流程
1、在浏览器数地址栏输入URL
2、浏览器查看缓存,如果请求资源在缓存中并且新鲜(即是有没有过期的意思),跳转到转码步骤
Expires
和 Cache-Control
3、浏览器解析URL获取协议,主机,端口,path
4、浏览器组装一个HTTP(GET)请求报文
5、浏览器获取主机ip地址,过程如下:
6、打开一个sokcet与目标地址端口建立TCP链接, 三次握手如下:
7、TCP链接建立后发送HTTP请求
8、服务器接受请求并解析,将请求转发到服务程序,如虚拟机使用HTTP Host头部判断请求的服务程序
9、服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码
10、处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作
11、服务器将响应报文通过TCP链接发送回浏览器
12、浏览器接受HTTP响应,然后根据情况选择关闭TCP连接或者保留重用,关闭TCP连接的四次握手如下:
13、浏览器检查响应状态码:是否为1XX、3XX、4XX、5XX,这些情况处理与2XX不同
14、如果资源可缓存,进行缓存
15、对响应进行解码(例如gzip压缩)
16、根据资源类型决定如何处理(假设资源为HTML文档)
17、解析HTML文档、构件DOM树,下载资源,构造CSSOM树,执行js脚本,这些操作没有严格的先后顺序,以下分别解释
18、构建DOM树:
19、解析过程中遇到图片、样式表、js文件,启动下载
20、构建CSSOM树
21、根据DOM树和CSSOM树构建渲染树:
script,meta
这样本身不可见的标签。2)被CSS隐藏的节点,入display:none
22、js解析如下
23、显示页面(HTML解析过程中会逐步显示页面)
1、通过meta标签设置viewport,移动端的理想适口。
2、设置rem单位来进行适配、加上Flex布局、百分比布局
3、其它方案,响应式适配、vw+rem
与我们常见的很多语言不同,JavaScript 函数中的 this 指向并不是在函数定义的时候确定的,而是在调用的时候确定的。换句话说,函数的调用方式决定了 this 指向。
官方解释:箭头函数表达式的语法比函数表达式更简洁,并且没有自己的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 和 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) 主线程不断重复上面的第三步
先执行宏观任务,再执行微观任务
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重写。
数据存储方案:
大概说一下Cookie和localStorage、sessionStorage的功能特性。问到的话,Cookie的缺点就是,存储量少、数据大影响性能、只能储存字符串、安全性问题、需要检查Cookie能否使用
/**
* @desc 深拷贝,支持常见类型
* @Param {Any} values
* @Return {Any}
*/
function deepClone(values) {
var copy;
// Handle the 3 simple types, and null or undefined
if (null == values || "object" != typeof values) return values;
// Handle Date
if (values instanceof Date) {
copy = new Date();
copy.setTime(values.getTime());
return copy;
}
// Handle Array
if (values instanceof Array) {
copy = [];
for (var i = 0, len = values.length; i < len; i++) {
copy = deepClone(values);
}
return copy;
}
// Handle Object
if (values instanceof Object) {
copy = {};
for (var attr in values) {
if (values.hasOwnProperty(attr)) copy[attr] = deepClone(values[attr]);
}
return copy;
}
throw new Error("Unable to copy values! Its type isn't supported.");
}
如果是一个数组,就声明一个数据组,然后循环遍历,递归赋值。
如果是一个对象,就声明一个对象,然后判断是否子元素,递归赋值
除了递归,我们还可以借用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添加对应的前缀,通过引入模快的方式来进行操作。
<meta name="renderer" content="webkit"/> // 强制Chromium内核,作用于360浏览器、QQ浏览器等国产双核浏览器
<meta name="force-rendering" content="webkit"/> // 强制Chromium内核,作用于其他双核浏览器
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1"/> // 如果有安装 Google Chrome Frame 插件则强制为Chromium内核,否则强制本机支持的最高版本IE内核,作用于IE浏览器
```[/md]