1. 元素水平居中与垂直居中
1.1 水平居中
- 如果它是一个行内元素,就对它的父元素应用:
text-align: center
- 如果它是一个块级元素,就对它自身应用:
margin: auto
1.2 垂直居中
- 基于绝对定位的解决方案(分元素宽高固定和不固定两种情况)
- 基于视口单位的解决方案(只适用于在视口中居中的场景
- 基于
Flexbox
的解决方案(最佳解决方案),Flexbox
还可以将匿名容器(即没有被标签包裹的文本节点)垂直居中
2. 简述box-sizing属性的作用
- content-box:默认的盒子,设置padding和border会撑开盒子,使盒子比原本的宽高更大
- border-box:包含了padding和border,设置两者不会撑开盒子,盒子大小不会改变
- inherit:规定应从父元素继承 box-sizing 属性的值
3. 事件循环
所有的任务可以分为同步任务和异步任务,同步任务,顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行;而异步任务,就是异步执行的任务,比如ajax网络请求,setTimeout 定时函数等都属于异步任务,异步任务会通过任务队列的机制(先进先出的机制)来进行协调。
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。
4. 词法作用域
词法作用域是作用域的一种工作模型。作用域有两种工作模型:词法作用域和动态作用域。
词法作用域是一套关于引擎如何寻找变量以及会在何处找到变量的规则。词法作用域最重要的特征是它的定义过程发生在代码的书写阶段(假设你没有使用eval()
或with
)。
而动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用。换句话说,作用域链是基于调用栈的,而不是代码中的作用域嵌套。
JavaScript 并不具有动态作用域,它只有词法作用域,简单明了。但是this 机制某种程度上很像动态作用域。
主要区别:词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定
的。(this 也是!)词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。
5. 闭包
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
6. var、let、const
let和const定义的变量都会被提升,但是不会被初始化,不能被引用。主要是因为const。const,顾名思义:常量,const的引用不应被改变。如果编译器把const初始化为undefined,之后,又让它等于我们定义的那个值,就改变了const的引用。因此,委员会决定let和const虽然也会发生变量提升,但是没有任何初始值。
var、let和const另外一个重要区别就是let和const只在块级作用域中有效。
const定义的变量不能被更改,其实是const定义的变量的引用不能被更改
let 和 const 变量提升但存在暂时性死区,未初始化或赋值前不允许访问(ReferenceError)
在全局作用域下使用 let
/const
声明变量,变量并不会被挂载到 window
上(不是全局window对象的属性)
7. Vue的双向绑定原理?
首先需要一个监听器Observer监听数据(model)的变化,具体实现是利用Observer中Object.definePrototype()函数实现监听数据。当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data
选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty
把这些属性全部转为 [getter/setter]。这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在属性被访问和修改时通知变更。
每个组件实例都对应一个 watcher 实例,当数据发生变化就通知订阅者Watcher,并执行绑定的更新函数,从而更新视图。
最后需要一个Compile解析器,解析V-model、V-on等节点,绑定对应的更新函数并初始化这类节点的模板数据。
8. 判断运行中 this 的绑定
需要找到这个函数的直接调用位置,找到之后就可以顺序应用下面这四条规则来判断 this 的绑定对象。
- 由 new 调用?绑定到新创建的对象。(new绑定)
- 由 call 或者 apply(或者 bind)调用?绑定到指定的对象。(显式绑定)
- 由上下文对象调用?绑定到那个上下文对象。(隐式绑定,可能会出现隐式丢失)
- 默认:在严格模式下绑定到 undefined,否则绑定到全局对象。(默认绑定)
一定要注意,有些调用可能在无意中使用默认绑定规则。如果想“更安全”地忽略 this 绑定,你可以使用一个 DMZ 对象,比如 ∅ = Object.create(null),以保护全局对象。
回调函数丢失 this 绑定是很常见的
ES6 中的箭头函数并不会使用四条规则的绑定规则,而是根据当前的词法作用域来决定 this,具体来说,箭头函数会集成外层函数调用的 this 绑定(无论 this 绑定到什么)。这其实和 ES6 之前代码中的 self = this 机制一样的。
显式绑定
使用 call
或者 apply
在某个对象上强制调用函数,这两个方法,第一个参数是一个对象,是给 this
准备的,接着在调用函数时将其绑定到 this
,因为可以直接指定 this
的绑定对象,所以称之为显式绑定。
call
和 apply
两个方法的区别在其他参数上。
显式绑定仍然无法解决隐式绑定中提到的丢失绑定问题,显式绑定的一个变种-硬绑定可以解决这个问题。
硬绑定
function foo() {
console.log(this.a);
}
var obj = {
a:2
}
var bar = function() {
foo.call(obj);
}
var a = 3;
bar(); // 2
setTimeout(bar, 100); // 2
// 硬绑定的 bar 不可能再修改它的 this
bar.call(window); // 2
硬绑定的典型应用场景是创建一个包裹函数(包裹函数内部调用 call
或 apply
),负责接收参数并返回值
function foo(something) {
console.log(this.a, something);
return this.a + something;
}
var obj = {
a: 2
}
var bar = function() {
return foo.apply(obj, arguments);
}
var b = bar(3); // 2 3
console.log(b);
另一种使用方法是创建一个可以重复使用的辅助函数:
function foo(something) {
console.log(this.a, something);
return this.a + something;
}
// 简单地辅助绑定函数
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments);
};
}
var obj = {
a:2
};
var bar = bind(foo, obj);
var b = bar(3); // 2 3
console.log(b); // 5
硬绑定过的函数不可能再修改它的 this
。
ES5
中实现了硬绑定的内置方法:Function.prototype.bind
第三方库的许多函数,以及 JavaScript
语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”,其作用和 bind(...)
一样,确保你的回调函数使用指定的 this
,比如 forEach
new绑定
在 JavaScript
中,构造函数只是一些使用 new
操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被 new
操作符调用的普通函数而已。实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
使用 new
来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行 [[Prototype]] 连接。
- 这个新对象会绑定到函数调用的 this。
- 如果函数没有返回其他对象,那么
new
表达式中的函数调用会自动返回这个新对象。
软绑定
硬绑定会大大降低函数的灵活性,使用硬绑定之后就无法使用隐式绑定或者显示绑定来修改 this
。
如果可以给默认绑定制定一个全局对象和 undefined
以外的值,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显示绑定修改 this
的能力。
软绑定方法:
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this;
// 捕获所有的 curried 参数
var curried = [].slice.call(arguments, 1);
var bound = function() {
return fn.apply(
(!this || this === (window || global)) ?
obj : this,
curried.concat.apply(curried, arguments)
);
};
bound.prototype = Object.create(fn.prototype);
return bound;
};
}
它会对指定的函数进行封装,首先检查调用的 this
,如果 this
绑定到全局对象或者 undefined
,那就把指定的默认对象 obj
绑定到 this
,否则不会修改 this
。
9. JS有没有指针
JavaScript中没有指针,引用的工作机制也不尽相同。在JavaScript中变量不可能成为指向另一个变量的引用。
JavaScript引用指向的是值。如果一个值有10个引用,这些引用指向的都是同一个值,他们相互之间没有引用/指向关系。
10. 深拷贝浅拷贝
浅拷贝:复制一层对象的属性,并不包括对象里面的引用类型的数据,当改变拷贝的对象里面的引用类型时,源对象也会改变。
深拷贝:重新开辟一个内存空间,需要递归拷贝对象里的引用,直到子属性都为基本类型。两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。
slice()、concat、Array.from()、… 操作符:只能实现一维数组的深拷贝
Object.assign({}, obj):只能实现一维对象的深拷贝
JSON.parse(JSON.stringify(obj)):可实现多维对象的深拷贝,但会忽略undefined、任意的函数、symbol 值
11. 箭头函数与普通函数的区别
- 箭头函数是匿名函数,不能作为构造函数,不能使用new
- 箭头函数不能绑定arguments,取而代之用rest参数
...
解决 - 箭头函数没有原型属性
- 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
12. 绝对定位与相对定位
-
Absolution:元素会脱离文档流,定位是相对于离它最近的且不是static定位的父元素而言,若该元素没有设置宽度,则宽度由元素里面的内容决定,且宽度不会影响父元素,定位为absolution后,原来的位置相当于是空的,下面的的元素会来占据。
-
Relative:元素仍处于文档流中,定位是相对于原本自身的位置,若没有设置宽度,则宽度为父元素的宽度,该元素的大小会影响父元素的大小。
13. JavaScript是单线程的
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
14. 说一说 http 和 https
https的SSL加密是在传输层实现的
14.1 http和https的基本概念
-
http:超文本传输协议,是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP) ,用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。
-
https:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层, HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
https协议的主要作用是:建立一个信息安全通道,来确保数组的传输,确保网站的真实性。
14.2 http和https的区别?
http传输的数据都是未加密的,也就是明文的,网景公司设置了SSL协议来对http协议传输的数据进行加密处理,简单来说https协议是由http和ssl协议构建的可进行加密传输和身份认证的网络协议,比http协议的安全性更高。
主要的区别如下:
- Https协议需要ca证书,费用较高。
- http是超文本传输协议,信息是明文传输, https则是具有安全性的ssI加密传输协议。
- 使用不同的链接方式,端口也不同,一般而言,http协议的端口为80,https的端口为443
- http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
14.3 https协议的工作原理
客户端在使用HTTPS方式与Web服务器通信时 有以下几个步骤
客户使用https url 访问服务器,则要求web服务器建立ssI链接。
web服务器接收到客户端的请求之后,会将网站的证书(证书中包含了公钥)返回或者说传输给客户端。
客户端和web服务器端开始协商SSL链接的安全等级,也就是加密等级。
客户端浏览器通过双方协商一致的安全等级, 建立会话密钥,然后通过网站的公钥来加密会话密钥,并传送给网站。
web服务器通过自己的私钥解密出会话密钥。 web服务器通过会话密钥加密与客户端之间的通信。
14.4 https协议的优点
使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;
HTTPS协议是由SSL+HTTP协议构建的可进行加密传输,身份证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取,改变,确保数据的完整性。
HTTPS是现行框架最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人的攻击成本。
谷歌曾在2014年8月份调整搜索算法,并称“比起同等HTTP网站,采用HTTPS加密的网站在搜索结果中的排名更高”。
14.5 https协议的缺点
https握手阶段比较费时,会使页面加载时间延长50%,增加10%~20%的耗电。
https缓存不如http高效,会增加数据开销。
SSL证书也需要钱,功能越大的证书费用越高。
SLL证书需要绑定IP ,不能再同一个ip绑定多个域名,ipv4资源支持不了这种消耗。
15. 为什么说 http 是无状态的
- 协议的状态是指下一次传输可以“记住”这次传输信息的能力。
- http是不会为了下一次连接而维护这次连接所传输的信息,为了保证服务器内存。
- 比如客户获得一张网页之后关闭浏览器,然后再一次启动浏览器,再登录该网站,但是服务器并不知道客户关闭了一次浏览器。
- 由于Web服务器要面对很多浏览器的并发访问,为了提高Web服务器对并发访问的处理能力,在设计HTTP协议时规定Web服务器发送HTTP应答报文和文档时,不保存发出请求的Web浏览器进程的任何状态信息。这有可能出现一个浏览器在短短几秒之内两次访问同一对象时,服务器进程不会因为已经给它发过应答报文而不接受第二期服务请求。由于Web服务器不保存发送请求的Web浏览器进程的任何信息,因此HTTP协议属于无状态协议(Stateless Protocol)。
Cookie是客户端的存储空间,由浏览器来维持。具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。cookie和session起到保存客户端状态的作用,但是它们并没有改变http协议本身这种无状态的性质,可以理解为在应用上做了状态保留。
16. HTTP协议是无状态的和Connection: keep-alive的区别
- 无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。从另一方面讲,打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系。
HTTP
是一个无状态的面向连接的协议,无状态不代表HTTP
不能保持TCP
连接,更不能代表HTTP
使用的是UDP
协议(无连接)。- 从
HTTP/1.1
起,默认都开启了Keep-Alive,保持连接特性,简单地说,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。 Keep-Alive
不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。
17. 简述flex-box布局
又叫弹性盒子布局。简单来说,它是一种CSS快速布局方式,相比于传统文档流布局方式,具有简洁、高效和响应式等优点。它主要在轴和容器(父容器和子容器)上做文章。
父容器可以设置子容器统一排列方式,主轴方向:justify-content,交叉轴方向:align-items。
主轴决定容器水平方向的排列,交叉轴决定容器垂直方向的排列,可以自定义主轴方向(上下左右),主轴沿逆时针旋转90°得到交叉轴。子容器可以设置自身排列方式。
元素水平垂直居中采用flex-box,相比于传统布局,就很简单
18. 简述babel的作用
babel是一个node命令行工具,它的作用是对我们源代码进行转码(把es6转为es5),因为有些浏览器不支持ES6语法的JavaScript代码
19. 生命周期函数的调用顺序
实例创建阶段
- beforeCreate():在实例被完全创建出来之前执行,该函数执行时,data 和 methods 中的数据还没有被初始化
- created():data 和 methods 已经被初始化好了
- beforeMounte():表示模板已经在内存中编译完成了,但尚未渲染到页面中,页面中元素的内容还只是模板字符串
- mounted():表示内存中的模板已经渲染到页面上了,用户已经可以看到渲染好的页面了
运行中的两个函数
- beforeUpdate():表示页面还没有被更新(但数据已经更新了)
- update():页面和 data 数据已经同步了,都是最新的
附上在各过程中父子组件的执行顺序
- 加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
- 子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
- 父组件更新过程
父beforeUpdate->父updated
- 销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
20. vue 组件交互的几种方式
20.1 props/$emit
-
父组件向子组件传值:父组件通过 props 向下传递数据给子组件。
父组件以属性绑定的形式传递到子组件内部,供子组件使用;子组件把父组件传递过来的属性,先在 props 数组中定义一下,这样才能使用这个数据
-
子组件向父组件传值(通过事件形式):子组件通过events给父组件发送消息,实际上就是子组件把自己的数据发送到父组件
20.2 $emit / $on
这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级
20.3 vuex
Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走Action,但Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。
20.4 $attrs / $listeners
多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此Vue2.4 版本提供了另一种方法----$attrs
/$listeners
$attrs
与$listeners
是两个对象,$attrs
里存放的是父组件中绑定的非 Props 属性(class 和 style 除外),$listeners
里存放的是父组件中绑定的(不含 .native 修饰器的) 非原生事件。
20.5 provide/inject
Vue2.2.0新增API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过provide来提供变量,然后在子孙组件中通过inject来注入变量。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
20.6 $parent / $children与 ref
ref
:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例$parent
/$children
:访问父 / 子实例
需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。
这两种方法的弊端是,无法在跨级或兄弟间通信。
20.7 总结
常见使用场景可以分为三类:
- 父子通信:
父向子传递数据是通过 props,子向父是通过 events($emit
);通过父链 / 子链也可以通信($parent
/ $children
);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners
- 兄弟通信:
Bus;Vuex
- 跨级通信:
Bus;Vuex;provide / inject API、$attrs/$listeners
21. 块级元素 行内元素的区别是什么?除了这两种还有哪些
-
块级元素会独占一行,其宽度自动填满其父元素宽度;行内元素不会独占一行,相邻的行内元素会排列在同一行里,直到一行排不下,才会换行,其宽度随元素的内容而变化
-
块级元素可以设置 width, height属性,行内元素设置width, height无效
-
块级元素可以设置margin 和 padding. 行内元素的水平方向的padding-left,padding-right,margin-left,margin-right 都产生边距效果,但是竖直方向的padding-top,padding-bottom,margin-top,margin-bottom都不会产生边距效果。(水平方向有效,竖直方向无效)
除了这两种元素还有空元素(不存在子节点的元素)、行内块元素(宽、高、margin、padding都有效)、可变元素(可变元素根据上下文语境决定该元素为块元素或者行内元素。
22. call apply bind 区别
apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数。
apply 和 call 基本类似,他们的区别只是传入的参数不同。
apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。
bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。
bind 是创建一个新的函数,我们必须要手动去调用
23. 浏览器的渲染机制
- Create/Update DOM And request css/image/js:浏览器请求到HTML代码后,在生成DOM的最开始阶段(应该是 Bytes → characters 后),并行发起css、图片、js的请求,无论他们是否在HEAD里。注意:发起 js 文件的下载 request 并不需要 DOM 处理到那个 script 节点,比如:简单的正则匹配就能做到这一点,虽然实际上并不一定是通过正则:)。这是很多人在理解渲染机制的时候存在的误区。
- Create/Update Render CSSOM:CSS文件下载完成,开始构建CSSOM
- Create/Update Render Tree:所有CSS文件下载完成,CSSOM构建结束后,和 DOM 一起生成 Render Tree。
- Layout:有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。下一步操作称之为Layout,顾名思义就是计算出每个节点在屏幕中的位置。
- Painting:Layout后,浏览器已经知道了哪些节点要显示(which nodes are visible)、每个节点的CSS属性是什么(their computed styles)、每个节点在屏幕中的位置是哪里(geometry)。就进入了最后一步:Painting,按照算出来的规则,通过显卡,把内容画到屏幕上。
24. Vue-Router 的实现原理
vue-router是vue的核心插件,而当前vue项目一般都是单页面应用,也就是说vue-router是应用在单页面应用中的。
单页面
第一次进入页面的时候会请求一个html文件,刷新清除一下。切换到其他组件,此时路径也相应变化,但是并没有新的html文件请求,页面内容也变化了。
原理是:JS会感知到url的变化,通过这一点,可以用js动态的将当前页面的内容清除掉,然后将下一个页面的内容挂载到当前页面上,这个时候的路由不是后端来做了,而是前端来做,判断页面到底是显示哪个组件,清除不需要的,显示需要的组件。这种过程就是单页应用,每次跳转的时候不需要再请求html文件了。
多页面
每一次页面跳转的时候,后台服务器都会给返回一个新的html文档,这种类型的网站也就是多页网站,也叫做多页应用。
原理是:传统的页面应用,是用一些超链接来实现页面切换和跳转的
vue-router 实现原理
原理核心就是 更新视图但不重新请求页面。
路由模式
- hash: 使用 URL hash 值来作路由。默认模式。
- history: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式。
- abstract: 支持所有 JavaScript 运行环境,如 Node.js 服务器端
hash 模式
hash 即浏览器 url 中 # 后面的内容,包含 #。hash 是 URL 中的锚点,代表的是网页中的一个位置,单单改变 # 后的部分,浏览器只会加载相应位置的内容,不会重新加载页面。
- #是用来指导浏览器动作的,对服务器端完全无用,HTTP请求中,不包含#。
- 每一次改变 # 后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置。
所以说Hash模式通过锚点值的改变,根据不同的值,渲染指定DOM位置的不同数据。
history 模式
HTML5 History API提供了一种功能,能让开发人员在不刷新整个页面的情况下修改站点的URL,就是利用 history.pushState API 来完成 URL 跳转而无须重新加载页面;
由于hash模式会在url中自带#,如果不想要很丑的 hash,我们可以用路由的 history 模式,只需要在配置路由规则时,加入"mode: ‘history’",这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。
有时,history模式下也会出问题:
eg:
hash模式下:xxx.com/#/id=5 请求地址为 xxx.com,没有问题。
history模式下:xxx.com/id=5 请求地址为 xxx.com/id=5,如果后端没有对应的路由处理,就会返回404错误;
为了应对这种情况,需要后台配置支持:
在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
abstract 模式
abstract模式是使用一个不依赖于浏览器的浏览历史虚拟管理后端。
根据平台差异可以看出,在 Weex 环境中只支持使用 abstract 模式。 不过,vue-router 自身会对环境做校验,如果发现没有浏览器的 API,vue-router 会自动强制进入 abstract 模式,所以 在使用 vue-router 时只要不写 mode 配置即可,默认会在浏览器环境中使用 hash 模式,在移动端原生环境中使用 abstract 模式。 (当然,你也可以明确指定在所有情况下都使用 abstract 模式)。
25. MVVM框架的原理
MVVM分别指View,Model,View-Model,View通过View-Model的DOM Listeners将事件绑定到Model上,而Model则通过Data Bindings来管理View中的数据,View-Model从中起到一个连接桥的作用。
MVVM的实现主要是三个核心点:
- 响应式:vue如何监听data的属性变化
- 模板解析:vue的模板是如何被解析的
- 渲染:vue模板是如何被渲染成HTML的
响应式
通过 Object.defineProperty 将 data 里的每一个属性的访问与修改都变成了一个函数,在函数get和set中我们即可监听到data的属性发生了改变
模板解析
模板本质上是一串字符串,它看起来和 html 很像,实际上有很大的区别,因为模板本身还带有逻辑运算,比如v-if,v-for等等,模板最后还是要转换为html来显示。模板中的这些逻辑运算等,要解析成 js,才能实现逻辑运算,以及渲染 html 页面。
这里就引出了vue中一个特别重要的函数——render
render函数中的核心就是with函数。
with函数将某个对象添加到作用域链的顶部,如果在 statement中有某个未使用命名空间的变量,跟作用域链中的某个属性同名,则这个变量将指向这个属性值。
最终render函数返回的是一个虚拟DOM。
模板渲染
模板渲染为html分为两种情况,第一种是初次渲染的时候,第二种是渲染之后数据发生改变的时候,它们都需要调用updateComponent,后者使用diff算法完成新旧对比并修改。
26. promise
应用场景
- 解决回调地狱问题
- 实现在多个请求发送完成后 再得到或者处理某个结果
原理
1.promise
有三种状态,分别是pedding
(Promise
对象实例创建时候的初始状态 )、Fulfilled
(成功的状态)、 Rejected
(失败的状态)。
-
构造一个
Promise
实例需要给Promise
构造函数传入一个函数。传入的函数需要有两个形参,两个形参都是function
类型的参数。分别是resolve
和reject
。 -
Promise
上还有then
方法,then
方法就是用来指定Promise
对象的状态改变时确定执行的操作,resolve
时执行第一个函数(onFulfilled),reject
时执行第二个函数(onRejected) -
Promise对象的状态改变,只有两种可能:
从pending变为fulfilled
从pending变为rejected。
状态改变后就不会再变了。
27. 传输层协议 TCP/UDP
计算机与网络设备要相互通信,双方就必须基于相同的方法。比如,如何探测到通信目标、由哪一边先发起通信、使用哪种语言进行通信、怎样结束通信等规则都需要事先确定。不同的硬件、操作系统之间的通信,所有的这一切都需要一种规则。而我们就把这种规则称为协议(protocol)。
TCP/IP 是互联网相关的各类协议族的总称,比如:TCP,UDP,IP,FTP,HTTP,ICMP,SMTP 等都属于 TCP/IP 族内的协议。
TCP/IP模型是互联网的基础,它是一系列网络协议的总称。这些协议可以划分为四层,分别为链路层、网络层、传输层和应用层。
-
链路层:负责封装和解封装IP报文,发送和接受ARP/RARP报文等。
-
网络层:负责路由以及把分组报文发送给目标网络或主机。
-
传输层:负责对报文进行分组和重组,并以TCP或UDP协议格式封装报文。
-
应用层:负责向用户提供应用程序,比如HTTP、FTP、Telnet、DNS、SMTP等。
OSI七层模型 | TCP/IP概念模型 | 功能 | TCP/IP协议族 |
---|---|---|---|
应用层 | 应用层 | 文件传输,电子邮件,文件服务,虚拟终端 | TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet |
表示层 | 数据格式化,代码转换,数据加密 | 没有协议 | |
会话层 | 解除或建立与别的接点的联系 | 没有协议 | |
传输层 | 传输层 | 提供端对端的接口 | TCP,UDP |
网络层 | 网络层 | 为数据包选择路由 | IP,ICMP,RIP,OSPF,BGP,IGMP |
数据链路层 | 链路层 | 传输有地址的帧以及错误检测功能 | SLIP,CSLIP,PPP,ARP,RARP,MTU |
物理层 | 以二进制数据形式在屋里媒体上传输数据 | ISO2110,IEEE802,IEEE802.2 |
在网络体系结构中网络通信的建立必须是在通信双方的对等层进行,不能交错。 在整个数据传输过程中,数据在发送端时经过各层时都要附加上相应层的协议头和协议尾(仅数据链路层需要封装协议尾)部分,也就是要对数据进行协议封装,以标识对应层所用的通信协议。接下去介绍TCP/IP 中有两个具有代表性的传输层协议——TCP 和 UDP。
27.1 UDP
UDP协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。在OSI模型中,在第四层——传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。
它有以下几个特点:
1、面向无连接
首先 UDP 是不需要和 TCP一样在发送数据前进行三次握手建立连接的,想发数据就可以开始发送了。并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。
具体来说就是:
-
在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了;
-
在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作。
2、有单播,多播,广播的功能
UDP 不止支持一对一的传输方式,同样支持一对多、多对多、多对一的方式,也就是说 UDP 提供了单播、多播、广播的功能。
3、UDP是面向报文的
发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此,应用程序必须选择合适大小的报文。
4、不可靠性
首先不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠。并且收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。
再者网络环境时好时坏,但是 UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP。
从上面的动态图可以得知,UDP只会把想发的数据报文一股脑的丢给对方,并不在意数据有无安全完整到达。
5、头部开销小,传输数据报文时是很高效的
UDP 头部包含了以下几个数据:
-
两个十六位的端口号,分别为源端口(可选字段)和目标端口;
-
整个数据报文的长度;
-
整个数据报文的检验和(IPv4 可选 字段),该字段用于发现头部信息和数据中的错误。
因此 UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的。
27.2 TCP
当一台计算机想要与另一台计算机通讯时,两台计算机之间的通信需要畅通且可靠,这样才能保证正确收发数据。例如,当你想查看网页或查看电子邮件时,希望完整且按顺序查看网页,而不丢失任何内容。当你下载文件时,希望获得的是完整的文件,而不仅仅是文件的一部分,因为如果数据丢失或乱序,都不是你希望得到的结果,于是就用到了TCP。
TCP协议全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议,由 IETF 的RFC 793定义。TCP 是面向连接的、可靠的流协议。流就是指不间断的数据结构,你可以把它想象成排水管中的水流。
1、TCP连接过程
如下图所示,可以看到建立一个TCP连接的过程为(三次握手的过程):
-
第一次握手:客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态。
-
第二次握手:服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。
-
第三次握手:当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。
这里可能大家会有个疑惑:为什么 TCP 建立连接需要三次握手,而不是两次?这是因为这是为了防止出现失效的连接请求报文段被服务端接收的情况,从而产生错误。
2、TCP断开链接
TCP 是全双工的,在断开连接时两端都需要发送 FIN 和 ACK。
-
第一次握手:若客户端 A 认为数据发送完成,则它需要向服务端 B 发送连接释放请求。
-
第二次握手:B 收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明 A 到 B 的连接已经释放,不再接收 A 发的数据了。但是因为 TCP 连接是双向的,所以 B 仍旧可以发送数据给 A。
-
第三次握手:B 如果此时还有没发完的数据会继续发送,完毕后会向 A 发送连接释放请求,然后 B 便进入 LAST-ACK 状态。
-
第四次握手:A 收到释放请求后,向 B 发送确认应答,此时 A 进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有 B 的重发请求的话,就进入 CLOSED 状态。当 B 收到确认应答后,也便进入 CLOSED 状态。
3、TCP协议的特点
-
面向连接:面向连接,是指发送数据之前必须在两端建立连接。建立连接的方法是“三次握手”,这样能建立可靠的连接。建立连接,是为数据的可靠传输打下了基础。
-
仅支持单播传输:每条TCP传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播和广播传输方式。
-
面向字节流:TCP不像UDP一样那样一个个报文独立地传输,而是在不保留报文边界的情况下以字节流方式进行传输。
-
可靠传输:对于可靠传输,判断丢包,误码靠的是TCP的段编号以及确认号。TCP为了保证报文传输的可靠,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传。
-
提供拥塞控制:当网络出现拥塞的时候,TCP能够减小向网络注入数据的速率和数量,缓解拥塞。
-
TCP提供全双工通信:TCP允许通信双方的应用程序在任何时候都能发送数据,因为TCP连接的两端都设有缓存,用来临时存放双向通信的数据。当然,TCP可以立即发送一个数据段,也可以缓存一段时间以便一次发送更多的数据段(最大的数据段大小取决于MSS)。
27.3 TCP和UDP的比较
1、对比
UDP | TCP | |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输,使用流量控制和拥塞控制 |
连接对象个数 | 支持一对一,一对多,多对一,多对多交互通信 | 只能是一对一通信 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
适用场景 | 适用于实时应用(IP电话、视频会议、直播等) | 适用于要求可靠性传输的应用,例如文件传输 |
2、总结
TCP向上层提供面向连接的可靠服务 ,UDP向上层提供无连接不可靠服务。
虽然 UDP 并没有 TCP 传输来的准确,但是也能在很多实时性要求高的地方有所作为。
对数据准确性要求高,速度可以相对较慢的,可以选用TCP。
28. 简述 JavaScript 的各种模块机制
在 ES6
之前,JS
对模块机制一直没有很好的支持,良好的模块化,是代码复用与工程解耦的关键。
28.1 简单的模块机制
-
最简单的模块化可以理解成一个一个的封装函数,每一个封装的函数去完成特定的功能,调用函数的方式进行复用。但是存在着类似于a,b污染了全局变量的缺点
-
使用对象封装。缺点:往往存在不想让外部访问的变量(module1._count),这种方式就不能满足了(不考虑使用Object.defineProperty)
-
立即执行函数的方式。通过自执行函数可以只返回想返回的东西。如果此模块内想继承使用类似于jquery等库则就需要显示的将库传入到自执行函数中了
28.2 浏览器传统加载模块机制
-
默认方法:通过
<script>
标签加载 JavaScript 脚本,默认是同步加载执行的,渲染引擎如果遇到<script>
会停下来,直到脚本下载执行完成 -
异步方法:defer 和 async 属性
- defer 会让该标签引用的脚本在DOM完全解析之后,并且引用的其他脚本执行完成之后,才会执行;多个defer会按照在页面上出现的顺序依次执行
- async 类似于异步回调函数,加载完成或,渲染引擎就会立即停下来去执行该脚本,多个async脚本不能后保证执行的顺序
28.3 ES6 中模块的解决方案
ES6在语言层面上提供了JS一直都没有的模块功能,使得在继Commonjs之于服务端,AMD之于浏览器之外提供了一个通用的解决方案。
设计思想
尽量静态化(静态加载),使得编译时就能确定模块间的依赖关系以及输入输出的变量。
关键语法
- export
- export可以输出变量:
export var a = 1
- 输出函数:
export function sum(x, y) { return x + y; };
- 输出类:export class A{}
- 结尾大括号写法:export {a , sum , A}
- 尤为注意的一点就是export所导出的接口一定要和模块内部的变量建立一一对应的关系
- export可以输出变量:
对于一个模块来说,它就是一个默认使用了严格模式的文件(‘use strict’),而别的文件要想使用该模块,就必须要求该模块内有export主动导出的内容
export个人最为重要的一点就是可以取到模块内的实时的值
- export default
实质: 导出一个叫做default(默认的)变量,本质是将后面的值,赋给default变量,所以情况就和export 不同了
- import
与导出export对应,引用则是import
使用import加载具有提升的效果,即会提到文件头部进行:
浏览器对ES6模块的加载
type=‘module’,此时浏览器就会知道这是ES6模块,同时会自动给他加上前文提到的defer属性,即等到所有的渲染操作都执行完成之后,才会执行该模块
29. 简述webpack原理
Webpack 的构建流程可以分为以下三大阶段:
- 初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler。
- 编译:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理。
- 输出:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统。
如果只执行一次构建,以上阶段将会按照顺序各执行一次。但在开启监听模式下,流程将变为如下:
在每个大阶段中又会发生很多事件,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
30. em 和 rem
em相对于父元素,rem相对于根元素。
em
- 子元素字体大小的em是相对于父元素字体大小
- 元素的width/height/padding/margin用em的话是相对于该元素的font-size