前端常见20道高频面试题深入解析

基于 person 返回了一个新对象 -—— person2,新对象不仅具有 person 的所有属性和方法,而且还有自己的 sayHi() 方法。在考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。

缺点:

  • 使用寄生式继承来为对象添加函数,会由于不能做到函数复用而效率低下。

  • 同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。

6. 寄生组合式继承

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法,基本思路:

不必为了指定子类型的原型而调用超类型的构造函数,我们需要的仅是超类型原型的一个副本,本质上就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。寄生组合式继承的基本模式如下所示:

  • 第一步:创建超类型原型的一个副本

  • 第二步:为创建的副本添加 constructor 属性

  • 第三步:将新创建的对象赋值给子类型的原型

至此,我们就可以通过调用 inheritPrototype 来替换为子类型原型赋值的语句:

优点:

只调用了一次超类构造函数,效率更高。避免在 SuberType.prototype上面创建不必要的、多余的属性,与其同时,原型链还能保持不变。

因此寄生组合继承是引用类型最理性的继承范式。

10. 隐藏页面中的某个元素的方法有哪些?

隐藏类型

屏幕并不是唯一的输出机制,比如说屏幕上看不见的元素(隐藏的元素),其中一些依然能够被读屏软件阅读出来(因为读屏软件依赖于可访问性树来阐述)。为了消除它们之间的歧义,我们将其归为三大类:

  • 完全隐藏:元素从渲染树中消失,不占据空间。

  • 视觉上的隐藏:屏幕中不可见,占据空间。

  • 语义上的隐藏:读屏软件不可读,但正常占据空。

完全隐藏

1. display 属性

display: none;

2.hidden 属性

HTML5 新增属性,相当于 display:none

视觉上的隐藏

1.利用 position 和 盒模型 将元素移出可视区范围
  1. 设置 posoition 为 absolute 或 fixed,通过设置 top、 left 等值,将其移出可视区域。

position:absolute;

left: -99999px;

  1. 设置 position 为 relative,通过设置 top、 left 等值,将其移出可视区域。

position: relative;

left: -99999px;

height: 0

  1. 设置 margin 值,将其移出可视区域范围(可视区域占位)。

margin-left: -99999px;

height: 0;

2.利用 transfrom
  1. 缩放

transform: scale(0);

height: 0;

  1. 移动 translateXtranslateY

transform: translateX(-99999px);

height: 0

  1. 旋转 rotate

transform: rotateY(90deg);

3.设置其大小为0
  1. 宽高为0,字体大小为0:

height: 0;

width: 0;

font-size: 0;

  1. 宽高为0,超出隐藏:

height: 0;

width: 0;

overflow: hidden;

4.设置透明度为0

opacity: 0;

5. visibility属性

visibility: hidden;

6.层级覆盖, z-index 属性

position: relative;

z-index: -999;

再设置一个层级较高的元素覆盖在此元素上。

7.clip-path 裁剪

clip-path: polygon(0 0, 0 0, 0 0, 0 0);

语义上的隐藏

aria-hidden 属性

读屏软件不可读,占据空间,可见。

11. let、const、var 的区别有哪些?

1.let/const 定义的变量不会出现变量提升,而 var 定义的变量会提升。

2.相同作用域中,let 和 const 不允许重复声明,var 允许重复声明。

3.const 声明变量时必须设置初始值

4.const 声明一个只读的常量,这个常量不可改变。

这里有一个非常重要的点即是:在JS中,复杂数据类型,存储在栈中的是堆内存的地址,存在栈中的这个地址是不变的,但是存在堆中的值是可以变得。有没有相当常量指针/指针常量~

一图胜万言,如下图所示,不变的是栈内存中 a 存储的 20,和 b 中存储的 0x0012ff21(瞎编的一个数字)。而 {age: 18, star: 200} 是可变的。

12. 说一说你对JS执行上下文栈和作用域链的理解?

在开始说明JS上下文栈和作用域之前,我们先说明下JS上下文以及作用域的概念。

JS执行上下文

执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念, JavaScript 中运行任何的代码都是在执行上下文中运行。

执行上下文类型分为:

  • 全局执行上下文

  • 函数执行上下文

执行上下文创建过程中,需要做以下几件事:

  1. 创建变量对象:首先初始化函数的参数arguments,提升函数声明和变量声明。

  2. 创建作用域链(Scope Chain):在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。

  3. 确定this的值,即 ResolveThisBinding

作用域

作用域负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。—— 摘录自《你不知道的JavaScript》(上卷)

作用域有两种工作模型:词法作用域和动态作用域,JS采用的是词法作用域工作模型,词法作用域意味着作用域是由书写代码时变量和函数声明的位置决定的。( with 和 eval 能够修改词法作用域,但是不推荐使用,对此不做特别说明)

作用域分为:

  • 全局作用域

  • 函数作用域

  • 块级作用域

JS执行上下文栈(后面简称执行栈)

执行栈,也叫做调用栈,具有 LIFO (后进先出) 结构,用于存储在代码执行期间创建的所有执行上下文。

规则如下:

  • 首次运行JavaScript代码的时候,会创建一个全局执行的上下文并Push到当前的执行栈中,每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push当前执行栈的栈顶。

  • 当栈顶的函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文的控制权将移动到当前执行栈的下一个执行上下文。

以一段代码具体说明:

GlobalExecutionContext (即全局执行上下文)首先入栈,过程如下:

伪代码:

//全局执行上下文首先入栈

ECStack.push(globalContext);

//执行fun1();

ECStack.push( functionContext);

//fun1中又调用了fun2;

ECStack.push( functionContext);

//fun2中又调用了fun3;

ECStack.push( functionContext);

//fun3执行完毕

ECStack.pop();

//fun2执行完毕

ECStack.pop();

//fun1执行完毕

ECStack.pop();

//javascript继续顺序执行下面的代码,但ECStack底部始终有一个 全局上下文(globalContext);

作用域链

作用域链就是从当前作用域开始一层一层向上寻找某个变量,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链。

如:

fn2作用域链 = [fn2作用域, fn1作用域,全局作用域]

13. 防抖函数的作用是什么?请实现一个防抖函数

防抖函数的作用

防抖函数的作用就是控制函数在一定时间内的执行次数。防抖意味着N秒内函数只会被执行一次,如果N秒内再次被触发,则重新计算延迟时间。

举例说明: 小思最近在减肥,但是她非常吃吃零食。为此,与其男朋友约定好,如果10天不吃零食,就可以购买一个包(不要问为什么是包,因为包治百病)。但是如果中间吃了一次零食,那么就要重新计算时间,直到小思坚持10天没有吃零食,才能购买一个包。所以,管不住嘴的小思,没有机会买包(悲伤的故事)… 这就是 防抖

防抖函数实现

  1. 事件第一次触发时, timer 是 null,调用 later(),若 immediate 为 true,那么立即调用 func.apply(this,params);如果 immediate 为 false,那么过 wait 之后,调用 func.apply(this,params)

  2. 事件第二次触发时,如果 timer 已经重置为 null(即 setTimeout 的倒计时结束),那么流程与第一次触发时一样,若 timer 不为 null(即 setTimeout 的倒计时未结束),那么清空定时器,重新开始计时。

immediate 为 true 时,表示函数在每个等待时延的开始被调用。 immediate 为 false 时,表示函数在每个等待时延的结束被调用。

防抖的应用场景

  1. 搜索框输入查询,如果用户一直在输入中,没有必要不停地调用去请求服务端接口,等用户停止输入的时候,再调用,设置一个合适的时间间隔,有效减轻服务端压力。

  2. 表单验证

  3. 按钮提交事件。

  4. 浏览器窗口缩放,resize事件(如窗口停止改变大小之后重新计算布局)等。

14. 节流函数的作用是什么?有哪些应用场景,请实现一个节流函数

节流函数的作用

节流函数的作用是规定一个单位时间,在这个单位时间内最多只能触发一次函数执行,如果这个单位时间内多次触发函数,只能有一次生效。

节流函数实现

禁用第一次首先执行,传递 {leading:false} ;想禁用最后一次执行,传递 {trailing:false}

节流的应用场景

  1. 按钮点击事件

  2. 拖拽事件

  3. onScoll

  4. 计算鼠标移动的距离(mousemove)

15. 什么是闭包?闭包的作用是什么?

闭包的定义

《JavaScript高级程序设计》:

闭包是指有权访问另一个函数作用域中的变量的函数

《JavaScript权威指南》:

从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链。

《你不知道的JavaScript》

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

创建一个闭包

闭包使得函数可以继续访问定义时的词法作用域。拜 fn 所赐,在 foo() 执行后,foo 内部作用域不会被销毁。

闭包的作用
  • 能够访问函数定义时所在的词法作用域(阻止其被回收)。

  • 私有化变量

  • 模拟块级作用域

  • 创建模块

模块模式具有两个必备的条件(来自《你不知道的JavaScript》)

  • 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)

  • 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

16. 实现 Promise.all 方法

在实现 Promise.all 方法之前,我们首先要知道 Promise.all 的功能和特点,因为在清楚了 Promise.all 功能和特点的情况下,我们才能进一步去写实现。

Promise.all 功能

Promise.all(iterable) 返回一个新的 Promise 实例。此实例在 iterable 参数内所有的 promise 都 fulfilled 或者参数中不包含 promise 时,状态变成 fulfilled;如果参数中 promise 有一个失败 rejected,此实例回调失败,失败原因的是第一个失败 promise 的返回结果。

let p = Promise.all([p1, p2, p3]);

p的状态由 p1,p2,p3决定,分成以下;两种情况:

(1)只有p1、p2、p3的状态都变成 fulfilled,p的状态才会变成 fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被 rejected,p的状态就变成 rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

Promise.all 的特点

Promise.all 的返回值是一个 promise 实例

  • 如果传入的参数为空的可迭代对象, Promise.all 会 同步 返回一个已完成状态的 promise

  • 如果传入的参数中不包含任何 promise, Promise.all 会 异步 返回一个已完成状态的 promise

  • 其它情况下, Promise.all 返回一个 处理中(pending) 状态的 promise.

Promise.all 返回的 promise 的状态

  • 如果传入的参数中的 promise 都变成完成状态, Promise.all 返回的 promise 异步地变为完成。

  • 如果传入的参数中,有一个 promise 失败, Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成

  • 在任何情况下, Promise.all 返回的 promise 的完成状态的结果都是一个数组

Promise.all 实现

17. 请实现一个 flattenDeep 函数,把嵌套的数组扁平化

例如:

flattenDeep([1, [2, [3, [4]], 5]]); //[1, 2, 3, 4, 5]

利用 Array.prototype.flat

ES6 为数组实例新增了 flat 方法,用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数组没有影响。

flat 默认只会 “拉平” 一层,如果想要 “拉平” 多层的嵌套数组,需要给 flat 传递一个整数,表示想要拉平的层数。

当传递的整数大于数组嵌套的层数时,会将数组拉平为一维数组,JS能表示的最大数字为 Math.pow(2,53)-1,因此我们可以这样定义 flattenDeep 函数

利用 reduce 和 concat

使用 stack 无限反嵌套多层嵌套数组

18. 请实现一个 uniq 函数,实现数组去重

例如:

uniq([1, 2, 3, 5, 3, 2]);//[1, 2, 3, 5]

法1: 利用ES6新增数据类型 Set

Set类似于数组,但是成员的值都是唯一的,没有重复的值。

法2: 利用 indexOf

法3: 利用 includes

法4:利用 reduce

法5:利用 Map

19. 可迭代对象有哪些特点

ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,换个角度,也可以认为,一个数据结构只要具有 Symbol.iterator 属性( Symbol.iterator 方法对应的是遍历器生成函数,返回的是一个遍历器对象),那么就可以其认为是可迭代的。

可迭代对象的特点
  • 具有 Symbol.iterator 属性, Symbol.iterator() 返回的是一个遍历器对象

  • 可以使用 for...of 进行循环

  • 通过被 Array.from 转换为数组

原生具有 Iterator 接口的数据结构:
  • Array

  • Map

  • Set

  • String

  • TypedArray

  • 函数的 arguments 对象

  • NodeList 对象

20. JSONP 的原理是什么?

尽管浏览器有同源策略,但是 <script> 标签的 src 属性不会被同源策略所约束,可以获取任意服务器上的脚本并执行。 jsonp 通过插入 script 标签的方式来实现跨域,参数只能通过 url 传入,仅能支持 get 请求。

实现原理:

  • Step1: 创建 callback 方法

  • Step2: 插入 script 标签

  • Step3: 后台接受到请求,解析前端传过去的 callback 方法,返回该方法的调用,并且数据作为参数传入该方法

  • Step4: 前端执行服务端返回的方法调用

jsonp源码实现

使用:

服务端代码(node):

最后

技术是没有终点的,也是学不完的,最重要的是活着、不秃。零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。

jsonp源码实现

使用:

服务端代码(node):

最后

技术是没有终点的,也是学不完的,最重要的是活着、不秃。零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。

[外链图片转存中…(img-TSvYsSuF-1714571226105)]

  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值