2020.06-09 上午·湖南铭宗面试复盘
1. 关于Vue的两大核心是什么(问到双向绑定以及组件化开发)
Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据
- 输入框内容变化时,Data 中的数据同步变化。即 View => Data 的变化。
- Data 中的数据变化时,文本节点的内容同步变化。即 Data => View 的变化。
其中,View 变化更新 Data ,可以通过事件监听的方式来实现,所以 Vue 的数据双向绑定的工作主要是如何根据 Data 变化更新 View。
Vue 主要通过以下 4 个步骤来实现数据双向绑定的:
实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
Vue组件开发:在实际的Vue项目中,页面中每一个小块都是由一个个组件(.vue文件)组成,经过抽离后,然后再合并一起组成一个页面。
2.对Vue中的:key了解都有哪些,一般采用什么值去绑定:key,为什么不用index?
key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。Vue 的 diff 过程可以概括为:oldCh 和 newCh 各有两个头尾的变量 oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex,它们会新节点和旧节点会进行两两对比,即一共有4种比较方式:newStartIndex 和oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,如果以上 4 种比较都没匹配,如果设置了key,就会用 key 再进行比较,在比较的过程中,遍历会往中间靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。具体有无 key 的 diff 过程。
所以 Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速
更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。
更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快,源码如下:
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
3.使用过Vue中的哪些生命周期钩子,在created中和mounted里面发送Ajax请求有哪些区别?
可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。但是本人推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
-
能更快获取到服务端数据,减少页面 loading 时间;
-
ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
简单来说:如果你修改了某个dom中的数据,视图并不会立即更新。Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。,此时获取关于此dom的一切操作都是无效的,怎么办?在nextTick的回调中执行即可。nextTick,则可以在回调中获取更新后的 DOM。
4.常用的的跨域都有哪些方式? (九种方式)
什么是同源策略及其限制内容?
同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略限制内容有
- Cookie、LocalStorage、IndexedDB 等存储性内容
- DOM 节点
- AJAX 请求发送后,结果被浏览器拦截了
但是有三个标签是允许跨域加载资源:
<img src=XXX>
<link href=XXX>
<script src=XXX>
- jsonp跨域
- cors跨域
- post Message
- websocket
- Node中间件代理
- nginx反向代理
- window.name + iframe
- location.hash + iframe
- document.domain + iframe
5.有没有修改过elementui的框架样式,对样式穿透有了解吗?
深度选择器deep
如果您希望scoped样式中的选择器“深入”,即影响子组件,则可以使用>>>组合子:
<style scoped>
.a /deep/ .b{ /* 第一种写法 */
}
.a >>> .b { /* 第二种写法 */
}
</style>
// less 写法
<style lang='less' scoped>
.a {
/deep/ .b{ /* less 写法 */
}
}
</style>
6.关于箭头函数this指向问题?
es5中的this要看函数在什么地方调用(即要看运行时),通过谁是最后调用它该函数的对象来判断this指向。但es6的箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined。箭头函数的 this 始终指向函数定义时的 this,而非执行时。
7.谈谈你对this、call、apply和bind的理解
每个函数都包含两个非继承而来的方法:apply()和 call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。
apply()
apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是arguments 对象。
function sum(num1, num2){
return num1 + num2;
}
function callSum1(num1, num2){
return sum.apply(this, arguments); // 传入 arguments 对象
}
function callSum2(num1, num2){
return sum.apply(this, [num1, num2]); // 传入数组
}
console.log(callSum1(10,10)); //20
console.log(callSum2(10,10)); //20
call()
call()方法与 apply()方法的作用相同,它们的唯一区别在于接收参数的方式不同。在使用call()方法时,传递给函数的参数必须逐个列举出来。
function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
return sum.call(this, num1, num2);
}
console.log(callSum(10,10)); //20
call()方法与 apply()方法返回的结果是完全相同的,至于是使用 apply()还是 call(),完全取决于你采取哪种给函数传递参数的方式最方便。
- 参数数量/顺序确定就用call,参数数量/顺序不确定的话就用apply。
- 考虑可读性:参数数量不多就用call,参数数量比较多的话,把参数整合成数组,使用apply。
bind()方法会创建一个函数的实例,其 this 值会被绑定到传给 bind()函数的值。意思就是 bind() 会返回一个新函数。例如:
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
call/apply与bind的区别
执行:
- call/apply改变了函数的this上下文后马上执行该函数
- bind则是返回改变了上下文后的函数,不执行该函数
function add (a, b) {
return a + b;
}
function sub (a, b) {
return a - b;
}
add.bind(sub, 5, 3); // 这时,并不会返回 8
add.bind(sub, 5, 3)(); // 调用后,返回 8
返回值:
- call/apply 返回fun的执行结果
- bind返回fun的拷贝,并指定了fun的this指向,保存了fun的参数。
7.Mysql的Mongodb有什么区别?
非关系型数据库:
非关系型数据库(感觉翻译不是很准确)称为 NoSQL,也就是 Not Only SQL,不仅仅是 SQL。非关系型数据库不需要写一些复杂的 SQL 语句,其内部存储方式是以 key-value 的形式存在可以把它想象成电话本的形式,每个人名(key)对应电话(value)。常见的非关系型数据库主要有 Hbase、Redis、MongoDB 等。非关系型数据库不需要经过 SQL 的重重解析,所以性能很高;非关系型数据库的可扩展性比较强,数据之间没有耦合性,遇见需要新加字段的需求,就直接增加一个 key-value 键值对即可。
关系型数据库:
关系型数据库以表格的形式存在,以行和列的形式存取数据,关系型数据库这一系列的行和列被称为表,无数张表组成了数据库,常见的关系型数据库有 Oracle、DB2、Microsoft SQL Server、MySQL等。关系型数据库能够支持复杂的 SQL 查询,能够体现出数据之间、表之间的关联关系;关系型数据库也支持事务,便于提交或者回滚。
8.说说Promise以及async和await?
Promise是 CommonJS 提出来的这一种规范,有多个版本,在 ES6 当中已经纳入规范,原生支持 Promise 对象,非 ES6 环境可以用类似 Bluebird、Q 这类库来支持。
Promise 可以将回调变成链式调用写法,流程更加清晰,代码更加优雅。
简单归纳下 Promise:三个状态、两个过程、一个方法,快速记忆方法:3-2-1
三个状态:pending、fulfilled、rejected
两个过程:
- pending→fulfilled(resolve)
- pending→rejected(reject)
一个方法:then
async 和 await
一个函数如果加上 async ,那么该函数就会返回一个 Promise
async function test() {
return "1"
}
console.log(test())
// -> Promise {<resolved>: "1"}
async 就是将函数返回值使用 Promise.resolve() 包裹了下,和 then 中处理返回值一样,并且 await 只能配套 async 使用。
async function test() {
let value = await sleep()
}
async 和 await 可以说是异步终极解决方案了,相比直接使用 Promise 来说,优势在于处理 then 的调用链,能够更清晰准确的写出代码,毕竟写一大堆 then 也很恶心,并且也能优雅地解决回调地狱问题。
当然也存在一些缺点,因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。
async function test() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch(url)
await fetch(url1)
await fetch(url2)
}
看一个使用 await 的例子:
let a = 0
let b = async () => {
a = a + await 10
console.log('2', a)
}
b()
a++
console.log('1', a)
//先输出 ‘1’, 1
//在输出 ‘2’, 10
- 首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
- 因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码
- 同步代码 a++ 与打印 a 执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10
上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator。
9.单页面和多页面的区别?
单页应用( SinglePage Application,SPA )
指只有一个主页面的应用,一开始只需加载一次 js, css 等相关资源。所有的内容都包含在主页面,对每一个功能模块组件化。单页应用跳转,就是切换相关组件,仅刷新局部资源。
多页应用( MultiPage Application,MPA )
指有多个独立的页面的应用,每个页面必须重复加载 js, css 等相关资源。多页应用跳转,需要整页资源刷新。
| 对比项 \ 模式 | MPA | SPA |
|---|---|---|
| 结构 | 许多完整的页面 | 一个主页面 + 许多模块的组件 |
| 体验 | 页面切换慢,网速慢的时候,体验尤其不好 | 页面切换快,体验佳;当初次加载文件过多时,需要做相关的调优。 |
| 资源文件 | 每个页面都要自己加载公用的资源 | 组件公用的资源只需要加载一次 |
| 资源文件 | 每个页面都要自己加载公用的资源 | 组件公用的资源只需要加载一次 |
| 适用场景 | 适用于对 SEO 要求较高的应用 | 对体验度和流畅度有较高要求的应用,不利于 SEO(可借助 SSR 优化 SEO) |
| 过渡动画 | 很难实现 | Vue 提供了 transition 的封装组件,容易实现 |
| 内容更新 | 整体 HTML 的切换,费钱(重复 HTTP 请求) | 相关组件的切换,即局部更新 |
| 路由模式 | 普通链接跳转 | 可以使用 hash ,也可以使用 history |
| 数据传递 | cookie 、localStorage 等缓存方案,URL 参数,调用接口保存等 | 因为单页面,使用全局变量就好(Vuex) |
| 相关成本 | 前期开发成本低,后期维护就比较麻烦,因为可能一个功能需要改很多地方 | 前期开发成本较高,后期维护较为容易 |