写点什么

2022 秋招前端面试题(二)(附答案)

  • 2022 年 8 月 06 日
  • 本文字数:8905 字

    阅读完需:约 29 分钟

UDP 协议为什么不可靠?

UDP 在传输数据之前不需要先建立连接,远地主机的运输层在接收到 UDP 报文后,不需要确认,提供不可靠交付。总结就以下四点:


  • 不保证消息交付:不确认,不重传,无超时

  • 不保证交付顺序:不设置包序号,不重排,不会发生队首阻塞

  • 不跟踪连接状态:不必建立连接或重启状态机

  • 不进行拥塞控制:不内置客户端或网络反馈机制

浏览器是如何对 HTML5 的离线储存资源进行管理和加载?

  • 在线的情况下,浏览器发现 html 头部有 manifest 属性,它会请求 manifest 文件,如果是第一次访问页面 ,那么浏览器就会根据 manifest 文件的内容下载相应的资源并且进行离线存储。如果已经访问过页面并且资源已经进行离线存储了,那么浏览器就会使用离线的资源加载页面,然后浏览器会对比新的 manifest 文件与旧的 manifest 文件,如果文件没有发生改变,就不做任何操作,如果文件改变了,就会重新下载文件中的资源并进行离线存储。

  • 离线的情况下,浏览器会直接使用离线存储的资源。

说一下类组件和函数组件的区别?

1. 语法上的区别:
函数式组件是一个纯函数,它是需要接受props参数并且返回一个React元素就可以了。类组件是需要继承React.Component的,而且class组件需要创建render并且返回React元素,语法上来讲更复杂。
2. 调用方式
函数式组件可以直接调用,返回一个新的React元素;类组件在调用时是需要创建一个实例的,然后通过调用实例里的render方法来返回一个React元素。
3. 状态管理
函数式组件没有状态管理,类组件有状态管理。
4. 使用场景
类组件没有具体的要求。函数式组件一般是用在大型项目中来分割大组件(函数式组件不用创建实例,所有更高效),一般情况下能用函数式组件就不用类组件,提升效率。复制代码
复制代码

Set 和 Map 有什么区别?

1、Map是键值对,Set是值得集合,当然键和值可以是任何得值2、Map可以通过get方法获取值,而set不能因为它只有值3、都能通过迭代器进行for...of 遍历4、Set的值是唯一的可以做数组去重,而Map由于没有格式限制,可以做数据存储复制代码
复制代码

说一说 js 是什么语言

JavaScript是一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在HTML(标准通用标记语言下的一个应用)网页上使用,用来给HTML网页增加动态功能。
js语言是弱语言类型, 因此我们在项目开发中当我们随意更该某个变量的数据类型后有可能会导致其他引用这个变量的方法中报错等等。复制代码
复制代码

说说浏览器缓存

缓存可以减少网络 IO 消耗,提高访问速度。浏览器缓存是一种操作简单、效果显著的前端性能优化手段很多时候,大家倾向于将浏览器缓存简单地理解为“HTTP 缓存”。但事实上,浏览器缓存机制有四个方面,它们按照获取资源时请求的优先级依次排列如下:
Memory CacheService Worker CacheHTTP CachePush Cache
缓存它又分为强缓存和协商缓存。优先级较高的是强缓存,在命中强缓存失败的情况下,才会走协商缓存 实现强缓存,过去我们一直用 expires。 当服务器返回响应时,在 Response Headers 中将过期时间写入 expires 字段,现在一般使用Cache-Control 两者同时出现使用Cache-Control 协商缓存,Last-Modified 是一个时间戳,如果我们启用了协商缓存,它会在首次请求时随着 Response Headers 返回:每次请求去判断这个时间戳是否发生变化。 从而去决定你是304读取缓存还是给你返回最新的数据复制代码
复制代码

原型

构造函数是一种特殊的方法,主要用来在创建对象时初始化对象。每个构造函数都有prototype(原型)(箭头函数以及Function.prototype.bind()没有)属性,这个prototype(原型)属性是一个指针,指向一个对象,这个对象的用途是包含特定类型的所有实例共享的属性和方法,即这个原型对象是用来给实例对象共享属性和方法的。每个实例对象的__proto__都指向这个构造函数/类的prototype属性。
面向对象的三大特性:继承/多态/封装
关于new操作符:
1. new执行的函数, 函数内部默认生成了一个对象
2. 函数内部的this默认指向了这个new生成的对象
3. new执行函数生成的这个对象, 是函数的默认返回值
ES5例子:function Person(obj) { this.name = obj.name this.age= obj.age}// 原型方法Person.prototype.say = function() { console.log('你好,', this.name )}// p为实例化对象,new Person()这个操作称为构造函数的实例化let p = new Person({name: '番茄', age: '27'})console.log(p.name, p.age)p.say()
ES6例子:class Person{ constructor(obj) { this.name = obj.name this.age= obj.age } say() { console.log(this.name) }}
let p = new Person({name: 'ES6-番茄', age: '27'})console.log(p.name, p.age)p.say()复制代码
复制代码

webpack 配置入口出口

module.exports={    //入口文件的配置项    entry:{},    //出口文件的配置项    output:{},    //模块:例如解读CSS,图片如何转换,压缩    module:{},    //插件,用于生产模版和各项功能    plugins:[],    //配置webpack开发服务功能    devServer:{}}简单描述了一下这几个属性是干什么的。描述一下npm run dev / npm run build执行的是哪些文件通过配置proxyTable来达到开发环境跨域的问题,然后又可以扩展和他聊聊跨域的产生,如何跨域最后可以在聊聊webpack的优化,例如babel-loader的优化,gzip压缩等等复制代码
复制代码

说一下 vue3.0 你了解多少?

 <!-- 响应式原理的改变 Vue3.x 使用Proxy取代 Vue2.x 版本的Object.defineProperty --> <!-- 组件选项声明方式Vue3.x 使用Composition API setup 是Vue3.x新增的一个选项,他    是组件内使用Composition API 的入口 --> <!-- 模板语法变化slot具名插槽语法 自定义指令 v-model 升级 --> <!-- 其它方面的更改Suspense支持Fragment(多个根节点) 和Protal (在dom其他部分渲染组建内容)组件     针对一些特殊的场景做了处理。基于treeshaking优化,提供了更多的内置功能。 -->复制代码
复制代码

你在工作终于到那些问题,解决方法是什么

经常遇到的问题就是Cannot read property ‘prototype’ of undefined解决办法通过浏览器报错提示代码定位问题,解决问题
Vue项目中遇到视图不更新,方法不执行,埋点不触发等问题一般解决方案查看浏览器报错,查看代码运行到那个阶段未之行结束,阅读源码以及相关文档等然后举出来最近开发的项目中遇到的算是两个比较大的问题。复制代码
复制代码

说一下 data 为什么是一个函数而不是一个对象?

JavaScript中的对象是引用类型的数据,当多个实例引用同一个对象时,只要一个实例对这个对象进行操作,其他实例中的数据也会发生变化。而在Vue中,我们更多的是想要复用组件,那就需要每个组件都有自己的数据,这样组件之间才不会相互干扰。所以组件的数据不能写成对象的形式,而是要写成函数的形式。数据以函数返回值的形式定义,这样当我们每次复用组件的时候,就会返回一个新的data,也就是说每个组件都有自己的私有数据空间,它们各自维护自己的数据,不会干扰其他组件的正常运行。

说一下常见的 git 操作

git branch 查看本地所有分支git status 查看当前状态 git commit 提交 git branch -a 查看所有的分支git branch -r 查看远程所有分支git commit -am "nit" 提交并且加注释 git remote add origin git@192.168.1.119:ndshowgit push origin master 将文件给推到服务器上 git remote show origin 显示远程库origin里的资源 git push origin master:developgit push origin master:hb-dev 将本地库与服务器上的库进行关联 git checkout --track origin/dev 切换到远程dev分支git branch -D master develop 删除本地库developgit checkout -b dev 建立一个新的本地分支devgit merge origin/dev 将分支dev与当前分支进行合并git checkout dev 切换到本地dev分支git remote show 查看远程库git add .git rm 文件名(包括路径) 从git中删除指定文件git clone git://github.com/schacon/grit.git 从服务器上将代码给拉下来git config --list 看所有用户git ls-files 看已经被提交的git rm [file name] 删除一个文件git commit -a 提交当前repos的所有的改变git add [file name] 添加一个文件到git indexgit commit -v 当你用-v参数的时候可以看commit的差异git commit -m "This is the message describing the commit" 添加commit信息git commit -a -a是代表add,把所有的change加到git index里然后再commitgit commit -a -v 一般提交命令git log 看你commit的日志git diff 查看尚未暂存的更新git rm a.a 移除文件(从暂存区和工作区中删除)git rm --cached a.a 移除文件(只从暂存区中删除)git commit -m "remove" 移除文件(从Git中删除)git rm -f a.a 强行移除修改后文件(从暂存区和工作区中删除)git diff --cached 或 $ git diff --staged 查看尚未提交的更新git stash push 将文件给push到一个临时空间中git stash pop 将文件从临时空间pop下来
复制代码

webpack3 和 webpack4 区别

1.mode
webpack增加了一个mode配置,只有两种值development | production。对不同的环境他会启用不同的配置。
2.CommonsChunkPlugin
CommonChunksPlugin已经从webpack4中移除。可使用optimization.splitChunks进行模块划分(提取公用代码)。但是需要注意一个问题,默认配置只会对异步请求的模块进行提取拆分,如果要对entry进行拆分需要设置optimization.splitChunks.chunks = 'all'。
3.webpack4使用MiniCssExtractPlugin取代ExtractTextWebpackPlugin。
4.代码分割。
使用动态import,而不是用system.import或者require.ensure
5.vue-loader。
使用vue-loader插件为.vue文件中的各部分使用相对应的loader,比如css-loader等
6.UglifyJsPlugin
现在也不需要使用这个plugin了,只需要使用optimization.minimize为true就行,production mode下面自动为true
optimization.minimizer可以配置你自己的压缩程序复制代码
复制代码

New 操作符做了什么事情?

1、首先创建了一个新对象2、设置原型,将对象的原型设置为函数的prototype对象3、让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性)4、判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象复制代码
复制代码

JS 闭包,你了解多少?

应该有面试官问过你:


  1. 什么是闭包?

  2. 闭包有哪些实际运用场景?

  3. 闭包是如何产生的?

  4. 闭包产生的变量如何被回收?


这些问题其实都可以被看作是同一个问题,那就是面试官在问你:你对JS闭包了解多少?


来总结一下我听到过的答案,尽量完全复原候选人面试的时候说的原话。


答案1: 就是一个function里面return了一个子函数,子函数访问了外面那个函数的变量。


答案2: for 循环里面可以用闭包来解决问题。


for(var i = 0; i < 10; i++){    setTimeout(()=>console.log(i),0)}// 控制台输出10遍10.for(var i = 0; i < 10; i++){    (function(a){    setTimeout(()=>console.log(a),0)    })(i)} // 控制台输出0-9复制代码
复制代码


答案3: 当前作用域产产生了对父作用域的引用。


答案4: 不知道。是跟浏览器的垃圾回收机制有关吗?


开杠了。请问,小伙伴的答案和以上的内容有多少相似程度?


其实,拿着这些问题好好想想,你就会发现这些问题都只是为了最终那一个问题。


闭包的底层实现原理


1. JS执行上下文


我们都知道,我们手写的 js 代码是要经过浏览器 V8 进行预编译后才能真正的被执行。例如变量提升、函数提升。举个栗子。


// 栗子:var d = 'abc';function a(){    console.log("函数a");};console.log(a);   // ƒ a(){ console.log("函数a"); }a();              // '函数a'var a = "变量a";  console.log(a);   // '变量a'a();              // a is not a functionvar c = 123;
// 输出结果及顺序:// ƒ a(){ console.log("函数a"); }// '函数a'// '变量a'// a is not a function
// 栗子预编后相当于:function a(){ console.log("函数a");};var d;console.log(a); // ƒ a(){ console.log("函数a"); }a(); // '函数a'
a = "变量a"; // 此时变量a赋值,函数声明被覆盖
console.log(a); // "变量a"a(); // a is not a function
复制代码
复制代码


那么问题来了。 请问是谁来执行预编译操作的?那这个谁又是在哪里进行预编译的?


是的,你的疑惑没有错。js 代码运行需要一个运行环境,那这个环境就是执行上下文。 是的,js 运行前的预编译也是在这个环境中进行。


js 执行上下文分三种:


  • 全局执行上下文: 代码开始执行时首先进入的环境。

  • 函数执行上下文:函数调用时,会开始执行函数中的代码。

  • eval执行上下文:不建议使用,可忽略。


那么,执行上下文的周期,分为两个阶段:


  • 创建阶段

  • 创建词法环境

  • 生成变量对象(VO),建立作用域链作用域链作用域链(重要的事说三遍)

  • 确认this指向,并绑定this

  • 执行阶段。这个阶段进行变量赋值,函数引用及执行代码。


你现在猜猜看,预编译是发生在什么时候?


噢,我忘记说了,其实与编译还有另一个称呼:执行期上下文


预编译发生在函数执行之前。预编译四部曲为:


  1. 创建AO对象

  2. 找形参和变量声明,将变量和形参作为 AO 属性名,值为undefined

  3. 将实参和形参相统一

  4. 在函数体里找到函数声明,值赋予函数体。最后程序输出变量值的时候,就是从AO对象中拿。


所以,预编译真正的结果是:


var AO = {    a = function a(){console.log("函数a");};    d = 'abc'}复制代码
复制代码


我们重新来。

1. 什么叫变量对象?

变量对象是 js 代码在进入执行上下文时,js 引擎在内存中建立的一个对象,用来存放当前执行环境中的变量。

2. 变量对象(VO)的创建过程

变量对象的创建,是在执行上下文创建阶段,依次经过以下三个过程:


  • 创建 arguments 对象。

  • 对于函数执行环境,首先查询是否有传入的实参,如果有,则会将参数名是实参值组成的键值对放入arguments 对象中。否则,将参数名和 undefined组成的键值对放入 arguments 对象中。


//举个栗子 function bar(a, b, c) {    console.log(arguments);  // [1, 2]    console.log(arguments[2]); // undefined}bar(1,2)复制代码
复制代码


  • 当遇到同名的函数时,后面的会覆盖前面的。


console.log(a); // function a() {console.log('Is a ?') }function a() {    console.log('Is a');}function a() {  console.log('Is a ?')}
/**ps: 在执行第一行代码之前,函数声明已经创建完成.后面的对之前的声明进行了覆盖。**/复制代码
复制代码


  • 检查当前环境中的变量声明并赋值为undefined。当遇到同名的函数声明,为了避免函数被赋值为 undefined ,会忽略此声明


console.log(a); // function a() {console.log('Is a ?') }console.log(b); // undefinedfunction a() {  console.log('Is a ');}function a() {console.log('Is a ?');}var b = 'Is b';var a = 10086;
/**这段代码执行一下,你会发现 a 打印结果仍旧是一个函数,而 b 则是 undefined。**/复制代码
复制代码


根据以上三个步骤,对于变量提升也就知道是怎么回事了。

3. 变量对象变为活动对象

执行上下文的第二个阶段,称为执行阶段,在此时,会进行变量赋值,函数引用并执行其他代码,此时,变量对象变为活动对象。


我们还是举上面的例子:


console.log(a); // function a() {console.log('fjdsfs') }console.log(b); // undefinedfunction a() {   console.log('Is a');}function a() { console.log('Is a?');}var b = 'Is b';console.log(b); // 'Is b'var a = 10086; console.log(a);  // 10086var b = 'Is b?';console.log(b); // 'Is b?'复制代码
复制代码


在上面的代码中,代码真正开始执行是从第一行 console.log() 开始的,自这之前,执行上下文是这样的:


// 创建过程EC= {  VO: {}; // 创建变量对象  scopeChain: {}; // 作用域链}VO = {  argument: {...}; // 当前为全局上下文,所以这个属性值是空的  a: <a reference> // 函数 a  的引用地址  b: undefiend  // 见上文创建变量对象的第三步}
复制代码
复制代码
词法作用域(Lexical scope

这里想说明,我们在函数执行上下文中有变量,在全局执行上下文中有变量。JavaScript的一个复杂之处在于它如何查找变量,如果在函数执行上下文中找不到变量,它将在调用上下文中寻找它,如果在它的调用上下文中没有找到,就一直往上一级,直到它在全局执行上下文中查找为止。(如果最后找不到,它就是 undefined)。


再来举个栗子:


 1: let top = 0; //  2: function createWarp() { 3:   function add(a, b) { 4:     let ret = a + b 5:     return ret 6:   } 7:   return add 8: } 9: let sum = createWarp()10: let result = sum(top, 8)11: console.log('result:',result)

复制代码
复制代码


分析过程如下:


  • 在全局上下文中声明变量top 并赋值为 0.

  • 2 - 8 行。在全局执行上下文中声明了一个名为 createWarp 的变量,并为其分配了一个函数定义。其中第 3-7 行描述了其函数定义,并将函数定义存储到那个变量(createWarp)中。

  • 第 9 行。我们在全局执行上下文中声明了一个名为 sum 的新变量,暂时,值为 undefined

  • 第 9 行。遇到(),表明需要执行或调用一个函数。那么查找全局执行上下文的内存并查找名为 createWarp 的变量。 明显,已经在步骤 2 中创建完毕。接着,调用它。

  • 调用函数时,回到第 2 行。创建一个新的createWarp执行上下文。我们可以在 createWarp 的执行上下文中创建自有变量。js 引擎createWarp 的上下文添加到调用堆栈(call stack)。因为这个函数没有参数,直接跳到它的主体部分.

  • 3 - 6 行。我们有一个新的函数声明,createWarp执行上下文中创建一个变量 addadd 只存在于 createWarp 执行上下文中, 其函数定义存储在名为 add 的自有变量中。

  • 第 7 行,我们返回变量 add 的内容。js 引擎查找一个名为 add 的变量并找到它. 第 4 行和第 5 行括号之间的内容构成该函数定义。

  • createWarp 调用完毕,createWarp 执行上下文将被销毁。add 变量也跟着被销毁。add 函数定义仍然存在,因为它返回并赋值给了 sum 变量。 (ps: 这才是闭包产生的变量存于内存当中的真相

  • 接下来就是简单的执行过程,不再赘述。。

  • ……

  • 代码执行完毕,全局执行上下文被销毁。sum 和 result 也跟着被销毁。


小结一下


现在,如果再让你回答什么是闭包,你能答出多少?


其实,大家说的都对。不管是函数返回一个函数,还是产生了外部作用域的引用,都是有道理的。


所以,什么是闭包?


  • 解释一下作用域链是如何产生的。

  • 解释一下 js 执行上下文的创建、执行过程。

  • 解释一下闭包所产生的变量放在哪了。

  • 最后请把以上 3 点结合起来说给面试官听。

深拷贝浅拷贝

浅拷贝:浅拷贝通过ES6新特性Object.assign()或者通过扩展运算法...来达到浅拷贝的目的,浅拷贝修改副本,不会影响原数据,但缺点是浅拷贝只能拷贝第一层的数据,且都是值类型数据,如果有引用型数据,修改副本会影响原数据。
深拷贝:通过利用JSON.parse(JSON.stringify())来实现深拷贝的目的,但利用JSON拷贝也是有缺点的,当要拷贝的数据中含有undefined/function/symbol类型是无法进行拷贝的,当然我们想项目开发中需要深拷贝的数据一般不会含有以上三种类型,如有需要可以自己在封装一个函数来实现。复制代码
复制代码

说一下你对盒模型的理解?

CSS3中的盒模型有以下两种:标准盒模型、IE盒模型盒模型都是由四个部分组成的,分别是margin、border、padding和content标准盒模型和IE盒模型的区别在于设置width和height时, 所对应的范围不同1、标准盒模型的width和height属性的范围只包含了content2、IE盒模型的width和height属性的范围包含了border、padding和content可以通过修改元素的box-sizing属性来改变元素的盒模型;1、box-sizing:content-box表示标准盒模型(默认值)2、box-sizing:border-box表示IE盒模型(怪异盒模型)复制代码
复制代码

CDN 的作用

CDN 一般会用来托管 Web 资源(包括文本、图片和脚本等),可供下载的资源(媒体文件、软件、文档等),应用程序(门户网站等)。使用 CDN 来加速这些资源的访问。


(1)在性能方面,引入 CDN 的作用在于:


  • 用户收到的内容来自最近的数据中心,延迟更低,内容加载更快

  • 部分资源请求分配给了 CDN,减少了服务器的负载


(2)在安全方面,CDN 有助于防御 DDoS、MITM 等网络攻击:


  • 针对 DDoS:通过监控分析异常流量,限制其请求频率

  • 针对 MITM:从源服务器到 CDN 节点到 ISP(Internet Service Provider),全链路 HTTPS 通信


除此之外,CDN 作为一种基础的云服务,同样具有资源托管、按需扩展(能够应对流量高峰)等方面的优势。

代码输出结果

Promise.resolve().then(() => {  return new Error('error!!!')}).then(res => {  console.log("then: ", res)}).catch(err => {  console.log("catch: ", err)})复制代码
复制代码


输出结果如下:


"then: " "Error: error!!!"复制代码
复制代码


返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的return new Error('error!!!')也被包裹成了return Promise.resolve(new Error('error!!!')),因此它会被 then 捕获而不是 catch。

Vuex 有哪些基本属性?为什么 Vuex 的 mutation 中不能做异步操作?

有五种,分别是 State、 Getter、Mutation 、Action、 Module1、state => 基本数据(数据源存放地)2、getters => 从基本数据派生出来的数据3、mutations => 提交更改数据的方法,同步4、actions => 像一个装饰器,包裹mutations,使之可以异步。5、modules => 模块化Vuex
1、Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过 Action 来提交 mutation实现,这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。2、每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。复制代码
复制代码


用户头像

还未添加个人签名 2022.07.31 加入

还未添加个人简介

评论

发布
暂无评论
2022秋招前端面试题(二)(附答案)_前端面试_helloworld1024fd_InfoQ写作社区