【前端面试记录】

28 篇文章 0 订阅
1 篇文章 0 订阅

【前端面试记录】

添加链接描述

1 北森云计算7.22

1. 一面

1. 为什么主流的框架(vue、react)都使用虚拟dom?

1.虚拟dom本质上是一个普通的js对象,树形结构对象,
包含了tag(标签)data(class和style以及绑定的事件)children(vnode的子节点)text(属性是文本属性)elm(vnode对应的真实dom节点)key(对vnode的标记,在diff过程中可以提高diff的效率)三个属性,
是对dom的抽象,比dom更加轻量。
2.为什么?
	1)前端性能优化,避免频繁的操作dom,减少回流和重绘,造成的效率低下。
	2)并不是所有使用虚拟dom都能提高性能,如果简单的操作普通的dom操作可能性能更优
	3)虚拟dom可以实现跨平台渲染,服务器渲染、小程序、原生应用都使用了虚拟dom
	4)使用虚拟dom,当前状态改变,不需要立即去更新dom,而是更新的内容进行比较,
	对于没有改变的内容不做任何操作,根据唯一标识key差异比较更新视图
	5)虚拟dom可以维护程序的状态,跟踪上一次的状态

2. 虚拟dom是怎么实现优化的?

虚拟dom实现步骤:
	1)用js模拟dom树,element.js
	2)比较两颗虚拟dom树的差异,diff.js
	3)将两个虚拟dom对象的差异应用到真实的dom树中,patch.js
diff算法:
	用来比较同层的新旧节点差异的高效算法
	实际代码中,会对新旧两颗树进行一个深度优先遍历,这样每个节点就有一个唯一的标记,
	如果两颗树完全比较时间复杂度为O(n^3),但是diff算法是在同层比较,所以时间复杂度为O(n).
	采取diff算法比较新旧节点时,只会在同级比较,不会跨层比较。
订阅者watcher会调用patch()比较新旧节点:
	1)old和vnode只存在一个时,vnode替换old;
	2)old和vnode都存在,判断是否是一个节点(key,tag),不是的话vnode替换old;
	是的话调用patchVnode()更新old:
		a.old和vnode完全相同,不操作;
		b.vnode是文本或者注释节点,更新真实dom中文本内容;
		c.old中有子节点,而vnode中没有,则删除其;
		d.如果vnode中有子节点,将vnode真实化后添加到dom中;
		e.两者都有子节点,则执行updateChildren()比较子节点:
			首先给oldch和newch分配startIndex和endIndex作为遍历索引,
			当oldch或者newch遍历完成后(oldch或者newch的startIndex>=endIndex)
			就停止比较oldch和newch的diff过程。

为啥要使用 虚拟DOM?

  • 当然是前端优化方面,避免频繁操作DOM频繁操作DOM会可能让浏览器回流和重绘,性能也会非常低,还有就是手动操作 DOM 还是比较麻烦的,要考虑浏览器兼容性问题,当前jQuery等库简化了 DOM操作,但是项目复杂了,DOM操作还是会变得复杂,数据操作也变得复杂

  • 并不是所有情况使用虚拟DOM 都提高性能,是针对在复杂的的项目使用。如果简单的操作,使用虚拟DOM,要创建虚拟DOM对象等等一系列操作,还不如普通的DOM 操作

  • 虚拟DOM 可以实现跨平台渲染,服务器渲染 、小程序、原生应用都使用了虚拟DOM

  • 使用虚拟DOM改变了当前的状态不需要立即的去更新DOM 而且更新的内容进行更新,对于没有改变的内容不做任何操作,通过前后两次差异进行比较

  • 虚拟 DOM 可以维护程序的状态,跟踪上一次的状态

缺点:

  • ⾸次渲染⼤量 DOM 时,由于多了⼀层虚拟 DOM 的计算,速度比正常稍慢
  • 在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化

3. v-if、v-show

**v-show:**
本质就是通过设置标签属性display为none,来控制隐藏;
只编译一次,后面的每次渲染其实就是控制css,开销较小;适用于频繁切换的场景

**v-if**
本质是动态的向DOM树内添加或者删除DOM元素;
根据值的真假不停的销毁和创建DOM,开销较大;适合于不频繁切换页面元素的场景

4. 重绘和回流的区别

重排( 回流 ):
无论通过什么方式影响了元素的**几何信息**(元素在视口内的位置和尺寸大小),
浏览器需要**重新计算**元素在视口内的几何属性,这个过程叫做重排。

重绘:
通过构造渲染树和重排(回流)阶段,我们知道了哪些节点是可见的,
以及可见节点的样式和具体的几何信息(元素在视口内的位置和尺寸大小),
接下来就可以将渲染树的每个节点都转换为屏幕上的**实际像素**,这个阶段就叫做重绘。

5. 如何改变大小,不造成回流

使用transform代替top、left,因为使用transform页面不会出现回流现象,
transition:过渡,元素从一种样式逐渐改变为另一种的效果
div {
  width:100px;
  height:100px;
  background-color: #87cefa;
  -webkit-transition: width 2s;/*时长为2s的宽度变化效果*/
  -moz-transition: width 2s;
  -o-transition: width 2s;
  transition: width 2s;
}
div:hover{
  width:300px;
}

6. react是单向数据流,vue是双向数据绑定,vue是如何实现双向数据绑定的

采用数据劫持和发布者-订阅者模式的方式,
主要涉及Observer、Compile和Watcher三者。
在`new Vue()`后, Vue 会调用`_init`函数进行初始化,
Observer通过`Object.defineProperty()`来劫持监听属性的`getter`/`setter`,
当对象被读取的时候执行`getter`函数,
当数据发生变化的时候,就会触发对应的`setter`,
然后通知订阅器(Dep)的`dep.notify()`
找到对应的 Watcher调用`update`中的`patch()`比较新旧vdom节点,
根据差异对视图进行更新渲染(有多少个data就有多少个订阅者Watcher),
此时通过Compile解析编译模板指令,找到数据变更的位置,然后更新视图(节点)。

7. 除了defineProperty()、和vue3中的Proxy,还有什么可以数据拦截

8. js实现消息订阅与发布

<input type="text">
 
<script>
// 获取到input输入框对象
let input = document.querySelector('input');
// 创建一个没有原型链的对象,用于监听该对象的某属性的变化
let model = Object.create(null);
// 当鼠标移开输入框的时候,view层数据通知model层数据的变化
input.addEventListener('blur',function() {
    model['user'] = this.value;
})
 
// 当model层数据发生变化的时候,通知view层数据的变化。
Object.defineProperty(model, 'user', {
    set(v) {
        user = v;
        input.value = v;
    },
    get() {
        return user;
    }
})
</script>

9. 垂直和水平居中

方案1:使用定位属性

.father {
    width: 200px;
    height: 200px;
    position: relative;
}
.content{ 
    position: absolute;
    top: 50%;
    left: 50%;
    /*margin-top: -50px; /*高度的一半*/
    /*margin-left: -50px; /*宽度的一半*/
    transform: translate( -50%, -50%);
}

方案2:使用flex布局

.content{
    display: flex;
    align-items: center;/*定义body的元素垂直居中*/
    justify-content: center;/*定义body的元素水平居中*/
}

方案2.1:使用flex布局

.content{
    display: flex;
}
.son{
	margin:auto; //子元素
}

方案3:margin: auto;实现绝对定位元素的居中

.content{
   width: 100px;
   height: 100px;
   position: absolute;
   top: 0;
   left: 0;
   right: 0;
   bottom: 0;
   margin: auto;
}

10. JS闭包是什么?解决什么问题?

**闭包是指有权访问另一个函数作用域中的变量的函数。
**创建闭包的最常见的方式就是在一个函数内创建另一个函数,
创建的函数可以访问到当前函数的局部变量。
闭包的优点(用途)
- 在函数外部能够访问到函数内部的变量。
- 被闭包引用的变量对象在程序结束后可以继续留在内存中,实现变量数据共享。
- **(缺陷,内存泄露)**
- 利于代码封装,访问私有变量,方便调用上下文的局部变量。
- **(缺陷,不要随便改变父函数内部变量的值)**
### 闭包的应用场景
- setTimeout,闭包可以实现setTimeout传参效果
- 对DOM事件绑定,设置回调函数
- 函数防抖,触发事件n秒后再执行回调,如果在n秒内再次被触发,则重新计时。
- 封装私有变量

面试官说:总结一下,可以实现私有变量,或者私有function

11. 浅拷贝和深拷贝

> **浅拷贝:**不可以拷贝对象里的子对象。()

> **深拷贝:**可以拷贝对象里的子对象。

12. JS 的event loop

JS是单线程的,同一时间只能执行一件事。

当执行栈为空时,即Script**主体**代码执行完毕,
js引擎会先去检查**微任务**队列,如果不为空,
则依次将**微任务**队列中的任务加入到执行栈中执行;
如果**微任务**队列为空了,则将宏任务队列的中的任务逐个加入到执行栈中执行。
执行完后再检查**微任务**队列是否为空,如此往复,就是事件循环机制。
(注意:script(整体代码)作为一个宏任务,一旦全部执行完毕就会去执行其后的微任务。)

**宏任务**,
包括script(整体代码),setTimeout,setInterval,
setImmediate,I/O,UI交互事件,postMessage,MessageChannel ;

**微任务**,包括Promise,process.nextTick,Object.observe,
MutationObserver,Async/Await(实际就是promise) 

面试官:当前宏任务执行完毕,开始检查渲染,每一轮的循环结束开始渲染。

13. 如何保证UI比计算的先渲染

面试官:其实推到异步任务里就可以了,设置setTimeout设置为0就可以了。

14. http2.0的改变对前端会带来什么变化?

**服务端推送**
增量地获取更多的资源,它允许服务端推送资源给浏览器,
在浏览器明确的请求之前,免得客户端再次创建连接发送请求到服务器端获取。
这样客户端可以直接从本地加载这些资源,不用再通过网络。

面试官:比如以前想要拿到最新的东西,需要用轮询机制,前端要每五秒每十秒可以发请求,后端收到新的数据,可以向前端推送,按需的意思。

15. 数组去重

1 HashMap

var arr = [1, 2, 1, 1, '1', 3, 3];
const unique = function(arr){
   let res = []
   let tmp = []
   for(let item of arr) {
       if(!tmp[item]) {
           tmp[item] = 1
           res.push(item)
       }
   }
   return res
}
console.log(unique(arr));

2 ES5 filter

function method(arr) {
    return arr.filter((value, index, self) => {
        return self.indexOf(value) === index
    })
}

3 ES6 set

let arr1 = [1, 1, 1, 2, 3, 6, 5, 5, 8, 7, 7, 7, 8];
let arr2 = Array.from(new Set(arr1));
 // return [...new Set(arr)]
console.log(arr2);

2 易保8.1

1. 一面(20min)

1. JS 数据类型

- **基本数据类型8种:**`Number`(精度是53位)、
- `String`、`Boolean`、`undefined`、`Null`、
- `symbol`(ES6新增,表示独一无二的值)、`BigInt` 
- **引用数据类型:**`Object`(Array、Date、Function等)
- 其中 `Symbol` 和 `BigInt` 是 ES6 新增的数据类型 

2. 判断数据类型

  1. typeof 对于基本类型,除了 null 都可以显示正确的类型
typeof null ='object'
  1. instanceof主要用于判断某个实例是否属于某个类型,也可用于判断某个实例是否是其父类型或者祖先类型的实例(不能判断具体是哪种类型,只能用来判断对象和函数,不能用来判断字符串和数字等)(通过原型链来判断)
原理:遍历left.__proto__==right.prototype
直到找到右边变量的prototype

[] instanceof Array; =》true
[] instanceof Object; =》true
'aaa' instanceof String =》 false
134 instanceof Number =》false
  1. constructor任何一个对象都有constructor属性,指向创建这个对象的构造函数。可以判断变量对象具体是哪种数据类型。

null和undefined是无效的对象,所以没有constructor的存在)

null.constructor === Null 				// Cannot read property 'constructor' of null    // 注意点
undefined.constructor === Undefined		// Cannot read property 'constructor' of null    // 注意点
(1).constructor === Number  			// true
  1. Object.prototype.toString.call()/apply()/bind()
    toStringObject原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是 toString运行时this指向的对象类型, 返回的类型格式为[object,xxx],xxx是具体的数据类型,其中包括:String,Number,Boolean,Undefined,Null,Function,Date,Array,RegExp,Error,HTMLDocument,… 基本上所有对象的类型都可以通过这个方法获取到。
    缺点:不能判断自定义对象,对于自定义对象只能返回[Object Object]
// Object.prototype.toString
Object.prototype.toString()  // 返回的都是[object Object]

// Object.prototype.toString.call
Object.prototype.toString.call(1)         // [object Number]

总结: constructorinstanceof不能判断null和undefined
instanceof,因为instanceof只能用来判断是不是对象或者函数,不能用来判断字符串数字
​constructor,因为在constructor中null和undefined是无效对象

typeof和Object.prototype.toString.call() 可以判断是不是null和undefined类型

typeof: typeof null 返回的是object,typeof undefined 返回的是undefined
Object.prototype.toString.call() 返回对象的类型, 通用但很繁琐

3. 数组的方法

方法描述
join()把数组转换成字符串。默认是逗号,
push()把里面的内容添加到数组末尾,并返回修改后的长度(会改变原数组)
pop()移除数组最后一项,返回移除的那个值。(会改变原数组)
shift()删除原数组第一项,并返回删除元素的值;如果数组为空则返回undefined 。(会改变原数组)
unshift()将参数添加到原数组开头,并返回数组的长度 。(会改变原数组)
sort()函数参数如果不设置的话,会以默认方式(字母顺序 / unicode字符编码的顺序)进行排序。(会改变原数组)
reverse()反转数组项的顺序。(会改变原数组)
concat()将参数添加到原数组中。这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给 concat()方法传递参数的情况下,它只是复制当前数组并返回副本
slice()返回从原数组中指定开始下标到结束下标之间的项组成的新数组。参数:arrayObj ,start,end可选项。
splice()删除、插入和替换。3 个参数:起始位置, 要删除的项数(0就是不删除),插入的项。;返回删掉的数据**(会改变原数组)**
reduce()(ES5新增)对一个数组进行遍历,然后返回一个累计
indexOf()(ES5新增)接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。从数组的开头 0 开始向后查找。
lastIndexOf()(ES5新增)接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。从数组的末尾开始向前查找。
forEach()(ES5新增)对数组进行遍历循环,对数组中的每一项运行给定函数。这个方法没有返回值。参数都是function类型,默认有传参,参数分别为:遍历的数组内容;对应的数组索引,数组本身。
map()(ES5新增)指“映射”,对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组
filter()(ES5新增)“过滤”功能,数组中的每一项运行给定函数,返回满足过滤条件组成的数组。
every()(ES5新增)判断数组中每一项都是否满足条件,只有所有项都满足条件,才会返回true。
some()(ES5新增)判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true。
flat()(ES6新增)用于将嵌套的数组“拉平”,变成一维数组,参数是拉多少层;如果有空位flat()会跳过
flatMap()(ES6新增)执行Array.prototype.map(),然后执行flat()方法。flatMap()只能展开一层数组
copyWithin()(ES6新增)在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。(会改变原数组)
fill()(ES6新增)将一个固定值替换数组的元素。(会改变原数组)

4. Promise的理解

> Promise 是异步编程的一种解决方案,
> 比传统的异步解决方案【回调函数】和【事件】更合理、更强大。

Promise对象特点:

  • **对象的状态不受外界影响。**Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
  • 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供了统一的接口

Promise缺点:

  • 无法取消Promise,一旦新建就立即执行,无法中途取消;
  • 如果不设置回调函数,Promise内部抛出错误;
  • 当处于pending状态时,无法得知目前进展到哪一个阶段。

5.是否有实习?现在研二吗?研三有课吗?

6. Call、apply、Bind的区别?

`apply()`和`call()`都是在特定的作用域中调用函数,**用于扩充函数赖以运行的作用域**。
区别仅在于传入参数的形式的不同。

- `apply()` 第一个参数是对象,第二个参数是参数数组;
- `call()` 第一个参数是对象,其余参数必须逐个列举出来;
- `bind()` 通过传入一个对象,返回这个对象绑定的新函数。
这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。

**call 方法比 apply 快的原因:**call 方法的参数格式正是内部方法所需要的格式。

**函数bind两次后this指向:
**在第一次bind完this就已经确定了,结果返回一个函数,这个函数体内不存在this问题
,后续无论bind多少次,this都指向第一次bind传入的context,但是后面bind再传入的参数会生效。
(类似柯里化过程)

7.ES6新特性

1. 箭头函数
2. let和const 
3. Promise
4. Async
5. Class
6. Set和Map
7. Proxy
8. Symbol
9. 解构赋值
10.模块化 

8. ES6的箭头函数和普通函数的区别

1.函数体内的this对象,定义时所在的对象,而不是使用时所在的对象。
2.不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
(因为箭头函数是匿名的,用完就扔了不需要给箭头函数加重复使用的构造能力)
3.不可以使用 arguments 对象,该对象在函数体内不存在。
如果要用,可以用 rest 参数代替。
(因为匿名函数一般不需要参数,但是可以调用外围函数的arguments对象)
4.不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
5.不能用 call()bind()apply() 修改里面的this
6.没有new和原型

9. 伪类和伪元素有哪些

**伪类:**通过选择器找到那些不存在于DOM树中的信息以及不能被常规CSS选择器获取到的信息。
如,`:active`,`:hover`,`:link`,`:focus`等。

**伪元素:**在DOM树中创建的虚拟容器,容器中不包含DOM元素,
		但是可以包含内容,另外还可以定制样式。
如,`::first-letter`,`::first-line`,`::after`,`:before`等。

10 display和visibility

**display: none,会从渲染树中消失**,元素不占据空间且无法点击;
会引起重排,性能消耗较大;
是非继承属性,父元素设置了 display:none 或 opacity: 0,子元素无论怎么设置都无法显示;

**visibility: hidden,**不会从渲染树中消失,**元素继续占据空间**但无法点击;
会引起重绘,性能消耗相对较小;
会被子元素继承,并且子元素可以通过设置设置 visibility: visible; 来取消隐藏。

11 cookie、sessionStorage、localStorage


SessionStorage,LocalStorage,Cookie
这三者都可以被用来在浏览器端存储数据,而且都是字符串类型的键值对。
区别在于前两者都是**html5 提供的浏览器本地存储的方法**。
而cookie是**服务器端用于记录用户状态的一种方式**,由服务器设置,在客户端存储。
cookie 数据始终在同源的http请求中携带(即使不需要),会在浏览器和服务器间来回传递。

**1)存储大小:**
- cookie 数据大小不能超过4 k。
- sessionStorage 和 localStorage 大多数桌面浏览器限制 5MB ,Chrome、Safari、Android 版 WebKit 限制 2.5MB。

**2)过期时间:**
- cookie在设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭。
- localStorage存储持久数据,浏览器关闭后数据不丢失,除非主动删除数据。
- sessionStorage数据在**页面会话结束**时会被清除。

**3)数据共享:**
- cookie和localStorage在所有同源窗口中数据都是共享的。
- sessionStorage只在同源的同窗口(或标签页)中共享数据,也就是只在当前会话中共享。

计算机网络(http缓存)| 强缓存、协商缓存、本地存储的方式

在这里插入图片描述

12 Vue组件传参的方式

  1. 父–>子
- **props**:父组件通过标签特性传递给子组件,子组件通过props来获取
- **provide / inject**:父组件通过provider传递数据,子组件通过inject来获取
- **$attrs / $$listeners**:子组件通过v-bind绑定`$$attrs`,其中包含着不被prop识别的父组件传递过来的数据。通过v-on绑定`$listeners`将事件传递给子组件。
- **$parent**:父组件的实例对象

props

// 父:传递list
<com-article :alist="list"></com-article>
// 子
props: ['alist']

$attrs /$listeners
子组件中的$attrs包含了父组件组件中不被prop所识别的特性绑定,使用v-bind绑定 $attrs在孙子组件上,将数据传递给孙子组件;

子组件中的$listeners包含了父组件绑定在子组件上的事件,使用v-on绑定$listeners在孙子组件上,将事件传递给孙子组件。

// 父A:传递list
<B :messagec="messagec" :message="message" @getCData="getCData" @getChildData="getChildData(message)">
methods: {
    getChildData(val) {
      console.log(`这是来自B组件的数据:${val}`);
    },
    getCData(val) {
      console.log(`这是来自C组件的数据:${val}`);
    }
}
// 子B
<!-- C组件中能直接触发 getCData 的原因在于:B组件调用 C组件时,使用 v-on 绑定了 $listeners 属性 -->
<!-- 通过v-bind 绑定 $attrs 属性,C组件可以直接获取到 A组件中传递下来的 props(除了 B组件中 props声明的) -->
<C v-bind="$attrs" v-on="$listeners"></C>
props: ['message'],
data(){
    return {
      mymessage: this.message
    }
},
methods: {
    passData(val){
      //触发父组件中的事件
      this.$emit('getChildData', val)
    }
}
// 子孙C
<input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)">
methods: {
    passCData(val) {
      // 触发父组件A中的事件
      this.$emit('getCData',val)
    }
}

provide / inject

// 父:传递list
provide: {
    list: 'test'
}
// 子/子孙
inject: ['list']

$children / $parent

// 父
this.$children[0].boymessage = 'hello';
// 子
data() {
    boymessage: this.$parent.message
}
this.$parent.message = this.boymessage;//通过如此调用可以改变父组件的值
  1. 子 -->父
- **$emit / v-on**:将数据作为`this.$$emit`方法的参数,
		回传给父组件用`v-on:[自定义事件]`监听的函数。
- **ref / $refs**:父组件通过调用子组件ref名获取数据或调用方法,并且获取的是实时数据。
- **$children**:子组件的实例对象,是数组类型的,并不保证顺序,也不是响应式的。

$emit / v-on

// 父组件
<son v-on:sendData='getSonText'></son>
import son from './son.vue'
export default {
  methods: {
    getSonText (text) {
      this.text = text
    }
  }
}
// 子组件
export default {
  methods: {
    sendData () {
      this.$emit('sendData', this.text)
    }
  }
}

ref / $refs

// 父组件
<child ref="msg"></child>
<script>
export default {
  ...
  methods:{
    submitForm () {
      var _this = this
      _this.data = _this.$refs.msg.tableData
      //只要子组件的data中有tableData这个数据,我们就能在父组件中获取到他
      _this.flag= _this.$refs.msg.sendMsg()
      //只要子组件的methods中有sendMsg这个方法,我们就能在父组件中调用他
    }
  }
}
</script>
  1. 兄–>弟
- **子传父、父传子**
- **eventBus**:新建一个 `Vue` 对象 `bus`,挂载到Vue.prototype,
		然后通过 `bus.$emit` 触发事件,`bus.$on` 监听触发的事件。
- **vuex**:使用`commit`触发一个`mutation`修改store中的state数据,
		通过`this.$store.state.[属性名]`获取vuex仓库中的数据

分类:全局守卫,路由独享守卫,组件内守卫。

eventBus中央事件总线

通过新建一个 Vue 对象 bus,挂载到Vue.prototype,然后通过 bus.$emit 触发事件,bus.$on 监听触发的事件。

**原理:**事件总线,是一种发布-订阅模式。注册时就将订阅者存在一个数组中,发布时就触发事件 bus.$emit,然后通过.notify()进行发布,订阅者通过bus.$on进行监听

相比之下,vuex会将通知的概念上升到共享状态层次,有利于后期代码的维护。

// 定义中央事件总线
const EventBus = new Vue();
// 将中央事件总线赋值到 Vue.prototype 上,这样所有组件都能访问到了
Vue.prototype.$EventBus = EventBus;

// 组件A
this.$EventBus.$emit('globalEvent', val)
// 组件B  绑定全局事件globalEvent
this.$EventBus.$on('globalEvent', (val) => {
    this.brothermessage = val;   // 组件A 传递过来的数据brothermessage
});

vuex

// 兄弟组件A(B结构一样)
<button @click="transform">点我让B组件接收到数据</button>
<p>因为你点了B,所以我的信息发生了变化:{{BMessage}}</p>

	data() {
      return {
        AMessage: 'Hello,B组件,我是A组件'
      }
    },
    computed: {
      BMessage() {
        // 这里存储从store里获取的B组件的数据
        return this.$store.state.BMsg
      }
    },
    methods: {
      transform() {
        // 触发receiveAMsg,将A组件的数据存放到store里去
        this.$store.commit('receiveAMsg', {
          AMsg: this.AMessage
        })
      }
    }

13. export && export default 差异总结

1. export default 在一个模块里只能有一个,但是export可以有多个
2. 模块中通过export 导出的(属性或者方法)可以修改,但是通过export default导出的不可以修改

14. forEach() 和 map() 的区别

| forEach() | (ES5新增)对数组进行遍历循环,对数组中的每一项运行给定函数。
这个方法没有返回值。
参数都是function类型,默认有传参,参数分别为:遍历的数组内容;
对应的数组索引,数组本身。 |forEach不会返回新数组,允许对原数组进行修改
| :-------: | :----------------------------------------------------------- |
|   map()   | (ES5新增)指“映射”,对数组中的每一项运行给定函数
,返回每次函数调用的结果组成的**数组**|map不改变原数组但是会 返回新数组
[1, 2, 3].map(function(value, index, originalArray) {
	  console.log(`${index}: ${value} / ${originalArray} /`);
	  console.log(this);
	  return value + 1;
}, { test: 1 });    #返回值:[2, 3, 4]

[1, 2, 3].forEach(function(value, index, originalArray) {
	  console.log(`${index}: ${value} / ${originalArray} /`);
	  console.log(this);
}, { test: 1 });  # 返回值:undefined
map & forEach 都是用来更方便地遍历数组的。
map 接收两个参数:callback 函数,它会在 map 执行之后被触发。
上下文变量,即执行 callback 函数时 this 指向的对象。map 会返回一个新数组。
使用场景
因为 map & forEach 的主要区别是有无返回,所以,当你想基于一个原数组返回一个新数组,
可以选择 map,当你只是想遍历数据不需要考虑返回时可以选择 forEach。

3 mataApp 8.1

1 一面(15min)

1. 写过移动端吗? 会其他框架吗?

2. Vue生命周期?有子组件的时候执行顺序?keep-alive

`beforeCreate` `created`
`beforeMount` `mounted` (重要) 做初始化的事
`beforeUpdate` `updated`
`beforeDestroy` `destroy` (重要) 取消订阅 收尾
**加载渲染过程(父等子mounted)**
父 beforeCreate -> 父 created -> 父 beforeMount 
-> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

**子组件更新过程(父等子update)**
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

**销毁过程(父等子destroyed)**
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

如果加入了keep-alive,第一次进入组件会执行哪些生命?
 beforeCreate
 created
 beforeMount
 mounted
 activated
如果加入了keep-alive,第二次或者第N次进入组件会执行哪些生命周期?
只执行一个生命周期:activated

3 子父组件通信?(这种问题,一般要回答全部的通信方式)

4 Vuex的属性

Vuex中有5个内容需要学习:
state: 统一定义公共数据(类似于data(){return {a:1, b:2,xxxxxx}})
mutations : 使用它来修改数据(类似于methods)
getters: 类似于computed(计算属性,对现有的状态进行计算得到新的数据-------派生 )
actions: 发起异步请求
modules: 模块拆分

5 watch和computed的理解

Computed:	是计算属性的意思, 根据一个属性计算出一个值,
	它会根据你所依赖的数据动态显示新的计算结果。计算结果会被缓存, 
	如果数据没有发生改变则使用缓存,不会重新加载。
Watch: 是用来监听数据的。主要用法是当某个数据变化后,做一些操作。
	当 data 的数据发生变化时,就会发生一个回调,
	他有两个参数,一个val(修改后的 data 数据),一个oldVal(原来的 data数据)
1.computed能完成的功能,watch都可以完成。
2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。

6 Vue-router中$route$router 的区别

`$route` :是“路由跳转对象”,跳转时的对象,包括路由信息参数。
	如 path,params,hash,query,fullPath,matched,name 等。
	通过 this.$route 访问的是当前路由,获取和当前路由有关的信息
	
` $router` :是“路由实例”,包括了路由的跳转方法,钩子函数,mode等。
	this.$router.replace(path:'/user'),$router.push('/login') ,跳转到指定路由

$route在这里插入图片描述

7 导航守卫

导航守卫就是在路由跳转过程中的一些钩子函数,通过钩子函数在不同时段可以做不同的事。
分类:全局守卫,路由独享守卫,组件内守卫。
1.全局守卫

是指路由实例($router)上直接操作的钩子函数,触发路由就会触发这些钩子函数。

  • 全局前置守卫:router.beforeEach()
  • 全局解析守卫:router.beforeResolve()
  • 全局后置钩子:router.afterEach()
在路由跳转前触发,这个钩子作用主要是用于登录验证。
router.beforeEach((to, from, next) => {
	next()
})

router.beforeResolve:这和 `router.beforeEach` 类似,区别是在导航被确认之前,
	**同时在所有组件内守卫和路由组件被解析之后**,解析守卫就被调用,
	即在 beforeEach 和 组件内beforeRouteEnter 之后,afterEach之前调用。
router.afterEach((to, from) => {
  // ...
})

2.路由守卫

可以在单个路由配置上直接定义beforeEnter守卫

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

3.组件守卫

可以在组件内直接定义以下路由导航守卫:

  • beforeRouteEnter
  • beforeRouteUpdate (2.2 新增)
  • beforeRouteLeave

8 JS数据类型?判断数据类型

9 this指向

- **默认绑定:**函数调用模式,当一个函数直接作为函数来调用时,this 指向全局对象。

  - 非严格模式下`this`指向全局对象
  - 严格模式下`this`会绑定到`undefined`,并且不会改变全局中`this`的指向
  - 全局变量用`let 或 const`定义,变量不会被绑定到`window`上

- **隐式绑定:**方法调用模式,当函数引用有上下文对象时,
		如 `obj.foo()`的调用方式,`foo`内的`this`指向`obj`

  			隐式丢失其实就是被隐式绑定的函数在特定的情况下会丢失绑定对象。有两种情况:
  			- 使用另一个变量来给函数取别名
 		    - 将函数作为参数传递时会被隐式赋值,回调函数丢失this绑定

- **显示绑定:**apply 、call 和 bind 调用模式,
    -  通过`call()`或者`apply()`方法直接指定`this`的绑定对象,如`foo.call(obj)`
	-  如果`call、apply、bind`接收到的第一个参数是空或者`null、undefined`的话,则会忽略这个参数。

- **new绑定:**构造器调用模式
- **箭头函数绑定:**`this`的指向由外层作用域决定的。

  箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,
  如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined。

10 Call、apply、Bind的区别?

11 字符串和数组的方式?

charAt(x)
charCodeAt(x)
concat(v1,v2..)
fromCharcode(c1,c2)
indexOf(substr, [start])
lastIndexOf(substr, [start])
match(regexp)
replace(regexp/substr, replacetext)
search(regexp)
slice(start, [end])
split(delimiter, [limit])
substr(start, [length])
substring(from, [to])
toLowerCase()
toUpperCase()
includes()
endsWith()
repeat()
valueOf()
trim()

12 数组的遍历方法

- **for循环**
- **forEach:**`arr.forEach((item, index) =>{})`
- **map函数:**遍历数组每个元素,并回调操作,需要返回值,返回值组成新的数组,原数组不变
- **filter函数:**过滤通过条件的元素组成一个新数组, 原数组不变
- **some函数:**遍历数组中是否有符合条件的元素,返回Boolean值
- **every函数:**遍历数组中是否每个元素都符合条件, 返回Boolean值
- **in:** 不仅可以用来 遍历对象,还可以用来遍历数组, 不过 i 对应与数组的 key值

13 什么时候用map?

14 移动端适配

小程序提供了一个自己的单位, rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。

15 在浏览器输入一个链接,发生了什么

第一步:用户输入网址
第二步:**进行DNS域名解析,将地址解析成ip地址**
	1.浏览器**DNS域名解析;
	2.系统**(本地)**NDS域名解析;
	3.路由器**DNS域名解析;
	4.网络运行商**DNS域名解析;
	5.递归DNS域名解析;
第三步:建立TCP连接(TCP三次握手)
	第一次握手:浏览器(客户端)向服务端发起,告诉服务求,我准备好了,请求建立连接,你也准备一下;
	第二次握手:服务器向浏览器发起,告诉浏览器,我准备好了,我同意建立连接,你发送过来吧;
	第三次握手:浏览器向服务器发起,告诉服务器,我发送了,你接受吧;
第四步:请求报文(发送请求)
第五步:响应报文(响应请求)
第六步:HTML渲染
	遇到HTML标签,浏览器会通知HTML解析器去进行解析,最终生成Token并生成dom树;
	遇到style/link标签,浏览器会通知css解析器去进行解析,最终生成cssom树;
	遇到script标签,浏览器会通识javascript解析器去进行解析,包括事件处理,操作dom,修改css等;
	解析完成之后dom树和cssom树会生成一个渲染树,根据计算来实现页面布局;
	最后渲染到页面;
第七步:断开TCP连接(TCP的四次挥手)
	第一次挥手由浏览器向服务器发起,告诉服务器,我请求发送完毕(请求报文),你准备关闭吧;
	第二次挥手由服务器向浏览器发起,告诉浏览器,我收到请求了(请求报文),你也准备关闭吧;
	第三次挥手由服务器向浏览器发起,告诉浏览器,我的响应报文也发送完毕,你准备关闭吧;
	第四次挥手由浏览器向服务器发起,告诉服务器,我收到响应了(响应报文),你关闭吧;

16 闭包的使用场景

- setTimeout,闭包可以实现setTimeout传参效果
- 对DOM事件绑定,设置回调函数
- 函数防抖,触发事件n秒后再执行回调,如果在n秒内再次被触发,则重新计时。
- 封装私有变量
function makeSize(size) {
    return function () {
        document.body.style.fontSize = size + 'px';
    }
}
document.getElementById('size-12').onclick = makeSize(12);

17 原型和原型链

## 原型
函数的 prototype(原型)属性是一个指针,指向一个对象,
其中**包含了可以由特定类型的**所有实例共享**的属性和方法**。
主要是为了解决创建对象时出现的代码复用问题,大大减少内存消耗。

## 原型链

**每个对象(除null)都有 `__proto__` 属性**,
这个属性指向了创建该对象的构造函数的原型。
而原型对象也通过`__proto__`指向它自己的原型对象,
层层向上直到Object.prototype,这样就形成了原型链。

函数拥有:prototype
对象拥有:__proto__

4 百度(8.5)

4.1 一面

1. 项目?可视化图表?图表多吗?有进行二次封装吗?

2. webpack的搭建?有优化吗?一个webpack如何搭建?需要哪些配置?

【Entry:入口(Entry)】
指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。

【Ouput:输出(Output)】
指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名

【Loader:Loader 翻译官】
让 webpack 能 够去处理那些非 JavaScript文件(webpack 自 身 只 理 解JavaScript)。

【Plugins:插件(Plugins)】
可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量等。Plugins使用方式:先下载再引入再使用

【Mode:模式(Mode)】
指示 webpack使用相应模式的配置,有生成模式production和开发模式development

3 html 盒模型

两种盒子模型都是由 `content + padding + border + margin` 构成
其大小都是由 `content + padding + border` 决定的
但是盒子内容宽/高度(即 `width/height`)
的计算范围根据盒模型的不同会有所不同:

- 标准盒模型:只包含 `content` 。
- IE(替代)盒模型:`content + padding + border` 。

可以通过 `box-sizing` 来改变元素的盒模型:

- `box-sizing: content-box` :标准盒模型(默认值)。
- `box-sizing: border-box` :IE(替代)盒模型。

4 行元素和块元素

# 块级元素(block)
块级元素独占一行,自上而下排列,可以设置宽高。宽度是它容器的100%,除非自己设定一个宽度。它可以容纳内联元素和其他块级元素。
常见块元素:**p、div、ul**、ol、 li、dl、dt、dd、**h1**、h2等。

# 行内元素(inline)
和其他元素都在同一行,从左到右排列,不可设置宽高。内联元素只能容纳文本或者其他内联元素,但容纳块级元素也不会出错,但不建议。
常见的行内元素:**span、a、b**、select、**strong**(强调的语气)等。

# 行块内元素(inline-block)
和其他元素都在一行上;可以设置元素的高宽、行高以及顶和底边距都可设置。
常见:**img、input**

# 空元素
标签内没有内容的 HTML 标签。
常见:**br、hr、**img、input、link、meta等

5 h5中的语义化有什么用处?

正确的标签做正确的事情,以前我们一般采用DIV+CSS布局来写我们的页面
但是H5新增了header、footer等标签,让代码结构化,
有利于后期代码的阅读维护和理解,
没有CSS样式的时候也可以以一种文档格式显示,方便阅读。
并且有利于SEO,即让搜索引擎更快地找到我们的网页。

- 正确的标签做正确的事情
- 页面内容结构化
- 无CSS样式时也以一种文档格式显示,容易阅读
- 便于浏览器、搜索引擎解析。 利于爬虫标记、**利于SEO**
- 便于后期源码的阅读维护和理解

6 BFC了解吗?怎么触发?

Block Formatting Contexts (**块级格式化上下文)**,
它属于定位机制中的普通流。BFC就是页面上的一个隔离的独立容器,
容器内的子元素和外部的元素不会互相影响。

**创建BFC**:( 只要元素满足下面任一条件即可 )

- `html`根元素`body`下
- `float`的值不为`none`
- `overflow`的值非`visible`
- `position`的值不为`absolute `或`fixed`
- `display`的值为`flex`、`table-cell`、`table-caption`、或`inline-block`

**BFC特性及应用:**

1. **阻止外边距重叠
	**同一个 BFC下的两个元素外边距会发生折叠,可以设置不同的BFC或只给一个元素设置BFC
3. **清除浮动,防止高度塌陷:**父元素开启BFC,包含浮动的子元素。
4. **阻止元素被浮动元素覆盖:**设置第二个float的元素为BFC
5. 容器内的子元素和外部的元素不会互相影响。

7 常用的存储方式?他们有哪些区别?一般什么场景用?

SessionStorage,LocalStorage,Cookie
这三者都可以被用来在浏览器端存储数据,而且都是字符串类型的键值对。
区别在于前两者都是**html5 提供的浏览器本地存储的方法**。
而cookie是**服务器端用于记录用户状态的一种方式**,由服务器设置,在客户端存储。
cookie 数据始终在同源的http请求中携带(即使不需要),会在浏览器和服务器间来回传递。

**1)存储大小:**
- cookie 数据大小不能超过4 k。
- sessionStorage 和 localStorage 大多数桌面浏览器限制 5MB ,Chrome、Safari、Android 版 WebKit 限制 2.5MB。

**2)过期时间:**
- cookie在设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭。
- localStorage存储持久数据,浏览器关闭后数据不丢失,除非主动删除数据。
- sessionStorage数据在**页面会话结束**时会被清除。

**3)数据共享:**
- cookie和localStorage在所有同源窗口中数据都是共享的。
- sessionStorage只在同源的同窗口(或标签页)中共享数据,也就是只在当前会话中共享。

8 三次握手的过程?这样做的作用?

建立TCP连接(TCP三次握手)
第一次握手:浏览器(客户端)向服务端发起,告诉服务求,我准备好了,请求建立连接,你也准备一下;
第二次握手:服务器向浏览器发起,告诉浏览器,我准备好了,我同意建立连接,你发送过来吧;
第三次握手:浏览器向服务器发起,告诉服务器,我发送了,你接受吧;

主要防止已经失效的连接请求报文段突然又传送到了服务器,从而产生错误。所以最后一次请求需要进行确认。
A发送请求,但连接请求报文堵塞,B并未收到确认,所以A再重传一次连接请求并根据这次请求建立了连接,数据传输完后就释放了连接。此时第一次发送的堵塞住的报文到达了B,B会误以为A又一次发送了连接请求,然后发出确认报文,然后新的连接就建立了。
因为三次握手才能确认双方的接收与发送能力是否正常,如果只有两次握手的话,服务端无法确认客户端是否有接收数据的能力。

9 css选择器的优先级

选择器权重
! importantInfinity
行间样式style1000
id选择器100
class选择器、属性选择器、伪类选择器10
标签选择器、伪元素选择器1
通配符选择器(所有的元素都符合的样式,用*)、继承选择器0

10 闭包?应用?缺点?

闭包的优点(用途)
- 在函数外部能够访问到函数内部的变量。
- 被闭包引用的变量对象在程序结束后可以继续留在内存中,实现变量数据共享。
- **(缺陷,内存泄露)**
- 利于代码封装,访问私有变量,方便调用上下文的局部变量。
- **(缺陷,不要随便改变父函数内部变量的值)**
- 闭包会在父函数外部,**改变父函数内部变量的值**。
所以,如果你把父函数当作对象(object)使用,
把闭包当作它的公用方法(Public Method)
把内部变量当作它的私有属性(private value),
这时一定要小心,不要随便改变父函数内部变量的值。

- 闭包会使得函数中的变量都被保存在内存中,内存消耗很大
所以不能滥用闭包,否则会造成网页的性能问题。
解决方法是,在退出函数之前,将不使用的局部变量全部删除。

11 ES6觉得比较好的特性?let和const相对于var有什么区别?

**var / let / const区别**

- **var:**存在变量提升,可以提升到全局作用域 / 函数作用域,
不存在暂时性死区,允许重复声明。
- **let / const**都不存在变量提升,都存在暂时性死区,不允许重复声明。
- **const**声明的是不能修改的只读常量,一定要初始化。
不能修改的本质是不能修改内存中保存的数据,对于基本数据类型来说肯定不能修改
但是对于引用数据类型来说存的只是引用指针,而引用指针指向的堆内存空间中的数据是可以改变的。

12 箭头函数和原来的function有什么区别?

1.函数体内的this对象,定义时所在的对象,而不是使用时所在的对象。
2.不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
(因为箭头函数是匿名的,用完就扔了不需要给箭头函数加重复使用的构造能力)
3.不可以使用 arguments 对象,该对象在函数体内不存在。
如果要用,可以用 rest 参数代替。
(因为匿名函数一般不需要参数,但是可以调用外围函数的arguments对象)
4.不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
5.不能用 call()bind()apply() 修改里面的this
6.没有new和原型

13 Call、apply、Bind的区别?

`apply()`和`call()`都是在特定的作用域中调用函数,**用于扩充函数赖以运行的作用域**。
区别仅在于传入参数的形式的不同。

- `apply()` 第一个参数是对象,第二个参数是参数数组;
- `call()` 第一个参数是对象,其余参数必须逐个列举出来;
- `bind()` 通过传入一个对象,返回这个对象绑定的新函数。
这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。

**call 方法比 apply 快的原因:**call 方法的参数格式正是内部方法所需要的格式。

**函数bind两次后this指向:
**在第一次bind完this就已经确定了,结果返回一个函数,这个函数体内不存在this问题
,后续无论bind多少次,this都指向第一次bind传入的context,但是后面bind再传入的参数会生效。
(类似柯里化过程)

14 JS事件循环机制?微任务有哪些?平时用promise和async/await哪个多?

JS是单线程的,同一时间只能执行一件事。

当执行栈为空时,即Script**主体**代码执行完毕,
js引擎会先去检查**微任务**队列,如果不为空,
则依次将**微任务**队列中的任务加入到执行栈中执行;
如果**微任务**队列为空了,则将宏任务队列的中的任务逐个加入到执行栈中执行。
执行完后再检查**微任务**队列是否为空,如此往复,就是事件循环机制。
(注意:script(整体代码)作为一个宏任务,一旦全部执行完毕就会去执行其后的微任务。)

**宏任务**,
包括script(整体代码),setTimeout,setInterval,
setImmediate,I/O,UI交互事件,postMessage,MessageChannel ;

**微任务**,包括Promise,process.nextTick,Object.observe,
MutationObserver,Async/Await(实际就是promise) 
**Promise:**
将回调函数的嵌套改写成链式  代码冗余,then堆积,流程控制也不强

**async:**

- 有内置执行器;
- 返回Promise,必须等到内部所有的await命令的Promise对象执行完,
才会发生状态改变;
- 更好的语义:await
- 更好的适应性:await后面可以是Promise对象和原始类型的值
如果不是Promise对象的话
也会用`Promise.resolve()`转换成一个Promise对象。

15 vue的数据传递方式

父–>子

- **props**:父组件通过标签特性传递给子组件,子组件通过props来获取
- **provide / inject**:父组件通过provider传递数据,子组件通过inject来获取
- **$attrs / $$listeners**:子组件通过v-bind绑定`$$attrs`,其中包含着不被prop识别的父组件传递过来的数据。通过v-on绑定`$listeners`将事件传递给子组件。
- **$parent**:父组件的实例对象

子 -->父

- **$emit / v-on**:将数据作为`this.$$emit`方法的参数,
		回传给父组件用`v-on:[自定义事件]`监听的函数。
- **ref / $refs**:父组件通过调用子组件ref名获取数据或调用方法,并且获取的是实时数据。
- **$children**:子组件的实例对象,是数组类型的,并不保证顺序,也不是响应式的。

兄–>弟

- **子传父、父传子**
- **eventBus**:新建一个 `Vue` 对象 `bus`,挂载到Vue.prototype,
		然后通过 `bus.$emit` 触发事件,`bus.$on` 监听触发的事件。
- **vuex**:使用`commit`触发一个`mutation`修改store中的state数据,
		通过`this.$store.state.[属性名]`获取vuex仓库中的数据

16 vue的数据双向绑定实现?

采用数据劫持和发布者-订阅者模式的方式,
主要涉及Observer、Compile和Watcher三者。
在`new Vue()`后, Vue 会调用`_init`函数进行初始化,
Observer通过`Object.defineProperty()`来劫持监听属性的`getter`/`setter`,
当对象被读取的时候执行`getter`函数,
当数据发生变化的时候,就会触发对应的`setter`,
然后通知订阅器(Dep)的`dep.notify()`
找到对应的 Watcher调用`update`中的`patch()`比较新旧vdom节点,
根据差异对视图进行更新渲染(有多少个data就有多少个订阅者Watcher),
此时通过Compile解析编译模板指令,找到数据变更的位置,然后更新视图(节点)。

17 proxy相对于defineProperty的优化?

- **Object.defineProperty()缺点**
  - 无法监听数组变化
  - 只能劫持对象的属性,监听时需要遍历属性。
- **Vue3使用proxy:**在获取目标对象之前架设一层“拦截”,可以对外界的访问进行过滤和改写。 
  - proxy可以监听对象而非属性,并返回一个新对象(proxy可以直接监听数组的变化)。
  - 除了 get 和 set,proxy有多达13种的拦截方法。速度加倍、内存减半
- **兼容性:**Proxy 对 IE 不友好,vue3 在检测到使用 IE 的情况下(包括 IE11),会自动降级为 Object.defineProperty 的数据监听系统

参数一: obj:劫持对象,参数二:“name”:劫持对象属性 , 参数三:给属性添加set,get方法

let obj = { name: "tom", age: 10 };
   Object.defineProperty(obj, "name", {
     get: () => {
       console.log("访问了name属性");
     },
     set: (newVule) => {
       console.log("设置了name属性");
     },
   });
   obj.name; //触发get
   obj.name = "jack";//触发set

参数一: target:劫持对象,参数二:prop:劫持对象属性 , 参数三:vaule:新的属性值, p:本身

 let p = new Proxy(obj, {
   get: (target, prop, p) => {
     console.log("获取");
     return prop in target ? target[prop] : "默认值";
   },
   set: (target, prop, vaule, p) => {
     console.log("设置");
     target[prop] = vaule;
   },
 });
 console.log(p.name); //访问了name属性
 console.log((p.name = "java")); //设置了name属性

18 虚拟dom的优点?

1.当然是前端优化方面,避免频繁操作DOM,
频繁操作DOM会可能让浏览器回流和重绘,性能也会非常低,
还有就是手动操作 DOM 还是比较麻烦的,
要考虑浏览器兼容性问题,当前jQuery等库简化了 DOM操作,
但是项目复杂了,DOM操作还是会变得复杂,数据操作也变得复杂

2.并不是所有情况使用虚拟DOM 都提高性能,
是针对在复杂的的项目使用。如果简单的操作,
使用虚拟DOM,要创建虚拟DOM对象等等一系列操作,还不如普通的DOM 操作

3.虚拟DOM 可以实现跨平台渲染,
服务器渲染、小程序、原生应用都使用了虚拟DOM

4.使用虚拟DOM改变了当前的状态不需要立即的去更新DOM 
而且更新的内容进行更新,对于没有改变的内容不做任何操作,
通过前后两次差异进行比较

虚拟 DOM 可以维护程序的状态,跟踪上一次的状态

19 vue 的生命周期?他们有什么用?

20 爬楼梯

function palouti(x){
    if(x==1) return 1;
    else if(x==2) return 2;
    else return palouti(x-1)+palouti(x-2);
}
console.log(palouti(6));

21 数字反转 -123变成-321,123变成321


function reverseMy(n){
    if(n>0){
        str=""+n;
        return str.split('').split('').reverse().join('');
    }else{
        str=""+n;
        return '-'+str.split('-')[1].split('').reverse().join('');
    }
}
console.log(reverseMy(-123))

5 兴业数金(8.10)

5.1 一面

1. js 原型链

2. 闭包,闭包有什么缺点?

3. JS事件循环机制

4. 讲一讲PromisePromise.all

1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

5. asyncawait

promise让异步执行看起来更清晰明了,通过then让异步执行结果分离出来。
async/await其实是基于Promise的。
async函数其实是把promise包装了一下。使用async函数可以让代码简洁很多,不需要promise一样需要些then,不需要写匿名函数处理promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码。

**Promise:**
将回调函数的嵌套改写成链式  代码冗余,then堆积,流程控制也不强

**async:**
- 有内置执行器;
- 返回Promise,必须等到内部所有的await命令的Promise对象执行完,
才会发生状态改变;
- 更好的语义:await
- 更好的适应性:await后面可以是Promise对象和原始类型的值
如果不是Promise对象的话
也会用`Promise.resolve()`转换成一个Promise对象。

6 ES6中的Map()

**普通对象和map的区别:**

1. key的类型无限制 

   普通对象只接受字符串和符号作为键值,其他类型将被强制转换为字符串类型,
   而 Map 可以接受任何类型的键值(包括函数、对象或任何原语)。 

2. 可直接遍历 
常规对象里,为了遍历keys、values和entrlEs,你必须将它们转换为**数组**,
如Object.keys()、Object.values()和Object.entrIEs(),
或[for ... in,另外for ... in循环还有一些限制:
它仅仅遍历可枚举[属性]、非Symb[属性],并且遍历的顺序是任意的。 

3. Map 对象在涉及频繁添加和删除键值对的场景中表现更好,而普通对象没有优化 
> 我们想在 JSON 和原始数据之间转换或包含特定的业务逻辑,那么我们应该使用普通对象。因为当我们只想*存储**键值对和**循环**操作或不断**添加****删除**属性时,使用 Map 对象是更好的选择。
> Map对象虽然也是继承自底层的**Object.prototype**,但它为我们提供了很多实用的方法来减轻我们的认知负担,比普通对象更高级

6. 跨域?如果后端不用cors怎么实现跨域?反向代理

当一个请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域。

常见跨域机制:JSONP、CORS、Web Sockets、Nginx、postMessage
**代理模式:**
给某个对象提供一个代理对象,并由代理对象控制原对象的引用。

**反向代理和正向代理的区别**:
正向代理隐藏真实客户端,反向代理隐藏真实服务端。

**正向代理**,
正向代理服务器位于客户端和服务器之间,为了向服务器获取数据,
客户端要向代理服务器发送一个请求,并指定目标服务器,
代理服务器将目标服务器返回的数据转交给客户端。
这里客户端是要进行一些正向代理的设置的。
例如我们在访问 Google 时,先连上 VPN 服务器将我们的 IP 地址变成美国的 IP 地址,然后就可以顺利的访问了。

**反向代理**,
其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问,
我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,
在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,
暴露的是代理服务器地址,隐藏了真实服务器IP地址。

7 Vue的双向绑定原理

采用数据劫持和发布者-订阅者模式的方式,
主要涉及Observer、Compile和Watcher三者。
在`new Vue()`后, Vue 会调用`_init`函数进行初始化,
Observer通过`Object.defineProperty()`来劫持监听属性的`getter`/`setter`,
当对象被读取的时候执行`getter`函数,
当数据发生变化的时候,就会触发对应的`setter`,
然后通知订阅器(Dep)的`dep.notify()`
找到对应的 Watcher调用`update`中的`patch()`比较新旧vdom节点,
根据差异对视图进行更新渲染(有多少个data就有多少个订阅者Watcher),
此时通过Compile解析编译模板指令,找到数据变更的位置,然后更新视图(节点)。

8 发布订阅和观察者模式的区别

**观察者模式:**
目标(Subject)和观察者之间是没有事件调度中心的,
观察者需要在目标中进行统一管理,并亲自去**通知所有的观察者****发布-订阅者模式:**
相比观察者模式多了个**事件调度中心**,
管理事件的订阅和发布工作,彻底隔绝了订阅者和发布者的依赖关系

9 wabpack

模块打包工具,根据模块的依赖关系进行静态分析,
然后将这些模块按照指定规则生成浏览器能使用的静态资源。

一切文件皆模块;按需加载。

10 用到哪些loader、plugin

在这里插入图片描述

在这里插入图片描述

11 webpack如何实现拆分

CodeSplit

6 顺丰(9.6)

6.1 一面

1. 有看过echarts源码吗

2.会手写CSS吗,比如垂直居中?

flex
绝对定位

3. CSS3 有什么常用属性?transform可以做哪些?比如旋转两圈可以用什么快捷方式?

偏移 缩放 旋转

4. 盒子模型?有哪两种? 你平时用哪种?(面试官:其实怪异的更好用)

5. 用过less吗?可以做什么?比如定义变量?(面试官:写特殊的动画,用less很快,比如花朵绽放,不需要每一片花旋转)

6. Vue的导航守卫有哪些?一般用路由守卫做什么?除了用路由守卫还可以用什么做登陆验证?(面试官:自定义一个生命周期,判断登陆后的状态

7. Vue的生命周期?一般在哪个生命周期进行请求后端的数据(我说的Mounted,面试官:可以在beforeMounted去请求)

8.父子组件的生命周期?(面试官:如果从这一点考虑,父组件中放在beforeMounted中比较好)

9.ElementUI的源码看过吗?alert的源码了解吗?(面试官:这个封装特别好玩,可以好好去看看)

10.colorUI是什么组件库?是小程序的组件库吗?开发PC端和小程序有什么不一样?(面试官:小程序的功能很多,看相册、照相也比较好用)

11. ES6的新特性?

11.1 Symbol用在哪里?
11.2 Promise说说,回调地狱是什么?Promise的常用方法?实例身上的方法?如果无论成功和失败都想走用什么API?(这里忘记了finally,面试官:还有done,可能比较冷门)
11.3 箭头函数?和普通函数有什么不同?为什么不能new,普通函数要改this有什么方法?callapply有什么不同?
11.4 ES6数组有什么新方法?someevery返回的是什么?遍历还有什么方法?(漏了几个,面试官: for infor ofreduce
11.5 ES6解构赋值的问题?这里好像回答不太对,面试官说有一点误解
11.6 Set、Map?Map和对象有什么不同?WeakMap和Map是什么不同?最主要的区别?(面试官:WeakMap只接收对象作为Key

12. 正则有了解哪些?怎么匹配数字?快捷方式?(面试官:/d)

13. 还接触过什么框架?Vue3Vue2有什么区别?proxy为什么更好?(面试官:Vue2只能遍历绑定每个字段,Vue3可以遍历整个对象,一个宏观一个微观)

7 携程

7.1 一面(9.7)

1. 自我介绍

2. html5 新增的特性?

3. CSS3 新增的特性?

4. js的事件循环机制?

5. es6 的新特性?

6. 写一个扁平化,数组去重

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
  return arr.flat(Infinity);
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]
let arr1 = [1, 1, 1, 2, 3, 6, 5, 5, 8, 7, 7, 7, 8];
let arr2 = Array.from(new Set(arr1));
 // return [...new Set(arr)]
console.log(arr2);

7. promise的api用过哪些?promise.all()和promise.allSettled()的区别?

 Promise.all() 执行过个 promise 时,只要其中任何一个promise 失败都会执行 reject ,并且 reject 的是第一个抛出的错误信息,只有所有的 promise 都 resolve 时才会调用 .then 中的成功回调
Promise.allSettled() 可以获取数组中每个 promise 的结果,无论成功或失败

8. Webpack工作原理?用过哪些loader 和plugins? 除了转html?还有压缩?还有做过其他的吗?

9. MySQL用过吗?做过什么?多少数据量?几张表?命令熟不熟?

10. git 版本控制工具? 工作流? 常用命令? git fork 了解吗

11. flex 布局?有什么属性?

12. MVVM?

MVVM(Model-View-ViewModel)是一种软件架构设计模式,它是一种简化用户界面的事件驱动编程方式。

  • Model, 代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。
  • View, 代表UI组件,它负责将数据模型转化成UI 展现出来。
  • ViewModel, 监听模型数据的改变和控制试图行为、处理用户交互、简单理解就是一个同步View和Model的对象,连接Model和View。通过实现一套数据响应式机制自动响应Model中的数据变化,同时Viewmodel会实现一套更新策略自动将数据变化转换为视图更新,通过事件监听响应View中用户交互修改Model中数据,这样在ViewModel中就减少了大量DOM操作代码。

MVVM 的设计思想: MVVM中的View通过使用模板语法来声明式的将数据渲染进DOM,当ViewModel对Model进行更新的时候,会通过数据绑定更新到View。(本质是观察者模式)

**前端具体应用:**AngularJs、EmberJs、VueJs

1)MVVM缺点
  • 数据绑定使得一个位置的 Bug 被快速传递到别的位置,问题的定位变得困难。可能是 View / Model 代码有问题。
  • 数据双向绑定中,一个View绑定一个model,不同模块的model都不同,不利于代码重用。
  • 一个模块中model很大的情况下,如果长期持有不释放内存会造成内存泄漏。
    img
2)MVVM和MVC区别
  • MVC中Controller演变成MVVM中的ViewModel
  • MVVM通过数据来显示视图层,并采用数据双向绑定原理,MVC是通过Controller来进行节点的操作
  • MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验

13. Nodejs用过什么? Nodejs有哪些模块?nodejs用到过什么?

14. 讲一下url输入回车,后面的过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RZjev9QC-1663077940825)(fig\d8a31e587c3d4d4ca7b8b6b352b76441.png)]

15. 讲一下页面渲染流程?

16. 跨域机制,跨域有哪些方法?jsonp讲一下原理

7.2 二面(9.16)

1.介绍你最近一次的项目?空气质量预测。

2. 你这个数据是自己生成的,还是有团队来做的

3. 你在做这个可视化的时候,你有遇到数据量很大,而导致加载慢?你是否做了优化?如何优化的?你还有没有其他方法优化? 类似于懒加载按需加载是吗?

4. 实时更新是如何更新的?客户端如何接收?是拉的方式还是推的方式?你是如何客户端和服务端通信?

5. 拉回来的数据如何比较?有没有增量的更新? 怎么监听到上一次的数据有没有变化?

6. 自定义组件是做了哪些组件?拆分组件的目的?为了体积变小是吗?

7. 管理系统这个项目,mork是做什么,什么时候用到这个?

8. 管理系统遇到了什么难点?

9. 这几个项目的部署和上线是你负责的吗?

10. 页面中的资源,缓存机制有了解过吗,强缓存和协商缓存?

11. 运行过程中,有遇到缓存问题吗,发布后没有更新? 如何解决的

12. 你觉得在开发中异步请求和同步请求有什么区别?

13. 继续聊到第一个项目,比如定时器设置10秒,那么一定会在10秒的时候发请求吗?

14. 上一个面试官,有跟你聊的Promise.allSettled() ,这个你是怎么理解的?

15. nodeJs做过服务端的开发吗?

16. 你的开发过程中会自己写单元测试?

17. 做一个题aaaTTTTssssssaaaaaaa,输出连续出现最多的字符,输出{s:6,a:6},你这样写不能把两个一样长都返回,我大概理解你的思路了

18. 你现在研究生的方向是什么,为什么没有往这个方向去做,为什么要做前端

反问:

1. 提建议:对于数据类型不太数量,数据更新可以尝试websocket,nodejs也可以多了解,现在前端会node要自己写服务。

2. 面试的结果不能告诉你,hr会联系,一周出结果,两轮技术面。

7.3 hr业务面(9.23)

1. 自我介绍

2. 为什么选携程,你觉得携程的能带给你什么?

3. 你之前两轮技术面下来,你觉得有什么技术和你之前想的不一样?

4. 你现在看机会,除了携程,你会看什么样的互联网的方向,为什么?为什么看好旅游平台?

5. 你用过我们的平台吗,购买机票的场景,涉及哪些前端的技术流程?

6. 你在用我们的产品,你觉得有些让你觉得不太好的?

7. 你觉得和哪些平台的很像?我说了去哪儿,她说 去哪儿是被携程收购了,讲了旅游平台不止是toC,还有toB,对航空和酒店的,业务链很复杂,后端涉及到的搜索算法很严格,搜索技术设计到mapSearch什么的也有很严格的要求。

8. 以前做的项目,你是自驱力很强倾向于从0到1,还是执行力很强适合在传统项目下做迭代?

9. 你觉得在小程序,从0到1,有哪些短板,需要加强?那你当时去做了哪些事情去解决了这个困难?

10. 你从0到1的交流,和上级的有产生意见不合的时候吗,怎么解决的?

11. 反问:

11.1 部门,还不确定部门,今年下旬有双选会,具体的团队进行沟通
11.2 前端在携程的培训机制,工作3-4个月目标,然后答辩来检查,配导师。三四个月以后有训练营,就公司业务和产品和技术开拓,有虚拟组。
11.3 后续的推进,比四六级难度低(听说读写),测评完3-5天出录用决策。
11.4 作息,看团队需求,总的原则9个小时,弹性工作,中间一个小时午休。
115. 待遇,现在是业务hr,不太方便透露。

8 小公司(长沙)

1. 谷歌浏览器字体显示12px以下

  1. 方法一
    更改谷歌浏览器的基础设定12px值,用来解决开发人员的调试问题
  2. 方法二
使用缩放transform:scale(),使用较为广泛
优点:单行、多行文本都可使用
缺点:只是视觉效果变小,并不会改变盒子的实际占位,在对齐其他盒子时不太友好

9 收钱吧(10.19)

9.1 页面隐藏的方式? 有什么区别?

属性描述
display: none会从渲染树中消失,元素不占据空间且无法点击;会引起重排,性能消耗较大;是非继承属性,父元素设置了 display:none 或 opacity: 0,子元素无论怎么设置都无法显示;
visibility: hidden不会从渲染树中消失,元素继续占据空间但无法点击;会引起重绘,性能消耗相对较小;会被子元素继承,并且子元素可以通过设置设置 visibility: visible; 来取消隐藏。
opacity: 0不会从渲染树消失,元素占据空间且可点击。会重建图层,性能较高;是非继承属性。
  • 重排( 回流 ):无论通过什么方式影响了元素的几何信息(元素在视口内的位置和尺寸大小),浏览器需要重新计算元素在视口内的几何属性,这个过程叫做重排。

  • 重绘:通过构造渲染树和重排(回流)阶段,我们知道了哪些节点是可见的,以及可见节点的样式和具体的几何信息(元素在视口内的位置和尺寸大小),接下来就可以将渲染树的每个节点都转换为屏幕上的实际像素,这个阶段就叫做重绘。

9.2 为什么有let const,解决了什么问题?

  • **var:**存在变量提升,可以提升到全局作用域 / 函数作用域,不存在暂时性死区,允许重复声明。
  • **let / const:**都不存在变量提升,都存在暂时性死区,不允许重复声明。
  • const:声明的是不能修改的只读常量,一定要初始化。不能修改的本质是不能修改内存中保存的数据,对于基本数据类型来说肯定不能修改,但是对于引用数据类型来说存的只是引用指针(栈),而引用指针指向的堆内存空间中的数据是可以改变的。

变量提升是 JavaScript 引擎在执行一段代码时做的预处理工作,预处理会把变量的声明放到函数或全局的顶部,这样能保证变量在使用的时候都声明过了。 (相当于预编译)

9.3 操作系统,cpu计算和存储是串行的还是并行的?

CPU内部主要由运算器、控制器、寄存器三大部分组成。
运算器 负责算术运算(+ - * / 基本运算和附加运算)和逻辑运算(包括 移位、逻辑测试或比较两个值等)。
控制器 负责应对所有的信息情况,调度运算器把计算做好。
寄存器 它们可用来暂存指令、数据和地址。既要对接控制器的命令,传达命令给运算器;还要帮运算器记录处理完或者将要处理的数据。

CPU 计算是串行的,存储的时候可以并行

9.4 https 建立连接详细过程

一、客户端发起https连接

当用户在浏览器,地址栏敲击,浏览器去到DNS服务器获取此url对应的ip,然后客户端连接上服务端的443端口,将此请求发送给到服务端,此时客户端同时将自己支持的加密算法带给服务端;

二、服务端发送证书

在讲这一段之前插播一条小知识点:私钥加密的密文只有公钥才能解开;公钥加密的密文只有私钥才能解开。

服务端收到这套加密算法的时候,和自己支持的加密算法进行对比,如果不符合,就断开连接;如果符合,服务端就将SSL证书发送给客户端,此证书中包括了数字证书包含的内容:1、证书颁发机构;2、使用机构;3、公钥;4、有效期;5、签名算法;6、指纹算法;7、指纹。

这里服务端发送的东西是用私钥进行加密的,公钥都能解开,并不能保证发送的数据包不被别人看到,所以后面的过程会和客户端商量选择一个对称加密(只能用私钥解开,这里详情请移步非对称、对称加解密相关问题)来对传输的数据进行加密。

三、客户端验证服务端发来的证书

1、验证证书

客户端验证收到的证书,包括发布机构是否合法、过期,证书中包含的网址是否与当前访问网址一致等等。

2、生成随机数(此随机数就是后面用的对称加密的私钥)

客户端验证证书无误后(或者接受了不信任的证书),会生成一个随机数,用服务端发过来的公钥进行加密。如此一来,此随机数只有服务端的私钥能解开了。

3、生成握手信息

用证书中的签名hash算法取握手信息的hash值,然后用生成的随机数将[握手信息和握手信息的hash值]进行加密,然后用公钥将随机数进行加密后,一起发送给服务端。其中计算握手信息的hash值,目的是为了保证传回到服务端的握手信息没有被篡改。

四、服务端接收随机数加密的信息,并解密得到随机数,验证握手信息是否被篡改。

服务端收到客户端传回来的用随机数加密的信息后,先用私钥解密随机数,然后用解密得到的随机数解密握手信息,获取握手信息和握手信息的hash值,计算自己发送的握手信息的hash值,与客户端传回来的进行对比验证。

如果验证无误,同样使用随机字符串加密握手信息和握手信息hash值发回给到客户端

五、客户端验证服务端发送回来的握手信息,完成握手

客户端收到服务端发送过来的握手信息后,用开始自己生成的随机数进行解密,验证被随机数加密的握手信息和握手信息hash值。

验证无误后,握手过程就完成了,从此服务端和客户端就开始用那串随机数进行对称加密通信了(常用的对称加密算法有AES)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值