交控科技 CPBU 前端面试题
最近的工作经历及项目经验,用到了哪些技术?
遇到了什么问题及怎么处理的?
Css
实现一个三角形 用 css
.triangle {
width: 0px;
height: 0px;
border-left: 10px solid #ddd;
border-top: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid transparent;
}
/* 通过旋转 遮挡的方式 */
2
3
4
5
6
7
8
9
div 垂直水平居中
方法 1:绝对定位+margin:auto
div {
width: 200px;
height: 200px;
background: green;
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
margin: auto;
}
2
3
4
5
6
7
8
9
10
11
12
方法 2:绝对定位+负 margin
div {
width: 200px;
height: 200px;
background: green;
position: absolute;
left: 50%;
top: 50%;
margin-left: -100px;
margin-top: -100px;
}
2
3
4
5
6
7
8
9
10
11
方法 3:绝对定位+transform
div {
width: 200px;
height: 200px;
background: green;
position: absolute;
left: 50%; /* 定位父级的50% */
top: 50%;
transform: translate(-50%, -50%); /*自己的50% */
}
2
3
4
5
6
7
8
9
10
方法 4:flex 布局
.box {
height: 600px;
display: flex;
justify-content: center; //子元素水平居中
align-items: center; //子元素垂直居中
/* aa只要三句话就可以实现不定宽高水平垂直居中。 */
}
.box > div {
background: green;
width: 200px;
height: 200px;
}
2
3
4
5
6
7
8
9
10
11
12
13
方法 5:table-cell 实现居中
div {
display: table-cell;
text-align: center;
vertical-align: middle;
}
2
3
4
5
rem 与 em 的区别
- rem 单位翻译为像素值是由 html 元素的字体大小决定的。 此字体大小会被浏览器中字体大小的设置影响,除非显式重写一个具体单位。
- em 单位转为像素值,取决于他们使用的字体大小。 此字体大小受从父元素继承过来的字体大小,除非显式重写与一个具体单位。em 继承父级元素的字体大小
1rem 等于多少 px?
如果 html 没有设置font-size
属性 默认是 16px
1rem = 16px
样式权重
<body>
<div class="container" id="box" style="background:green"></div>
</body>
<style>
.container {
width: 100px;
height: 100px;
background: red;
}
#box {
background: yellow;
}
body .container {
background: blue;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Html
输入地址后的过程
- 域名解析
- 发起 TCP3 次握手
- 建立 TCP 链接后发起 http 请求
- 服务器响应 http 请求,浏览器获得 html 代码
- 浏览器解析 html 代码,并请求 html 中的资源
- 浏览器对页面进行渲染并呈现给用户
浏览器渲染过程
- 解析 HTML 生成 DOM 树。
- 解析 CSS 生成 CSSOM 规则树。
- 将 DOM 树与 CSSOM 规则树合并在一起生成渲染树。
- 遍历渲染树开始布局,计算每个节点的位置大小信息。
- 将渲染树每个节点绘制到屏幕。
Javascript
判断执行顺序
综合上面所讲的分析下这些代码。先不看答案自己尝试做一下。
第一阶段,执行调用栈。new 一个 promise 的时候里面的代码立即会执行,所以先输出{1},接着运行函数 foo2 ,将 setTimeout 放到消息队列中,执行函数 foo 并输出{2},往后执行输出{3},之后将 p.then 放进微任务队列中继续执行函数,输出{4},结束函数 foo2 执行并销毁。
第二阶段,微任务队列的消息放入调用栈执行,里面第一个任务是第一个 then 所以输出{5},之后执行第二个 then 输出{6},这时微任务队列清空。
第三阶段,消息队列的内容放入调用栈执行,输出{7} 并清空带哦用栈。
console.log('00');
function foo() {
console.log('11');
setTimeout(() => {
console.log('22');
}, 0);
console.log('33');
}
foo();
console.log('44');
2
3
4
5
6
7
8
9
10
var p = new Promise((resolve) => {
console.log('111');
resolve(555);
});
function foo() {
console.log('222');
}
function foo2() {
setTimeout(() => {
console.log('777');
}, 0);
foo();
console.log('444');
p.then((resolve) => {
console.log(resolve);
}).then(() => {
console.log('666');
});
console.log('333');
}
foo2();
// 111 222 444 333 555 666 777
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
浮点数精度问题
0.1 + 0.2 === 0.3;
// true or false
2
null 和 undefined 区别
null:Null 类型,代表 “空值”,代表一个空对象指针,使用 typeof 运算得到 “object” ,所以可以认为它是一个特殊的对象值。
undefined:Undefined 类型,当一个声明了一个变量未初始化时,得到的就是 undefined。
HTTP 304 与 200 区别
请求 200 还是请求 304 是根据什么区分的
首次请求:200
缓存有效期内请求:200(from cache)——再次请求文件未超时不发送请求
缓存过期后请求:304(Not Modified)——文件缓存超时,发送请求到服务器,并携带本地缓存的文件信息,服务器对比信息(最后修改时间),可继续使用时,返回 304 表示可据需使用
如果项目中引入一个外部样式表 XX.css 修改当前样式表的内容 如何做到 不用 304 缓存里面的资源,加载最先的资源? XX.css?version=1
箭头函数 能作为构造函数 通过 new 生成实例吗?为什么?
箭头函数是普通函数的简写,可以更优雅的定义一个函数,和普通函数相比,有以下几点差异:
1、函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
2、不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
3、不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。-
4、不可以使用 new 命令,因为:
没有自己的 this,无法调用 call,apply。 没有 prototype 属性 ,而 new 命令在执行时需要将构造函数的 prototype 赋值给新的对象的 proto
Vue
vue 常用的修饰符
.stop
:等同于 JavaScript 中的 event.stopPropagation(),防止事件冒泡;.prevent
:等同于 JavaScript 中的 event.preventDefault(),防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播);.capture
:与事件冒泡的方向相反,事件捕获由外到内;.self
:只会触发自己范围内的事件,不包含子元素;.trim
: 如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:.number
:如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 .number 修饰符:keyup
: 键盘事件
v-if 和 v-for 的优先级
当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中。所以,不推荐 v-if 和 v-for 同时使用。 如果 v-if 和 v-for 一起用的话,vue 中的的会自动提示 v-if 应该放到外层去。
为什么使用 key?
需要使用 key 来给每个节点做一个唯一标识,Diff 算法就可以正确的识别此节点。 作用主要是为了高效的更新虚拟 DOM。
单项数据流
举个例子,父组件维护了一个状态,假设子组件可随意更改父组件甚至祖宗组件的状态,那各组件的状态改变就会变得难以追溯,父组件的状态也可能被子组件意外修改而不可察觉。而单向数据流保证了父组件的状态不会被子组件意外修改如果要修改,只能通过在子组件中 dispatch 一个 action 来对全局状态修改,全局状态在通过 props 分发给子组件;又或是调用父组件的方法;又或是发事件,这些操作是肉眼可见且可控的(用函数式来说,保证了组件就是无副作用的纯函数),不至于造成状态总被意外修改而导致难以维护的情况。
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
这里有两种常见的试图变更一个 prop 的情形:
1.这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
2
3
4
5
6
这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
2
3
4
5
6
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
引进组件的步骤
- 在 template 中引入组件;
- 在 script 的第一行用 import 引入路径;
- 用 component 中写上组件名称。
项目中如何解决跨域问题?
- CORS
- Proxy proxyTable
'use strict';
const path = require('path');
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/api': {
target: 'http://localhost:7001', //后端接口地址
changeOrigin: true, //是否允许跨越
pathRewrite: {
'^/api': '/api', //重写,
},
},
},
host: '192.168.0.104',
port: 8081,
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,
poll: false,
useEslint: true,
showEslintErrorsInOverlay: false,
devtool: 'eval-source-map',
cacheBusting: true,
cssSourceMap: false,
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- Nginx
vuex
vuex 是一个专为 vue.js 应用程序开发的状态管理器,它采用集中式存储管理应用的所有组件的状态,并且以相 应的规则保证状态以一种可以预测的方式发生变化。
state: vuex 使用单一状态树,用一个对象就包含来全部的应用层级状态
mutation: 更改 vuex 中 state 的状态的唯一方法就是提交 mutation
action: action 提交的是 mutation,而不是直接变更状态,action 可以包含任意异步操作
getter: 相当于 vue 中的 computed 计算属性
Promise 是什么,解决了什么,之前怎么实现的
- Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
- 解决来之前在请求中回调请求产生的回调地狱,使得现在的代码更加合理更加优雅,也更加容易定位查找问题。
Promise
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve();
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
// 1
// 2
// 4
// 3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
解析
首先 Promise 新建后立即执行,所以会先输出 1,2,而 Promise.then() 内部的代码在 当次 事件循环的 结尾 立刻执行 ,所以会继续输出 4,最后输出 3。
const promise = new Promise((resolve, reject) => {
resolve('success1');
reject('error');
resolve('success2');
});
promise
.then((res) => {
console.log('then:', res);
})
.catch((err) => {
console.log('catch:', err);
});
// then: success1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
解析 resolve 函数将 Promise 对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject 函数将 Promise 对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
而一旦状态改变,就不会再变。 所以 代码中的 reject('error'); 不会有作用。
Promise 只能 resolve 一次,剩下的调用都会被忽略。 所以 第二次的 resolve('success2'); 也不会有作用。
如何声明一个全局变量
深度选择器 /deep/
vue 组件的 scoped 属性的作用
在 style 标签上添加 scoped 属性,以表示它的样式作用于当下的模块,很好的实现了样式私有化的目的;
但是也得慎用:样式不易(可)修改,而很多时候,我们是需要对公共组件的样式做微调的;
$route 和 $router 的区别
答:$router是VueRouter的实例,在script标签中想要导航到不同的URL,使用$router.push 方法。返回上一个历史 history
用 $router.to(-1)
$route 为当前 router 跳转对象。里面可以获取当前路由的 name,path,query,parmas 等。
$route从当前router跳转对象里面可以获取name、path、query、params等(<router-link
>传的参数由 this.$route.query 或者 this.$route.params 接收)
$router为VueRouter实例。想要导航到不同URL,则使用$router.push 方法;返回上一个 history 也是使用$router.go 方法
params 和 query 的区别?如何获取传递的参数
url 地址显示:query 更加类似于我们 ajax 中 get 传参,params 则类似于 post,说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显示 注意点:query 刷新不会丢失 query 里面的数据 params 刷新 会 丢失 params 里面的数据。
this.$route.query.name和this.$route.params.name
vue-router 的有哪两种模式? history 模式的问题是是什么?
hash 模式:即地址栏 URL 中的 # 符号;
history 模式:window.history 对象打印出来可以看到里边提供的方法和记录长度。利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持)
hash 模式是通过改变锚点(#)来更新页面 URL,并不会触发页面重新加载,我们可以通过 window.onhashchange 监听到 hash 的改变,从而处理路由。
history 模式是通过调用 window.history 对象上的一系列方法来实现页面的无刷新跳转。
history 常用方法
- back():后退到上一个路由;
- forward():前进到下一个路由,如果有的话;
- go(number):进入到任意一个路由,正数为前进,负数为后退;
- pushState(obj, title, url):前进到指定的 URL,不刷新页面;
- replaceState(obj, title, url):用 url 替换当前的路由,不刷新页面;
前面的 hashchange,你只能改变#后面的 url 片段。而 pushState 设置的新 URL 可以是与当前 URL 同源的任意 URL。 history 模式则会将 URL 修改得就和正常请求后端的 URL 一样,如后端没有配置对应/user/id 的路由处理,则会返回 404 错误
如何加快首屏渲染?你有哪些方案?
- 降低请求量:合并资源,减少 HTTP 请求数,minify / gzip 压缩,webP,lazyload。
- 加快请求速度:预解析 DNS,减少域名数,并行加载,CDN 分发。
- 增加缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存 localStorage、PWA。
- 渲染优化:首屏内容最小化,JS/CSS 优化,加载顺序,服务端渲染,pipeline。
怎样理解前端工程化?
- 前端工程化是使用软件工程的技术和方法来进行前端项目的开发、维护和管理
- 所有能降低成本,并且能提高效率的事情的总称为工程化
- 软件工程化关注的是性能、稳定性、可用性、可维护性等方面,一切以这些为目标的工作都是"前端工程化"