该文记录本人在面试中遇到的面试题 。
1、盒子模型是什么?
盒子模型的范围包括margin、border、padding、content。盒子模型有两种:W3C标准盒子模型、IE盒子模型。
延伸说出他们的区别:
(1)W3C标准盒子模型:
height:只包含content部分内容区域
(2)IE盒子模型:
height:content+padding+border部分区域
2、介绍一下js对象
3、http状态码
HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为5种类型:
常见的状态码:
- 200 - 请求成功
- 301 - 资源(网页等)被永久转移到其它URL
- 404 - 请求的资源(网页等)不存在
- 500 - 内部服务器错误
4、vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
Vuex 主要有四部分:
- state:包含了
store
中存储的各个状态。 - getter: 类似于 Vue 中的计算属性,根据其他 getter 或 state 计算返回值。
- mutation: 一组方法,是改变
store
中状态的执行者,只能是同步操作。在组件中可以使用this.$store.commit('xxx') - action: 一组方法,其中可以包含异步操作。Action 提交的是 mutation,而不是直接变更状态。Action 通过
store.dispatch
方法触发。 - model:Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。
5、Vue 组件间通信有哪些方式?
(1)props/$emit
父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。
(2)$emit/$on
这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级
var Event=new Vue();
Event.$emit(事件名,数据);
Event.$on(事件名,data => {});
(3) vuex
(4) $attrs/$listeners
$attrs
:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
$listeners
:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
(5)provide/inject
Vue2.2.0新增API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
(6)$parent/$children 与 ref
ref
:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
$parent
/ $children
:访问父 / 子实例
参考:https://segmentfault.com/a/1190000019208626
6、防抖和节流函数
函数节流(throttle)与 函数防抖(debounce)都是为了限制函数的执行频次,以优化函数触发频率过高导致的响应速度跟不上触发频率,出现延迟,假死或卡顿的现象。
(1)函数防抖(debounce):
触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。
原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
(2)函数节流(throttle):
高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。
原理是通过判断是否有延迟调用函数未执行。意思就是说,假设一个用户一直触发这个函数,且每次触发小于既定值,函数节流会每隔这个时间调用一次
区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
用一句话总结防抖和节流的区别:防抖是将多次执行变为最后一次执行,节流是将多次执行变为每隔一段时间执行
7、webpack
8、事件冒泡和事件捕获
W3C制定的事件模型中,一次事件的发生包含三个过程: (1).事件捕获阶段,(2).事件目标阶段,(3).事件冒泡阶段。
事件捕获:当某个元素触发某个事件(如onclick),顶层对象document就会发出一个事件流,随着DOM树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程中,事件相应的监听函数是不会被触发的。
事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。
事件冒泡:从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被一次触发。
注意:所有的事件类型都会经历事件捕获但是只有部分事件会经历事件冒泡阶段,例如submit事件就不会被冒泡。
9、es6
10、typescript
11、服务器渲染
12、git
13、vue双向数据绑定原理
mvvm 双向绑定,采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()
来劫持各个属性的 setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
几个要点:
1、实现一个数据监听器 Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
2、实现一个指令解析器 Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
3、实现一个 Watcher,作为连接 Observer 和 Compile 的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
4、mvvm 入口函数,整合以上三者
具体步骤:
- 需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化
- compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
- Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是:
- 在自身实例化时往属性订阅器(dep)里面添加自己
- 自身必须有一个 update() 方法
- 待属性变动 dep.notice() 通知时,能调用自身的 update() 方法,并触发 Compile 中绑定的回调,则功成身退。
- MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过Observer来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。
14、浏览器时间循环机制(eventLoop)
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。
(1).Js为什么是单线程的?
设想一下如果JS是多线程的,现在有两个线程同时对同一个dom进行操作,一个删除该dom,一个修改该dom,
若同时下达这个两个命令,浏览器该如何执行?这样想Js为什么被设计成单线程就应该容易理解了。
(2).Js为什么需要异步?
如果js中不存在异步,只能自上而下执行,如果上一行解析时间很长,那么下面的代码就会被堵塞。
对于用户而言,堵塞就意味着卡死,用户体验极差。所以Js中存在异步。
(3).Js单线程又是如何实现异步的呢?
通过事件循环(event loop)实现异步。 理解了事件循环(event loop)机制,就理解了js引擎的执行机制。
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
执行机制:
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
Event Loop:
调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操作,就形成了事件循环。
上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。
异步任务又分:宏任务和微任务
而宏任务一般是:包括整体代码script,setTimeout,setInterval。
微任务:Promise,process.nextTick。
运行机制
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
(1)执行一个宏任务(栈中没有就从事件队列中获取)
(2)执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
(3)宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
(4)当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
(5)渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
具体细节可查看:
https://juejin.cn/post/6844903512845860872
15、computed和watch的区别
16、为什么vue中data必须是一个函数
Object是引用数据类型,如果不用function返回,每个组件的data都是内存的同一个地址,一个数据改变了其他也改变了;如果两个实例同事引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着该;
组件中的data
写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data
,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data
,就会造成一个变了全都会变的结果。
17、this指向
this
指的是函数运行时所在的环境
http://www.ruanyifeng.com/blog/2018/06/javascript-this.html
18、vuex中 mutation和action的区别和使用
action 和 mutations 也很类似,主要的区别在于mutations 只能是同步操作,,action 可以包含异步操作,而且可以通过 action 来提交 mutations
19、什么是闭包
函数执行后返回结果是一个内部函数,并被外部变量所引用,如果内部函数持有被执行函数作用域的变量,即形成了闭包。
可以在内部函数访问到外部函数作用域。使用闭包,一可以读取函数中的变量,二可以将函数中的变量存储在内存中,保护变量不被污染。而正因闭包会把函数中的变量值存储在内存中,会对内存有消耗,所以不能滥用闭包,否则会影响网页性能,造成内存泄漏。当不需要使用闭包时,要及时释放内存,可将内层函数对象的变量赋值为null。
20、watch、methods 和 computed 的区别?
watch 为了监听某个响应数据的变化。computed 是自动监听依赖值的变化,从而动态返回内容,主要目的是简化模板内的复杂运算。所以区别来源于用法,只是需要动态值,那就用 computed ;需要知道值的改变后执行业务逻辑,才用 watch。
methods是一个方法,它可以接受参数,而computed 不能,computed 是可以缓存的,methods 不会。computed 可以依赖其他 computed,甚至是其他组件的 data。
21、Vue 中 key 值的作用?
当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key 的作用主要是为了高效的更新虚拟DOM。
22、为什么需要 nextTick
Vue 是异步修改 DOM 的并且不鼓励开发者直接接触 DOM,但有时候业务需要必须对数据更改--刷新后的 DOM 做相应的处理,这时候就可以使用 Vue.nextTick(callback)这个 api 了。
23、虚拟DOM
虚拟DOM就是为了解决浏览器性能问题而被设计出来的。如前,若一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地一个JS对象中,最终将这个JS对象一次性attch到DOM树上,再进行后续操作,避免大量无谓的计算量。所以,用JS对象模拟DOM节点的好处是,页面的更新可以先全部反映在JS对象(虚拟DOM)上,操作内存中的JS对象的速度显然要更快,等更新完成后,再将最终的JS对象映射成真实的DOM,交由浏览器去绘制。
虚拟DOM与真实DOM的区别(注意:需不需要虚拟DOM,其实与框架的DOM操作机制有关):
-
虚拟DOM不会进行排版与重绘操作
-
虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分(注意!),最后并在真实DOM中进行排版与重绘,减少过多DOM节点排版与重绘损耗
-
真实DOM频繁排版与重绘的效率是相当低的
-
虚拟DOM有效降低大面积(真实DOM节点)的重绘与排版,因为最终与真实DOM比较差异,可以只渲染局部(同2)
24、jsonp原理
由于浏览器同源策略的限制,非同源下的请求,都会产生跨域问题,jsonp即是为了解决这个问题出现的一种简便解决方案。
同源策略即:同一协议,同一域名,同一端口号。当其中一个不满足时,我们的请求即会发生跨域问题。
其本质是利用了标签具有可跨域的特性,由服务端返回预先定义好的javascript函数的调用,并且将服务端数据以该函数参数的形式传递过来。
添加一个<script>标签,用于发起跨域请求;注意看请求地址后面带了一个callback=showData的参数;
showData即是回调函数名称,传到后台,用于包裹数据。数据返回到前端后,就是showData(result)的形式,因为是script脚本,所以自动调用showData函数,而result就是showData的参数。
<script>
function fuc(data){
console.log(data.name);
}
</script>
<script src="http://www.baidu.com/api.php?callback=fuc"></script>
jQuery ajax方式以jsonp类型发起跨域请求,其原理跟<script>脚本请求一样,因此使用jsonp时也只能使用GET方式发起跨域请求。跨域请求需要服务端配合,设置callback,才能完成跨域请求。
https://www.cnblogs.com/chiangchou/p/jsonp.html