1.vue2和vue3的区别
- 双向数据绑定原理的改变:
vue2的双向数据绑定是利用ES5的一个 APIObject.definePropert()对数据进行劫持,结合发布订阅模式的方式来实现的。
vue3 中使用了 es6 的 Proxy API对数据进行代理。
- 相比于vue2,使用proxy的优势如下:
defineProperty只能监听某个属性,不能对全对象监听
可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)
可以监听数组,不用再去单独的对数组做特异性操作 vue3可以检测到数组内部数据的变化。
- Vue2与Vue3最大的区别 — Vue2使用选项类型API(Options API)对比Vue3合成型API(Composition API)
Vue2的API分割成了不同的属性: data,computed属性,methods等。Vue3合成型API能让我们用方法(function)来分割,相比于旧的API使用属性来分组,这样代码会更加简便和整洁。
- Vue2 是把数据放到data属性中,而Vue3是采用setup()方法来建立数据。
-
- setup()是在beforeCreate和created之前执行,创建的是data和method。
- setup函数中不能使用this,Vue 为了避免我们错误的使用,直接将 setup函数中的this修改成了 undefined。
- setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新。但是,因为 props 是响应式的,不能使用 ES6 解构,因为它会消除 prop 的响应性。如果需要解构 prop,可以通过使用 setup 函数中的toRefs 来完成此操作
- setup()内使用响应式数据时,需要通过.value获取。
- setup函数只能是同步的不能是异步的
-
生命周期钩子的不同:
Vue2--------------vue3
beforeCreate -> setup()
created -> setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
activated -> onActivated
deactivated -> onDeactivated
2.vuex干嘛的 什么情况下使用
Vuex是为Vue开发的管理状态模式,集中存储管理所有组件的状态。Vuex 的状态存储是响应式的,当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会得到更新。
属性:state(数据源)、mutations(处理同步事件)、actions(处理异步事件)、getters(过滤数据)和modules(模块)。
(1)state用来定义所要存储的数据,通过$store.state调用数据
(2)getters可以认为是store的计算属性,通过$store.getters调用属性
(3)mutations用来存放改变state数据的同步方法,每个方法接收的参数都是state,用$store.commit来调用mutations中的同步方法
(4)actions用来存放异步方法,接收的参数是context(mutations中的方法),通过$store.dispatch调用actions中的方法
(5)module将store分割成模块,每个模块有自己的state、getters、mutations、actions、甚至嵌套子模块,从上至下进行同步分割,前四个属性除了用$store的方法调用,还能通过
import {mapState/mapGetters/... } from 'vuex'引入,再用...mapState/mapGetter/...(['属性/方法名'])的形式映射进来调用
3.css怎样实现水平垂直居中
- 绝对定位的盒子水平居中:
left : 50%
margin-left : -100px // 让盒子左移动自身宽度的一半
绝对定位的盒子垂直居中:
top : 50%
margin-top : -100px //向上移动自身高度的一半
2. 升级:绝对定位配合transform
.father {
position: relative;
}
.son {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
3. flex布局
.father {
display: flex / grid; //弹性盒子 / 网格元素
justify-content: center; // 主轴的居中
align-items: center; //侧轴的居中
}
4. 绝对定位配合margin : auto 的实现方案
.father {
position: relative;
}
.son {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
}
5. 图片、表单和文字对齐:css样式
.father {
display: table-cell
}
.son {
text-align: center;
`vertical-align : middle;`
}
4.作用域和闭包
作用域就是变量的使用范围。js中常见的作用域有全局作用域和局部作用域,全局作用域不能访问局部作用域中的数据,局部作用域中访问变量如果当前作用域中有可以直接访问,如果没有就向上级作用域中访问。
作用域链本质上是底层的变量查找机制。从当前作用域开始查找,如果当前作用域没有就往上一级作用域查找,一直查找直到顶层全局作用域。但子作用域能访问父作用域,但是父作用域访问不了子作用域。作用域链就像链子一样,是按照从小到大的顺序查找的。
闭包是指有权访问另一个函数作用域中变量的函数。即一个作用域可以访问另外一个函数内部的局部变量。
一个函数A,return其内部的函数B,被return出去的B函数能够在外部访问A函数内部的变量,这时候就形成了一个B函数的变量背包,A函数执行结束后这个变量背包也不会被销毁,并且这个变量背包在A函数外部只能通过B函数访问。
闭包形成的原理:作用域链,当前作用域可以访问上级作用域中的变量。
闭包解决的问题:能够让函数作用域中的变量在函数执行结束之后不被销毁,同时也能在函数外部可以访问函数内部的局部变量。 延伸了变量的作用范围,保存局部上下文环境。防止全局变量污染,将私有上下文保护起来,使用立即执行函数形成闭包。
闭包带来的问题:由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。
闭包的应用:防抖、节流,能够模仿块级作用域,能够实现柯里化,在构造函数中定义特权方法、Vue中数据响应式Observer中使用闭包等。
柯里化(Currying)指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。
防抖函数debounce:应用在input框中
function debounce( fun, wait ){
let timer = null ;
return function () {
if ( timer ) clearTimeout ( timer );
timer = setTimeout (() => {
fun.call ( this, arguments )
timer = null ;
} , wait )
}
}
节流函数throttle:应用在拖拽、scroll
function throttle ( fn , wait ){
let timer = null ;
return function () {
if ( timer ) return ;
timer = setTimeout (() => {
fn.apply ( this, arguments )
timer = null
}, wait )
}
}
5.同源策略:
同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域;
为了保护用户数据安全,同源限制策略限制了以下几种行为:
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 和 js 对象无法获得
- ajax 请求不能发送
-
- 防止恶意网页可以获取其他网站的本地数据。
- 为了防止恶意网站iframe其他网站的时候,获取数据。
- 为了防止恶意网站在自已网站有访问其他网站的权利,以免通过cookie免登,拿到数据。
6.跨域问题:
什么是跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。协议、域名、端口号不同,称之为跨域。(网页和后端)
解决跨域:JSONP、CORS、代理
都是让请求发给代理服务器,静态页面和代理服务器是同源的,然后代理服务器再向后端服务器发请求,服务器和服务器之间不存在同源限制。
7.从输入URL到页面展示发生了什么
网络进程拿到url后,先会进行DNS域名解析得到IP地址,如果请求协议是HTTPS,那么还需要建立TLS连接然后拿到ip地址;接下来就是利用IP地址和服务器建立TCP连接,进行三次握手,向服务器发送请求,拿到html文件。当浏览器的网络线程收到html文档后,会产生一个渲染任务,并将其传递给渲染主线程的消息队列。在事件循环机制的作用下,渲染主线程取出消息队列中的渲染任务,开启渲染流程。
整个渲染流程分为多个阶段,每个阶段都有明确的输入和输出,上一个阶段的输出会成为下一个阶段的输入。解析过程中为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载html中的外部 css文件和外部的js文件。
如果主线程解析到 Link 位置,此时外部的 css 文件还没有下载解析好,主线程不会等待,继续解析后续的html。这是因为下载和解析 css 的工作是在预解析线程中进行的。这就是 css 不会阻塞 html解析的根本原因。如果主线程解析到 script 位置,会停止解析html,转而等待 JS文件下载好,并将全局代码解析执行完成后,才能继续解析html。这是因为 JS 代码的执行过程可能会修改当前的 DOM ,所以 DOM 树的生成必须暂停。这就是JS会阻塞 HTML 解析的根本原因。
生成dom树和cssom树之后,会将dom树可见的结点添加到渲染树中,根据生成的渲染树进行布局,接着生成分层树,合成线程会把分层树的图层变成图块,GPU的栅格化把图块变成位图,然后保存在GPU的进程中。栅格化完成之后,浏览器进去GPU进程里取出页面内容显示在屏幕上,这就完成了渲染阶段)
预解析(面试考核点)
- js引擎运行 js 分为两步:预解析、代码运行
- 预解析:js 引擎会把 js 里面所有的 var 还有 function 提升到当前作用域的最前面。
- 代码执行:按照代码书写的顺序从上往下执行
- 预解析分为 变量预解析(var提升) 和 函数预解析(函数提升),若变量和函数都有时,先提升变量,再函数。
8.浏览器事件循环模型(宏任务和微任务)
事件循环又叫做消息循环,是浏览器渲染主线程的工作方式。在 chrome 的源码中,它开启一个不会结束的 for 循环,每次循环从消息队列中取出第一个任务执行,而其他线程只需要在合适的时候将任务加入到队列未尾即可。
根据 W3C官方的解释,每个任务有不同的类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪一个队列的任务。但浏览器必须有一个微队列,微队列的任务一定具有最高的优先级,必须优先调度执行。
宏任务: ( setTimeout、setInterval )
宏任务是JavaScript中最原始的异步任务,包括setTimeout、setInterVal、ajax等,在代码执行环境中按照同步代码的顺序,逐个进入工作线程挂起,再按照异步任务到达的时间节点,逐个进入异步任务队列,最终按照队列中的顺序进入函数执行栈进行执行。
微任务: ( Promise.then catch finally 、nextTick)
每⼀个宏任务执行前,程序会先检测是否有当次事件循环未执行的微任务,优先清空本次的微任务后,再执行下⼀个宏任务,每⼀个宏任务内部可注册当次任务的微任务队列,在下⼀个宏任务执行前运行,微任务也是按照进入队列的顺序执行的。
总结:
- 默认的同步代码按照顺序从上到下,从左到右运行,运行过程中注册本次的微任务和后续的宏任务:
- 执行本次同步代码中注册的微任务,并向任务队列注册微任务中包含的宏任务和微任务;
- 将下⼀个宏任务开始前的所有微任务执行完毕;
- 执行最先进入队列的宏任务,并注册当次的微任务和后续的宏任务,宏任务会按照当前任务队列的队尾继续向
下排列。
9.Vue的双向绑定原理和diff算法
vue2:
mvvm 双向绑定,采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来 劫持各个属性的 setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
响应式原理:获取属性值会触发getter方法,设置属性值会触发setter方法,在setter方法中调用修改dom的方法。
vue3:
通过 Proxy 来劫持数据,当数据发⽣变化时发出通知。
Vue3.0 是通过Proxy实现的数据双向绑定,Proxy是ES6中新增的一个特性,实现的过程是在目标对象之前设置了一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。在Vue2中,Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应,但Proxy则可以劫持整个数组对象,并返回一个新对象。
diff算法
首先,代码初次运行,会走生命周期,当生命周期走到created到beforeMount之间的时候,会编译template模板成render函数。然后当render函数运行时,h函数被调用,而h函数内调用了vnode函数生成虚拟dom,并返回生成结果。故虚拟dom首次生成。(h函数)
diff是采用先序深度优先遍历的方式进行节点比较的,即当比较某个节点时,如果该节点存在子节点,那么会优先比较他的子节点,直到所有子节点全部比较完成,才会开始去比较该节点的下一个同层级节点。
比较根节点的选择器和key是否相同,如果不同则执行替换操作,此操作是在patch函数中进行的。如果节点相同,那么进去第二部分,即比较两个节点的属性是否相同,节点是否存在文本,文本是否相同。是否存在子节点,子节点是否相同。这部分主要在patchVnode中执行。
最后,主要在updateChildren函数中执行,主要用于比较某个节点下的子节点差异。
diff算法就是用于比较新旧两个虚拟dom之间差异的一种算法,可以帮助我们在页面渲染的时候,计算出Virtual DOM真正变化的部分,并只针对该部分进行的原生DOM操作,而不是渲染整个页面,从而保证了每次操作后,页面的高效渲染。
vue响应式与diff算法
当项目非常大的时候,dom树是非常复杂的,如果每次一个小小的改动,就要通过diff算法去精确找到改动的地方,这个计算量是非常大,产生的性能损耗也巨大,就需要结合vue响应式解决。
vue通过Object.defineproperty的数据劫持,会劫持到每一个状态数据,给他们加上getter,setter。并且创建一个发布者Dep,同时,会给依赖这个状态数据的每个依赖者添加一个订阅者watcher。这样,当数据发生变化时,会触发对应的setter,从而Dep会发布通知,通知每一个订阅者watcher,然后watcher更新对应的数据。但是,如果任何一个数据的依赖我都增加一个watcher。那么项目中的watcher数量是会非常庞大的。细粒度太高,会带来内存和依赖关系维护的巨大消耗。这样一种情况下,vue采用响应式+diff的方式。通过响应式的getter,setter快速知道数据的变化发生在哪个组件中,然后组件内部再通过diff的方式去获取更详细的更新情况,并更新数据。
- Session 和 Cookie
Session 是存储在服务器端的,Cookie 是存储在客户端的。
Cookie:
-
- HTTP 是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息):每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。而这个状态需要通过 cookie 或者 session 去实现。
- cookie 存储在客户端: cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。
- cookie 是不可跨域的: 每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的(靠的是 domain)。
Session:
-
- 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session
- 请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器
- 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
- 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
10.HTML5新增了那些标签
本地存储:Window.sessionStorage 和 Window.localStorage
新增选择器:document.querySelector、document.querySelectorAll
拖拽释放(Drag and drop) API
多媒体标签:video 和 audio
语意化标签:article、footer、header、nav、section、aside
增强input类型表单控件:date、time、email、url、search、calendar、tel、color限制输入的内容
离线应用 manifest
桌面通知 Notifications
地理位置 Geolocation
多任务 webworker
全双工通信协议 websocket
历史管理 history
跨域资源共享(CORS) Access-Control-Allow-Origin
页面可见性改变事件 visibilitychange
跨窗口通信 PostMessage
Form Data 对象
绘画 canvas
11.HTTP在哪一层
应用层
TCP在传输层
12. HTTP与HTTPS的区别
- HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。
- HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- HTTP 的连接很简单,是无状态的。HTTPS 协议是由 HTTP+SSL 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)
https:对称加密具有解密速度快,性能高的特点 ,也是 HTTPS 最终采用的加密形式 。
13.axios特点
Axios是一个基于promise的 HTTP 库,简单的讲就是可以发送get、post等请求,可以用在浏览器和node.js 中。
- 支持Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换JSON数据
- 客户端支持防御XSRF攻击
五种axios的请求方法:
get、post、put、patch、delete
put/patch: 编辑、更新数据,区别如下:
put: 是要讲所有数据全部推送到后端 (一般用于更新)
patch: 只将修改的数据推送到后端
delete: 删除数据
14.get和post请求区别
- get请求一般是去获取数据(其实也可以提交,但常见的是获取数据);
post请求一般是去提交数据。
- get因为参数会放在url中,所以隐私性,安全性较差,请求的数据长度是有限制的,不同的浏览器和服务器不同,一般限制在 2~8K 之间,更加常见的是 1k 以内;post请求是没有的长度限制,请求数据是放在body中;
- get请求刷新服务器或者回退没有影响,post请求回退时会重新提交数据请求。
- get请求可以被缓存,post请求不会被缓存。
- get请求会被保存在浏览器历史记录当中,post不会。
- get请求可以被收藏为书签,因为参数就是url中,但post不能,它的参数不在url中。
- get请求只能进行url编码(appliacation-x-www-form-urlencoded),post请求支持多种(multipart/form-data等)。
15.vue的data为什么是一个函数
- 如果data是一个函数的话,这样每复用一次组件,就会返回一份新的data(类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据)
- Object是引用数据类型,里面保存的是内存地址,单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。
16.是否自己封装过组件
分页器、日历
17.http的状态码
1XX:信息状态码
2XX:成功状态码
3XX:重定向
4XX:客户端错误
5XX:服务端错误
18.this指向问题
普通函数调用:window;
构造函数调用:实例对象,原型对象里面的方法也是指向实例对象
对象方法调用:该方法所属对象
事件绑定方法:绑定事件对象
定时器函数:window
立即执行函数:window
19. 如何理解JS的异步?
JS是一门单线程的语言,这是因为它运行在浏览器的渲染主线程中,而渲染主线程只有一个。渲染主线程承担着诸多工作,包括渲染页面、执行 JS等。如果使用同步的方式,就极有可能导致主线程产生阻塞,从而使得消息队列中的很多其他任务无法得到执行。这样一来,一方面会导致繁忙的主线程白白的消耗时间,另一方面导致页面无法及时更新,给用户造成卡死现象。
所以浏览器采用异步的方式来避免。具体做法是当某些任务发生时,比如计时器、网络请求、事件监听,主线程将任务交给其他线程去处理,自身立即结束任务的执行,转而执行后续代码。当其他线程完成时,将事先传递的回调函数包装成任务,加入到消息队列的末尾排队,等待主线程调度执行。在这种异步模式下,浏览器永不阻塞,从而很大限度的保证了单线程的流畅运行。
20. 事件流
- 事件流是事件完整执行过程中的流动路径。事件流分为两种:事件捕获和事件冒泡。
事件捕获:是从window开始向下捕获,一直至目标节点; DOM.addEventListener(事件类型,事件处理函数,是否捕获)
事件冒泡:是从目标节点开始向上传播,一直到window。阻止冒泡:事件对象.stopPropagation();
- 事件委托的原理是什么?
给父元素注册事件,触发子元素时,利用冒泡原理冒泡到父元素身上,从而触发父元素事件,影响设置每个子节点。( ul身上的 e.target 指向的是li.)。
好处:可以减少注册次数,提高程序性能。
21.原型链
原型就是一个普通对象,它是构造函数的实例共享属性和方法,所有实例中引用的原型都是同一个对象,使用prototype可以把方法挂在原型上。而_proto_指向构造函数的原型。
22.vue-cli 中 npm run serve 做了什么
- 命令依赖链:npm run serve -> node_modules.binue-cli-service.cmd serve -> node node_modules@vuecli-serviceinue-cli-service.js serve
- vue-cli 的开发环境实际上使用了 webpack-dev-server 模块实现。
- vue.config.js 的配置会作用到 webpack-dev-server 上。
npm run serve会运行package.json里面script下的serve指令,即webpack-dev-server模块,也就相当于输入了npx webpack-dev-server,然后就会去node modules下寻找webpack-dev-server这个包,找到里面的package根据里面的main去运行程序,这个在webpack里面是一个devServer服务器,应该是Koa写的,配置信息在webpack.config.js下的devServer里面,一般是不会进行跨域处理的,不过可以手动在devServer下配置proxy,进行返向代理,来解决这个跨越问题。
23.node.js是干什么的
Node.js是一个基于Chrome V8引擎的JavaScript运行环境。浏览器是JavaScript的前端运行环境,Node.js是JavaScript的后端运行环境。
Node.js是一项服务器技术。我们都知道客户端提出服务请求,而服务器端负责处理请求并提供服务。而对于互联网来说,在Node.js之前JavaScript是一项完全的客户端技术,被用于浏览器中实现各种动画,对DOM的操作等等。而后端,即服务端则是由PHP、Python、Ruby、Java等等语言来实现。
Node.js的出现,使得前后端使用同一种语言,统一模型的梦想得以实现。
所以Node.js到底解决了JavaScript的什么痛点和问题?
- 更好的组织代码,提升复用性。
- 处理文件与数据库。
- 与互联网进行沟通,以标准化的格式处理请求并发送回答。
24.flex :1; 什么含义
flex:1;的值是 flex-grow:1; flex-shrink:1; flex-basis:0%;
意思就是:
flex-grow(设置了对应元素的增长系数);
flex-shrink(指定了对应元素的收缩规则,只有在所有元素的默认宽度之和大于容器宽度时才会触发);
flex-basis(指定了对应元素在主轴上的大小)。
元素占据剩余宽度的 1 份,收缩比例为 1,因为 flex-basis 具有最高优先级,元素首次分配宽度如果父元素有设置宽度,则为 0%;父元素没有设置宽度,则和 auto 效果一致。
25.cookie localStorage sessionStorage 的异同
- 都是浏览器存储
- 都存储在浏览器本地
- 区别:
-
- cookie由服务器写入, sessionStorage以及localStorage都是由前端写入
- cookie的生命周期由服务器端写入时就设置好的,localStorage是写入就一直存在,除非手动清除,sessionStorage是由页面关闭时自动清除
- cookie存储空间大小约4kb, sessionStorage及localStorage空间比较大,大约5M
- 3者的数据共享都遵循同源原则,sessionStorage还限制必须是同一个页面
- 前端给后端发送请求时,自动携带cookie,session 及 local都不携带
- cookie一般存储登录验证信息或者token,localStorage常用于存储不易变动的数据,减轻服务器压力,sessionStorage可以用来监测用户是否是刷新进入页面。
26.bigInt
bigInt 是一种内置对象,它提供了一种方法来表示大于 2^53-1的整数。这原本是 Javascript 中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。
1n === BigInt(1) 即要表示大数可以在整数末尾直接加n
Symbol是ES6新出的一种数据类型,这种数据类型的特点就是没有重复的数据,可以作为object的key。
27.Promise
Promise的作用:Promise是异步微任务,解决了异步多层嵌套回调的问题,让代码的可读性更高,更容易维护。
Promise使用:Promise是ES6提供的一个构造函数,可以使用Promise构造函数new一个实例,Promise构造函数接收一个函数作为参数,这个函数有两个参数,分别是两个函数 resolve和reject,resolve将Promise的状态由等待变为成功,将异步操作的结果作为参数传递过去;reject则将状态由等待转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。实例创建完成后,可以使用then方法分别指定成功或失败的回调函数,也可以使用catch捕获失败,then和catch最终返回的也是一个Promise,所以可以链式调用。
Promise的特点:
- 对象的状态不受外界影响(Promise对象代表一个异步操作,有三种状态)- pending(执行中) - Resolved(成功,又称Fulfilled) - rejected(失败) 其中pending为初始状态,Resolved和rejected为结束状态;
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。 Promise对象的状态改变,只有两种可能: - 从Pending变为Resolved - 从Pending变为Rejected
- resolve 方法的参数是then中回调函数的参数,reject 方法中的参数是catch中的参数;
- then 方法和 catch方法 只要不报错,返回的都是一个Resolved状态的promise。
Promise的其他方法:
- Promise.resolve() :返回的Promise对象状态为Resolved,并且将该value传递给对应的then方法。
- Promise.reject():返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法。
- Promise.all():返回一个新的promise对象,该promise对象在参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。
- Promise.any():接收一个Promise对象的集合,当其中的一个 promise 成功,就返回那个成功的promise的值。
- Promise.race():当参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。
const promise = new Promise(function(resolve, reject) {
try {
// throw new Error
let a = 18 + 3
resolve(a)
} catch (e) {
reject(e)
}
})
promise.then((val) => {
console.log(val)
}).catch((err) => {
console.log(err);
}) //链式调用
28.axios二次封装
Axios是一个基于promise的 HTTP 库,简单的讲就是可以发送get、post等请求,可以用在浏览器和node.js 中。
为什么要用到axios二次封装:
- 主要是要用到请求拦截器和响应拦截器;
- 请求拦截器:可以在发请求之前可以处理一些业务;比如为每个请求带上相应的参数(token,时间戳等)。
响应拦截器:当服务器数据返回以后,可以处理一些事情。比如对返回的状态进行判断(token是否过期)。
axios.get('url?参数1=z值&参数2=值')
axios.post('url',
参数1:值,
参数2:值
)
axios({
url:'请求的路径",
method:'请求的方式,默认是get',
params:{ } //get请求方式:前端给后端传递的数据
data:{ } //post请求方式:前端给后端传递的数据
headers:{ } //自定义请求头
timeout: 1000 // 如果请求时间超过timeout的值,则请求会被中断
responseType:'默认是json格式数据' //响应的数据类型
})
29.bind apply call 之间的异同
- call、apply、bind的作用都是改变函数运行时的this指向。
- bind和call、apply在使用上有所不同,bind在改变this指向的时候,返回一个改变执行上下文的函数,不会立即执行函数,而是需要调用该函数的时候再调用即可,但是call和apply在改变this指向的同时执行了该函数。
- bind只接收一个参数,就是this指向的执行上文。 call、apply接收多个参数,第一个参数都是this指向的执行上文,后面的参数都是作为改变this指向的函数的参数。
- call和apply参数的格式不同,call是一个参数对应一个原函数的参数,但是apply第二个参数是数组,数组中每个元素代表函数接收的参数,数组有几个元素函数就接收几个元素。
30.JavaScript有几种方法判断变量的类型
JavaScript有4种方法判断变量的类型,分别是typeof、instanceof、Object.prototype.toString.call()(对象原型链判断方法)、 constructor (用于引用数据类型) 。
typeof:一般被用于判断基本数据类型,对于引用数据类型不能准确判断。我们可以利用 typeof 来判断 number, string, object, boolean, function, undefined, symbol 这七种类型,这种判断能帮助我们搞定一些问题;但是,typeof 在判断一个 "object" 数据时判断出这个数据是 object,,而不能具体到是哪一种 object。null 也被判断为 object 类型。
instanceof:主要的作用就是判断一个实例是否属于某种类型,只能判断引用数据类型,不能判断基本数据类型。obj instanceof Array instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false。
- 无法判断 null、undefined
- 无法判断字面量形式数据的数据类型
constructor:构造函数可以判断除了undefined和null之外的任何数据类型,解决了instanceof的问题。
Object.prototype.toString.call():适用于所有类型的判断检测,检测方法是Object.prototype.toString.call(数据) 返回的是该数据类型的字符串。 这四种判断数据类型的方法中,各种数据类型都能检测且检测精准的就是Object.prototype.toString.call()这种方法。
31.Vue中的hooks
vue3 中的 hooks 就是将文件的一些单独功能的js代码进行抽离出来,放到单独的js文件中,或者说是一些可以复用的公共方法/功能。类似于vue2 的mixin,但是相对 mixins 而言, hooks 更清楚复用功能代码的来源,更清晰易懂。
32.组件间的通信方式
- props: 用于父子组件通信。 props 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 props 只读,不可被修改,所有修改都会失效并警告。
- Vue3中组件间通信provide(父辈)inject(子)
- emit向父组件传递数据,可以实现子给父通信
- 全局事件总线:emit发送出去,在接收的组件中用$on进行接收。
- pubsub-js:vue当中几乎不用 全能
- 插槽 父子通信
- vuex,创建数据存储在store中,在合适的时候调用里面的属性取数据,实现通信。
33.手撕深拷贝
function deepClone(obj) {
if (typeof obj !== 'object' || obj == null) {
return obj;
}
let result;
if (obj instanceof Array) {
result = [];
} else {
result = {};
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result;
}
34.CSS的优先级规则
先级:继承或者* < 元素选择器、伪元素选择器 < 类选择器、伪类选择器、属性选择器< ID选择器 < 行内样式style < !important
一定要优先考虑使用样式规则的优先级来解决问题而不是 !important
35.null和underfined的区别
undefind是全局对象的一个属性,当一个变量没有被赋值或者一个函数没有返回值或者某个对象不存在某个属性却去访问或者函数定义了形参但没有传递实参,这时候都是undefined。undefined通过typeof判断类型是'undefined'。undefined == undefined,undefined === undefined。
null代表对象的值未设置,相当于一个对象没有设置指针地址就是null。null通过typeof判断类型是'object'。null === null null == null null == undefined null !== undefined undefined 表示一个变量初始状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态。在实际使用过程中,不需要对一个变量显式的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可。 让一个变量为null,直接给该变量赋值为null即可。
36.map和forEach的区别
1、forEach()方法没有返回值,而map()方法有返回值。
2、forEach()会修改原来的数组。而map()方法会得到一个新的数组并返回。forEach遍历通常都是直接引入当前遍历数组的内存地址,生成的数组的值发生变化,当前遍历的数组对应的值也会发生变化。map遍历的后的数组通常都是生成一个新的数组,新的数组的值发生变化,当前遍历的数组值不会变化。
3、map 的速度大于forEach。
4、相同点是二者遍历的都是数组
37.computed和watch的区别
使用场景:computed适用于一个数据受多个数据影响使用;watch适合一个数据影响多个数据使用。
区别:computed属性默认会走缓存,只有依赖的数据发生变化,才会重新计算,不支持异步,有异步导致数据发生变化时,无法做出相应改变;watch不依赖缓存,一旦数据发生变化就直接触发响应操作,支持异步。
38.BFC是什么?BFC用途?怎样触发BFC
BFC全称是Block Formatting Context,意思就是块级格式化上下文。可以把BFC看做一个容器,容器里边的元素不会影响到容器外部的元素。
BFC有什么特性?
- BFC是一个块级元素,块级元素在垂直方向上依次排列。
- BFC是一个独立的容器,内部元素不会影响容器外部的元素。
- 属于同一个BFC的两个盒子,外边距margin会发生重叠,并且取最大外边距。
BFC作用:
- 解决当父级元素没有高度时,子级元素浮动会使父级元素高度塌陷的问题。
- 解决子级元素外边距会使父级元素塌陷的问题 。
如何创建BFC?
给父级元素添加以下任意样式overflow: hidden; display: flex; display: inline-flex; display: inline-block; position: absolute; ``position: fixed;
39.浮动
浮动的作用:
设置浮动的图片,可以实现文字环绕图片,设置了浮动的块级元素可以排列在同一行,设置了浮动的行内元素可以设置宽高,同时可以按照浮动设置的方向对齐排列盒子。
设置浮动元素的特点:
设置了浮动,该元素脱标,元素不占位置;
浮动可以进行模式转换(行内块元素) 浮动造成的影响,使盒子脱离文档流,如果父级盒子没有设置高度,需要被子盒子撑开,那么这时候父级盒子的高度就塌陷了,同时也会造成父级盒子后面的兄弟盒子布局受到影响。如果浮动元素后面还有其他兄弟元素,其他兄弟元素的布局也会受到影响。
清除浮动的方法:
- 伪元素清除浮动:给浮动元素父级增加
.clearfix::after { content: ''; display: table; clear: both; }/*兼容IE低版本 */.clearfix { *zoom: 1; } - overflow:hidden:给浮动元素父级增加
`overflow:hidden`属性 - 额外标签法:给浮动元素父级增加标签,必须为块级元素
40.箭头函数
箭头函数相当于匿名函数,简化了函数定义。
箭头函数有两种写法,当函数体是单条语句的时候可以省略{}和return。另一种是包含多条语句,不可以省略{}和return。
使用箭头函数,函数内部没有arguments,arguments 会指向 windows
箭头函数最大的特点就是没有this,所以this是从外部获取,就是继承外部的执行上下文中的this,由于没有this关键字所以箭头函数也不能作为构造函数,箭头函不能改变this指向,通过 call() 或 apply() 方法调用一个函数时,只能传递参数(不能绑定this),第一个参数会被忽略。
箭头函数也没有原型和super。不能使用yield关键字,因此箭头函数不能用作Generator函数。不能返回直接对象字面量。
箭头函数函数适用场景:
- 简单的函数表达式,内部没有this引用,没有递归、事件绑定、解绑定,适用于map、filter等方法中,写法简洁
var arr = [1,2,3];
var newArr = arr.map((num)=>num*num)
- 内层函数表达式,需要调用this,且this应与外层函数一致时
let group = {
title: "Our Group",
students: ["John", "Pete", "Alice"],
showList() {
this.students.forEach( student => alert(this.title + ': ' + student)
);
}
};
group.showList();
41.JS变量提升
变量提升是指JS的变量和函数声明会在代码编译期,提升到代码的最前面。
变量提升成立的前提是使用var关键字进行声明的变量,并且变量提升的时候只有声明被提升,赋值并不会被提升,同时函数的声明提升会比变量的提升优先。
变量提升的结果,可以在变量初始化之前访问该变量,返回的是undefined。在函数声明前可以调用该函数。
let 和 const 变量不提升。
42.$nextTick
nextTick是等待下一次DOM更新刷新的工具方法。
Vue在更新DOM时是异步执行的,在修改数据后,Vue不会立刻更新DOM,而是开启一个队列,把组件更新函数保存在队列中,在同一事件循环中发生的所有数据变更会异步的批量更新。所以修改完数据,立即在方法中获取DOM,获取的仍然是未修改的DOM。
$nextTick的作用是:该方法中的代码会在当前渲染完成后执行,就解决了异步渲染获取不到更新后DOM的问题了。
nextTick本质是返回一个Promise
应用场景:
- 在钩子函数created()里面想要获取操作Dom的时候,把操作DOM的方法放在$nextTick中;
- 响应式数据变化后获取DOM更新后的状态,比如希望获取列表更新后的高度。
43.token放在cookie中
token一般是用来判断用户是否登录的,它内部包含的信息有:
-
- uid(用户唯一的身份标识);
- time(当前时间的时间戳);
- sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)
token是否过期,由后端来判断,如果token失效,就让后端在接口中返回固定的状态表示token 失效,需要重新登录,再重新登录的时候,重新设置 cookie 中的 token 就可以。
44.v-show和v-if的区别和使用
v-show控制的元素无论是true还是false,都会被渲染出来。而v-if只有在状态为true的时候才会被渲染出来,状态为false的时候不会出现在dom树中。
应用:切换频率高的时候使用v-show,可以提高效率;切换频率低的时候使用v-if,就不会一会添加节点一会删除节点。
45.async和defer的区别
有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。
异步脚本不能保证按照它们在页面中出现的次序执行。
有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。
推迟的脚本原则上按照它们被列出的次序执行。
46.JSON
JSON是一种基于文本的数据格式,遵循 JavaScript 对象语法。可以通过网络传输数据,它基本上只是一个扩展名为 .json 的文本文件,以及一个 MIME 类型的 application/json。
JSON.stringify(arr), 将一个 JavaScript 对象或值转换为 JSON 字符串JSON.parse(xxx), 方法用来解析JSON字符串,构造由字符串描述的值或对象
47.block/inline/inline-block的区别
当display设置为block时,盒子独占一行,并且可以设置宽和高,因为block是块级元素。
而 inline 是把当前元素设置为行内元素,行内元素不能设置宽高,而且是一个接一个排列。
inline-block与 inline 类似,但 inline-block 可以设置宽和高。
48.手撕isEqual
function isEqual (obj1, obj2) {
if ( !isObject(obj1) || !isObject(obj2)) {
return obj1 === obj2; // 其中有一个数据是基本数据类型
}
let obj1Keys = Object.keys(obj1)
let obj2Keys = Object.keys(obj2)
if (obj1Keys.length !== obj2Keys.length) {
return false;
}
for (let key in obj1) {
let res = isEqual(obj1[key], obj2[key])
if (!res) {
return false;
}
}
return true;
}
49.前端性能优化
- 加载优化:减少http请求数
合并图片,使用雪碧图/精灵图,从而减少http请求数,充分利用缓存来提升性能。
异步加载第三方资源:第三方资源不可控,会影响页面加载,所以要异步加载第三方资源。
尽量减少不必要的 cookie。
- 图片优化
尽可能使用PNG格式的图片,体积较小。
同时在代码中进行图片的懒加载。
避免img、iframe等标签的src属性为空:空src会重新加载当前页面,影响速度和效率。
- 样式表和JS文件的优化
将css样式表文件放到文件的头部使用 link 引入,这样可以让CSS样式表尽早完成下载。
对应js脚本一般放在尾部并使用异步方式加载,这样可以最大限度减少样式和脚本对页面的阻塞。
- 脚本优化
复杂动画效果,使用绝对定位让其脱离文档流,避免循环DOM元素,用transform:translate 代替 position left、right...以此来尽量减少回流和重绘。
缓存.length的值:每次.length计算使用一个变量保存值。
尽量使用事件委托:不给每个子节点单独设置事件监听器,而是设置在其父节点上,然后利用冒泡原理设置每个子节点,避免批量绑定事件以此来减少内存消耗和DOM操作。
尽量使用id选择器:id选择器选择元素是最快的,具有唯一性,灵活性和优先性的优点。
- 代码优化
优化高频事件:对滚动事件等尽量使用节流防抖等来进行限制。
50.js数组有哪些方法
arr.concat()连接两个或更多的数组,返回新数组,不改变原数组。
arr.join()把数组所有元素放入一个字符串,可分隔可不分隔,不改变原数组。常见:join('');
arr.push() 添加
arr.pop() 删最后一个
arr.shift() 删第一个
arr.unshift() 加第一个
arr.splice() 分割 arr.splice(0,1,'学习') // 表示从数组第0个开始删,删1个,并把它换为“学习”
arr.sort() 比较排序
arr.reverse() 颠倒位置
arr.toString()转为字符串
arr.indexOf()返回第一个位置的下标,否则-1;
arr.filter()返回满足条件的新数组,原数组不变;
arr.forEach()遍历数组,调用指定函数;
arr.map()调用数组每个元素传递给指定参数,返回新数组,不改变原数组;
arr.every()判断数组中是否满足项,所有满足返回true;
arr.some()判断数组中是否满足项,有一个满足返回true;
arr.reduce()两个参数,第一项开始遍历到最后,执行函数内部条件;
Array.of()将参数值作为元素转换为数组。
Array.from()将伪数组转换为数组。
arr.keys()遍历键名;
arr.values()遍历键值。
总结:
原数组改变的方法:push pop shift unshift reverse sort splice
不改变原数组的方法:concat map filter join every some indexOf slice forEach
51.重绘和回流
重绘是当页面中元素样式的改变不影响它在文档流中的位置时,会将样式赋予元素并重新绘制它,这个过程称为重绘。
回流是当渲染树中部分或者全部元素的尺寸、结构,或某些属性发生改变时,路蓝旗重新渲染部分或全部文档的过程称为回流。
元素的颜色、透明度、text-align等的改变会引起重绘,添加或删除可见DOM元素以及文本变化、元素尺寸变化等会引起回流。
回流一定会触发重绘,而重绘不一定会触发回流。
52.CSS盒模型
在HTML页面中所有元素都可以看成一个盒子。
盒子组成:内容content、内边距padding、边框border、外边距margin
盒模型类型:
标准盒模型:margin + border + padding + conten ;
IE盒模型:margin + content ( padding + border ) ;
控制盒模型模式:box-sizing : content-box(默认标准盒模型)、border-box(IE盒模型);