前端最新面试题附解答(一)【10道】

前言

继上一篇文章内容,也是收到了很多大佬的反馈,今天整理了一下今年高频的前端面试题(一),希望对大家有所帮助,愿各位一面而就。

什么是闭包?

闭包 是JavaScript中一个非常重要的概念,它是指一个有权访问另一个函数作用域中的变量的函数。简单来说,闭包就是能够记住并访问其词法作用域(lexical scope)的函数,即使该函数在其词法作用域之外执行。

闭包有三个特性:

  • 函数嵌套函数。`
  • 内部函数可以引用外部函数的变量。`
  • 外部函数的变量生存周期延长,不会被垃圾回收机制回收。`

下面是一个简单的闭包例子

function outerFunction() {  
    var outerVariable = '前端开发屋';  
  
    // 返回一个内部函数  
    function innerFunction() {  
        console.log(outerVariable);  
    }  
  
    // 调用内部函数并返回其执行结果(虽然这里我们直接返回了函数本身)  
    return innerFunction;  
}  
  
// 将内部函数赋值给变量inner  
var inner = outerFunction();  
  
// 调用inner,此时它仍然可以访问outerFunction的变量outerVariable  
inner(); // 输出 '前端开发屋'

在这个例子中,outerFunction 是一个外部函数,它有一个局部变量 outerVariable 和一个内部函数 innerFunction。innerFunction 有权访问 outerVariable,因为它是在 outerFunction 的作用域内定义的。当 outerFunction 被调用时,它返回了 innerFunction 函数,而不是调用它。这意味着 innerFunction 函数现在被存储在变量 inner 中即使在 outerFunction 执行完毕后仍然可以访问 outerVariable。当我们调用 inner() 时,它会输出 outerVariable 的值,尽管 outerFunction 已经执行完毕。 这就是闭包的作用:它使得 outerVariable 的生命周期得以延长,并且可以在 outerFunction 外部被访问。

Css的选择器有哪些?优先级?哪些可以继承

CSS选择器是用于选择你想要样式化的HTML元素的模式。以下是一些主要的CSS选择器类型:

元素选择器:通过HTML元素来选择,例如 p,div,h1 等。
类选择器:使用.符号和类名来选择,例如 .myClass。
ID选择器:使用#符号和ID来选择,例如 #myID。每个ID在页面上应该是唯一的。
属性选择器:基于元素的属性和属性值来选择,例如 [type="text"]。
伪类选择器:用于选择元素的特定状态,例如 :hover,:active,:first-child 等。
伪元素选择器:用于选择元素的特定部分,例如 ::before,::after,::first-line 等。
组合选择器:你可以组合上述选择器来选择更具体的元素,例如 div.myClass 或 div#myID。

CSS选择器的优先级也称为特异性按照以下规则确定:

内联样式(在HTML元素内部,使用style属性)具有最高的优先级。
ID选择器的优先级高于类和属性选择器。
类选择器和属性选择器的优先级相同,但低于ID选择器。
元素选择器的优先级最低。
使用!important规则可以覆盖任何其他优先级规则
如果优先级相同,那么后出现的样式会覆盖先出现的样式。

请注意,过度使用!important可能会导致样式难以维护。

关于继承: CSS中的某些属性是可以继承的,这意味着如果父元素设置了这些属性,那么子元素会默认使用这些属性值,除非子元素自己明确设置了这些属性。以下是一些可以继承的属性:

文本相关属性:color,text-align,text-decoration,line-height,direction,letter-spacing,word-spacing,white-space,vertical-align等。
列表相关属性:list-style,list-style-image,list-style-position,list-style-type等。
表格相关属性:border-collapse,caption-side,empty-cells,table-layout等。
其他属性:cursor,visibility等。
然而,并非所有CSS属性都可以继承。例如,background,border,margin,padding,width,height等属性是不会被继承的。

在设计CSS时,了解哪些属性可以继承以及哪些不可以是非常重要的,这有助于更有效地控制元素的样式。

元素水平垂直居中的方法有哪些?如果元素不定宽高呢?

元素水平垂直居中的方法有多种,以下是几种常见的方法:

使用弹性盒(Flexbox):

  • 对于父元素,设置display: flex;,justify-content: center;(主轴居中)和align-items: center;(侧轴居中)。`
  • 仅对父元素设置display: flex;,然后在子元素上使用margin: auto;来实现水平和垂直居中。`

使用定位(Positioning):

  • 父元素设置position: relative;。 子元素设置position: absolute;,top: 50%;,left: 50%;,并通过transform: translate(-50%, -50%);来调整子元素的位置,使其中心点与父元素中心点对齐。`

使用绝对定位配合margin负值:

  • 这种方法要求子元素有明确的宽高。 子元素设置position: absolute;,top: 50%;,left: 50%;。 根据子元素的宽高,设置margin-top和margin-left为负的半个宽高值,以实现居中。`

使用CSS Grid:

  • 父元素设置display: grid;。 使用place-items: center;或分别设置justify-items: center;和align-items: center;来实现水平和垂直居中。`

如果元素不定宽高,可以考虑以下方法:

  • 使用弹性盒(Flexbox):弹性盒方法通常不依赖于子元素的宽高,因此适用于不定宽高的情况。`

  • 使用定位(Positioning)配合transform:即使子元素不定宽高,也可以使用position: absolute;,top: 50%;,left: 50%;,并结合transform: translate(-50%, -50%);来实现居中。因为transform是相对于元素自身的大小进行变换的,所以即使元素宽高不确定,也能正确居中。`

请注意,不同的方法可能适用于不同的布局和场景,因此在实际使用时需要根据具体情况选择合适的方法。同时,还需要确保兼容性问题,特别是在支持老旧浏览器的情况下。

怎么理解回流跟重绘?什么场景下会触发?

回流(Reflow)重绘(Repaint)是浏览器渲染页面时的两个关键概念,它们都与DOM(文档对象模型)的改变有关,但具体的影响和触发场景略有不同。

回流是指当页面的布局发生变化时,浏览器需要重新计算渲染树中的元素的位置和大小。这个过程涉及到遍历渲染树,计算每个元素的几何属性,然后更新布局。回流是一种非常消耗性能的操作,因为它会触发浏览器重新布局整个页面的过程。

而重绘则是指当页面的样式发生变化,但不影响其几何属性时,浏览器只需要重新绘制元素的外观,而不需要重新布局页面。这通常涉及到颜色、背景、边框等视觉属性的改变。相比于回流,重绘的性能开销较小。

回流可能在以下情况下触发:

DOM结构发生改变:比如添加新的节点或者移除节点。 布局发生改变:修改了元素的width、height、padding、font-size等属性。 窗口大小发生改变。 调用getComputedStyle方法获取元素的尺寸或位置信息。 内容发生变化:比如文本变化或图片被另一个不同尺寸的图片所代替。

重绘则可能在以下情况下触发:

修改网页内容的样式时,比如文字颜色、背景颜色、边框颜色、文本方向的修改等。

触发回流一定会触发重绘,因为回流会改变元素的布局,而布局的改变必然导致外观的重新绘制。

理解回流和重绘对于优化网页性能非常重要。通过减少回流和重绘的次数,合理使用CSS和JavaScript,可以提高页面的响应速度和用户体验。例如,避免设置多项内联样式,避免使用CSS的JavaScript表达式等,都是减少回流和重绘的有效手段。

说说你对事件循环的理解?

在JavaScript中,事件循环是一种核心机制,用于处理异步任务事件处理程序。这种机制的存在是为了解决单线程语言在执行异步操作时可能出现的阻塞问题。JavaScript引擎通过事件循环,能够在空闲时等待事件的到来,然后将事件添加到事件队列中,并不断地检查队列中是否有事件等待处理。

任务大致可以分为同步任务异步任务。同步任务会直接进入主线程执行,而异步任务则会被放入任务队列中等待。当主线程中的同步任务执行完毕后,事件循环会查看任务队列中是否有待处理的异步任务,如果有,则将其取出并放入主线程执行。这个过程会持续进行,形成一个循环,这就是所谓的事件循环

异步任务还可以进一步细分为微任务宏任务。微任务通常是一些需要异步执行但优先级较高的函数,如Promise的回调函数。而宏任务则包括一些优先级相对较低或执行时间可能较长的异步操作,如setTimeout、setInterval、ajax网络请求等。在事件循环中,每次主线程执行完毕一个宏任务后,会先查看并执行所有的微任务,然后再去取下一个宏任务执行

在JavaScript中如何实现继承?

原型链继承

function Parent() {  
    this.name = 'parent';  
}  
  
Parent.prototype.play = function() {  
    console.log(this.name + ' is playing');  
}  
  
function Child() {  
    this.type = 'child';  
}  
  
// 关键:将父类的实例作为子类的原型  
Child.prototype = new Parent();  
  
var child1 = new Child();  
console.log(child1.name); // parent  
child1.play(); // parent is playing

构造函数继承

注:这种方式有一个问题,就是所有子类的实例都会共享父类实例的属性,这可能会导致一些预期之外的结果
function Parent() {  
    this.name = 'parent';  
}  
  
function Child() {  
    Parent.call(this); // 继承Parent的属性  
    this.type = 'child';  
}  
  
var child1 = new Child();  
console.log(child1.name); // parent

组合继承

组合继承就是结合原型链继承和构造函数继承,从而既解决共享问题,又能继承父类的方法。
function Parent() {  
    this.name = 'parent';  
}  
  
Parent.prototype.play = function() {  
    console.log(this.name + ' is playing');  
}  
  
function Child() {  
    Parent.call(this); // 继承Parent的属性  
    this.type = 'child';  
}  
  
Child.prototype = new Parent(); // 继承Parent的方法  
Child.prototype.constructor = Child; // 修正构造函数的指向  
  
var child1 = new Child();  
console.log(child1.name); // parent  
child1.play(); // parent is playing

ES6的class继承

class Parent {  
    constructor() {  
        this.name = 'parent';  
    }  
    play() {  
        console.log(this.name + ' is playing');  
    }  
}  
  
class Child extends Parent {  
    constructor() {  
        super(); // 调用父类的constructor  
        this.type = 'child';  
    }  
}  
  
let child1 = new Child();  
console.log(child1.name); // parent  
child1.play(); // parent is playing

说说箭头函数和匿名函数的this指向问题?怎么改变this指向?

箭头函数

箭头函数不绑定自己的this,它会捕获其所在上下文的this值作为自己的this值。这意味着,无论箭头函数在哪里被调用,或者怎样被调用,它的this始终都是定义时所在的上下文中的this。

箭头函数不会创建自己的this上下文,因此你无法改变它的this指向。
function OuterFunction() {  
    this.value = '前端开发屋';  
    this.arrowFunc = () => {  
        console.log(this.value); // 输出 '前端开发屋',因为箭头函数的this指向OuterFunction的实例  
    };  
}  
  
const outer = new OuterFunction();  
outer.arrowFunc(); // 输出 '前端开发屋'

匿名函数

匿名函数与常规函数一样,其this指向取决于函数是如何被调用的。如果作为对象的方法被调用,this指向该对象;如果作为普通函数被调用,this通常指向全局对象(在浏览器中是window,在Node.js中是global);在严格模式下,this会是undefined。

function OuterFunction() {  
    this.value = '前端开发屋';  
    this.anonymousFunc = function() {  
        console.log(this.value); // 输出 '前端开发屋',因为匿名函数的this指向OuterFunction的实例  
    };  
}  
  
const outer = new OuterFunction();  
outer.anonymousFunc(); // 输出 '前端开发屋'

改变函数的this指向

1.call, apply, bind 方法:
这些方法可以用来调用一个函数,同时显式地指定this的值。
function MyFunction() {  
    console.log(this.value);  
}  

const obj = { value: '前端开发屋' };  

MyFunction.call(obj); // 输出 '前端开发屋',通过call改变this指向  
MyFunction.apply(obj); // 同上,apply通常用于传入数组作为参数  
const boundFunction = MyFunction.bind(obj);  
boundFunction(); // 输出 '前端开发屋'bind返回一个新的函数,其this已经被绑定


2.可以通过创建一个返回函数的函数(闭包),并在返回的函数中访问外部函数的this值。

function OuterFunction() {  
    this.value = '前端开发屋';  
    this.getFunc = function() {  
        return function() { // 返回一个匿名函数  
            console.log(this.value); // 这里的this实际上是指向OuterFunction的实例  
        }.bind(this); // 使用bind改变匿名函数的this指向  
    };  
}  

const outer = new OuterFunction();  
const func = outer.getFunc();  
func(); // 输出 '前端开发屋'

你知道HTTP与HTTPS有什么区别吗?

安全性:HTTP是超文本传输协议,信息以明文形式传输,不提供任何加密或身份验证机制。因此,HTTP在传输过程中存在数据泄露的风险。而HTTPS则是HTTP的安全版,它通过SSL/TLS协议进行加密处理,包括加密、解密、身份验证等功能,确保了数据的机密性和完整性,有效防止了数据在传输过程中被窃取篡改

端口:HTTP使用默认的80端口,而HTTPS则使用默认的443端口。

连接方式:HTTP的连接是无状态的,每次请求都需要建立新的连接。而HTTPS在HTTP的基础上加入了SSL层,除了进行三次握手外,还需要进行SSL握手,因此建立连接的过程相对复杂。

资源消耗:由于HTTPS需要进行加密和身份验证等操作,因此相比HTTP会消耗更多的服务器资源。

证书:HTTPS是需要申请证书的,通常需要付费购买,而HTTP则无需证书。

HTTPS相较于HTTP提供了更高的安全性,但也会带来一定的性能开销和成本增加。

说一下浏览器缓存?

浏览器缓存是一种提高网页加载速度性能的技术,它通过在客户端(即用户的浏览器)存储之前请求过的资源,使得在下次需要这些资源时,可以直接从本地获取,而无需再次从服务器请求。浏览器缓存主要分为强缓存协商缓存两种类型。

强缓存是指当浏览器再次请求某个资源时,首先会根据该资源的HTTP响应头中的某些字段(如Expires和Cache-Control)来判断该资源是否仍在缓存有效期内。如果资源仍在有效期内,则浏览器会直接使用缓存中的副本,而不会向服务器发送请求。这种缓存方式非常高效,因为它避免了网络传输的延迟和带宽的消耗

强缓存有一个问题,那就是它无法处理资源在服务器上的更新。如果资源在服务器上被修改,但由于缓存仍在有效期内,浏览器仍然会使用旧的缓存资源,这可能导致用户无法看到最新的内容

为了解决这个问题,引入了协商缓存。协商缓存是一种更为灵活的缓存策略,它允许浏览器在每次请求资源时都与服务器进行“协商”,以确定是否使用缓存资源。协商缓存的实现依赖于HTTP请求头中的某些字段(如If-Modified-SinceIf-None-Match)以及服务器响应头中的对应字段(如Last-ModifiedETag)。

当浏览器请求一个资源时,它会首先检查本地缓存中是否有该资源。如果有,并且资源的缓存状态是协商缓存,浏览器会发送一个带有If-Modified-Since和If-None-Match请求头的请求到服务器。服务器会根据这些请求头的值来检查资源自上次请求以来是否有所修改。

如果资源未修改,服务器会返回一个304 Not Modified的响应,告诉浏览器可以使用缓存中的资源。

如果资源已修改,服务器会返回新的资源内容,并更新响应头中的Last-Modified和ETag字段。这样,浏览器就会使用新的资源,并更新本地缓存。

协商缓存的好处在于它能够确保用户始终看到最新的内容,同时又能利用缓存来减少不必要的网络请求。然而,由于每次请求都需要与服务器进行通信,所以相比强缓存,协商缓存会带来一定的性能开销。

说一下浏览器中的垃圾回收机制?

浏览器中的垃圾回收机制是确保浏览器能够有效管理内存避免内存泄漏提高性能的关键部分。当代码运行时,它会分配内存空间存储变量和值。当这些变量或对象不再需要时,垃圾回收机制会负责释放它们占用的内存空间。

现代浏览器中通常使用两种主要的垃圾回收方法:标记清除引用计数

标记清除

当变量进入执行环境时,垃圾回收机制会将其标记为进入环境

当变量离开执行环境时,垃圾回收机制会将其标记为离开环境

在某个时刻,垃圾回收器会检查环境中的变量以及被环境变量引用的变量,过滤掉这些变量后,剩下的变量被视为准备回收的垃圾

然后,垃圾回收器会释放这些垃圾变量占用的内存空间。

这种方法的一个关键优势是它能够处理循环引用的情况,这是引用计数方法的一个主要问题。

目前,大多数现代浏览器如IE9+、Firefox、Opera、Chrome、Safari都使用标记清除或其类似的策略来进行垃圾回收,只不过垃圾收集的时间间隔可能互不相同。

引用计数

引用计数方法跟踪记录每个值被引用的次数。

每次当值被引用时,引用计数加一;当引用被释放时,引用计数减一。

当引用计数变为0时,说明该值不再被访问,因此可以将其内存空间回收

然而,由于存在循环引用的问题(即两个或多个对象相互引用,导致它们的引用计数永远不会变为0),引用计数方法在某些情况下可能导致内存泄漏。因此,尽管一些早期的浏览器(如Netscape Navigator 3)使用了这种方法,但现在大多数浏览器已经弃用了它

此外,全局变量局部变量的生命周期也是影响垃圾回收的重要因素

全局变量的生命周期会持续到页面卸载,而局部变量在函数执行结束后就会被销毁(除非它们被外部函数闭包引用)。因此,为了减少内存占用和提高性能,开发者应尽量减少全局变量的使用,并确保及时释放不再需要的资源。

最后

如果有什么问题,欢迎大家关注公众号:前端开发屋

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值