前端基础面试题集合

一、HTML部分

1、Cookies,SessionStorage,LocalStorage的区别。

SessionStorage,LocalStorage,Cookies三者都可以用来在浏览器端存储数据,而且都是字符串类型的键值对。区别在于前两者属于HTML5 WebStorage,创建它们的目的便于客户端存储数据。而cookies是网站为了标示用户身份而存储本地终端上的数据(通常经过加密)。cookie 数据始终在同源(协议、主机、端口相同)的 http 请求中携带(即使不需要),会在浏览器和服务器间来回传递。

存储空间:cookie数据大小不能超过4kb;而sessionStorage和localStorage虽然也有存储大小限制,但比cookies大得多,可以达到5M或更多。
生命周期:localStorage存储持久数据,浏览器关闭后数据不丢失,除非手动清除;sessionStorage只能在会话期间保持数据,即在浏览器关闭或标签页关闭后数据将被清除;cookies在设置的过期时间之前一直有效,即使窗口或浏览器关闭。
作用域:sessionStorage只能在同源的同一窗口(标签页)中共享数据,也就是只在当前会话中共享;localStorage和cookies在所有同源窗口都是共享的。
数据传输:Cookie在每次请求时都会发送到服务器端,而sessionStorage和LocalStorage只保存在客户端,不会随着请求发送到服务器。

 2、<script>、<script asyns>和<script defer>的区别。

  • <script>:是同步加载,会阻塞HTML解析,并立即执行JavaScript代码;
  • <script async>:是异步加载,不阻塞页面渲染,这种情况下浏览器会继续解析 HTML,同时异步加载 JavaScript 文件。一旦 JavaScript 文件下载完成,浏览器会立即执行它。多个异步加载的 JavaScript 文件之间的执行顺序不确定。
  • <script defer>:表示异步(延迟)加载,浏览器会异步加载 JavaScript 文件,但是在 HTML 解析完成之后按顺序执行 JavaScript 文件。注意会在 DOMContentLoaded 事件之前执行。

二、CSS部分

1、"resetting"和"normalizing"之间的区别?

“Resetting” CSS 是一种将所有浏览器的默认样式归零的方法,目标是提供一个干净的起点,从而减少默认样式对你的样式选择造成的影响;“Normalizing” CSS 是通过在浏览器之间提供一致的基准样式来创建一个更加一致的样式。它会修复浏览器之间的默认样式差异,使得不同浏览器在渲染网页时有更一致的效果。“Normalizing” CSS 通常会保留某些默认样式,以保证一致性和可访问性。

2、请描述 BFC(Block Formatting Context) 及其如何工作。

BFC(Block Formatting Context,块级格式化上下文)是用于控制页面布局的一种 CSS 渲染模型。每个元素都属于一个 BFC,并且 BFC 决定了它内部元素的排版规则。

BFC 的形成有以下几种方式:

根元素(<html>)
设置为 float 为 left 或 right 的元素
设置为 position 为 absolute 或 fixed 的元素
设置为 display 为 inline-block 或 table-cell 的元素
设置为 overflow 为除 visible 以外的值的元素(如 hidden、auto、scroll)

BFC 的工作原理及其影响包括:

浮动元素的父元素是一个 BFC,会形成一个新的 BFC,浮动元素会参与 BFC 的排版。
BFC 内部的块级元素在垂直方向上按照其在 DOM 中的顺序依次放置,保持各个元素的垂直间距,不会发生重叠。
BFC 内部的块级元素的左边边缘将与包含块(BFC 的父元素或是更外层的 BFC)的左边边缘产生边距折叠。
BFC 可以包含浮动元素并且阻止它们溢出父元素。
BFC 在渲染过程中不会被浮动元素覆盖,可以让包含它的元素能够正确计算高度。

通过创建 BFC,我们可以解决一些常见的布局问题,如清除浮动、避免边距折叠等。

3、请解释 CSS sprites,以及你要如何在页面或网站中实现它? 

CSS Sprites是一种将多个小图标、背景图片或其他图像合并成一张大图的技术。通过此技术,可以减少请求服务器的次数,提高网页加载速度和性能。

实现CSS Sprites的基本步骤如下:

  1. 创建一张包含所有小图标或背景图片的大图: 将多个小图标或背景图片合并到一张大图中,尽量紧密排列以减少空白区域。通常使用图像编辑软件(如Photoshop)来完成这一步骤。
  2. 定义CSS样式: 在CSS文件中,为需要使用的小图标或背景图片创建样式规则,并设置属性:​background-image:将该属性设置为指向大图的URL。width 和 height:设置合适的宽度和高度,以展示需要的小图标或背景图片。background-position:根据大图中每个小图标或背景图片在坐标系中的位置,设置正确的背景位置。
  3. 在网页中应用样式: 将相应的CSS类或选择器应用于需要显示小图标或背景图片的HTML元素。可以使用background 或者 background-image 属性来引用大图。

通过这种方式,在整个网页加载过程中,只需请求一次大图,然后通过才CSS样式将所需的图像片段显示在正确的位置上。这样可以减少HTTP请求的次数,提高性能。

需要注意的是,CSS Sprites适用于小图标或背景图片,且这些图像片段在整个页面中需要重复使用,这样才能充分发挥其优势。对于大尺寸图像或不经常使用的图像,不适合使用CSS Sprites技术。此外,随着新的CSS和前端技术的发展,也出现了更便捷的图像优化和加载方式,如WebP格式、lazy loading等,可以进一步提升网页性能。

4、如何为有功能限制的浏览器提供网页?又有哪些技术和处理方法?

1. 渐进增强(Progressive Enhancement):渐进增强是一种设计理念,即首先为支持较少功能的浏览器提供基本的功能和内容,并逐步增加更复杂的功能和样式。这样可以确保在功能受限的浏览器中仍能正常展示网页内容。

2. 优雅降级(Graceful Degradation):优雅降级是相对于渐进增强的概念,指在设计时首先考虑完整功能的实现,然后再向不支持某些功能的浏览器提供备选方案。当浏览器无法支持某些功能时,会回退到备选方案以保证网站的可用性。

3. 基础HTML和CSS :使用基础的HTML和CSS语法来构建网页,避免过于依赖新的特性和API。确保网页在不支持高级CSS选择器或布局的浏览器中也能正常显示。

4. 平稳退化(Graceful Fallback):在使用一些新的Web技术或API时,提供替代方案以防止在不支持该技术或API的浏览器中出现问题。例如,使用JavaScript检测浏览器功能并提供替代的解决方案。

5. 浏览器嗅探(Browser Detection)和特性检测(Feature Detection):可以使用浏览器嗅探技术来检测用户所使用的浏览器,并根据浏览器类型和版本提供相应的备选方案。另外,特性检测可以通过JavaScript检测浏览器是否支持某些特定的API或功能,并根据结果来确定如何处理。

6. 兼容性库和Polyfill :使用兼容性库(Compatibility Libraries)和Polyfill来填补不同浏览器之间的功能差异。兼容性库提供了一些常用功能的封装和解决方案,而Polyfill则是指通过JavaScript模拟新的Web标准API来弥补旧浏览器的功能限制。

总结起来,为有功能限制的浏览器提供网页需要采用渐进增强或优雅降级的策略,使用基础的HTML和CSS语法,提供平稳退化和浏览器嗅探/特性检测,同时考虑使用兼容性库和Polyfill来填补功能差异。这样可以确保网页在不同浏览器上都能正常展示和使用。

5、你用过栅格系统 (grid system) 吗?如果使用过,你最喜欢哪种?

Bootstrap栅格系统:
    Bootstrap是一个广泛使用的前端框架,其栅格系统提供了一种灵活且易于使用的方式来构建响应
式网页布局。它基于12栏网格,可以根据屏幕大小自动调整列的大小和排列顺序。我喜欢Bootstrap栅
格系统的原因是它具有广泛的文档和支持,并且容易学习和使用。

CSS Grid布局:
   CSS Grid布局是CSS中的一个功能强大的模块,旨在以网格形式进行页面布局。与Bootstrap栅格系
统相比,CSS Grid提供更多的布局选项和灵活性。它允许开发人员按照指定的列和行来定义布局,并支
持不同的对齐方式。我喜欢CSS Grid布局的原因是它提供了更精确的控制,使得创建复杂布局变得更加
简单和直观。

总的来说,我不能说我更喜欢哪种栅格系统,因为选择取决于项目需求和个人喜好。Bootstrap栅格系统适合快速搭建响应式网页,而CSS Grid布局适用于更复杂的布局需求。无论选择哪种栅格系统,了解其特点和使用方法都非常有助于开发出具有良好布局结构的网页。

6、SVG 样式的书写规则。

SVG是一种基于XML的矢量图形格式,可以用于定义各种图形、图像和动画效果,并且它可以无损地缩放到任意尺寸而不失真。要为SVG元素应用样式,可以使用以下几种方式:

1. 内联样式:直接在SVG元素的`style`属性中定义CSS样式规则。
   示例:
   <svg>
     <rect x="50" y="50" width="200" height="100" style="fill: blue; stroke: red;"/>
   </svg>

2. <style>元素:在SVG文件或HTML文档的`<style>`标签内定义CSS样式规则,类似于HTML中的样式表。
   示例:
   <svg>
     <style>
       <![CDATA[
         rect {
           fill: blue;
           stroke: red;
         }
       ]]>
     </style>
     <rect x="50" y="50" width="200" height="100"/>
   </svg>

3. CSS文件:将CSS样式规则写入外部CSS文件,并通过`<link>`标签引入到SVG文件或HTML文档中。
   示例:
   <svg>
     <link href="styles.css" rel="stylesheet" type="text/css"/>
     <rect x="50" y="50" width="200" height="100"/>
   </svg>

无论使用哪种方式,都可以在SVG中应用各种CSS样式属性,如填充颜色(fill)、描边颜色(stroke)、边框宽度(stroke-width)、字体样式等。需要注意的是,有些SVG属性与CSS属性名称相同,但它们可能具有不同的含义或值范围。在编写SVG样式时,请参考SVG规范以确保正确使用和理解各个属性。

7、如何优化网页的打印样式? 

1. 使用媒体查询:通过CSS的`@media print`媒体查询,针对打印设备设置特定的样式规则。可以隐藏不必要的元素、调整字体大小和间距,并进行布局的调整,以适应打印页面的尺寸和形式。
   示例:
   @media print {
     /* 隐藏不必要的元素 */
     .no-print {
       display: none;
     }
   
     /* 调整字体大小和间距 */
     body {
       font-size: 12pt;
       line-height: 1.5;
     }
   
     /* 调整布局 */
     .header, .footer {
       display: none;
     }
     .content {
       margin-top: 0;
     }
   }

2. 移除背景和边框:通过设置元素的背景和边框为透明或隐藏,可以使打印页面更清晰,避免浪费墨水。
   示例:
   @media print {
     /* 移除背景和边框 */
     body {
       background: none;
       border: none;
     }
   
     /* 或者针对特定元素移除背景和边框 */
     .no-background {
       background: none !important;
       border: none !important;
     }
   }

3. 调整图像尺寸和位置:根据需要,可以通过CSS设置图像的最大宽度、固定高度或将其居中显示,以确保打印页面中的图像按照预期进行布局。
   示例:
   @media print {
     /* 调整图像尺寸和位置 */
     img {
       max-width: 100%;
       height: auto;
       margin: 0 auto;
     }
   }

4. 隐藏不必要的内容:通过为不需要在打印页面上显示的元素添加类名或ID,并使用CSS样式隐藏这些元素,可以简化打印页面的内容,并减少打印用纸量。
   示例:
   @media print {
     /* 隐藏不必要的内容 */
     .no-print {
       display: none;
     }
   }

8、使用 CSS 预处理器的优缺点有哪些?

CSS预处理器是一种将类似于编程语言的特性引入到CSS中的工具,常见的CSS预处理器包括Sass、Less和Stylus。以下是CSS预处理器的优缺点:

优点:
1. 变量和计算:CSS预处理器允许定义变量,可以在样式中多次使用同一个值,提高了代码的重用性和可维护性。还支持各种数学运算,减少了样式计算的复杂性。
2. 嵌套规则:通过嵌套规则,可以更清晰地表示出DOM元素之间的层级关系,减少了选择器的书写量,并使样式的结构更加清晰。
3. Mixins(混合):Mixins允许将一组样式属性封装成可复用的代码块,并在需要时进行调用。这样可以减少重复的样式定义,提高代码的可维护性和扩展性。
4. 导入和模块化:CSS预处理器支持导入其他样式文件,使得代码的组织更加灵活,可以将样式按功能或模块进行分割和管理,便于团队协作。
5. 编译和压缩:CSS预处理器将预处理器语言编译为原生CSS,可以通过编译过程对样式进行优化和压缩,减少文件大小和网络传输时间。

缺点:
1. 学习曲线:使用CSS预处理器需要学习额外的语法和概念,对于初学者来说可能需要一定时间去适应和掌握。
2. 预处理步骤:为了使用预处理器,需要将预处理器语言编译为浏览器可解析的CSS文件。这个编译过程需要一些额外的工具和步骤,增加了开发流程的复杂性。
3. 开发环境配置:在开始项目之前,需要配置好相应的开发环境,确保编辑器和构建工具能够正确地编译和处理预处理器语言。
4. 依赖管理:使用CSS预处理器可能导致在项目中引入额外的依赖,需要合理管理和更新这些依赖,以避免版本冲突或安全问题。

9、请解释浏览器是如何判断元素是否匹配某个 CSS 选择器?

浏览器在确定一个元素是否匹配某个CSS选择器时,会按照以下步骤进行判断:
1. 解析选择器:浏览器首先会解析CSS选择器,将其分解为不同的组成部分,包括标签名、类名、ID等。
2. 从DOM树中找到匹配元素:浏览器会从DOM树种找到与选择器中指定的标签名匹配的元素。这可以通过遍历DOM树的方式来实现。
3. 检查选择器中的其他条件:对于匹配到的元素,浏览器会依次检查选择器中的其他条件,如类名、ID、属性选择器等,以确认元素是否满足这些条件。
4. 确定匹配结果:如果所有的选择器条件都满足,则该元素被认为是匹配的。

需要注意的是,浏览器在选择器匹配过程中通常会采用优化策略,比如从右向左逐步匹配,以提高选择器匹配的效率。
另外,选择器的复杂性和具体情况也会影响匹配结果。一些复杂的选择器可能会导致性能问题,因此在编写CSS选择器时,应尽量使用简洁且高效的选择器,以提升页面加载和渲染性能。 

10、请描述伪元素 (pseudo-elements) 及其用途。

伪元素(pseudo-elements)是CSS的一种特殊选择器,用于向选中的元素添加额外的样式和内容。它们可以在文档树中的元素的某个部分上应用样式,并且不需要实际存在对应的HTML元素。

伪元素由双冒号"::"表示,但为了保持向后兼容性,单冒号“:”也被接受。以下是常用的伪元素:

::before:用于在选中元素的内容前面插入新的内容。通常与content属性一起使用,用于添加装饰或图标等。
::after:与::before类似,用于在选中元素的内容后面插入新的内容。也常与content属性搭配使用,用于添加额外的样式或修饰。

伪元素的使用范围包括文本、块级元素、行内元素等各种类型的元素。通过为伪元素定义样式,可以实现一些常见的效果,例如:

添加装饰元素:可以使用伪元素在元素的前后插入装饰性的元素,如箭头、图标、线条等。
清除浮动:可以通过清除浮动的技巧,在内容区域之后插入一个空的块级元素来解决容器无法自适应浮动元素高度的问题。
创建网格布局:可以使用伪元素和CSS的排版功能,创建灵活的网格布局效果。
自定义列表样式:可以通过::before伪元素为列表项添加自定义的符号,替代默认的列表标记。

11、请罗列出你所知道的 display 属性的全部值。

block:将元素显示为块级元素,会独占一行并默认具有宽度和高度。
inline:将元素显示为内联元素,不会独占一行,只占据其内容的空间。
inline-block:结合了inline和block的特性,既可以设置宽高,又可以在同一行内显示。
none:将元素完全隐藏,不占据任何空间。
flex:使用弹性布局,将元素作为弹性容器,可以通过子元素的排列方式实现灵活的布局效果。
grid:使用网格布局,将元素作为网格容器,以网格行和列的形式进行布局。
table:将元素显示为表格,类似于使用<table>元素创建表格。
table-cell:将元素显示为表格单元格,只能在表格相关的上下文中使用。
table-row:将元素显示为表格行,只能在表格相关的上下文中使用。
table-header-group:将元素显示为表格标题组,只能在表格相关的上下文中使用。
table-footer-group:将元素显示为表格脚注组,只能在表格相关的上下文中使用。
list-item:将元素显示为列表项,类似于使用<li>元素创建列表。

12、为什么响应式设计 (responsive design) 和自适应设计 (adaptive design) 不同?

响应式设计和自适应设计是两种不同的网页设计方法,尽管它们都旨在使网站在不同设备和屏幕尺寸下具有良好的用户体验,但它们的实现方式和原理不同。

响应式设计(Responsive Design)是一种通过使用CSS媒体查询和弹性布局来自动调整和适应不同设备和屏幕尺寸的网页设计方法。具体来说,当页面加载到不同的设备或屏幕尺寸上时,响应式设计会根据视口宽度进行动态排列、布局和缩放元素。这意味着页面的内容和布局可以根据设备的大小和方向进行自动调整,以提供最佳的用户体验。

自适应设计(Adaptive Design)则是指针对特定的设备和屏幕尺寸创建不同版本的网站或应用程序。与响应式设计相比,自适应设计更加静态,设计师需要为不同的目标设备创建固定的布局和样式。根据用户的设备类型,服务器将决定并传送适合该设备的特定版本。

主要区别在于:

  1. 实现方式:响应式设计使用CSS媒体查询和弹性布局来实现灵活的布局和内容调整,而自适应设计则通过预定义的固定布局和样式来适应特定的设备和屏幕尺寸。
  2. 灵活性:响应式设计可以根据浏览器窗口的大小实时调整页面布局和内容,并适应各种不同的设备,提供一致而灵活的用户体验。自适应设计则需要为每个目标设备创建单独的版本,因此在新设备出现或者设备尺寸变化时可能需要手动更新适配。
  3. 开发成本:响应式设计可以减少开发和维护多个版本的工作量,因为只需维护一个代码库。相比之下,自适应设计需要创建和更新多个版本的网站或应用程序,增加了开发成本和工作量。

三、JS部分

1、 请解释事件代理 (event delegation)。

事件代理(Event Delegation)是一种在JavaScript中处理事件的技术。它通过将事件处理程序绑定到一个父容器上,而不是直接绑定到每个子元素,来管理和处理子元素的事件。

工作原理如下:

1. 将事件处理程序添加到父容器(通常是一个公共的祖先元素)上。
2. 当事件在子元素上触发时,事件会冒泡到父容器。
3. 父容器上的事件处理程序检查事件目标,即确定哪个子元素触发了事件。
4. 根据目标子元素的标识符或其他属性执行适当的操作。

使用事件代理的主要优势包括:

1. 减少内存消耗:只需一个事件处理程序而不是多个,避免重复的事件处理函数创建。
2. 更高的性能:只需绑定一个事件处理程序,减少浏览器需要管理的事件监听器数量。
3. 简化代码:无需为每个子元素单独添加事件处理程序,减少代码冗余。
4. 动态绑定:新添加的子元素也能自动继承事件处理,不需要重新绑定。

需注意的是,在使用事件代理时,应确保事件处理程序针对目标子元素进行正确的操作,并避免冒泡到其他不需要处理的元素上。适当地使用事件委托可以提高代码的可读性和可维护性,同时优化性能。

2、请解释 JavaScript 中 this 是如何工作的。

JavaScript中的this关键字是一个特殊的对象,它在函数被调用时动态地绑定到调用该函数的上下文。this的值取决于函数的调用方式和上下文。

以下是this的工作原理:

全局上下文:当在全局范围内使用this时,它指向全局对象(在浏览器中是window对象,在Node.js环境中是global对象),因为全局对象是默认的函数调用上下文。
函数调用:当函数作为独立函数调用时,this指向全局对象或undefined(在严格模式下)。这是因为当函数不作为对象的方法调用时,它们没有特定的所有者。
对象方法调用:当一个函数作为对象的方法调用时,this指向调用该方法的对象。this表示对包含该方法的对象的引用,从而可以在方法内部访问对象的属性和方法。
构造函数:当使用new关键字创建一个实例时,构造函数内部的this指向正在创建的新对象。构造函数中的this被绑定到新对象,以便您可以设置新对象的属性和方法。
显式绑定:通过使用call()、apply()、bind()等函数,可以显式地将this绑定到指定的对象,使函数在指定对象的上下文中执行。

箭头函数:箭头函数没有自己的this绑定,而是继承了外部作用域的this。箭头函数中的this引用的是定义时所在的上下文。

事件处理程序:当一个函数作为事件处理程序调用时,this通常指向触发该事件的元素。

3、请解释原型继承 (prototypal inheritance) 的原理。

原型继承(prototypal inheritance)是JavaScript中实现对象之间继承关系的一种机制。它利用对象的原型链来实现属性和方法的继承。在JavaScript中,每个对象都有一个指向其原型的链接,即__proto__属性(ES6之前的写法)。当我们访问一个对象的属性或方法时,如果该对象本身没有定义该属性或方法,JavaScript会自动查找该对象的原型链上的对象,直到找到相应的属性或方法或者到达原型链的顶端(也就是Object.prototype)为止。

原型继承的过程如下:
1. 每个函数都有一个默认的prototype属性,它指向一个普通对象。
2. 当通过构造函数创建一个新对象时,新对象的内部会有一个__proto__属性,指向构造函数的prototype属性所指向的对象。
3. 如果我们在构造函数的prototype对象上定义了属性和方法,那么通过该构造函数创建的所有对象都会共享这些属性和方法。
4. 当我们在访问对象的属性或方法时,如果对象本身没有定义,JavaScript会沿着对象的__proto__(即构造函数的prototype)链继续查找,直到找到相应的属性或方法或到达原型链的顶端。

这种基于原型的继承使得我们可以创建具有共享属性和方法的对象,而不必为每个对象单独定义它们。通过在原型上定义属性和方法,我们可以节省内存并提高代码的可维护性。

在ES6引入了class关键字来实现面向对象编程,但其背后仍然使用了原型继承的机制。class语法只是对原型继承的一种更友好的包装,使其看起来更像传统的类继承语法。需要注意的是,在操作原型链时需要谨慎,避免出现意外的副作用。

4、你怎么看 AMD vs. CommonJS?

AMD(Asynchronous Module Definition)和CommonJS是两种用于在JavaScript中组织模块的不同标准。

AMD是由RequireJS提出并实现的规范,旨在解决浏览器端异步加载模块的问题。它采用了声明式的定义方式,允许开发者使用define函数来定义模块,并使用require函数来异步加载依赖的模块。这使得开发者能够将代码按需加载,并在需要时以非阻塞的方式加载模块,提高了页面加载性能和用户体验。AMD主要应用于浏览器环境,例如前端开发。

CommonJS是一套用于服务器端JavaScript的模块规范,最初是为Node.js而设计的。它采用了同步的模块加载方式,通过require函数来引入模块,并使用module.exports来导出模块。CommonJS适用于服务器端应用程序开发,因为在服务器环境下,文件系统通常具有较快的读取速度,可以容忍同步加载模块的性能开销。

对于AMD vs. CommonJS的比较,可以从以下几个方面考虑:

环境适应性:AMD主要用于浏览器环境,CommonJS用于服务器环境。根据项目的需求选择适合的规范。
加载方式:AMD采用异步加载方式,适合于浏览器中按需加载模块。CommonJS采用同步加载方式,适合于服务器端应用。
语法风格:AMD使用define和require函数定义和引用模块,语法较为冗长。CommonJS采用require和module.exports来引用和导出模块,语法简洁明了。
生态系统支持:AMD有RequireJS等优秀的库支持,广泛应用于前端开发领域。CommonJS则在Node.js生态系统中得到广泛应用,并且NPM作为其包管理工具也极大地推动了模块化开发。

综上所述,选择AMD还是CommonJS取决于项目的环境和需求。如果是在浏览器环境下进行前端开发,可以考虑使用AMD。如果是在服务器端进行应用程序开发,可以选择使用CommonJS规范。

5、描述以下变量:null,undefined 或 undeclared? 该如何检测它们?

1. null表示一个被赋予空值的变量。它是一个特殊的关键字,表示一个没有对象值的指针。当你想要清空一个对象或者表示变量无效时,可以将其赋值为null。例如:

let foo = null;
console.log(foo); // 输出:null

2. undefined表示变量已经声明,但尚未被赋予一个值。如果你声明了一个变量但没有给它赋值,那么它的默认值就是undefined。此外,在函数中没有返回值的情况下,默认返回undefined。例如:

let bar;
console.log(bar); // 输出:undefined

function baz() {
  // 没有返回值
}

console.log(baz()); // 输出:undefined

3. undeclared(未声明)表示变量没有被声明过。如果你使用了一个没有声明的变量,JavaScript 会抛出一个ReferenceError: <variable> is not defined错误。例如:

console.log(undeclaredVariable); // 抛出 ReferenceError: undeclaredVariable is not defined

检测它们的方法如下:

要检测一个变量是否为null,可以使用严格相等操作符(`===`)进行比较:
if (variable === null) {
  // 变量为 null
}
要检测一个变量是否为undefined,可以使用严格相等操作符(`===`)进行比较:
if (typeof variable === 'undefined') {
  // 变量为 undefined
}
要检测一个变量是否已声明,可以使用typeof操作符并与undefined进行比较,或者使用try...catch来捕获可能的引用错误:
if (typeof variable === 'undefined') {
  // 变量未声明
}

// 或使用 try...catch 捕获未声明变量的错误
try {
  variable;
} catch (error) {
  // 捕获 ReferenceError 错误,说明变量未声明
}

请注意,在不声明变量的情况下尝试直接使用变量会导致 ReferenceError,因此使用 try...catch 捕获错误是检测未声明变量的一种方法。

6、什么是闭包?该怎么使用?为什么要使用?

闭包(closure),是一个函数以及该函数的词法环境,是指在函数内部创建的函数,它可以访问并记住其词法环境中的变量,即使该外部函数已经执行完毕。闭包通过捕获和保存了其自身作用域之外的变量,形成了一个封闭的函数及其相关变量的组合。简而言之,闭包就是能够读取其他函数内部变量的函数

要创建一个闭包,可以在一个函数内部定义另一个函数,并从函数内返回这个内部函数。内部函数将继续引用父函数的变量,使得这些变量在父函数执行完毕后仍然可用。以下是一个定义闭包的例子:

function outerFunction() {
  let outerVariable = 'I am outside!';

  function innerFunction() {
    console.log(outerVariable);
  }

  return innerFunction;
}

const closure = outerFunction();
closure(); // 输出:I am outside!

在上述例子中,innerFunction是一个闭包,它可以访问并打印出父函数outerFunction中的outerVariable。即使outerFunction执行完毕后,闭包仍然能够访问和保持对outerVariable的引用。

使用闭包有以下几个常见的原因:

1. 封装私有变量:闭包可以帮助创建类似于私有变量的概念,使得只有内部函数可以访问和修改这些变量,而外部作用域无法直接访问。

2. 保持变量的状态:闭包可以捕获外部函数的变量,并在多次调用内部函数时保持其值不变。这对于实现一些特定需求的功能很有帮助,例如计数器、迭代器和事件处理程序等。

3. 创建模块化代码:通过使用闭包,可以将代码组织为模块,避免全局命名空间污染,提高代码的可维护性和重用性。

需要注意的是,过度滥用闭包可能会导致内存泄漏,因为闭包会保留对外部变量的引用,使得这些变量无法被垃圾回收。因此,在使用闭包时应谨慎考虑变量的生命周期和内存管理问题。

7、匿名函数的用途有哪些?

1. 作为回调函数:匿名函数经常被用作回调函数,即作为另一个函数的参数传递进去,以在特定事件发生时执行特定的逻辑。例如,在JavaScript中,事件处理程序经常使用匿名函数定义,如点击事件、定时器回调等。

document.getElementById('myButton').addEventListener('click', function() {
  console.log('按钮被点击了!');
});

2. 立即执行函数表达式(Immediately Invoked Function Expression,IIFE):匿名函数也可用于创建立即执行的函数表达式,该函数在定义后立即执行。这样的函数常用于创建闭包并提供独立的作用域,避免污染全局作用域。

(function() {
  // 这里是函数内部的逻辑
  console.log('这个函数是立即执行的!');
})();

3. 函数作为值进行传递:匿名函数可以被当做普通变量一样进行赋值和传递。通过将匿名函数赋值给一个变量或传递给其他函数,可以实现更灵活的功能和代码组织。

var greet = function(name) {
  console.log('Hello, ' + name + '!');
};

greet('John'); // 输出:Hello, John!

通过使用匿名函数,可以在需要时临时定义和使用函数,而不必显式地命名函数,使代码更加简洁和灵活。但需要注意的是,由于匿名函数没有名称来标识其用途,因此应尽量保持匿名函数的代码清晰易读,避免过度复杂的逻辑或嵌套。

8、组织代码的方式有哪些?

1. 模块模式:

  • 特点:使用模块化的方式将代码划分为独立的模块,每个模块具有自己的功能和接口。通常通过导入和导出机制来引用其他模块。
  • 优点:提供了代码的封装性和复用性,避免全局变量污染,模块之间相互独立,易于维护和测试。
  • 适用场景:适用于大型应用程序或项目,多人协作开发,需要将代码划分为模块或组件,实现高内聚低耦合的架构。

2. 经典继承:

  • 特点:基于面向对象的编程思想,通过类和对象来组织代码,使用继承关系实现代码的重用性和扩展性。
  • 优点:封装和抽象具体的实现细节,通过继承建立对象之间的层次关系,方便代码的扩展和管理。
  • 适用场景:适用于需要根据具体对象之间的关系和行为进行建模或扩展的场景,例如在游戏开发、UI框架等领域经常使用继承来实现不同类型的对象。

9、请说明 JavaScript 宿主对象 (host objects) 和原生对象 (native objects) ?

1. 宿主对象(Host Objects):

  • 定义:宿主对象是由 JavaScript 运行环境(宿主环境)提供的对象,通常是为了支持特定的运行环境或平台而添加的对象。例如,在浏览器中,宿主对象可以是 window、document 等。
  • 来源:宿主对象是由宿主环境在 JavaScript 中提供的,并且它们的实现方式可以因宿主环境而异。
  • 特点:宿主对象的行为和功能可能因宿主环境的不同而有所差异。标准的 JavaScript 语言规范并未定义宿主对象的具体行为,因此宿主对象的行为和方法在不同的宿主环境下可能会有所变化。

2. 原生对象(Native Objects):

  • 定义:原生对象是 JavaScript 语言本身提供的内置对象,通常被称为全局对象。例如,Math、Array、String 是原生对象。
  • 来源:原生对象是直接由 JavaScript 语言规范定义和提供的,它们可被所有的 JavaScript 运行环境(包括浏览器和服务器端环境)所支持。
  • 特点:原生对象具有一组预定义的属性和方法,可以用来执行各种常见的任务。这些对象的行为在不同的 JavaScript 运行环境下是一致的,因为它们的实现遵循 JavaScript 语言规范。

总结:JavaScript宿主对象和原生对象之间的区别在于它们的来源和行为。宿主对象由特定的宿主环境提供而起到扩展和增强 JavaScript 功能的作用,而原生对象是 JavaScript 语言本身定义的内置对象,具有一致的行为和功能。

10、请指出以下代码的区别:function Person(){}、var person = Person()、var person = new Person()?

1. function Person(){} 定义了一个函数。

2. var person = Person() 调用函数 Person并将返回值赋给变量 person。如果在 Person 函数中没有显式返回值,那么 person 将被赋值为 undefined。

3. var person = new Person() 使用 new 创建了一个 Person 函数的实例,并将实例赋给变量 person。

11、请描述bind、call 和 apply,区别又是什么?

  • bind 方法:bind 方法会创建一个新函数,并将原函数绑定到指定的对象上。它不会立即调用函数,而是返回一个新的函数,可以稍后调用。通过 bind 方法,我们可以指定函数执行时的上下文(this 指向)以及参数

  • call 方法:call 方法用于在指定的上下文中将函数立即调用。与 bind 不同,call 方法会立即执行函数,并且可以传递多个参数给函数。使用 call 方法时,第一个参数是要绑定给函数的上下文(this 指向),后面的参数是函数执行时的参数列表

  • apply 方法:apply 方法与 call 方法类似,也是在指定的上下文中立即调用函数。然而,它接受一个数组作为参数列表,而不是单个参数。数组中的每一项对应于函数的参数。

12、请解释 Function.prototype.call?

Function.prototype.call 是 JavaScript 中 Function 对象的原型方法,用于在指定的上下文中立即调用函数。具体来说,call方法会立即执行函数,并将指定的上下文作为函数执行时的 this 值。除了传递上下文之外,call还可以接受多个参数,这些参数会作为函数执行时的参数列表传递进去。

call 方法的语法如下:

function.call(thisArg, arg1, arg2, ...)
 thisArg:要绑定到函数的上下文,即函数在执行时的 this 值。
 arg1, arg2, ...:可选参数,当函数被调用时,作为参数传递给函数。

示例:
function greet() {
  console.log(`Hello, ${this.name}!`);
}

const person = {
  name: 'John'
};

greet.call(person);

在上面的例子中,通过 `call` 方法将 `greet` 函数在 `person` 对象上调用。这会立即执行 `greet` 函数并以 `person` 对象作为函数执行时的上下文,输出为 "Hello, John!"。

除了传递上下文,`call` 方法还可以传递其他参数:

function greet(message) {
  console.log(`${message}, ${this.name}!`);
}

const person = {
  name: 'John'
};

greet.call(person, 'Welcome');

在这个例子中,`call` 方法将 `greet` 函数在 `person` 对象上调用,并传递了额外的参数 `'Welcome'`。这将输出 "Welcome, John!"。

需要注意的是,与 bind 方法不同,call 方法会立即执行函数。因此,在使用 call 调用函数时,函数会被立即执行并返回结果。

13、请指出浏览器特性检测,特性推断和浏览器 UA 字符串嗅探的区别?

浏览器特性检测、特性推断和浏览器 UA 字符串嗅探是用于判断浏览器功能和属性的不同方法。

1. 浏览器特性检测(Feature Detection):

  • 浏览器特性检测是一种通过检测浏览器是否支持某个特定的 JavaScript API、对象、属性或方法来确定浏览器的功能是否可用。
  • 它基于实际测试和查询浏览器属性来决定是否执行特定代码块。
  • 使用条件语句和现有的 JavaScript 特性进行逻辑判断,以确保代码在运行时不会引发错误。
  • 通常使用现代 JavaScript 提供的对象、属性或方法是否存在,如 `typeof`、`in`、`instanceof` 等进行检测。

2. 特性推断(Feature Inference):

  • 特性推断是基于已知的特性和功能来推断其他特性是否可用的一种方法。
  • 它基于已知的特定功能是否被浏览器支持,从而假设其他相关功能也会被支持。
  • 当已知的功能在当前环境下工作正常时,可以做出假设并使用相关的功能。

3. 浏览器 UA 字符串嗅探(Browser User-Agent String Sniffing):

  • 浏览器 UA 字符串嗅探是通过检测浏览器的用户代理(User-Agent)字符串来确定用户正在使用的浏览器和操作系统。
  • 浏览器通过在 HTTP 请求头中发送 User-Agent 字符串来识别自身。
  • 使用这种方法,可以根据特定的 UA 字符串来判断浏览器类型、版本以及所使用的操作系统等信息。
  • 然而,UA 字符串嗅探并不是一种可靠的浏览器特性检测方法,因为 UA 字符串可以被修改或伪装。

总结:

  • 浏览器特性检测是最可靠和推荐的方法,它基于实际功能的存在与否进行判断。
  • 特性推断是基于已知的功能作出合理假设。
  • 浏览器 UA 字符串嗅探通常用于识别浏览器类型和操作系统,但可靠性较低,容易被篡改。

14、请尽可能详尽的解释 Ajax 的工作原理。

Ajax(Asynchronous JavaScript and XML)是一种用于在Web应用程序中进行异步通信的技术。它使得在不刷新整个页面的情况下,可以向服务器发送请求并获取数据,然后使用JavaScript来动态更新页面的部分内容。

以下是Ajax的工作原理:

1. 准备 XMLHttpRequest 对象: 首先,通过 JavaScript 创建一个XMLHttpRequest对象,这是进行Ajax交互的关键。不同浏览器可能有不同的实现方式,但通常通过new XMLHttpRequest()来创建。

  // 创建XMLHttpRequest对象
  var xhr = new XMLHttpRequest();

2. 发送请求: 使用XMLHttpRequest对象的open()方法指定HTTP请求的类型(GET或POST)、URL以及是否为异步请求。然后调用send()方法将请求发送到服务器。

  // 设置请求方式和URL
  xhr.open("GET", "get_message.php", true);

  // 发送请求
  xhr.send();

3. 与服务器通信: 当请求被发送到服务器时,JavaScript代码将继续执行,并且页面不会被阻塞。服务器接收到请求后,处理请求并准备相应的数据。

4. 处理服务器响应: 一旦服务器准备好响应请求,它会返回一个HTTP响应。XMLHttpRequest对象的onreadystatechange属性允许开发人员提供一个回调函数来处理服务器响应。

// 注册回调函数处理服务器响应
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
      // 从响应中获取数据
      var message = xhr.responseText;

      // 更新消息容器的内容
      messageContainer.innerHTML = message;
    }
  };

5. 更新页面内容: 在回调函数中,可以通过检查XMLHttpRequest对象的状态和HTTP状态码来确定服务器响应是否成功。如果成功,可以使用JavaScript来解析并处理响应数据,并根据需要更新页面上的内容,例如更新文本、图片、表格等。

需要注意的是,Ajax并不仅局限于使用XML作为数据传输格式,也可以使用其他格式(如JSON),而且现代的Ajax技术通常使用了更多的技术和API,例如Fetch API、Promise等,以简化代码和提供更好的开发体验。

15、使用Ajax有哪些优劣?

优点:

  1. 异步通信:Ajax允许在不刷新整个页面的情况下与服务器进行异步通信。这意味着可以在后台发送请求和接收响应,同时保持用户界面的流畅性和可响应性。
  2. 提升用户体验:通过Ajax,可以实现动态加载内容、实时更新数据和交互性元素,无需重新加载整个页面。这提供了更快速、流畅的用户体验。
  3. 减轻服务器负载:由于只需要传输和处理所需的数据,而不是整个页面,因此减少了对服务器的请求量和响应时间。这可以降低服务器负载,并改善整体性能。
  4. 节省带宽:Ajax使用JSON等格式来传输数据,相比传统的HTML页面,数据量较小,可以节省带宽消耗。
  5. 支持多种数据格式:除了XML,Ajax还支持其他数据格式(如JSON、文本等),提供灵活的数据处理选项。

局限性:

  1. 依赖JavaScript:Ajax是基于JavaScript的技术,如果用户禁用了浏览器的JavaScript功能,将导致Ajax请求无法正常工作。
  2. 对SEO(搜索引擎优化)不友好:由于Ajax请求和响应是动态生成的,搜索引擎可能无法正确解析和索引这些内容。为了获得更好的SEO效果,需要使用其他技术手段来优化。
  3. 增加复杂性:相对于传统的页面刷新模型,使用Ajax会增加前端开发的复杂性,需要处理异步请求、错误处理、请求顺序管理等问题。
  4. 安全性风险:如果Ajax请求没有适当的安全措施,可能会面临跨站点脚本攻击(XSS)或跨站请求伪造(CSRF)等安全风险。
  5. 兼容性问题:不同浏览器对Ajax的支持程度有差异,需要处理兼容性问题。

16、解析JSONP的工作原理,以及为什么它不是真正的Ajax。

JSONP(JSON with Padding)是一种在网页中跨域获取数据的技术,它采用动态创建<script>标签的方式实现数据的异步加载。

工作原理:

  1. 客户端发起包含回调函数的请求:客户端使用<script>标签动态创建一个URL,该URL包含回调函数名作为查询参数。
  2. 服务器返回包装的数据:服务器接收到请求后,并根据回调函数名将数据包装成一个函数调用,并将结果作为响应返回给客户端。
  3. 数据解析和处理:当数据响应返回到客户端时,由于<script>标签的特性,浏览器会立即执行返回的JavaScript代码,并将数据交给预先定义好的回调函数进行处理和解析。

JSONP与真正的Ajax的区别:

  • 跨站请求方式:Ajax通过XMLHttpRequest对象实现跨域请求,属于同源策略限制。而JSONP通过动态创建<script>标签进行跨域请求,利用<script>标签不受同源策略限制的特性。
  • 数据格式:Ajax可使用多种数据格式(如JSON、XML、文本等),而JSONP通常只支持使用JSONP格式作为数据传输的形式。
  • 异步请求机制:Ajax提供了更细粒度的控制和处理方式,可以实现灵活的异步请求和响应处理。JSONP仅支持简单的回调方式,相对更为有限。
  • 安全性考虑:JSONP存在一定的安全风险,因为它需要将数据包装成可执行的JavaScript代码进行传输,在不可信源的情况下容易遭受XSS攻击。Ajax通过同源策略的限制,可以在一定程度上增强安全性。

综上所述,JSONP是一种利用动态创建<scirpt>标签实现跨域数据请求的技术,它与真正的Ajax跨域请求方式、数据格式、异步请求机制以及安全性等方面存在区别。

17、请解析变量声明提升(hoisting)。

请解析变量声明提升(hoisting)是JavaScript中的一种行为,它指的是在代码执行过程中,变量声明会提升到当前作用域的顶部,而变量的赋值仍然会按照代码的顺序进行执行。

变量声明的两个阶段:

  1. 声明阶段:在作用域中搜索变量声明并将其添加到内存中(初始化为undefined),这意味着你可以在变量声明之前使用该变量。
  2. 初始化阶段:在执行阶段,对变量进行初始化,即将变量赋予实际的值。

18、请描述事件冒泡机制(event bubbling)。

事件冒泡机制(event bubbling)是指在DOM结构中,当一个特定元素上的事件被触发时,该事件将沿着DOM树从目标元素逐级向父级元素传播,直到达到文档根节点为止。这意味着如果一个元素上发生了事件,它的父元素也会接收到相同的事件。

事件冒泡执行顺序是从最内层的目标元素开始的,然后逐级向上传播到其所有父元素,直到达到文档根节点。在传播过程中,可以针对每个父级元素执行相应的事件处理程序。

19、“attribute”和“property”的区别是什么?

Attribute(属性)是指HTML标签中的属性,它们被定义为HTML元素的一部分,并以键值对的形式出现。

<input type="text" id="myInput" value="Hello">

这里的type、id和value都是该<input>元素的属性,它们属于HTML的一部分。我们可以通过DOM对象的getAttribute()方法来访问和修改这些属性的值。

Property(属性)是指DOM对象的属性,它们表示了对象在JavaScript中的状态和行为。属性是DOM对象上的JavaScript对象属性。

var input=document.getElementById("myInput");

console.log(input.value);//"Hello"

input.value="World";

console.log(input.value);//"World"

在这里,value是<input>元素的属性,通过访问DOM对象的value属性,我们可以获取或修改值。

区别如下:

  • Attribute(属性)是HTML标签上定义的属性,属于HTML的一部分;Property(属性)是DOM对象的属性,代表了对象在JavaScript中的状态和行为。
  • Attribute和Property之间的联系:通常情况下,一个HTML标签的Attribute与对应的DOM对象上的Property是相关联的,它们具有相同的名称,并且在初始化渲染时会进行初始化。然而,它们的值可能会在JavaScript中独立地发生变化,而互不影响。
  • Attribute是字符串类型(除了一些特殊属性),而Property则根据具体属性的用途和取值来确定其数据类型。
  • 通过getAttribute()和setAttribute()方法可以访问和修改Attribute,而通过直接访问OM对象的Property来访问和修改Property。

需要注意的是,并非所有的Attribute都具有对应的Property,反之亦然。另外,某些属性具有自动同步机制,即当Attribute发生变化时,对应的Property也会相应更新,但并非所有属性都具备这种自动同步机制。

20、请指出document load和document DOMContentLoaded两个事件的区别。

触发时机:

  • load事件:当整个页面及其所包含的资源(如图像、脚本、样式表等)都已经加载完成时触发。
  • DOMContentLoaded事件:当初始的HTML文档已完全加载和解析,并且DOM树已经构建完成时触发。此时可能还在等待一些外部资源(如图像)的加载,但是页面的结构已经可以进行操作。

使用场景:

  • DOMContentLoaded事件适合在DOM结构准备好后执行一些操作,例如操作DOM元素、绑定事件处理程序等。
  • load事件适合在整个页面和所有外部资源都加载完成后执行一些需要依赖这些资源的操作,例如获取图片的尺寸、计算页面的大小。

21、请解析JavaScript的同源策略(same-origin policy)。

JavaScript的同源策略(same-origin policy)是一种安全机制,用于限制浏览器中运行的JavaScript代码与其他来源的文档或脚本进行交互。该策略确保了网页的数据和功能只能被来自同一源的文档访问。

根据同源策略,以下这些行为将受到限制:

  • DOM访问限制:JavaScript代码只能访问与其来源相同的窗口的DOM对象。换言之,无法通过编程方式获取或修改来自不同源的页面的DOM结构。
  • 跨域资源共享限制(CORS):通过Ajax或Fetch API发起的跨域请求需要目标服务器明确授权才能成功进行通信。
  • Cookie、LocalStorage和Indexed DB限制:跨域请求无法访问和操作其他来源的Cookie、LocalStorage和Indexed DB数据。
  • 脚本限制:使用<script>标签加载的代码无法读取或修改来自不同源的脚本文件。

22、什么是use strict?使用它的好处和坏处有哪些?

“use strict”是一个指令,用于在JavaScript代码中启用严格模式(strict mode)。它的作用是强制执行更严格的解析和错误规则。

优点:

  • 防止意外创建全局变量:在严格模式下,不允许隐式创建全局变量。如果忘记使用关键字声明变量,会导致错误而不是创建一个全局变量,有助于避免潜在的命名冲突和变量泄露问题。
  • 消除静默错误:严格模式会对一些常见的静默错误进行报错(例如将只读属性赋值、删除不可删除的属性、使用未声明的变量),这使得开发者能够及早发现并纠正这些错误。
  • 提升安全性:严格模式禁用了一些潜在的危险功能(如使用eval()执行字符串代码、使用with语句等),以减少攻击面和提高代码安全性。
  • 提升性能优化:由于严格模式小处理一些不必要的行为和容错机制,使得JavaScript引擎可以进行更有效的优化,从而提升性能。

坏处:

  • 可能导致向后兼容性问题:在现有的非严格模式JavaScript代码中添加严格模式指令时,某些行为可能会发生变化,导致旧的代码不在按预期工作,这可能需要进行相应的调整和修改。
  • 某些功能受限:在严格模式下,一些原本有效的功能可能受到限制或禁用(例如arguments.callee和this在严格模式下禁用,对于以来它们的代码可能需要做相应改动)。

23、请解释什么是单页应用 (single page app), 以及如何使其对搜索引擎友好 (SEO-friendly)。

单页应用 (single page app,SPA)是一种网站或web应用的架构模式,它在初始加载时会加载一个HTML页面,并随后通过JavaScript动态地替换该页面的内容,而无需加载整个页面。SPA通常使用Ajax等技术来实现与服务器之间的数据交互,通过动态更新页面内容,提供更流畅的用户体验。

为了使SPA对搜索引擎友好,可以采取以下措施:

  • 预渲染(Prerendering):在服务器端预先生成静态的HTML版本,并将其提供给搜索引擎爬虫。这样搜索引擎就能够看到完整的页面内容,并对其进行索引和分析。
  • 客户端路由和历史记录管理:使用合适的客户端路由库(如React Router、Vue Router)来管理SPA中的路由。确保每个路由都有对应的URL地址,并能够正确响应浏览器的前进和后退操作。这样搜索引擎爬虫可以通过访问不同的URL来获取不同的页面内容。
  • 合理使用Ajax和视图渲染:在SPA中,通过Ajax请求获取数据并动态更新页面是常见的做法。确保每个视图都有对应的静态HTML版本,并在Ajax请求之前加载这些静态HTML文件。这样搜素引擎爬虫就能够获取到完整的静态内容,并进行索引。
  • 使用合适的标记和结构化数据:遵循SEO最佳实践,为SPA的关键内容添加合适的HTML标记(如标题、段落、链接等),以及结构化数据(如Schema.org标记)。

24、使用Promise而非回调 (callbacks) 优缺点是什么?

优点:

  1. 更清晰的代码结构:Promise使用链式调用(chaining),使代码更易于阅读和理解。每个Promise方法返回一个新的Promise,允许以线性方式编写异步操作。
  2. 避免回调地狱:使用回调函数嵌套多个层级会导致回调地狱(callback hell),代码可读性差,难以维护。而Promise的链式调用消除了回调地狱问题,提高了代码可读性和可维护性。
  3. 更好的错误处理:通过.catch()方法,可以在Promise链中捕获和处理错误。这种机制更容易追踪和处理异常情况,提供了更好的错误处理能力。
  4. 支持并行执行:通过Promise.all()或Promise.race()等方法,可以并行执行多个异步操作,并在所有操作完成后进行统一处理。

缺点:

  1. 需要学习和理解新的语法:相对于传统的回调函数,Promises引入了一些新的语法和概念,需要一定的学习成本。
  2. 不完全支持旧版本浏览器:Promise是ES6标准引入的特性,在一些老版本浏览器中可能不被完全支持。为了兼容性,可能需要使用polyfill或其他转换工具。

25、请解释可变 (mutable) 和不变 (immutable) 对象的区别。 请举出 JavaScript 中一个不变性对象 (immutable object) 的例子? 不变性 (immutability) 有哪些优缺点? 如何用你自己的代码来实现不变性 (immutability)?

可变对象指的是其内部状态可以在创建后被修改。即使对象本身在内存中的引用不变,它的属性或内容仍然可以改变。

不变对象则是创建后无法被修改或更改其内部状态。一旦创建,不变对象的值和属性将保持不变。如果需要对不变对象进行修改,通常会创建一个新的对象,而不是在原始对象上直接进行修改。

在JavaScript中,基本数据类型(如字符串、数字、布尔值等)都是不变对象。每次对这些类型的操作和修改都会返回一个新的值,而不是直接修改原始值。

下面是一个不变性对象的例子:
const person = Object.freeze({
  name: "Alice",
  age: 30
});
在这个例子中,通过使用 Object.freeze() 方法可以冻结该对象,使其成为不变对象。无论是尝试修改属性值还是添加新的属性,都不会对原始对象产生任何影响。

不变性的优点包括:

简化复杂度:不变对象的值在创建后不会被修改,可以避免由于修改导致的错误和副作用,简化了代码的理解和调试。
线程安全:不变对象可以在多线程环境下使用,无需额外的同步和锁机制。
缓存优化:不变对象可以被安全地缓存,因为其值不会改变,可以提高性能。
函数式编程:不变性是函数式编程的重要概念,它鼓励使用纯函数(无副作用)进行开发,从而提升代码可维护性。

但不变性也有一些缺点:

需要更多内存:由于不会原地修改对象,每次修改都需要创建一个新的对象,可能会增加内存消耗。
性能影响:如果频繁修改不变对象,则创建大量新对象可能会对性能产生负面影响。
实现不变性的关键是避免直接修改对象,在需要修改时创建一个新的对象。

下面是用JavaScript代码展示如何实现不变性的一个简单例子:
function updateName(person, newName) {
  return { ...person, name: newName };
}

// 创建初始对象
const initialPerson = {
  name: "Alice",
  age: 30
};

// 更新名称并得到一个新的对象
const updatedPerson = updateName(initialPerson, "Bob");

console.log(initialPerson); // 输出: { name: "Alice", age: 30 }
console.log(updatedPerson); // 输出: { name: "Bob", age: 30 }

在上面的例子中,updateName()函数接受一个人员对象和一个新的名字,并返回一个新的对象,该对象具有更新后的名称。
通过使用展开操作符 (...) 来创建一个包含原始属性和新修改的属性的新对象person对象本身并没有被修改,而是返回了一个全新的对象。
这种方式可以确保不变性,同时实现对对象的更新。

26、请解释同步 (synchronous) 和异步 (asynchronous) 函数的区别。

同步 (synchronous)函数和异步 (asynchronous) 函数是指在程序执行过程中处理任务的方式不同。

同步函数是按照顺序执行的函数。当调用一个同步函数时,程序会等待该函数完成全部操作后才继续执行后续代码。在同步函数执行期间,程序将被阻塞,无法执行其他任务。

异步函数则是在调用后立即返回,并且不会等待操作完成。异步函数通常会将任务委托给其他机制(如回调函数、Promise、async/await),在后台进行处理。在异步函数执行期间,程序可以继续执行下面的代码,而无需等待异步函数的结果返回。

以下是同步和异步函数的区别总结:

执行顺序:同步函数按照顺序执行,而异步函数则在调用后立即返回,不会等待操作完成。
阻塞vs非阻塞:同步函数执行期间会阻塞程序执行,而异步函数不会阻塞程序。
响应性:由于不阻塞程序执行,异步函数可以提高程序响应性和并发性。
回调机制:异步函数通常会使用回调函数、Promise、async/await等机制处理异步操作结果。

27、什么是事件循环 (event loop)?

事件循环 (event loop)是指程序执行过程中用于处理事件和任务的机制,它是一种使得异步代码能够按照特定顺序执行的方式。

28、请描述调用栈 (call stack) 和任务队列 (task queue) 的区别是什么?

调用栈 (call stack),是用于跟踪函数调用的一种数据结构。当一个函数被调用时,相关的执行信息(包括参数、局部变量等)会被推入调用栈;而当函数执行结束后,相应的执行信息会从调用栈中弹出。

任务队列 (task queue),也称消息队列,用于存储待执行任务的一种数据结构。在事件循环中,任务队列中的任务可以是各种类型的,比如定时器回调函数、异步操作的完成回调等。

调用栈和任务队列之间的区别在于它们所存储的内容和处理方式:

  • 调用栈用于跟踪函数调用,它以后进先出(LIFO)的方式管理函数的执行上下文。当一个函数调用另一个函数时,调用栈会将当前函数的执行上下文暂时保存下来,并将新函数的执行上下文推入栈顶。当一个函数执行完毕后,其执行上下文会从栈顶弹出,程序继续执行上一个函数的代码。调用栈主要负责控制函数的执行顺序。
  • 任务队列用于存储待执行的任务,它以先进先出(FIFO)的方式管理任务的执行顺序。当一个任务完成时,会被添加到任务队列的末尾。而在事件循环中,主线程会不断地从任务队列中取出任务并执行。异步操作的完成回调、定时器回调等都会被添加到任务队列中。任务队列负责存储和安排异步任务的执行。

事件循环的工作机制是通过不断地将任务队列中的任务取出,并推入调用栈来实现的。当调用栈为空时,事件循环会查找任务队列中的任务,并将其添加到调用栈中执行。这样就保证了在事件循环中,异步任务得以按照约定的顺序执行,从而实现了非阻塞的异步编程模型

29、请解释 layout、painting 和 compositing 的区别。

  • 布局(Layout):布局阶段也称为回流(reflow),是确定元素在页面上的位置和大小的过程。在布局阶段,浏览器会计算出每个元素在文档流中的准确位置,并建立一个布局树(或称为渲染树)。布局过程涉及到处理盒模型、定位、尺寸计算、文本换行等,是相对耗时的操作。
  • 绘制(Painting):绘制阶段也称为重绘(repaint),是将元素的内容绘制到屏幕或内存中的过程。在绘制阶段,浏览器会遍历布局树,并将每个元素转化为对应的像素信息,然后根据样式进行填充、描边、渐变等绘制操作,生成最终的位图数据。
  • 合成(Compositing):合成阶段是将各个绘制好的位图按照正确的顺序组合起来,形成最终的页面图像。合成阶段也称为合成层的合成。浏览器会对页面进行分层处理,每个层都有独立的绘制和合成。在合成阶段,浏览器会将这些层进行合并,产生最终的复合图像。合成过程会使用硬件加速来提高性能,并通过合成树的方式进行优化。

综上所述,布局(Layout)阶段确定元素的位置和大小,绘制(Painting)阶段将元素内容转化为位图数据,而合成(Compositing)阶段将多个位图组合成最终的页面图像。

30、请罗列出你所知道的所有 HTTP method,并给出解释。

HTTP 方法(HTTP methods)是在客户端和服务器之间进行通信时,用于指定对资源执行不同操作的动作。以下是常见的 HTTP 方法:

GET:请求获取指定资源的内容。GET 方法是最常用的一种方法,在浏览器中输入 URL 地址时,默认使用 GET 方法来获取网页内容。
POST:向服务器提交数据,用于创建新资源或处理需要在服务器上进行处理的数据。POST 方法常用于表单提交或上传文件等操作。
PUT:向服务器传输新的数据,用于完全替代指定资源的内容。PUT 方法可以用于更新、修改已存在的资源。
DELETE:删除指定的资源。
HEAD:与 GET 方法类似,但只返回响应头部信息,而不返回实际的资源内容。HEAD 方法常用于检查资源是否存在,或获取资源的元数据信息,如文件大小、修改日期等。
OPTIONS:查询服务器支持的请求方法。
PATCH:用于对资源进行局部更新。
TRACE:用于回显服务器收到的请求,主要用于测试或诊断。
CONNECT:建立与目标资源的隧道连接。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值