面试题

面试题

十二月 08, 2018

最近公司有要招人,然后叫我负责一下技术方面的面试,然后问着问着,好像发现有一些知识点我也不是记得特别清楚。所以有必要列一下自己面试需要问的问题,并且把所有问题点给摸索清楚。不然被问住了就很尴尬了。。。

HTML

初级

1. HTML5有哪些新特性,你用过哪一些?

语义化标签、audio、video、canvas、geolocation、web worker、websocket

cookie、localStorage、sessionStorage的区别

cookie:每次请求都会被发送到服务器。不大于4k。可以自定义过期时间;服务端可以设置httponly,前端不可访问。
sessionStorage:页面关闭后失效(window.open新页面还会存在)
localStorage:永久存储

中级

如何在移动设备实现物理1像素的线

单边:1px的线然后再缩放 dpr 倍
四边:实现一个与目标元素dpr倍宽、dpr倍高的伪元素,然后整体缩放dpr倍。让其刚好覆盖到目标元素上。(兼容圆角)

网上的方案还有:border-image、viewport+rem、box-shadow来实现。但是最实用的还是上述方法。

升级:结合vue的指令可以更优雅的实现一像素问题。见项目笔记

高级

如何在页面间传值

  1. 监听storage事件,获取localStorage、sessionStorage 变化,从而传参(跨域限制)
  2. postMessage(无跨域限制、可用于iframe、openner)
  3. sharedWorker(高级、兼容性差)

CSS

初级

css选择器有哪些?以及他们的优先级

1.id选择器( # myid)
2.类选择器(.myclassname)
3.标签选择器(div, h1, p)
4.相邻选择器(h1 + p)
5.子选择器(ul > li)
6.后代选择器(li a)
7.通配符选择器( * )
8.属性选择器(a[rel = “external”])
9.伪类选择器(a:hover, li:nth-child)

id > class > 属性 > 标签 > 通配符

解释一下rem、em、px、vh、vw的区别

  • rem:相对于<htm>的font-size:来定义的单位。
  • em:相对于父元素的font-size 来定义的单位
  • px:物理像素,基本单位
  • vh:100vh === 1浏览器可视区域高度
  • vw:100vw === 1浏览器可视区域宽度

高级:

  • vmin:相对于视口的宽度或高度中较小的那个。其中最小的那个被均分为100单位的vmin
  • vmax:相对于视口的宽度或高度中较大的那个。其中最大的那个被均分为100单位的vmax
  • rpx:微信小程序使用的单位,750rpx等于屏幕宽度

对盒子模型的理解,IE盒子模型和标准模型有什么区别

盒模型: 内容(content)、填充(padding)、边界(margin)、 边框(border)

其实区别在于box-sizing属性:

content-box:标准盒子模型;
border-box:IE盒子模型;content部分把 border 和 padding计算了进去

中级

高级

JS

初级

js基本数据类型

undefined、null、boolean、string、Number、Symble(ES6)

事件冒泡与事件捕获的区别

IE使用的是事件冒泡、另一种是事件捕获

事件冒泡:事件开始于最具体的元素、然后逐级向上传播到较为不具体的节点

事件捕获:由不太具体的节点应该更早接收到事件、而最具体的节点最后接收到事件

通过addEventListener的第三个参数来启用监听事件冒泡还是事件捕获:true,事件捕获;false:事件冒泡;所以平时使用的都是false居多

场景提问:如果有一个列表,有数千个选项要绑定事件,应该如何处理;

判断一个变量是不是function

typeof variable === ‘function’

是不是array

Object.prototype.toString.call(variable) === ‘[object Array]‘

中级

宏任务与微任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log(1);

setTimeout(() => {
console.log(2)
}, 0)

new Promise(resolve => {
console.log(3)
resolve()
}).then(() => {
console.log(4)
});

console.log(5)

打印顺序:1、3、5、4、2

setTimeout是宏任务,Promise是微任务,微任务会比宏任务快,可以简单的理解,微任务是vip吧;

概念大概是这样的:js有个执行堆栈和排队队列的概念。如果是同步任务,则直接进入堆栈。如果是异步任务,则进入队列等待;
如果堆栈为空,则先查找微任务内的队列任务,再去查找宏任务的队列任务;

比较特殊的是,Node的process.nextTick();和前端的MessageChannel(Vue的nextTick是利用MessageChannel来实现的)。该方法是直接把任务插入到堆栈底部,会比微任务与宏任务都快;

属于微任务的有:Promise、MutationObserver、process.nextTick(Node)

箭头函数与普通函数的区别

  1. this指向不一样。无法通过call、apply、bind从新修改this
  2. 没有arguments、也没有caller和callee;
  3. 不可以使用new 命令,会抛出错误
  4. 不可以使用yield命令,因此不能用作 Generator 函数。

高级

Set和Map数据结构

Vue

初级

组件的生命周期,加上keep-alive、vue-router之后会新增什么钩子函数

beforeCreate、created、beforeMounte、mounted、beforeUpdate、updated、beforeDestroy、destroyed

keep-alive第二次进入不会触发beforeCreate、created、beforeMounte、mounted、beforeDestroy、destroyed
新增activateddeactivated

加上vue-router之后会新增:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

created与mounted的区别,能不能在created里访问dom

created是组件实例加载完成,可访问data、prop、methods等等

mounted可以访问除了created能访问的东西 + dom

不能再created里访问dom

keep-alive

从场景中提问:比如一个分页组件,已经跳转到第三页,然后跳转到其他路由,再跳转回来需要还保留在第三页,应该用keep-alive;

中级

vue-router的不刷新加载实现原理是什么

vue-router有两种模式:hash和history;

hash:修改window.location.hash时,不会发生页面刷新、会给路由历史新增记录。且可以监听hashchange来监听hash的变化,以此来实现页面不刷新渲染页面。

history:利用history.pushstate方法来实现。也是不刷新页面、给路由历史新增记录。更多的是,也可以修改url的相对路径且不发生页面刷新加载;然后可以通过监听popstate事件来实现监听路由变化。

自定义指令

通过Vue.directive全局注册自定义指令或者directives: {} 局部注册自定义指令;

自定义指令用于给某个dom扩展特定的功能,自定义指令当中允许对当前dom进行直接操作;

钩子函数:

bind:被绑定到元素时
inserted:绑定元素被插入到父节点时(仅保证父节点存在)
update:所在组件的vNode更新时
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用
unbind:只调用一次,指令与元素解绑时调用。

钩子函数参数:

el:指令所绑定的元素,可以用来直接操作 DOM
binding:一个对象包含各种属性:name、value、oldValue、expression、arg、modifiers
vNode:
oldVnode:

minxins

在混入和组件数据冲突时以组件数据优先;

钩子函数会被合并、先调用混入的钩子函数

组件v-model

允许一个自定义组件在使用 v-model 时定制 prop 和 event。默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event

使用model选项可以修改默认的prop和event

model: {
    prop: 'checked',
    event: 'change'
  }

高级

Vue.use一个插件到底做了什么

Vue.use用于安装插件,该方法需要传入一个function或者一个对象。如果传入的是function,则调用function。如果传入的是一个对象,则调用对象中的install方法。

Vue.use还做了一件事是防止重复安装插件

Vue.extends

Vue.extends接受一个对象,与Vue、Vue.components接收的参数完全一致。返回的是一个构造函数(constructor);new 该构造函数则返回一个组件实例(instance)。
该实例与new Vue生成的Vm实例、Vue.components生成的组件实例完全一致;不同的是一份配置=》一份构造函数=》多份实例。从而实现复用;

其他

初级

在提升代码性能做过哪些事情

发散、很多

中级

解释import的各种用法的区别

1
2
3
4
5
6
7
import defaultExport from "module-name";
import * as name from "module-name";
import { export } from "module-name";
import { export as alias } from "module-name";
import { export1 , export2 } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";

https与http的区别,怎样做到加密的

https是http + ssl;

然后:

  1. 客户端像服务器发送请求
  2. 服务器向客户端发送公钥
  3. 客户端先验证公钥有效性
  4. 通过后生成一个随机值(私钥),然后用公钥对随机值进行加密
  5. 服务器用公钥解析随机值,拿到私钥;这样双方都有了唯一私钥
  6. 则双方利用私钥对数据进行对称加密,进行数据传输

高级

CommonJs与ES6 module的区别

CommonJs:规范出来之前的野生规范,遵守CommonJS规范。在import规范出来之前被大规模使用

ES6 Module:ES6官方模块管理规范

使用:

  • import 必须写在模块头部,必须在模块最外层;require可以在任何地方require;可以export多个代码
  • 可以require export出来的模块;但是import不能引用module.export的模块;虽然可以,但是强烈不建议使用;只能定义一个modules.exports

原理:

  • import是引用;require是立即运行

webpack怎样实现tree shaking(去除不必要的代码)

应该结合ES6的imoprt、export、DefinePlug、sideEffects和uglifyjs-webpack-plugin来实现;

原理是,用import、export或者DefinePlugin来定义部分不能访问的代码。然后uglifyjs-webpack-plugin在压缩js时识别不能访问的代码,从而去除掉多余的代码;

而sideEffects可以参考:Webpack 中的 sideEffects 到底该怎么用

2022年8月11更新

tree shaking与sideEffect应该分开理解。

从tree shaking本身,其实不会有太多学问。只要代码中使用的是ESM模块,webpack就能分析出哪部分代码是不需要的。一般项目上有问题,大多是babel把ESM模板转化为commonJS。所以我们只需要指定babel不发生转换就行

.babelrc

1
2
3
4
5
{
"presets": [
[ "env", { "modules": false } ]
]
}

代码我们自己可以控制,但是node_module里的第三方库里往往就有很多commonJS的代码。如果第三方库也提供ESM的导出的话,最好引用ESM的,否则只能全量加载。

以redux为例,其发布到Npm上的目录结构为:

1
2
3
4
5
6
node_modules/redux
|-- es
| |-- index.js # 采用 ES6 模块化语法
|-- lib
| |-- index.js # 采用 ES5 模块化语法
|-- package.json

package.json有两个字段:

1
2
3
4
{
"main": "lib/index.js", // 指明采用 CommonJS 模块化的代码入口
"jsnext:main": "es/index.js" // 指明采用 ES6 模块化的代码入口
}

则我们可以在webpack中配置resolve.mainFields来指定优先加载哪种模块

1
2
3
4
5
6
module.exports = {
resolve: {
// 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
mainFields: ['jsnext:main', 'browser', 'main']
},
};

关于sideEffect

其实就是除了静态能分析之外做的其他额外操作,这种情况下webpack会尽可能的保留其副作用

1
2
3
4
5
6
7
8
9
10
11
12
13
// a.js
export function a() {}
// b.js
export function b(v){ return v }
console.log(b(1))

// package/index.js
import a from './a'
import b from './b'
export { a, b }
// app.js
import {a} from 'package'
console.log(a)

如果没有console.log(b(1))的情况下,b方法是不会被打包的。有了console.log(b(1))。则会输出一段

1
console.log(function (v){return v}(1))

另一个例子。以 mobx-react-devtool 为例,我们通常这样去用:

1
2
3
4
5
6
7
8
9
10
11
12
import DevTools from 'mobx-react-devtools';

class MyApp extends React.Component {
render() {
return (
<div>
...
{ process.env.NODE_ENV === 'production' ? null : <DevTools /> }
</div>
);
}
}

这是一个很常见的按需导入场景,然而在没有 sideEffects: false 配置时,即便 NODE_ENV 设为 production ,打包后的代码里依然会包含 mobx-react-devtools 包,虽然我们没使用过其导出成员,但是 mobx-react-devtools 还是会被 import,因为里面“可能”会有副作用。但当我们加上 sideEffects false 之后,tree shaking 就能安全的把它从 bundle 里完整的移除掉了。

所以为了更进一步的tree shaking。这种情况下的sideEffect也应该清除掉。

那么需要做的是

在对应的包的package.json添加sideEffect标记

1
2
3
4
{
"name": "your-project",
"sideEffects": false
}

webpack配置

1
2
3
4
5
6
module.exports = {
//...
optimization: {
sideEffects: true,
}
};

或者直接mode设置为production

webpack就只会打包静态分析出来的模块。不考虑任何副作用。

在我们写模块时,尽可能的不要写有副作用的代码。仅再写一些polyfill 、 shim 或修改全局变量之类的事情时,sideEffect才应该设置为true,否则可以无脑false。

引用:
使用 Tree Shaking
Webpack 中的 sideEffects 到底该怎么用