整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

前端面试题JavaScript(二)

请解释一下 JavaScript和CSS阻塞。

JavaScript的阻塞特性是所有浏览器在下载 JavaScript代码的时候,会阻止其他一切活动,比如其他资源的下载,内容的呈现等,直到 JavaScript代码下载、解析、执行完毕后才开始继续并行下载其他资源并渲染内容。

为了提高用户体验,新一代浏览器都支持并行下载 JavaScript代码,但是 JavaScript代码的下载仍然会阻塞其他资源的下载(例如图片、CSS文件等)。

为了防止 JavaScript修改DOM树,浏览器需要重新构建DOM树,所以就会阻塞其他资源的下载和渲染。

嵌入的 JavaScript代码会阻塞所有内容的呈现,而外部 JavaScript代码只会阻塞其后内容的显示,两种方式都会阻塞其后资源的下载。也就是说,外部脚本不会阻塞外部脚本的加载,但会阻塞外部脚本的执行。

CSS本来是可以并行加载的,但是当CSS后面跟着嵌入的 JavaScript代码的时候,该CSS就会阻塞后面资源的下载。

而当把嵌入的 JavaScript代码放到CSS前面时,就不会出现阻塞的情况了(在IE6下CSS都会阻塞加载)。

根本原因是因为浏览器会维持HTML中CSS和 JavaScript代码的顺序,样式表必须在嵌入的 JavaScript代码执行前先加载、解析完。而嵌入的 JavaScript代码会阻塞后面的资源加载,所以就会出现CSS阻塞资源加载的情况。

2、阐述一下事件冒泡。

Java Script允许DOM元素嵌套在一起。在这种情况下,如果单击子级的处理程序,父级的处理程序也将执行同样的工作。

3、和DOM事件流的区别是什么?

区别如下。

(1)执行顺序不一样

(2)参数不一样。

(3)事件名称是否加on不一样。

(4)this指向问题不一样。

4、解释一下什么是构造函数?

构造函数是一种特殊的方法,主要用来创建对象时初始化对象,经常与new运算符一起使用,创建对象的语句中构造函数的名称必须与类名完全相同。

5、解释一下for-in循环。

for-in循环用于循环对象的属性。

for-in循环的语法如下。

for (var iable name in object){}

在每次循环中,来自对象的一个属性与变量名相关联,循环继续,直到对象的所有属性都被遍历。

6、两个嵌套for循环的执行顺序

外层判断循环条件,满足进入外层循环体

内层判断循环条件

内层循环体执行

内层循环变量累加,回到2执行,直到不满足内层条件

外层循环变量累加,回到1执行,直到不满足外层循环条件,彻底退出循环

7、如何理解 JavaScript中的闭包?

闭包就是能够读取其他函数内部变量的函数。

闭包的用途有两个,一是可以读取函数内部的变量,二是让这些变量的值始终保持在内存中。

8、解释一下 window. onload和 onDocumentReady。

在载入页面的所有信息之前,不运行 window. onload。这导致在执行任何代码之前会出现延迟。

window.onDocumentReady在加载DOM之后加载代码。这允许代码更早地执行(早于 window. onload)。

9、在 JavaScript中, unshift方法的作用是什么?

unshift方法就像在数组开头工作的push方法。该方法用于将一个或多个元素添加到数组的开头。

10、typeof是用来做什么的?

typeof是一个运算符,用于返回变量类型的字符串描述。

件冒泡

冒泡意味着事件从目标元素(即button用户点击)向上传播到其祖先树,从最近的元素开始。默认情况下,所有事件都会冒泡。

为了更好地理解事件冒泡,请考虑以下 HTML 示例,我们将在本文的大部分内容中引用该示例:

HTML

<html>
  <body>
    <div id="btn-container">
      <button class="btn">Click me</button>
    </div>
  </body>
</html>

JavaScript

const ancestors = [
  window, document, document.documentElement,
  document.body, document.getElementById('btn-container')
];

// 目标阶段
document.querySelector('.btn').addEventListener('click', e => {
  console.log(`Hello from ${e.target}`);
});
// 泡沫阶段
ancestors.forEach(a => {
  a.addEventListener('click', e => {
    console.log(`Hello from ${e.currentTarget}`);
  });
});

如果我们为树中的每个元素添加一个事件侦听器,如上所示,我们会​看到第一个触发了一个侦听器button,然后其他每个元素都从最近的祖先一直触发到Window.

事件捕获

捕获与冒泡完全相反,这意味着外部事件处理程序在最具体的处理程序(即 上的处理程序button)之前被触发。请注意,首先运行所有捕获事件处理程序,然后运行所有冒泡事件处理程序。

您可以通过将第三个参数应用于 EventTarget.addEventListener,将其设置为 true 来使用事件捕获。例如:

// 捕获阶段
ancestors.forEach(a => {
  a.addEventListener('click', e => {
    console.log(`Hello from ${e.currentTarget}`);
  }, true);
});

给定这段代码,我们会看到第一个 button 的每个祖先触发了一个侦听器,然后触发了 button 侦听器。

事件传播

解释了事件冒泡和捕获之后,我们现在可以解释事件传播的三个阶段:

  • 捕获阶段,事件从根元素Document开始并向下移动到目标元素的祖先Window。
  • 目标阶段,事件在事件目标(例如button用户点击)上被触发。
  • 冒泡阶段,事件通过目标元素的祖先冒泡,直到根元素Document,最后为Window

事件委托

事件委托是指委托事件侦听父元素而不是直接将事件侦听器添加到事件目标的想法。使用这种技术,父级可以根据需要捕获和处理冒泡事件。

window.addEventListener('click', e => {
  if (e.target.className === 'btn') console.log('Hello there!');
});

在上面的示例中,我们将事件处理委托给button 到 Window并用Event.target获取原始事件的目标。

使用事件委托模式有两个优势:

  • 通过使用事件委托,我们可以在大量元素上侦听事件,而无需单独附加事件侦听器,这可以提供性能优势。
  • 通过使用事件委托,动态元素(即随着时间的推移从 DOM 中添加或删除)可以捕获和处理它们的事件,而无需注册或删除侦听器。

更多内容请访问我的网站:https://www.icoderoad.com

过JavaScript事件的冒泡来动态为元素绑定事件的方法称为事件委托(Event Delegation,也称为“事件代理”),是 JavaScript 中最热门的技术之一,在笔试和面试中是常考察的重点知识点,今天来简单介绍一下相关的原理知识。

1、DOM事件流

1.1 事件流描述的是从页面中接收事件的顺序。

1.2 事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即DOM事件流。

比如:我们给页面中的一个div注册了单击事件,当你单击了div时,也就单击了body,单击了html,单击了document。

1.3 DOM 事件流会经历3个阶段:

(1)、捕获阶段:事件从文档的根节点流向目标对象。

(2)、当前目标阶段:在目标对象上被触发。

(3)、冒泡阶段:回溯到文档的根节点。

2、事件捕获

事件从最不精确的对象(document 对象)开始触发,然后到最精确(也可以在窗口级别捕获事件,不过必须由开发人员特别指定),与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。同样形象的比喻一下可以想象成警察逮捕屋子内的小偷,就要从外面一层层的进入到房子内。

3、事件冒泡

微软提出了名为事件冒泡的事件流。事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发。可以想象把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象。

防止事件冒泡的方法:

  • event.stopPropagation(); // 阻止了事件冒泡,但不会阻击默认行为。
  • event.preventDefault(); // 阻止默认事件,比如a的跳转事件。

4、事件委托

什么是事件委托?

事件委托也称为事件代理。当有多个列表元素需要绑定事件时,一个一个去绑定即浪费时间,又不利于性能,这时候就可以用到事件委托,给他们的一个共同父级元素添加一个事件函数去处理他们所有的事件情况。

事件委托的原理

给父元素注册事件,利用事件冒泡,当子元素的事件触发,会冒泡到父元素,然后去控制相应的子元素。

案例

<ul>

<li>事件冒泡,事件委派</li>

<li>事件冒泡,事件委派</li>

<li>事件冒泡,事件委派</li>

<li>事件冒泡,事件委派</li>

<li>事件冒泡,事件委派</li>

</ul>

<script>

// 事件委托的核心原理:给父节点添加侦听器, 利用事件冒泡影响每一个子节点

var ul = document.querySelector('ul');

ul.addEventListener('click', function (e) {

// e.target 这个可以得到我们点击的对象

e.target.style.backgroundColor= 'aqua';

})

</script>

在上述代码没有给每个li标签绑定事件,而是通过给ul标签绑定事件,然后判断target的形式(冒泡)来设置每个子节点相应的处理。

tips:addEventListener(type,listener[,useCapture])第三个参数如果是true,表示在事件捕获阶段调用事件处理程序,如果是false(默认为false),表示事件冒泡阶段调用事件处理程序。

为什么要使用事件委托?

在 javascript 中,页面中事件处理程序的数量与页面整体性能直接相关。

每个函数都是对象,都占用内存空间,对象越多,性能越差。
为指定事件处理程序所需访问 DOM 的次数会造成整个页面交互的延迟。

“过多事件处理程序“的解决方案是使用事件委托。
事件委托是利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。
只要给所有元素共同的祖先节点添加一个事件处理程序,就可以解决大片雷同的只为指定事件处理程序的代码的问题。

事件委托的作用

  • 减少内存消耗和DOM操作,提高性能。
  • 节省花在设置页面事件处理程序上的时间。指定一个事件处理程序可以节省 DOM 引用,也可以节省时间。

事件委托的注意事项

使用“事件委托”时,并不是说把事件委托给的元素越靠近顶层就越好。事件冒泡的过程也需要耗时,越靠近顶层,事件的“事件传播链”越长,也就越耗时。如果DOM嵌套结构很深,事件冒泡通过大量祖先元素会导致性能损失。

不支持冒泡的事件

冒泡事件有很多,常见的不支持冒泡的事件如下:

  • 焦点事件:focus、blur。
  • 鼠标事件: mouseenter、mouseleave。
  • UI事件:load、unload、scroll、resize。

原因是在于:这些事件仅发生于自身上,而它的任何父节点上的事件都不会产生,所以不会冒泡。


总结

事件委托是基于事件冒泡实现的动态绑定事件的方法。因为把事件绑定到父节点上,因此减少了绑定事件(减少内存消耗)和减少DOM读取次数。就算后面新增的子节点也有了相关事件,删除部分子节点不用去销毁对应节点上绑定的事件。父节点是通过event.target来找对应的子节点的。