针对面试中出镜率比较高的重难点知识梳理。
相比于第一篇 前端面试必备技巧,本篇文章更贴合今年的面试实际。第一篇比较全面,也比较基础,建议先看一遍上一篇再看本篇会更容易理解。
第一篇无法访问问题已解决,我也不知道咋回事,又重新发布了一遍
关于 ES6(泛指 ECMAScript 2015 及以后的版本)几乎是面试必问的,一般的问法是:“平常会使用 ES6 吗?列举几个 ES6 的用法”。 回答出来三四个就差不多了,但回答的每一个都要弄清楚,有的面试官会延伸着追问。 如果时间充足,还是建议看看 阮一峰的 ES6 入门教程(https://juejin.cn[https//es6.ruanyifeng.com/%5D(https://es6.ruanyifeng.com/%29) 。
用作字符串拼接:你好,${name}
let a,b,c = 1,2,3
交换变量的值:a,b = b,a
提取 JSON 数据,获取服务器返回数据时有用:let {data, code} = res
输入模块的指定方法:
const { SourceMapConsumer, SourceNode } = require("source-map");
从函数返回多个值: 返回个数组或对象
扩展运算符(spread)是三个点(...
)。
resolve
,失败调用 reject
.then
获取结果,.catch
捕获异常。捕获异常还可通过 .then
的第二个参数.finally
无论成功失败都一定会调用Promise.all()
p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。let p = Promise.all([p1,p2,p3])
p.then(([res1, res2,res3]) => {};
Promise 示例:
new Promise(){
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
}.then().catch().finally()
async 函数是什么?一句话,它就是 Generator 函数的语法糖。 async
函数对 Generator 函数的改进:
next()
才能执行async
和 await
比 *
和 yield
更好理解箭头函数 () => {}
的 this
是在定义函数时绑定的,不是在执行过程中绑定的。简单的说,函数在定义时,this
就继承了定义函数的对象。this
一旦确定以后不会改变。
普通函数的 this 指向的是调用它的对象。
常用的适配方案是 rem + flex 布局。
先在应用的入口设置基准字体大小,越早执行越好。如果设计图尺寸宽度是 750,就除以 7.5。具体的值根据实际情况来算。
document.documentElement.style.fontSize = document.documentElement.clientWidth / 7.5 + 'px';
设置完基准字体大小后,通过 SASS 的函数封装一个方法方便使用
@function px2rem($value){
@return $value * 0.02rem
}
使用时直接调用封装好的函数即可,value 传 px 的值,方法会自动转换成 rem。
width: px2rem(100)
flex 布局推荐 阮一峰的 Flex 布局教程:语法篇
一般来说中小公司问框架会多一些,具体是 Vue 还是 React 根据公司所用技术栈而定。因为中小公司技术栈比较统一,很少出现同时用两种甚至三种框架的,且更注重干活的能力,所以框架的使用基本会占面试的一半时间。
大公司反而框架问的少,比较注重基础。他们的逻辑是考察你有没有这个能力,如果能力强用啥框架都能快速上手。
因为我对 React 不太熟,所以就只准备了 Vue 的一些问题。下面几个问题都是面试频率非常高的,一定要弄懂。
有一篇文章对 Vue 总结的比较全,这里也是摘录了几个答案。完整的内容看这里:30 道 Vue 面试题,内含详细讲解(涵盖入门到精通,自测 Vue 掌握程度
如果问框架的话 Vue 肯定会问这个问题,把下面这一段话背下来就好。
Vue 主要通过以下 4 个步骤来实现数据双向绑定的:
回答完这个问题有的面试官还会延伸问:Vue3 将会用
Proxy
替换Object.defineProperty()
,proxy
有什么优点?
建议把 proxy 相关介绍 看一下,说出几个优点。
这个题的重要性仅次于 Vue 双向绑定原理,建议掌握
虚拟 DOM 的实现原理主要包括以下 3 部分:
优点:
缺点:
深入剖析:Vue核心之虚拟DOM https://juejin.cn/post/6844903895467032589#heading-14
使用 <keep-alive>
可缓存路由。
它有 include
和 exclude
两个属性,分别表示包含或排除某些路由,值可以是字符串、数组或者正则表达式。
独有的生命周期方法:activited
,deactivited
路由跳转的几种方式
// 字符串
this.$router.push('home')
// 命名的路由
this.$router.push({
name: 'user',
params: {userId: '123'}
})
//接收参数
this.userId = this.$route.params.userId
// 带查询参数,变成 /user?userId=123
this.$router.push({
path: '/user',
query: {userId: '123'}
})
//接收
this.userId = this.$route.query.userId;
name 和 path 跳转的区别在于
注意一定要调用 next();
,否则钩子就不会被 resolved。
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
next();
})
在 2.5.0+ 你可以用 router.beforeResolve
注册一个全局守卫。这和 router.beforeEach
类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
router.afterEach((to, from) => {
// ...
})
你可以在路由配置上直接定义 beforeEnter
守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
这些守卫与全局前置守卫的方法参数是一样的。
最后,你可以在路由组件内直接定义以下路由导航守卫:
beforeRouteEnter
beforeRouteUpdate
(2.2 新增)beforeRouteLeave
注意要调用 next()
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
这块内容比较多,更详细的介绍看 vue-router 官方文档
Vue 的表单可以使用 v-model
支持双向绑定,相比于 React 来说开发上更加方便,当然了 v-model
其实就是个语法糖,本质上和 React 写表单的方式没什么区别。
改变数据方式不同,Vue 修改状态相比来说要简单许多,React 需要使用 setState
来改变状态,并且使用这个 API 也有一些坑点。并且 Vue 的底层使用了依赖追踪,页面更新渲染已经是最优的了,但是 React 还是需要用户手动去优化这方面的问题。
React 16以后,有些钩子函数会执行多次,这是因为引入 Fiber 的原因,这在后续的章节中会讲到。
React 需要使用 JSX,有一定的上手成本,并且需要一整套的工具链支持,但是完全可以通过 JS 来控制页面,更加的灵活。Vue 使用了模板语法,相比于 JSX 来说没有那么灵活,但是完全可以脱离工具链,通过直接编写 render
函数就能在浏览器中运行。
在生态上来说,两者其实没多大的差距,当然 React 的用户是远远高于 Vue 的。
在上手成本上来说,Vue 一开始的定位就是尽可能的降低前端开发的门槛,然而 React 更多的是去改变用户去接受它的概念和思想,相较于 Vue 来说上手成本略高。
组件间传值又分为父子组件传值和非父子组件传值
props
传值<custom content="hello world"></custom>
复制代码
emit
发送事件this.$emit('chooseType', type)
复制代码
父组件接收事件:
<custom content="hello world" @chooseType="handleType"></custom>
复制代码
主要通过事件总线传值
在根节点给 Vue
挂载一个空的 Vue
对象
Vue.prototype.bus = new Vue();
复制代码
需要发送事件的组件里
this.bus.$emit("change", params)
复制代码
接收事件的组件
this.bus.$on("change", (msg) => {
//do yourself work
})
除了以上这几个问题,还有 Vue 生命周期,v-show 和 v-if,vuex 等知识点经常会问。 更多 Vue 开发技巧可以看我的另一篇文章:Vue 开发经验小记
基本语法:
module.exports = value
或exports.xxx = value
require(xxx)
,如果是第三方模块,xxx为模块名;如果是自定义模块,xxx为模块文件路径加载某个模块,其实是加载该模块的module.exports属性。 require命令用于加载模块文件。require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。 CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。这点与ES6模块化有重大差异。
export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
return a + b;
};
export { basicNum, add };
/** 引用模块 **/
import { basicNum, add } from './math';
function test(ele) {
ele.textContent = add(99 + basicNum);
}
也可以用 export default
// export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'
它们有两个重大差异:
添加 header
//创建 axios 实例
let instance = axios.create({timeout: 1000 * 12});
//设置post请求头
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
instance.defaults.withCredentials=true; //让ajax携带cookie
在 api 方法中添加 header
postJson(url, params) {
return axios.post(`${base}${url}`, params, {
headers: {'content-type': 'application/json;charset=UTF-8'}
})
},
//基本的get方法
get(url, params) {
return axios.get(`${base}${url}`, {params: params})
},
//基本的post方法
post(url, params) {
return axios.post(`${base}${url}`, JSON.stringify(params))
},
性能优化几乎是必问的,最好把下面五种方式都说出来。 第二、三点会延伸了问,小括号里是一般延伸的方向。详细的解释参见 十二、页面性能
目标:加快界面展示速度,减少数据请求次数。
提升页面性能的方法有哪些?
//强制打开 <a> 标签的 dns 解析
<meta http-equiv="x-dns-prefetch-controller" content="on">
//DNS预解析
<link rel="dns-prefetch" href="//host_name_to_prefetch.com">
缓存策略的分类:
缓存策略都是通过设置 HTTP Header 来实现的。 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识。 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中。
强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。
缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,需要和Last-modified结合使用。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。 Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。Expires: Wed, 22 Oct 2018 08:41:00 GMT
表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期,需要再次请求。
在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存。比如当Cache-Control:max-age=300
时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。 Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令:
其实这两者差别不大,区别就在于 Expires 是http1.0的产物,Cache-Control是http1.1的产物,两者同时存在的话,Cache-Control优先级高于Expires;在某些不支持HTTP1.1的环境下,Expires就会发挥用处。所以Expires其实是过时的产物,现阶段它的存在只是一种兼容性的写法。 强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。
浏览器在第一次访问资源时,服务器返回资源的同时,在response header中添加 Last-Modified 的header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和 header;
Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT
浏览器下一次请求这个资源,浏览器检测到有 Last-Modified这个header,于是添加If-Modified-Since这个header,值就是Last-Modified中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200。 但是 Last-Modified 存在一些弊端:
既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?所以在 HTTP / 1.1 出现了 ETag
和If-None-Match
Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。
Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体 现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last- Modified也有可能不一致。
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。
强缓存与协商缓存的区别可以用下表来表示:
缓存类型 | 获取资源形式 | 状态码 | 发送请求到服务器 |
---|---|---|---|
强缓存 | 从缓存取 | 200(from cache) | 否,直接从缓存取 |
协商缓存 | 从缓存取 | 304(Not Modified) | 是,通过服务器来告知缓存是否可用 |
用户行为对缓存的影响
用户操作 | Expires/Cache-Control | Last-Modied/Etag | |
---|---|---|---|
地址栏回车 | 有效 | 有效 | |
页面链接跳转 | 有效 | 有效 | |
新开窗口 | 有效 | 有效 | |
前进回退 | 有效 | 有效 | |
F5刷新 | 无效 | 有效 | |
Ctrl+F5强制刷新 | 无效 | 无效 | |
在chrome浏览器中的控制台Network中size栏通常会有三种状态
三种的区别:
状态 | 类型 | 说明 |
---|---|---|
200 | form memory cache | 不请求网络资源,资源在内存当中,一般脚本、字体、图片会存在内存当中。 |
200 | form disk ceche | 不请求网络资源,在磁盘当中,一般非脚本会存在内存当中,如css等。 |
200 | 资源大小数值 | 资源大小数值 从服务器下载最新资源。 |
304 | 报文大小 | 请求服务端发现资源没有更新,使用本地资源,即命中协商缓存。 |
面试高级前端开发的话肯定会问 webpack,稍微准备一下总比啥都不知道强
webpack 的默认配置文件是 webpack.config.js
。
webpack 默认只能处理 js 文件,如果想处理图片等其他文件,则需要用到相应的 loader。比如 file-loader
、 url-loader
、 css-loader
、 style-loader
,如果用 sass 的话会用到 sass-loader
。
其他几个重要的概念是:
[name]
表示。配置方法
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
//添加这行
const webpack = require('webpack');
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
entry: {
main: './src/index.js',
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
devServer: {
contentBase: './dist',
open: true,
port: 9090,
//添加下面两行
hot: true,
hotOnly: true
},
plugins: [
new HtmlWebpackPlugin({
template: "src/index.html"
}),
new CleanWebpackPlugin(),
//添加这行
new webpack.HotModuleReplacementPlugin(),
],
module: {
rules: [
{
test: /\.(png|jpg|gif|jpeg)/,
use: {
loader: 'file-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/'
}
}
}, {
test: /\.(eot|woff|svg|ttf)/,
use: {
loader: 'file-loader',
}
}, {
test: /\.(css|scss)/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
// modules: true
}
},
'sass-loader',
'postcss-loader'
]
}
]
}
}
CSS热重载 style-loader 和 css-loader 已经帮我们实现了。
JS热重载
//判断是否开启了热更新
if (module.hot){
//监听 hotTest.js 文件,当文件有变动时执行箭头函数里的方法
module.hot.accept('./hotTest.js', () => {
//文件变动时执行的操作
hotTest();
})
}
目前最成熟的JavaScript代码压缩工具是UglifyJS,它会分析JavaScript代码语法树,理解代码含义,从而能做到诸如去掉无效代码、去掉日志输出代码、缩短变量名等优化。
要在Webpack中接入UglifyJS需要通过插件的形式,目前有两个成熟的插件,分别是:
压缩图片使用 image-webpack-loader
开启 gzip 也可显著压缩大小。
前端安全分两类:CSRF、XSS
常考点:基本概念和缩写、攻击原理、防御措施
CSRF(Cross-site request forgery)跨站请求伪造。
攻击原理
防御措施
XSS(cross-site scripting)跨域脚本攻击
攻击原理
往 Web 页面里插入恶意Script代码
防御措施
HTML:对以下这些字符进行转义:
&:&
<:&alt;
>:>
':'
":"
/:/
Javascript:把所有非字母、数字的字符都转义成小于256的ASCII字符;
URL:使用Javascript的encodeURIComponent()方法对用户的输入进行编码,该方法会编码如下字符:
, / ? : @ & = + $ #
问 js 基础的时候一般会问到深浅拷贝的问题,出镜率也比较高
Object.assign
...
JSON.parse(JSON.stringify(object))
来解决。
但有几个需要注意的地方- 会忽略 `undefined`
- 会忽略 `symbol`
- 不能序列化函数
- 不能解决循环引用的对象如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用
http 相关的也比较重要,经常被问。
HTTP(HyperText Transfer Protocol:超文本传输协议)是一种用于分布式、协作式和超媒体信息系统的应用层协议。 简单来说就是一种发布和接收 HTML 页面的方法,被用于在 Web 浏览器和网站服务器之间传递信息。 HTTP 默认工作在 TCP 协议 80 端口,用户访问网站 http:// 打头的都是标准 HTTP 服务。 HTTP 协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此,HTTP协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。 HTTPS(Hypertext Transfer Protocol Secure:超文本传输安全协议)是一种透过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包。HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。 HTTPS 默认工作在 TCP 协议443端口,它的工作流程一般如以下方式:
在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接
参考文档:https://www.runoob.com/w3cnote/http-vs-https.html
简单来说,HTTP/2(超文本传输协议第2版,最初命名为HTTP2.0),是HTTP协议的第二个主要版本。HTTP/2是HTTP协议自1999年HTTP1.1发布后的首个更新,主要基于SPDY协议。 HTTP2.0的特点是:在不改动HTTP语义、方法、状态码、URI及首部字段的情况下,大幅度提高了web性能。
刚刚对HTTP2.0的介绍中引出了一个名词 —— SPDY协议,这又是什么呢? SPDY是Speedy的昵音,意为“更快”。它是Google开发的基于TCP协议的应用层协议。目标是优化HTTP协议的性能,通过压缩、多路复用和优先级等技术,缩短网页的加载时间并提高安全性。SPDY协议的核心思想是尽量减少TCP连接数。SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。
HTTP2.0特点
参考文档:https://segmentfault.com/a/1190000016656529?utm_source=tag-newest
GET、POST、PUT、DELETE、PATCH
Linux 相关的一般后端问的多一些,前端问的少
列出在内存中运行的 全部进程信息
ps -aux
//列出 *** 进程的详细信息
ps -aux | grep ***
杀死进程
kill -9 -pid
算法还是得在平时积累
二分法查找,也称为折半法,是一种在有序数组中查找特定元素的搜索算法。 二分法查找的思路如下:
二分法查找的时间复杂度O(logn)。
动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。 动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
更基础的 js 基础相关问题请移步上一篇文章 前端面试必备技巧。
最后祝各位大佬都能拿到满意的 offer~