每天10个前端小知识 【Day 1】

news2024/11/20 2:34:11

在这里插入图片描述

前端面试基础知识题

1. 什么是尾调用优化和尾递归?

尾调用的概念非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。

function f(x){ 
    return g(x); 
}

上面代码中,函数f的最后一步是调用函数g,这就叫尾调用。

尾调用优化

尾调用之所以与其他调用不同,就在于它的特殊的调用位置。

我们知道,函数调用会在内存形成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用记录上方,还会形成一个B的调用记录。等到B运行结束,将结果返回到A,B的调用记录才会消失。如果函数B内部还调用函数C,那就还有一个C的调用记录栈,以此类推。所有的调用记录,就形成一个"调用栈"(call stack)。

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用记录,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用记录,取代外层函数的调用记录就可以了。

function f() {
    let m = 1; 
    let n = 2; 
    return g(m + n); 
} 
f(); 

// 等同于 
function f() { 
    return g(3); 
} 
f(); 

// 等同于 
g(3);

上面代码中,如果函数g不是尾调用,函数f就需要保存内部变量m和n的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除 f() 的调用记录,只保留 g(3) 的调用记录。

这就叫做"尾调用优化"(Tail call optimization),即只保留内层函数的调用记录。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。这就是"尾调用优化"的意义。

尾递归

函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误(stack overflow)。但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误。

function factorial(n) { 
    if (n === 1) return 1; 
    return n * factorial(n - 1); 
} 
factorial(5) // 120

上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。

function factorial(n, total) { 
    if (n === 1) return total; 
    return factorial(n - 1, n * total); 
} 
factorial(5, 1) // 120

“尾调用优化"对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6也是如此,第一次明确规定,所有 ECMAScript 的实现,都必须部署"尾调用优化”。这就是说,在 ES6 中,只要使用尾递归,就不会发生栈溢出,相对节省内存。

2. 堆与栈有什么区别?

堆(Heap)与栈(Stack)是开发人员必须面对的两个概念,在理解这两个概念时,需要放到具体的场景下,因为不同场景下,堆与栈代表不同的含义。一般情况下,有两层含义:

程序内存布局场景下,堆与栈表示两种内存管理方式;
数据结构场景下,堆与栈表示两种常用的数据结构。
程序内存分区中的堆与栈

栈简介

栈由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等,其操作方式类似于数据结构中的栈。

其中函数中定义的局部变量按照先后定义的顺序依次压入栈中,也就是说相邻变量的地址之间不会存在其它变量。栈的内存地址生长方向与堆相反,由高到底,所以后定义的变量地址低于先定义的变量,比如上面代码中变量 s 的地址小于变量 b 的地址,p2 地址小于 s 的地址。栈中存储的数据的生命周期随着函数的执行完成而结束。

堆简介

堆由开发人员分配和释放, 若开发人员不释放,程序结束时由 OS 回收,分配方式类似于链表。

堆的内存地址生长方向与栈相反,由低到高,但需要注意的是,后申请的内存空间并不一定在先申请的内存空间的后面,即 p2 指向的地址并不一定大于 p1 所指向的内存地址,原因是先申请的内存空间一旦被释放,后申请的内存空间则会利用先前被释放的内存,从而导致先后分配的内存空间在地址上不存在先后关系。堆中存储的数据若未释放,则其生命周期等同于程序的生命周期。

关于堆上内存空间的分配过程,首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确地释放本内存空间。由于找到的堆节点的大小不一定正好等于申请的大小,系统会自动地将多余的那部分重新放入空闲链表。

堆与栈区别

堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别:
(1)管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;
(2)空间大小不同。每个进程拥有的栈的大小要远远小于堆的大小。理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小 64bits 的 Windows 默认 1MB,64bits 的 Linux 默认 10MB;
(3)生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。
(4)分配方式不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由操作系统进行释放,无需我们手工实现。
(5)分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。
(6)存放内容不同。栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内容(EIP),然后是当前栈帧的底部地址,即扩展基址指针寄存器内容(EBP),再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是被调函数的局部变量,注意静态变量是存放在数据段或者BSS段,是不入栈的。出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。
从以上可以看到,堆和栈相比,由于大量malloc()/free()或new/delete的使用,容易造成大量的内存碎片,并且可能引发用户态和核心态的切换,效率较低。栈相比于堆,在程序中应用较为广泛,最常见的是函数的调用过程由栈来实现,函数返回地址、EBP、实参和局部变量都采用栈的方式存放。虽然栈有众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,主要还是用堆。

无论是堆还是栈,在内存使用时都要防止非法越界,越界导致的非法内存访问可能会摧毁程序的堆、栈数据,轻则导致程序运行处于不确定状态,获取不到预期结果,重则导致程序异常崩溃,这些都是我们编程时与内存打交道时应该注意的问题。

数据结构中的堆与栈

数据结构中,堆与栈是两个常见的数据结构,理解二者的定义、用法与区别,能够利用堆与栈解决很多实际问题。

栈简介

栈是一种运算受限的线性表,其限制是指只仅允许在表的一端进行插入和删除操作,这一端被称为栈顶(Top),相对地,把另一端称为栈底(Bottom)。把新元素放到栈顶元素的上面,使之成为新的栈顶元素称作进栈、入栈或压栈(Push);把栈顶元素删除,使其相邻的元素成为新的栈顶元素称作出栈或退栈(Pop)。这种受限的运算使栈拥有“先进后出”的特性(First In Last Out),简称FILO。

栈分顺序栈和链式栈两种。栈是一种线性结构,所以可以使用数组或链表(单向链表、双向链表或循环链表)作为底层数据结构。使用数组实现的栈叫做顺序栈,使用链表实现的栈叫做链式栈,二者的区别是顺序栈中的元素地址连续,链式栈中的元素地址不连续。

栈的基本操作包括初始化、判断栈是否为空、入栈、出栈以及获取栈顶元素等。

堆简介

堆是一种常用的树形结构,是一种特殊的完全二叉树,当且仅当满足所有节点的值总是不大于或不小于其父节点的值的完全二叉树被称之为堆。堆的这一特性称之为堆序性。因此,在一个堆中,根节点是最大(或最小)节点。如果根节点最小,称之为小顶堆(或小根堆),如果根节点最大,称之为大顶堆(或大根堆)。堆的左右孩子没有大小的顺序。

堆的存储一般都用数组来存储堆,i节点的父节点下标就为( i – 1 ) / 2 (i – 1) / 2(i–1)/2。它的左右子节点下标分别为 2 ∗ i + 1 2 * i + 12∗i+1 和 2 ∗ i + 2 2 * i + 22∗i+2。如第0个节点左右子节点下标分别为1和2。

3. JS代码中的use strict是什么意思?

use strict是一种ECMAscript5添加的(严格)运行模式,这种模式使得Javascript 在更严格的条件下运行。 设立"严格模式"的目的,主要有以下几个: 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;消除代码运行的一些不安全之处,保证代码运行的安全; 提高编译器效率,增加运行速度; 为未来新版本的Javascript 做好铺垫。

区别: 禁止使用with语句。 禁止this关键字指向全局对象。 对象不能有重名的属性。

4. 如何判断一个对象是不是空对象?

// 方法1 Object.keys(obj).length === 0
// 方法2 JSON.stringify(obj) === ‘{}’

5. [] == ![]结果是什么?

== 中,左右两边都需要转换为数字然后进行比较。 []转换为数字为0。 ![] 首先是转换为布尔值,由于[]作为一个引用类型转换为布尔值为true, 因此![]为false,进而在转换成数字,变为0。 0 == 0 , 结果为true

6. Instanceof能否判断基本数据类型?

能。比如下面这种方式:

class PrimitiveNumber {     
    static [Symbol.hasInstance](x) {         
        return typeof x === 'number'         
    }    
}     
console.log(111 instanceof PrimitiveNumber) // true

其实就是自定义instanceof行为的一种方式,这里将原有的instanceof方法重定义,换成了typeof,因此能够判断基本数据类型。

7. 0.1+0.2为什么不等于0.3?

0.1和0.2在转换成二进制后会无限循环,由于标准位数的限制后面多余的位数会被截掉,此时就已经出现了精度的损失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成 0.30000000000000004。

8. 如何判断一个元素是否在可视区域中?

判断一个元素是否在可视区域,我们常用的有三种办法:

offsetTop、scrollTop
getBoundingClientRect
Intersection Observer

具体描述请点击此链接

9. 什么是防抖和节流,以及如何编码实现?

本质上是优化高频率执行代码的一种手段

如:浏览器的 resize、scroll、keypress、mousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能

为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用throttle(节流)和debounce(防抖)的方式来减少调用频率

定义

节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时

一个经典的比喻:

想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应

假设电梯有两种运行策略 debounce 和 throttle,超时设定为15秒,不考虑容量限制

电梯第一个人进来后,15秒后准时运送一次,这是节流

电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖。

10. 说说 Javascript 为什么会存在数字精度丢失的问题,以及如何进行解决?

计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式,然后计算机以自己的规则{符号位+(指数位+指数偏移量的二进制)+小数部分}存储二进制的科学记数法

因为存储时有位数限制(64位),并且某些十进制的浮点数在转换为二进制数时会出现无限循环,会造成二进制的舍入操作(0舍1入),当再转换为十进制时就造成了计算误差

解决方法

使用 toPrecision 凑整并 parseFloat 转成数字后再显示:

function strip(num, precision = 12) { 
    return +parseFloat(num.toPrecision(precision)); 
}

对于运算类操作,如 ±*/,就不能使用 toPrecision 了。正确的做法是把小数转成整数后再运算。以加法为例:

/** 
* 精确加法 
*/ 
function add(num1, num2) { 
    const num1Digits = (num1.toString().split('.')[1] || '').length; 
    const num2Digits = (num2.toString().split('.')[1] || '').length; 
    const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits)); 
    return (num1 * baseNum + num2 * baseNum) / baseNum; 
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/191406.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Axios网络请求

哈喽~大家好,这篇来看看Axios网络请求。 ​文章推荐链接SpringCloud Sentinel 使用SpringCloud Sentinel 使用将Nacos注册到springboot使用以及Feign实现服务调用将Nacos注册到springboot使用以及Feign实现服务调用微服务介绍与 SpringCloud Eureka微服务介绍与 Sp…

Spark07: 宽窄依赖、Stage的划分

一、宽依赖和窄依赖 1. 窄依赖 窄依赖(Narrow Dependency):指父RDD的每个分区只被子RDD的一个分区所使用,例如map、filter等这些算子。 一个RDD,对它的父RDD只有简单的一对一的关系,也就是说,RDD的每个partition仅仅…

Python分支循环规范:if elif for while

分支与循环 条件是分支与循环中最为核心的点, 解决的问题场景是不同的问题有不同的处理逻辑。 当满足单个或者多个条件或者不满足条件进入分支和循环, 这里也就说明这个对相同问题处理执行逻辑依据具体参数动态变化, 由此产生多种可能性&…

GAMES101笔记:辐射度量学(下)

Irradiance 定义:irradiance是单位面积上的power,这个单位面积是和入射光线垂直的方向上的单位面积。如果受光表面不垂直于光线,需要投影到垂直方向上进行计算(cosθ\thetaθ)。 Irradiance Falloff 光的Intensity…

零入门容器云网络实战-8->veth pair设备介绍

在介绍veth pair之间,先看一下下面的图, 这类东西有没有见过? 如果没有见过,赶紧看看你方圆10米之内有没有? 这就是网线,最明显的特征是有两端!即,两个水晶头 一端可以链接普通的电脑&#…

【技术调研】关于仪表盘转图片推送钉钉的技术方案调研

方案1—纯后端实现 后端写定时任务,定时启动查询服务。查询出数据集结果,拼接成Table样式,再转换成图片。推送至钉钉。 优点:只需要后端开发,不涉及前端。 缺点:太定制化,不通用,样…

Dart语法学习-数据类型

一、Dart 语言对以下类型具有支持 Numbers (int, double) Strings (String) Booleans (bool) Lists (List, also known as arrays) Sets (Set) Maps (Map) Runes (Runes; often replaced by the characters API) Symbols (Symbol) The value null (Null) Dart 要求以 main 函数…

正大国际期货:什么是黄金保证金交易?黄金保证金交易包含哪些要点?

黄金保证金交易是指在黄金买卖业务中,市场参与者不需对所交易的黄金进行全额资金划拨,只需按照黄金交易总额支付一定比例的价款,作为黄金实物交收时的履约保证。黄金保证金交易根据类型不同,主要分为期货黄金保证金交易和现货黄金…

visual studio 调试增强,实现一行代码打印调用栈

如何主动打印调用栈?如果是Java、Js,那么很简单,三行就能实现。但 VisualStudio 就复杂多了。如果不下断点,那么只能在崩溃的时候被动查看。 而使用 Backward-Cpp ,只需在项目中拖入一个hpp文件,就可以主动…

[Android]ProgressBar进度条

ProgressBar ProgressBar是进度条控件,ProgressBar的应用场景很多,比如用户登录时,后台发送请求,以及进行等待服务器返回信息等一些比较耗时的操作。这个时候如果没有提示,用户可能会以为程序崩溃了或手机死机了&#…

【数据结构】1.2 数据结构的基本概念和术语

文章目录1. 数据、数据元素、数据项和数据对象2. 数据结构逻辑结构的种类存储结构的种类3. 数据类型和抽象数据类型数据类型抽象数据类型概念小结1. 数据、数据元素、数据项和数据对象 数据(Data) 能输入计算机且能被计算机处理的各种符号的集合。 信息…

AcWing1074. 二叉苹果树(树形DP +分组背包)

AcWing1074. 二叉苹果树(树形DP 分组背包)一、问题二、分析1、状态表示2、状态转移3、循环设计三、代码一、问题 二、分析 这道题是一个在数上做分组背包问题的模型,那么为什么是分组背包呢?作者在之前的文章中进行过详细地讲解&…

VUE2常用知识

1、Vue的基本原理 【】当一个Vue实例创建时,Vue会遍历data中的属性,用 Object.defineProperty(vue3.0使用proxy )将它们转为 getter/setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每个组…

工时管理:按工作时间还是完成的任务来跟踪员工的生产力?

据中国社科院的一项调查显示:我国有86%的职场人都患有拖延症;50%的人不到最后一刻绝不开始工作;13%的人没有人催不能完成工作。 拖延症对小型或成长型企业的影响是很大的,毕竟,任务永远不会因为逃避或简单地坐在那里而…

【Linux】信号保存、信号处理、可重入函数、volatile关键字、SIGCHLD信号

目录 一、信号保存 1.1 信号相关的概念名词 1.2 在内核中的表示 1.3 sigset_t与操作函数 1.4 信号设定 二、信号处理 2.1 内核空间与用户空间 2.2 内核态和用户态 2.3 信号的捕捉流程 2.4 sigaction 函数 三、可重入函数 四、volatile 五、SIGCHLD信号 一、信号保…

当今主流的网络服务应用

文件传输协议 主机之间传输文件是IP网络的一个重要功能,如今人们可以方便地使用网页、邮箱进行文件传输。 然而在互联网早期,Web(World Wide Web,万维网)还未出现,操作系统使用命令行的时代,…

webpack前端应用之基础打包

目录 前言:初识 Webpack 5 一、前端工程化 1、webpack ​ (2)主要功能: 2、webpack的使用:配置文件所需要的信息(五大配置属性) 3、示例 强调: 4、webpack中使用的loader 二…

【Java基础】003 -- Java基础概念(计算机的存储规则)

目录 计算机的存储规则 1、什么是二进制? 2、为什么计算机要使用二进制存储数据? 3、进制之间可以转换吗? 4、码表(Text文本) 5、图片数据 6、声音数据 计算机的存储规则 在计算机中,任意的数据都是…

java集成RSA非对称加密数据传输

使用场景: 前端请求后端接口时如:登录接口,这时候需要传账号密码到后端接口请求这样就会暴露请求的数据。RSA非对称加密分公钥和私钥,公钥将数据进行加密,私钥对加密的数据进行解密 (当然前端最好是封装一下不要暴露出来公钥) 代码实现: 1、RSA工具类(或访问http:…

大数据舆情监控应用平台,TOOM大数据舆情监控系统的作用

大数据舆情监控应用是利用大数据技术对社会舆情的收集、分析、挖掘和展示的工具。它通常会收集和分析各种社交媒体、新闻媒体、博客等信息,以了解舆情动态和趋势。大数据舆情监控应用可以帮助企业和政府了解市场和社会动态,为决策提供支持。然而&#xf…