1.HTML5
1.1 html5新增了哪些特性?
- 新增了几个语义化元素(nav、header、aside、footer等)
- 新增了两个媒体元素(video和audio)
- 等等
2.CSS3
2.1 圣杯/双飞翼布局
圣杯布局和双飞翼布局解决的问题是一样的,中间栏要在放在标准流前面以优先渲染。
而双飞翼是圣杯的优化版
需求
- 3列布局
- 两侧定宽,中间自适应
结构
<div class="main">
<div class="center">中间自适应</div>
<div class="left">左列定宽</div>
<div class="right">右列定宽</div>
</div>
样式
.center {
float: left;
width: 100%;
height: 500px;
background: lightgray;
}
.left {
float: left;
width: 200px;
height: 500px;
background: lightpink;
}
.right {
float: left;
width: 300px;
height: 500px;
background: lightgreen;
}
由于中间栏占满父容器,所以左右两栏换行排
如何使他们同一行并排?
.left {
margin-left: -100%;
}
.right {
margin-left: -300px;
}
参考文章www.cnblogs.com/2050/archiv…~
问题
“中间自适应”几个字不见了,这是因为 left 和 right 已经盖在了 center 上边。如果 center 中有更多内容,依然将无法显示。
圣杯布局
float+margin负值+父元素margin+相对定位
1、给三列的父元素(main), 加上 左margin 和 右margin(也可以使用 padding),将三列挤到中间来,这样左边和右边就会预留出位置。
.main {
margin-left: 200px;
margin-right: 300px;
}
2、给 left 和 right 设置相对定位,将它们移动到相应的位置。
.left {
position: relative;
left: -200px;
}
.right {
position: relative;
right: -300px;
}
这时,你可以放大或缩小,看看中间栏是否自适应
双飞翼
float+margin负值+子元素margin
1、给 center 加一个子元素 inner。
<div class="center">
<div class="inner">中间自适应</div>
</div>
2、给 inner 设置 左margin 和 右 margin,将 inner 挤到中间显示。
.inner {
/* 为了突出显示 inner 元素,给它加上了 height 和 border 以及 上margin */
height: 300px;
border: 1px solid red;
margin-top: 10px;
/* 以下是设置的 margin */
margin-left: 200px;
margin-right: 300px;
}
总结
圣杯布局和双飞翼布局解决问题的方案在前一半是相同的:三栏全部float浮动,但左右两栏加上负margin让其跟中间栏div并排,以形成三栏布局
不同在于解决”中间栏div内容不被遮挡“问题的思路不一样:
1)圣杯布局为了中间div内容不被遮挡,将父容器设置了左右margin-left和margin-right后,将左右两个div用相对定位position: relative并分别配合right和left属性,以便左右两栏div移动后不遮挡中间div;
2)双飞翼布局为了中间div内容不被遮挡,直接在中间div内部创建子div用于放置内容,在子div里用margin-left和margin-right为左右两栏div留出位置。
2.2 flex布局
两个重要的概念
- flex container(开启flex的元素)
- flex items(直接子元素)
开启
display: flex;
flex-container上的css属性
| 属性-值 | 默认 | |||||
|---|---|---|---|---|---|---|
| flex-derection(决定主轴) | row | row-revers | column | column-reverse | ||
| justify-content(决定item在主轴上的对齐方式) | flex-start | flex-end | center | space-between | space-evenly | space-around |
| align-content(决定多行item在交叉轴上的对齐方式) | 类似 | |||||
| align-items(决定item在交叉轴上的对齐方式) | normal | stretch | flex-start | flex-end | center | baseline |
| flex-wrap | no-wrap | wrap |
flex items上的css属性
flex
是flex-grow || flex-shrink || flex-basis的简写
flex-grow
决定了flex items如何拓展
- 默认值 0
- 当flex container在main axis方向上有剩余size时,flex-grow属性才会有效
- flex-grow总和大于等于1时,等比拓展(加上乘以size的值,用完剩余size);小于1时,各自乘以小数(有剩余size)
flex-shrink
决定了flex items如何收缩
- 默认 1
- 当flex items在main axis方向上超过了flex container的size时(oversize),flex-grow属性才会有效
- 当flex items 的**flex-shrink总和大于1时,**等比收缩(减去乘以oversize的值);小于1时就用得少,因为还是溢出
flex-basis
决定flex items在main axis主轴上的base size
- auto 自身宽/高
- 具体值
决定flex items在main axis主轴上的base size的优先级
- max-size...
- flex-base
- size
- 内容本身的size
order
决定flex items的排布顺序,值越小,排越前
- 默认是0
flex实现圣杯布局
思路
- header、content、footer垂直flex,其中header、footer高度固定,content高度flex:1(高度自适应)
- content里面的nav、main、aside水平flex,其中nav、aside宽度固定,flex的收缩和拓展属性设为0,main的宽度flex:1(宽度自适应)
- 最后别忘了nav的flex-order:-1,将它排在main前面
html
<header>
header
</header>
<div class="content">
<main>main</main>
<nav>nav</nav>
<aside>aside</aside>
</div>
<footer>
footer
</footer>
css
html,
body {
display: flex;
flex-direction: column;
min-height: 600px;
height: 100%;
font-size: 28px;
font-weight: bolder;
}
header,
footer {
height: 150px;
background-color: #666;
/* 子元素水平、垂直居中三件套 */
display: flex;
justify-content: center;
align-items: center;
}
.content {
flex: 1; /* 高度自适应 */
display: flex;
}
nav,
aside {
background-color: #eb6f43;
flex: 0 0 200px;
display: flex;
justify-content: center;
align-items: center;
}
main {
display: flex;
justify-content: center;
align-items: center;
flex: 1; /* 宽度自适应 */
background-color: #d6d6d6;
}
nav {
order: -1; /* 调整顺序在main前面 */
}
2.3 定位
2.4 清除浮动
为什么要清除浮动?
真实开发父元素高度是不确定的(auto),但是浮动会使子元素脱标,不再向父元素汇报高度,这时父元素计算总高度时,就不会计算浮动子元素的高度,导致了高度坍塌问题
而解决父元素高度坍塌问题的过程,叫清除浮动
如何清除浮动?(推荐方案)
伪元素
.clear-fix::after {
content: "";
clear: both;
display: block;
}
给需要的元素加上clear-fix类即可
2.5 margin负值
2.6 你怎么理解盒模型的?
盒模型分两种:标准盒模型、IE盒模型(替代盒模型、怪异盒模型)
标准盒模型:如果你给盒子设置width和height,设置的是content,盒子的实际宽高是border+padding+content
IE盒模型:如果给盒子设置width和height,设置的是 border+padding+content
默认使用的是标准盒模型,如果想使用IE盒模型,使用 box-sizing: border-box 切换
2.7 常见的水平和垂直居中方式有哪些?
方式一,flex布局
<div class="father">
<div class="son"></div>
</div>
.father {
display: flex;
justify-content: center;
align-items: center;
width: 100px;
height: 100px;
background-color: skyblue;
}
.son {
width: 50px;
height: 50px;
background-color: grey;
}
方式二,父相子绝+translate
先将元素的左上角定位到父元素中间,然后再将子元素的中心点移动到父元素的中间
<div class="father">
<div class="son"></div>
</div>
.father {
position: relative;
width: 100px;
height: 100px;
background-color: skyblue;
}
.son {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50px;
height: 50px;
background-color: grey;
}
3.JavaScript
3.1 作用域、作用域链、执行上下文
作用域(Scope)
可以理解为变量、函数、对象的可访问范围
3.2 什么是原型?什么是原型链?
原型
js中,原型分隐式原型和显式原型。
每个对象都有一个隐式原型__proto__
每个函数都有一个显式原型prototype,而js函数又特殊,也是个对象,所以也有隐式原型__proto__,但是这两个不相等。(指向的对象不一样)
- 函数的prototype来自哪里?创建了一个函数 foo.prototype = {constructor: foo }
- 函数的**
__proto__来自哪里?** new Function(), foo.__proto__= Function.prototype, 而 Function.prototype = { constructor: Function }
原型链
从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上面获取,如果它的原型上还没有,它原型本质是个对象,也有对应的原型,就再去它原型的原型上找,以此类推,沿着原型一层一层往上找,像链条一样
3.3 箭头函数和普通函数有什么区别?
-
箭头函数不会绑定this、arguments属性
-
箭头函数不能作为构造函数来使用(不能和new关键字一起使用)
-
箭头函数不绑定this,而是根据外层作用域来决定this
3.4 New操作符做了什么事情?
js中,使用new的时候,是调用了某个构造函数
- 在构造函数内部新建一个空对象obj
- 将构造函数的显式原型prototype赋值给刚刚创建的空对象obj的隐式原型
__proto__ - 构造函数内部的this,会指向创建出来的新对象obj
- 然后执行构造函数的代码
- 如果该函数没有返回对象,则返回this
3.5 说一下eventloop(事件循环)
先说说浏览器的事件循环吧
有个前提,js是单线程的
参与事件循环有3个角色:
- js线程
- 其它线程
- 事件队列
- js线程执行js代码
- 当发现耗时操作时,会将这操作(会有回调函数)交给其它线程处理
- 当其它线程处理完,会将回调函数放到事件队列中
- js线程会定时地来事件队列执行那些回调函数
这3个角色形成一个闭环,不停地循环着这过程,所以叫事件循环
而node的事件循环原理大同小异,不过比浏览器多了一些阶段,对事件队列的划分更加详细,暂时就了解这么多~
3.6 什么是闭包,闭包的应用场景是什么
广义上,js中所有函数都是闭包(可以访问外层作用域的自由变量)
狭义上,js中的函数如果访问了外层作用域的变量(访问了),那这函数就是一个闭包
闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。
for (let i = 0; i < 5; i++) {
(function p(){
console.log(i)
})()
}
闭包应用场景:
- 一个函数返回另外一个对外层作用域存在引用的函数
- IIFE(立即执行函数)
- 循环赋值、独立的计数器(闭包可以形成互不干扰的私有作用域)
- 用闭包可以模拟私有方法(无法在外部直接访问,必须通过内部返回的函数访问,也就是模块模式)
3.7 Set 和 Map有什么区别?
- Map是键值对,Set是值的集合
- Map有get(key)方法,而set只有值,没有get(key)方法
- Set更多用于数组去重,而Map更多用于存储数据
3.8 localstorage sessionstorage cookie 有什么区别
| cookie | sessionStorage | localStorage | |
|---|---|---|---|
| 生命周期 | 会话级,如果不设置有效期,存储在内存中;如果设置有效期,存在硬盘里,有效期到自动消失~ | 会话级 | 永久,除非主动删除 |
| 网络流量 | 每次都会发送给服务器 | 不会与服务器通信,纯粹为了保存数据,所以webStorage更节省网络流量 | 同样 |
| 大小限制 | 4kb | 5M | 5M |
| 安全性 | 明文传输 | WebStorage不会随着HTTP header发送到服务器端,所以安全性相对于cookie来说比较高一些,不会担心截获 | 同样 |
| 方便? | WebStorage提供了一些方法,数据操作比cookie方便 | 同样 |
3.9 深拷贝和浅拷贝
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。(两个对象依然共享引用类型属性的内存)
深拷贝是从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象,(两个对象不再共享引用类型属性的内存)
如何实现浅拷贝?
- 展开运算符
如何实现深拷贝?
- JSON的序列化(stringify)和解析(parse),但是这不会对函数进行处理~
4.计算机网络
4.1 post和get的区别
参数角度
- GET参数通过URL传递,POST放在Request body中
- GET请求在URL中传送的参数是有长度限制的,而POST没有。(服务器处理长
URL要消耗比较多的资源,为了性能和安全考虑,会给URL长度加限制)
安全角度
- GET比POST不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
- GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
然而,从传输的角度来说,他们都是不安全的,因为 HTTP 在网络上是明文传输的,只要在网络节点上捉包,就能完整地获取数据报文
只有使用HTTPS才能加密安全
请求方面
- GET在浏览器回退时是无害的,而POST会再次提交请求。
4.2 浏览器输入url到页面加载完毕发生了什么
1.启动浏览器网络线程
2.根据是否有缓存,浏览器进行自己的请求规则,决定是否发出请求
3.dns域名解析(查询)
4.tcp建立连接
5.接到数据,开始页面渲染,启动浏览器渲染引擎(渲染引擎与js引擎互斥,只能运行一个)
第5步还分以下步骤:
1.解析HTML,构建DOM树
2.构建CSS树
3.解析HTML过程中如果遇到script元素,要停止渲染,启动js引擎执行script中的代码,如果是src格式,要启动网络线程,加载js脚本,加载完毕开始执行js脚本
4.当DOM树构建完毕,css树也构建完毕时,浏览器进入layout阶段
5.合成渲染树render-tree
6.下一步进入painting阶段,页面绘制
7.绘制完毕,结束
4.3 UDP和TCP有什么区别
| UDP | TCP | |
|---|---|---|
| 连接? | 无连接 | 面向连接 |
| 可靠? | 不可靠,不能流量控制和拥塞控制 | 可靠 |
| 连接对象 | 支持一对一、一对多和多对多 | 只能一对一 |
| 传输方式 | 面向报文 | 面向字节流 |
| 首部开销 | 开销少,8字节 | 最小20字节,最大60字节 |
| 适用场景 | 实时应用,比如视频会议、直播 | 可靠传输应用,比如文件传输 |
4.4 怎么解决跨域问题?
为什么会出现跨域?
浏览器从一个域名的网页去请求另一个域名的资源时,根据同源策略 协议、主机名、端口号任一不同,就出现了跨域
同源政策是什么?
两个域名的协议、主机名、端口号任一不同,都不能发送请求~
为什么需要同源策略?
购物网站、银行卡、sessionStorage、危险网站,剩下脑补~
怎么解决?
方案一,CORS
Cross-origin resource sharing,跨域资源共享
跨域请求的请求头需要设置
Access-Control-Allow-:
- Origin 为发起请求的主机地址
- Credentials 当它被设置为 true 时,允许跨域
- Headers 设置跨域请求允许的请求头
- Methods 设置跨域请求允许的请求方式
一般使用第三方包~
方案二,jsonp
只支持get请求~
JSONP的优势在于支持老式浏览器~,对于不支持CORS浏览器可以使用JSONP
- 接口参数要带一个自定义函数名
- 通过该函数名去接收后台返回数据
var script = document.createElement("script");
script.src = "HTTP://127.0.0.1:8888/index.php?callback=jsonpCallback";
document.head.appendChild(script);
//通过定义函数名去接收后台返回数据
function jsonpCallback(data){
//注意 jsonp 返回的数据是 json 对象可以直接使用 //Ajax 取得数据是 json 字符串需要转换成 json 对象才可以使用。
}
方案三,反向代理
webpack.config.js
module.exports = {
//...
devServer: {
proxy: {
'/api': 'http://localhost:3000',
},
},
};
现在,对 /api/users 的请求会将请求代理到 http://localhost:3000/api/users
5.算法
6.框架
Vue
6.1 vue实现双向数据绑定原理是什么?
双向数据绑定的的由来
对于传统的dom操作,当数据变化时更新视图需要先获取到目标节点,然后将改变后的值放入节点中,视图发生变化时,需要绑定事件修改数据。
Vue实现双向数据绑定是采用数据劫持和发布者-订阅者模式。
核心组成部分:
- 监听器
Observer: 数据劫持 - 订阅者容器: 监听器监听到数据变动时,遍历订阅者容器发布消息
Compile:解析模板指令,将模板中的变量替换成数据,比如{{title}}Watcher: 连接Observe和Compile的桥梁
vue2
数据劫持是利用ES5的Object.defineProperty方法来劫持每个属性的getter和setter,在数据变动时发布消息给订阅者
Object.defineProperty有一些缺陷,不仅要遍历data逐个劫持,还不能监听到数组的改变~
vue3
Vue3的数据劫持使用了ES6的Proxy-Reflect,(Proxy的监听数组实现是把数组变成了一个类数组对象),
为什么使用Proxy?
首先,Object.defineProperty设计的初衷,不是为了监听一个对象中所有属性的,而是定义普通的属性
其次,Object.defineProperty要是想监听其它操作,如新增、删除属性,它做不到
Proxy可以监听原对象进行了哪些操作
reflect有什么用呢?
提供很多操作js对象的方法,有点像Object操作对象的方法
已经有Object了,为什么还要reflect呢?
- 早期ECMA规范没有考虑到对对象本身的一些操作如何设计更加规范,所以将这些api都放到Object上
- 但是Object作为构造函数,这些操作放在它身上并不合适
- 所以,es6新增了reflect对象,将这些操作集中到reflect身上
6.2 v-model语法糖是怎么实现的?
v-model的原理就是v-bind数据绑定 和 v-on监听事件的语法糖
作用在普通表单元素上
<input
v-bind:value="message"
v-on:input="message=$event.target.value"
/>
//event.target 指代当前触发的事件对象的dom; //$event.target.value 就是当前dom的value值;
- 「接收一个value属性」
- 「在value值改变时 触发xxx事件」
在自定义组件中
v-model 默认会利用名为 value 的 prop 和名为 input 的事件
6.3 vue-router如何实现?
为什么会出现前端路由?
因为后端路由有一个很大的缺点,每次路由切换的时候都需要去刷新页面,然后发出ajax请求,然后将请求数据返回
所以我们的需求而是更新视图而不发生请求。这需要通过hash值来实现。而hash 值的变化,并不会导致浏览器向服务器发出请求,所以不会刷新页面,另外每次 hash 值的变化,还会触发hashchange 事件,通过这个事件我们就可以知道 hash 值发生了哪些变化。(hash 出现在 URL 中,但不会被包含在 http 请求中)
6.4 hash/history的区别?
用户体验角度
- hash模式会在url中自带#
- history 模式则不会带#(但是这需要服务器把所有路由都重定向到根页面)
改变路径方式
- 通过改变location.hash
- 而history通过pushState()和replaceState()
6.5 Vuex有哪些基本属性 怎么使用?
state
存放所有共享的状态
getters
基于state的派生属性,类似于计算属性,当需要计算后在使用时就可以使用getters
mutations
提交mutation是更改Vuex中的store中的状态的唯一方法
mutation必须是同步的,如果要异步需要使用action
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
//无提交荷载
increment(state) {
state.count++
}
//提交荷载
incrementN(state, obj) {
state.count += obj.n
}
}
})
//无提交荷载
store.commit('increment')
//提交荷载
store.commit('incrementN', {
n: 100
})
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
incrementA (context) {
setInterval(function(){
context.commit('increment')
}, 1000)
}
}
})
Action 通过 store.dispatch 方法触发
store.dispatch('incrementA')
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象
因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
modules
使用单一状态树,导致应用的所有状态集中到一个很大的对象。但是,当应用变得很大时,store 对象会变得臃肿不堪。
为了解决以上问题,Vuex 允许我们将 store 分割到模块(module)。每个模块拥有自己的 state、mutation、action、getters
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
6.6 计算属性和watch有什么区别?以及它们的运用场景?
来认识一下计算属性的缓存妙处~
<div id="example">
<p>"{{ message }}"</p>
<p>"{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})
当然,使用methods结果也是一样
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
但是,计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。
为什么需要缓存?
假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!
当然,如果你不希望有缓存,也可以使用方法来替代~
区别
-
computed支持缓存,watch不支持~
-
计算属性是声明式编程,而watch是命令式编程,而且可能有很多重复代码
运用场景
- 当数据需要经过处理再显示时,使用计算属性computed~
- 当需要在数据变化时执行异步或开销较大的操作时,使用watch~
6.7 你知道组件之间的传值方式有哪些?
| props | 父传子 | |
|---|---|---|
| $emit/v-on | 子传父 | |
| EventBus | 非父子可以使用 |
props
父组件fpn
<div id="fpn">
<spn :s-msg="fMsg"></spn>
</div>
data: {
fMsg: '父亲的数据'
}
子组件spn
<div>
<h2>{{ sMsg }}</h2>
</div>
props: {
sMsg: {
type: String,
default: 'hhh'
}
父组件的fMsg通过props传给子组件的sMsg
HTML 中的 attribute 名是大小写不敏感的,浏览器会把所有大写字符解释为小写字符,并在前面加个横线分隔(js中是sMsg,在html中就变s-msg啦~),组件名注册时大写,使用时可以小写横线分隔就是这个原因~
$emit/v-on
子组件spn
<div>
<button @click="spn"></button>
</div>
data () {
return {
list: [
{id: 1, name: '热门'}
]
}
},
methods: {
spn () {
// 发射事件
this.$emit('spnClick',this.list)
}
}
父组件fpn
@是v-on的语法糖~
<div id="fpn">
<spn @spnClick="fpn"></spn>
</div>
methods: {
fpn (value) {
console.log(value)
}
}
子组件通过点击,将自定义事件spnClick发射出去~,并且可以携带一些信息(this.list)一起发射;
当父组件fpn使用子组件spn时,要是监听到子组件的点击,可以绑定一个处理函数,该处理函数的参数可以拿到子组件传递过来的信息(spn的list)。
EventBus
1.初始化
第一种方式
将一个空的vue对象挂载到Vue原型上,这样每个组件对象都可以使用~
Vue.prototype.$EventBus = new Vue()
第二种方式
创建一个模块Bus.js,导出一个空的vue对象,需要就导入
// Bus.js
import Vue from 'vue'
export const EventBus = new Vue();
实质上,它是一个不具备 DOM 的组件,它具有的仅仅只是组件的实例方法而已,因此它非常的轻便。
2.发送和接收事件
EventBus.$emit('emit事件名',数据)发送EventBus.$on("emit事件名", callback(payload1,…))接收
举例导入Bus.js模块的方式通过事件总线传递信息
<!-- A.vue -->
<template>
<p>{{msgB}}</p>
<button @click="sendMsgA()">-</button>
</template>
<script>
import { EventBus } from "../Bus.js";
export default {
data(){
return {
msg: ''
}
},
mounted() {
EventBus.$on("bMsg", (msg) => {
// a组件接受 b发送来的消息
this.msg = msg;
});
},
methods: {
sendMsgA() {
EventBus.$emit("aMsg", '来自A页面的消息'); // a 发送数据
}
}
};
</script>
<!-- B.vue -->
<template>
<p>{{msgA}}</p>
<button @click="sendMsgB()">-</button>
</template>
<script>
import { EventBus } from "../event-bus.js";
export default {
data(){
return {
msg: ''
}
},
mounted() {
EventBus.$on("aMsg", (msg) => {
// b组件接受 a发送来的消息
this.msg = msg;
});
},
methods: {
sendMsgB() {
EventBus.$emit("bMsg", '来自b页面的消息'); // b发送数据
}
}
};
</script>
如果只想接收一次,可以使用EventBus.$once('事件名', callback(payload1,…)
优缺点
优点
- 解决了多层组件之间繁琐的事件传播。
- 使用原理十分简单,代码量少。
缺点
- vue是单页面应用,如果在某一个页面刷新了之后,与之相关的EventBus会被移除,这样可能出现一下意外bug
- 如果有反复操作的页面,EventBus在监听的时候就会触发很多次,也是一个非常大的隐患。通常会用到,在vue页面销毁时,同时移除EventBus事件监听。
- 由于是都使用一个Vue实例,所以容易出现重复触发的情景,两个页面都定义了同一个事件名,并且没有用$off销毁(常出现在路由切换时)。
6.8 父组件到子组件更新的方式是什么样的?
prop使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部直接改变 prop。
那要是想变更prop呢?
通过发射事件去通知父组件修改
两种常见的试图变更一个 prop 的情形:
一,这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用
最好定义一个本地的 data property 并将这个 prop 用作其初始值(仅限于基本类型)
props: ['initialCounter'],
data () {
return {
counter: this.initialCounter
}
}
二
这个 prop 以一种原始的值传入且需要进行转换。最好依赖于这个 prop 的值来派生一个计算属性
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
注意
在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
6.9 vue的生命周期?
vue的生命周期怎么理解?
每个 Vue 实例从创建到销毁的过程叫vue的生命周期。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
vue有哪些生命周期钩子?
| 钩子 | 调用时机 | 工作内容 | 注意 |
|---|---|---|---|
| beforeCreate | 在实例初始化之后,同步调用。 | 数据侦听、事件/侦听器配置 | |
| created | 在实例创建完成后,立即同步调用。 | 计算属性、方法、事件/侦听器的回调函数。 | $el property 目前尚不可用。 |
| beforeMount | 在挂载开始之前调用 | 相关的 render 函数首次被调用 | 该钩子在服务器端渲染期间不被调用 |
| mounted | 实例被挂载后调用 | el 被新创建的 vm.el 也在文档内。 | 1.不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 2.vm.$nextTick;该钩子在服务器端渲染期间不被调用 |
| beforeUpdate | 在数据发生改变后,DOM 被更新之前调用。 | 这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。 | 该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务器端进行 |
| updated | 数据更改导致的虚拟 DOM 重新渲染和更新完毕之后调用 | 当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之 | 1.不会保证所有的子组件也都被重新渲染完毕。如果你希望等到整个视图都渲染完毕,可以在 updated 里使用 vm.$nextTick; 2.该钩子在服务器端渲染期间不被调用 |
| activated | 被 keep-alive 缓存的组件激活时调用。 | 该钩子在服务器端渲染期间不被调用 | |
| deactivated | 被 keep-alive 缓存的组件失活时调用。 | 该钩子在服务器端渲染期间不被调用 | |
| beforeDestroy | 实例销毁之前调用,在这一步,实例仍然完全可用 | 该钩子在服务器端渲染期间不被调用 | |
| destroyed | 实例销毁后调用。 | 对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。 | 该钩子在服务器端渲染期间不被调用。 |
6.10 你了解nextTick多少?
在此之前,需要聊聊
什么是异步更新队列?
Vue 在更新 DOM 时是异步执行的。
只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
如果同一个 watcher 被多次触发,只会被推入到队列中一次。
在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。
在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。
Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。
要是想基于更新后的 DOM 状态来做点什么呢?
那就使用nextTick吧!这样回调函数将在 DOM 更新完成后被调用。
$nextTick() 返回一个 Promise 对象哦~
6.11 keep-alive是什么?
是一个抽象组件。(它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。)
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
当组件在 <keep-alive> 内被切换,该组件的的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。
应用场景
当需要保存组件的某些状态(如滚动位置)下一次返回就恢复那些状态时,就不能销毁组件,而是将这些状态缓存起来,这时就可以使用keep-alive
6.12 你怎么使用插槽?
插槽有个非常棒的的功能:
复用组件的前提下,你还可以动态的拓展该组件。
<navigation-link> 的模板
<navigation-link url="/profile">
<slot>123</slot>
</navigation-link>
复用
<navigation-link url="/profile">
Your Profile
</navigation-link>
当组件渲染的时候,<slot></slot> 将会被替换为“Your Profile”。插槽内可以包含任何模板代码,当没内容替换插槽的内容时,会默认使用插槽的模板代码(123)
<navigation-link url="/profile">
Your Profile
</navigation-link>
这个又叫匿名插槽
这时你可能会想:一个插槽不够用啊~
你放心,可以使用多个插槽。
这时你可能又会问:那多个插槽之间怎么区分使用的哪一个?
起个名字呗
这就是具名插槽的由来~
具名插槽的使用
- 定义时
slot元素加name="xxx"属性 - 使用时 替换元素加
v-slot="xxx"属性
基础模板
<base-layout> 组件
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
拓展
在向具名插槽提供内容的时候,可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称
(<template> 元素可以换成任何html元素~)
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
最终会被渲染出
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
你可以理解为对号入座式拓展,有了名字,拓展起来的不会乱啦~
6.13 你怎么使用自定义指令的
除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。
有的情况下,仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
全局注册一个v-focus指令
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
局部注册也可以
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
然后你可以在template中的元素上使用了
<input v-focus>
7.webpack
7.1 Loader和Plugin 有什么区别
Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。
Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。
Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
8.性能
8.1 项目中常用的性能优化方式是什么?
- 减少HTTP请求
- 静态资源使用 CDN托管
- 将样式表放在顶部,将脚本放在底部
- 善用缓存,不重复加载相同的资源
- 图片懒加载,滑动到可视区域再加载
- webpack 按需加载代码,提取第三库代码,减少 ES6 转为 ES5 的冗余代码
- 压缩代码(利用插件js丑化、html和css也有相应的压缩插件,还有就是gzip)
- 等等
参考:segmentfault.com/a/119000002…
8.2 你知道重排和重绘吗?
先说说重排(relayout)。
构建渲染树后,开始布局。
浏览器中的布局是什么?
第一次确定节点大小和位置称为布局
随后对节点大小和位置的重新计算叫回流,也叫重排
比如,假设初始布局发生在返回图像之前。由于没有声明图像的大小,一旦知道图像大小,就会有回流
再说重绘(repaint)。
在元素的绘制阶段,会将元素的每个可视部分绘制到屏幕上,包括文本、颜色、边框、阴影和替换的元素(如按钮和图像)。
当元素的可视部分发生更新,将会重新绘制,这个过程便是重绘。
网页生成的时候,至少会渲染一次,而在用户访问的过程中,还会不断重复触发重排和重绘,重排非常浏览器消耗性能,所以开发中尽量少触发重排~
当你修改元素的可视部分,不会影响元素的布局,但是,但你修改元素布局,在绘制阶段会重新绘制该元素。也就是说,重绘不一定导致重排,但重排一定会导致重绘
如何减少重排?
- 样式集中改变
- 改类名而不是直接修改样式
- 让元素脱离文档流,减少重排的Render Tree的规模
9.安全
10.适配
10.1 rem你是怎么做适配的?
- 根据不同设备屏幕,设置不同html的font-size大小
- 将所有需要适配的图片、元素、字体大小的单位,统一使用rem
第1步可以使用媒体查询和js动态计算(用的最多)
但是px值转换rem太过于复杂,可以使用less(强大的计算能力)解决这一问题