写点什么

20 道高级前端面试题解析

作者:loveX001
  • 2022-11-01
    浙江
  • 本文字数:9064 字

    阅读完需:约 30 分钟

对对象与数组的解构的理解

解构是 ES6 提供的一种新的提取数据的模式,这种模式能够从对象或数组里有针对性地拿到想要的数值。 1)数组的解构 在解构数组时,以元素的位置为匹配条件来提取想要的数据的:


const [a, b, c] = [1, 2, 3]
复制代码


最终,a、b、c 分别被赋予了数组第 0、1、2 个索引位的值:


数组里的 0、1、2 索引位的元素值,精准地被映射到了左侧的第 0、1、2 个变量里去,这就是数组解构的工作模式。还可以通过给左侧变量数组设置空占位的方式,实现对数组中某几个元素的精准提取:


const [a,,c] = [1,2,3]
复制代码


通过把中间位留空,可以顺利地把数组第一位和最后一位的值赋给 a、c 两个变量:


2)对象的解构 对象解构比数组结构稍微复杂一些,也更显强大。在解构对象时,是以属性的名称为匹配条件,来提取想要的数据的。现在定义一个对象:


const stu = {  name: 'Bob',  age: 24}
复制代码


假如想要解构它的两个自有属性,可以这样:


const { name, age } = stu
复制代码


这样就得到了 name 和 age 两个和 stu 平级的变量:


注意,对象解构严格以属性名作为定位依据,所以就算调换了 name 和 age 的位置,结果也是一样的:


const { age, name } = stu
复制代码

代码输出结果

var length = 10;function fn() {    console.log(this.length);}
var obj = { length: 5, method: function(fn) { fn(); arguments[0](); }};
obj.method(fn, 1);
复制代码


输出结果: 10 2


解析:


  1. 第一次执行 fn(),this 指向 window 对象,输出 10。

  2. 第二次执行 arguments[0],相当于 arguments 调用方法,this 指向 arguments,而这里传了两个参数,故输出 arguments 长度为 2。

说一下 HTTP 和 HTTPS 协议的区别?

1、HTTPS协议需要CA证书,费用较高;而HTTP协议不需要2、HTTP协议是超文本传输协议,信息是明文传输的,HTTPS则是具有安全性的SSL加密传输协议;3、使用不同的连接方式,端口也不同,HTTP协议端口是80,HTTPS协议端口是443;4、HTTP协议连接很简单,是无状态的;HTTPS协议是具有SSL和HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP更加安全
复制代码

说一下购物车的逻辑?

//vue中购物车逻辑的实现1. 购物车信息用一个数组来存储,数组中保存对象,对象中有id和count属性
2. 在vuex中state中添加一个数据 cartList 用来保存这个数组
3. 由于商品详情页需要用到加入购物车功能,所以我们需要提供一个mutation, 用来将购物车信息加入 cartList中
4. 加入购物车信息的时候,遵照如下规则: 如果购物车中已经有了该商品信息,则数量累加,如果没有该商品信息,则新增一个对象
5. 在商品详情页,点击加入购物车按钮的时候,调用vuex提供的addToCart这个mutation将当前的商品信息 (id count)传给addTocart this.$store.commit("addToCart", {id: , count:})
// js中购物车逻辑的实现1.商品页点击“加入购物车”按钮,触发事件
2.事件调用购物车“增加商品”的Js程序(函数、对象方法)
3.向Js程序传递传递“商品id”、“商品数量”等数据
4.存储“商品id”、“商品数量”到浏览器的localStorage中
**展示购物车中的商品******
1.打开购物车页面
2.从localStorage中取出“商品Id”、“商品数量”等信息。
3.调用服务器端“获得商品详情”的接口得到购物车中的商品信息(参数为商品Id)
4.将获得的商品信息显示在购物车页面。
**完成购物车中商品的购买******
1.用户对购物车中的商品完成购买流程,产生购物订单
2.清除localStorage中存储的已经购买的商品信息
备注1:购物车中商品存储的数据除了“商品id”、“商品数量”之外,根据产品要求还可以有其他的信息,例如完整的商品详情(这样就不用掉服务器接口获得详情了)、购物车商品的过期时间,超过时间的购物车商品在下次打开网站或者购物车页面时被清除。
备注2:购物车商品除了存储在localStorage中,根据产品的需求不同,也可以存储在sessionStorage、cookie、session中,或者直接向服务器接口发起请求存储在服务器上。何种情况使用哪种方式存储、有啥区别请自己分析。
复制代码

如果一个构造函数,bind 了一个对象,用这个构造函数创建出的实例会继承这个对象的属性吗?为什么?

不会继承,因为根据 this 绑定四大规则,new 绑定的优先级高于 bind 显示绑定,通过 new 进行构造函数调用时,会创建一个新对象,这个新对象会代替 bind 的对象绑定,作为此函数的 this,并且在此函数没有返回对象的情况下,返回这个新建的对象

10 个 Ajax 同时发起请求,全部返回展示结果,并且至多允许三次失败,说出设计思路

这个问题相信很多人会第一时间想到 Promise.all ,但是这个函数有一个局限在于如果失败一次就返回了,直接这样实现会有点问题,需要变通下。以下是两种实现思路


// 以下是不完整代码,着重于思路 非 Promise 写法let successCount = 0let errorCount = 0let datas = []ajax(url, (res) => {     if (success) {         success++         if (success + errorCount === 10) {             console.log(datas)         } else {             datas.push(res.data)         }     } else {         errorCount++         if (errorCount > 3) {            // 失败次数大于3次就应该报错了             throw Error('失败三次')         }     }})// Promise 写法let errorCount = 0let p = new Promise((resolve, reject) => {    if (success) {         resolve(res.data)     } else {         errorCount++         if (errorCount > 3) {            // 失败次数大于3次就应该报错了            reject(error)         } else {             resolve(error)         }     }})Promise.all([p]).then(v => {  console.log(v);});
复制代码

Loader 和 Plugin 有什么区别

Loader:直译为"加载器"。Webpack 将一切文件视为模块,但是 webpack 原生是只能解析 js 文件,如果想将其他文件也打包的话,就会用到loader。 所以 Loader 的作用是让 webpack 拥有了加载和解析非 JavaScript 文件的能力。 Plugin:直译为"插件"。Plugin 可以扩展 webpack 的功能,让 webpack 具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。


参考:前端进阶面试题详细解答

数组扁平化

ES5 递归写法 —— isArray()、concat()

function flat11(arr) {    var res = [];    for (var i = 0; i < arr.length; i++) {        if (Array.isArray(arr[i])) {            res = res.concat(flat11(arr[i]));        } else {            res.push(arr[i]);        }    }    return res;}
复制代码


如果想实现第二个参数(指定“拉平”的层数),可以这样实现,后面的几种可以自己类似实现:


function flat(arr, level = 1) {    var res = [];    for(var i = 0; i < arr.length; i++) {        if(Array.isArray(arr[i]) || level >= 1) {            res = res.concat(flat(arr[i]), level - 1);        }        else {            res.push(arr[i]);        }    }    return res;}
复制代码

ES6 递归写法 — reduce()、concat()、isArray()

function flat(arr) {    return arr.reduce(        (pre, cur) => pre.concat(Array.isArray(cur) ? flat(cur) : cur), []    );}
复制代码

ES6 迭代写法 — 扩展运算符(...)、some()、concat()、isArray()

ES6 的扩展运算符(...) 只能扁平化一层


function flat(arr) {    return [].concat(...arr);}
复制代码


全部扁平化:遍历原数组,若arr中含有数组则使用一次扩展运算符,直至没有为止。


function flat(arr) {    while(arr.some(item => Array.isArray(item))) {        arr = [].concat(...arr);    }    return arr;}
复制代码

toString/join & split

调用数组的 toString()/join() 方法(它会自动扁平化处理),将数组变为字符串然后再用 split 分割还原为数组。由于 split 分割后形成的数组的每一项值为字符串,所以需要用一个map方法遍历数组将其每一项转换为数值型。


function flat(arr){    return arr.toString().split(',').map(item => Number(item));    // return arr.join().split(',').map(item => Number(item));}
复制代码

使用正则

JSON.stringify(arr).replace(/[|]/g, '') 会先将数组arr序列化为字符串,然后使用 replace() 方法将字符串中所有的[] 替换成空字符,从而达到扁平化处理,此时的结果为 arr 不包含 [] 的字符串。最后通过JSON.parse() 解析字符串。


function flat(arr) {    return JSON.parse("[" + JSON.stringify(arr).replace(/\[|\]/g,'') + "]");}
复制代码

类数组转化为数组

类数组是具有 length 属性,但不具有数组原型上的方法。常见的类数组有 arguments、DOM 操作方法返回的结果(如document.querySelectorAll('div'))等。

扩展运算符(...)

注意:扩展运算符只能作用于 iterable 对象,即拥有 Symbol(Symbol.iterator) 属性值。


let arr = [...arrayLike]
复制代码

Array.from()

let arr = Array.from(arrayLike);
复制代码

Array.prototype.slice.call()

let arr = Array.prototype.slice.call(arrayLike);
复制代码

Array.apply()

let arr = Array.apply(null, arrayLike);
复制代码

concat + apply

let arr = Array.prototype.concat.apply([], arrayLike);
复制代码

HTML5 有哪些更新

1. 语义化标签

  • header:定义文档的页眉(头部);

  • nav:定义导航链接的部分;

  • footer:定义文档或节的页脚(底部);

  • article:定义文章内容;

  • section:定义文档中的节(section、区段);

  • aside:定义其所处内容之外的内容(侧边);

2. 媒体标签

(1) audio:音频


<audio src='' controls autoplay loop='true'></audio>
复制代码


属性:


  • controls 控制面板

  • autoplay 自动播放

  • loop=‘true’ 循环播放


(2)video 视频


<video src='' poster='imgs/aa.jpg' controls></video>
复制代码


属性:


  • poster:指定视频还没有完全下载完毕,或者用户还没有点击播放前显示的封面。默认显示当前视频文件的第一针画面,当然通过 poster 也可以自己指定。

  • controls 控制面板

  • width

  • height


(3)source 标签因为浏览器对视频格式支持程度不一样,为了能够兼容不同的浏览器,可以通过 source 来指定视频源。


<video>     <source src='aa.flv' type='video/flv'></source>     <source src='aa.mp4' type='video/mp4'></source></video>
复制代码

3. 表单

表单类型:


  • email :能够验证当前输入的邮箱地址是否合法

  • url : 验证 URL

  • number : 只能输入数字,其他输入不了,而且自带上下增大减小箭头,max 属性可以设置为最大值,min 可以设置为最小值,value 为默认值。

  • search : 输入框后面会给提供一个小叉,可以删除输入的内容,更加人性化。

  • range : 可以提供给一个范围,其中可以设置 max 和 min 以及 value,其中 value 属性可以设置为默认值

  • color : 提供了一个颜色拾取器

  • time : 时分秒

  • data : 日期选择年月日

  • datatime : 时间和日期(目前只有 Safari 支持)

  • datatime-local :日期时间控件

  • week :周控件

  • month:月控件


表单属性:


  • placeholder :提示信息

  • autofocus :自动获取焦点

  • autocomplete=“on” 或者 autocomplete=“off” 使用这个属性需要有两个前提:

  • 表单必须提交过

  • 必须有 name 属性。

  • required:要求输入框不能为空,必须有值才能够提交。

  • pattern=" " 里面写入想要的正则模式,例如手机号 patte="^(+86)?\d{10}$"

  • multiple:可以选择多个文件或者多个邮箱

  • form=" form 表单的 ID"


表单事件:


  • oninput 每当 input 里的输入框内容发生变化都会触发此事件。

  • oninvalid 当验证不通过时触发此事件。

4. 进度条、度量器

  • progress 标签:用来表示任务的进度(IE、Safari 不支持),max 用来表示任务的进度,value 表示已完成多少

  • meter 属性:用来显示剩余容量或剩余库存(IE、Safari 不支持)

  • high/low:规定被视作高/低的范围

  • max/min:规定最大/小值

  • value:规定当前度量值


设置规则:min < low < high < max

5.DOM 查询操作

  • document.querySelector()

  • document.querySelectorAll()


它们选择的对象可以是标签,可以是类(需要加点),可以是 ID(需要加 #)

6. Web 存储

HTML5 提供了两种在客户端存储数据的新方法:


  • localStorage - 没有时间限制的数据存储

  • sessionStorage - 针对一个 session 的数据存储

7. 其他

  • 拖放:拖放是一种常见的特性,即抓取对象以后拖到另一个位置。设置元素可拖放:


<img draggable="true" />
复制代码


  • 画布(canvas ): canvas 元素使用 JavaScript 在网页上绘制图像。画布是一个矩形区域,可以控制其每一像素。canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。


<canvas id="myCanvas" width="200" height="100"></canvas>
复制代码


  • SVG:SVG 指可伸缩矢量图形,用于定义用于网络的基于矢量的图形,使用 XML 格式定义图形,图像在放大或改变尺寸的情况下其图形质量不会有损失,它是万维网联盟的标准

  • 地理定位:Geolocation(地理定位)用于定位用户的位置。‘


总结: (1)新增语义化标签:nav、header、footer、aside、section、article(2)音频、视频标签:audio、video(3)数据存储:localStorage、sessionStorage(4)canvas(画布)、Geolocation(地理定位)、websocket(通信协议)(5)input 标签新增属性:placeholder、autocomplete、autofocus、required(6)history API:go、forward、back、pushstate


移除的元素有:


  • 纯表现的元素:basefont,big,center,font, s,strike,tt,u;

  • 对可用性产生负面影响的元素:frame,frameset,noframes;

行内元素有哪些?块级元素有哪些? 空(void)元素有那些?

  • 行内元素有:a b span img input select strong

  • 块级元素有:div ul ol li dl dt dd h1 h2 h3 h4 h5 h6 p


空元素,即没有内容的 HTML 元素。空元素是在开始标签中关闭的,也就是空元素没有闭合标签:


  • 常见的有:<br><hr><img><input><link><meta>

  • 鲜见的有:<area><base><col><colgroup><command><embed><keygen><param><source><track><wbr>

用过 TypeScript 吗?它的作用是什么?

为 JS 添加类型支持,以及提供最新版的 ES 语法的支持,是的利于团队协作和排错,开发大型项目

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

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

代码输出结果

function runAsync(x) {  const p = new Promise(r =>    setTimeout(() => r(x, console.log(x)), 1000)  );  return p;}function runReject(x) {  const p = new Promise((res, rej) =>    setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)  );  return p;}Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])  .then(res => console.log("result: ", res))  .catch(err => console.log(err));
复制代码


输出结果如下:


0Error: 0123
复制代码


可以看到在 catch 捕获到第一个错误之后,后面的代码还不执行,不过不会再被捕获了。


注意:allrace传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被 then 的第二个参数或者后面的 catch 捕获;但并不会影响数组中其它的异步任务的执行。

知道 ES6 的 Class 嘛?Static 关键字有了解嘛

为这个类的函数对象直接添加方法,而不是加在这个函数对象的原型对象上

动态规划求解硬币找零问题

题目描述:给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1


示例1:输入: coins = [1, 2, 5], amount = 11输出: 3解释: 11 = 5 + 5 + 1
示例2:输入: coins = [2], amount = 3输出: -1
复制代码


实现代码如下:


const coinChange = function (coins, amount) {  // 用于保存每个目标总额对应的最小硬币个数  const f = [];  // 提前定义已知情况  f[0] = 0;  // 遍历 [1, amount] 这个区间的硬币总额  for (let i = 1; i <= amount; i++) {    // 求的是最小值,因此我们预设为无穷大,确保它一定会被更小的数更新    f[i] = Infinity;    // 循环遍历每个可用硬币的面额    for (let j = 0; j < coins.length; j++) {      // 若硬币面额小于目标总额,则问题成立      if (i - coins[j] >= 0) {        // 状态转移方程        f[i] = Math.min(f[i], f[i - coins[j]] + 1);      }    }  }  // 若目标总额对应的解为无穷大,则意味着没有一个符合条件的硬币总数来更新它,本题无解,返回-1  if (f[amount] === Infinity) {    return -1;  }  // 若有解,直接返回解的内容  return f[amount];};
复制代码

JS 隐式转换,显示转换

一般非基础类型进行转换时会先调用 valueOf,如果 valueOf 无法返回基本类型值,就会调用 toString


字符串和数字


  • "+" 操作符,如果有一个为字符串,那么都转化到字符串然后执行字符串拼接

  • "-" 操作符,转换为数字,相减 (-a, a * 1 a/1) 都能进行隐式强制类型转换


[] + {} 和 {} + []
复制代码


布尔值到数字


  • 1 + true = 2

  • 1 + false = 1


转换为布尔值


  • for 中第二个

  • while

  • if

  • 三元表达式

  • || (逻辑或) && (逻辑与)左边的操作数


符号


  • 不能被转换为数字

  • 能被转换为布尔值(都是 true)

  • 可以被转换成字符串 "Symbol(cool)"


宽松相等和严格相等


宽松相等允许进行强制类型转换,而严格相等不允许


字符串与数字


转换为数字然后比较


其他类型与布尔类型


  • 先把布尔类型转换为数字,然后继续进行比较


对象与非对象


  • 执行对象的 ToPrimitive(对象)然后继续进行比较


假值列表


  • undefined

  • null

  • false

  • +0, -0, NaN

  • ""

代码输出结果

function a() {  console.log(this);}a.call(null);
复制代码


打印结果:window 对象


根据 ECMAScript262 规范规定:如果第一个参数传入的对象调用者是 null 或者 undefined,call 方法将把全局对象(浏览器上是 window 对象)作为 this 的值。所以,不管传入 null 还是 undefined,其 this 都是全局对象 window。所以,在浏览器上答案是输出 window 对象。


要注意的是,在严格模式中,null 就是 null,undefined 就是 undefined:


'use strict';
function a() { console.log(this);}a.call(null); // nulla.call(undefined); // undefined
复制代码

说一下常见的 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下来

复制代码

代码输出结果

const promise = Promise.resolve().then(() => {  return promise;})promise.catch(console.err)
复制代码


输出结果如下:


Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
复制代码


这里其实是一个坑,.then.catch 返回的值不能是 promise 本身,否则会造成死循环。

事件流

事件流是网页元素接收事件的顺序,"DOM2 级事件"规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。首先发生的事件捕获,为截获事件提供机会。然后是实际的目标接受事件。最后一个阶段是时间冒泡阶段,可以在这个阶段对事件做出响应。虽然捕获阶段在规范中规定不允许响应事件,但是实际上还是会执行,所以有两次机会获取到目标对象。


<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>事件冒泡</title></head><body>    <div>        <p id="parEle">我是父元素    <span id="sonEle">我是子元素</span></p>    </div></body></html><script type="text/javascript">var sonEle = document.getElementById('sonEle');var parEle = document.getElementById('parEle');parEle.addEventListener('click', function () {    alert('父级 冒泡');}, false);parEle.addEventListener('click', function () {    alert('父级 捕获');}, true);sonEle.addEventListener('click', function () {    alert('子级冒泡');}, false);sonEle.addEventListener('click', function () {    alert('子级捕获');}, true);
</script>
复制代码


当容器元素及嵌套元素,即在捕获阶段又在冒泡阶段调用事件处理程序时:事件按 DOM 事件流的顺序执行事件处理程序:


  • 父级捕获

  • 子级捕获

  • 子级冒泡

  • 父级冒泡


且当事件处于目标阶段时,事件调用顺序决定于绑定事件的书写顺序,按上面的例子为,先调用冒泡阶段的事件处理程序,再调用捕获阶段的事件处理程序。依次 alert 出“子集冒泡”,“子集捕获”。


用户头像

loveX001

关注

还未添加个人签名 2022-09-01 加入

还未添加个人简介

评论

发布
暂无评论
20道高级前端面试题解析_JavaScript_loveX001_InfoQ写作社区