放肆青春的博客
首页
前端
算法
网络
面试
技术
后端
运维
杂项
数据库
工具
网址
电脑
个人
文章
  • 分类
  • 标签
  • 归档
github (opens new window)
gitee (opens new window)

放肆青春

一个前端菜鸟的技术成长之路
首页
前端
算法
网络
面试
技术
后端
运维
杂项
数据库
工具
网址
电脑
个人
文章
  • 分类
  • 标签
  • 归档
github (opens new window)
gitee (opens new window)
  • 前端

    • 前端 概览
    • 前端汇总

    • front 博文

    • front 项目总结

    • front 高级

    • front tools

  • vue

    • vue 概览
    • vue 汇总

    • vue 博文

    • vue 项目总结

    • vue 高级

  • html

    • html 概览
    • html 汇总

    • html 博文

  • css

    • css 概览
    • css 汇总

    • css 博文

    • sass

    • less

  • js

    • javascript 概览
    • JS 汇总

    • ES6

    • JS 博文

      • js 基础语法
      • js 数据类型
      • js 字符串
      • js 数组
      • js 对象
      • js 变量
      • js 函数
      • js 事件
        • js 事件相关概念
          • 事件机制组成
          • 事件流
          • 事件冒泡
          • 事件捕获
          • 事件委托
          • js 执行机制(事件循环)
          • js 任务
          • 消息队列
        • JS 事件的操作
          • 添加监听事件和删除事件
          • JS 阻止事件冒泡
          • 取消默认事件
          • 监听滚动条事件
        • js 事件问题汇总
          • 1.mouseover 和 mouseenter 区别
          • 2.事件绑定与事件监听的区别
          • 2. javascript 中 script 整体代码属于宏任务怎么理解呢?
          • 3. e.target 和 e.currentTarget 的区别
        • js 单线程问题
          • 1、什么是单线程?
          • 2、Js 为什么是单线程?
          • 3、单线程带来的问题?
          • 4、如何解决单线程的性能问题?
        • JS 中自定义事件的使用与触发
          • 1. 事件的创建
          • 2. 事件的监听
          • 3. 事件的触发
      • js 循环
      • js 浅拷贝和深拷贝
      • js 动画
      • js DOM
      • js 防抖节流
      • js 原型及原型链
      • js this
      • js 作用域
      • js 继承
      • js 闭包
      • js 内存
      • js垃圾回收
    • JS 工具

  • node

    • node 概览
    • node 汇总

    • node 框架

    • node 博文

  • react

    • react 概览
    • react 汇总

    • react 博文

    • react 高级

  • 微信小程序

    • 微信小程序 概览
    • 微信小程序总结
    • 微信小程序文章
    • 微信小程序 博文

    • 微信小程序 高级

  • 微信公众号

    • 微信公众号 概览
    • 微信公众号总结
    • 微信公众号文章
  • 多端开发

    • 多端开发
    • dsbridge 概览
    • jsbridge 概览
    • webview
    • uniapp

      • uniapp 概览
    • taro

      • taro 概览
    • flutter

      • flutter 概览
      • flutter 环境搭建
    • electron

      • electron 概览
  • front
放肆青春
2020-07-09

js 事件

# js 事件相关概念

# 事件机制组成

  • 1.事件源:即事件的发送者

  • 2.事件:事件源发出的一种信息或状态

  • 3.事件侦听者:对事件作出反应的对象

# 事件流

事件流描述的是从页面中接受事件的顺序

“DOM2 级事件”规定的事件流包括三个阶段: 事件捕获阶段、处于目标阶段和事件冒泡阶段。从最不具体的元素(document) 捕获到最具体的元素,再从最具体的元素冒泡到最不具体的元素。最后在冒泡阶段对事件进行相应。

IE 的事件流是事件冒泡流(event bubbling),而 Netscape(网景开发团队) 的事件流是事件捕获流(event capturing)。

为什么一般在冒泡阶段,而不是在捕获阶段注册监听?

为事件代理(委托)提供条件,即事件代理依赖事件冒泡。

  1. 取消事件冒泡 event.stopPropagation()
  2. 取消默认行为 event.preventDefault()
  3. 事件代理的原理:event 有个 target 属性,永远指向最具体的元素

# 事件冒泡

  1. 事件冒泡:是自下而上的去触发事件

  2. 设置冒泡:通过 addEventListener() 的第三个属性来设置 false,(默认值为 false)事件冒泡

  3. 阻止冒泡:e.stopPropagation

  4. 支持事件冒泡的事件:

click,input

keydown,keyup

mousedown,mousemove,mouseout,mouseover,mouseup

scroll,select

drag 相关事件 dragstart 、 drag 、 dragenter 、 dragexit 、 dragleave 、 dragover 、 drop 、 dragend 均冒泡

  1. 不支持冒泡的事件:

blur,focus

mouseenter,mouseleave

resize

# 事件捕获

事件捕获指的是从 document 到触发事件的那个节点,即自上而下的去触发事件

通过 addEventListener() 的第三个属性来设置 true,事件捕获

# 事件委托

事件委托就是利用 js 事件冒泡的特性,将内层元素的事件委托给外层处理

优点:

  1. 可以大量节省内存占用,减少事件注册

  2. 可以实现当新增子对象时无需再次对其绑定事件

最适合采用事件委托技术的事件包括 click、mousedown、mouseup、keydown、keyup 和 keypress

# js 执行机制(事件循环)

事件的执行顺序,是先执行宏任务,然后执行微任务

宏任务队列可以有多个,微任务队列只有一个

宏任务耗费的时间是大于微任务的

JavaScript 事件循环机制分为浏览器和 Node 事件循环机制,两者的实现技术不一样,

浏览器 Event Loop 是 HTML 中定义的规范,Node Event Loop 是由 libuv 库实现。这里主要讲的是浏览器部分。

Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。

  1. JS 调用栈

JS 调用栈是一种后进先出的数据结构。当函数被调用时,会被添加到栈中的顶部,执行完成之后就从栈顶部移出该函数,直到栈内被清空。

  1. 同步任务、异步任务

JavaScript 单线程中的任务分为同步任务和异步任务。同步任务会在调用栈中按照顺序排队等待主线程执行,异步任务则会在异步有了结果后将注册的回调函数添加到任务队列(消息队列)中等待主线程空闲的时候,也就是栈内被清空的时候,被读取到栈中等待主线程执行。任务队列是先进先出的数据结构。

  1. Event Loop

调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操作,就形成了事件循环。

事件循环过程:

  1. 取且仅取一个宏任务来执行(第一个宏任务就是 script 任务)。执行过程中判断是同步还是异步任务,如果是同步任务就进入主线程执行栈中,如果是异步任务就进入异步处理模块,异步任务则会在异步有了结果后将注册的回调函数添加到任务队列(消息队列)中等待主线程空闲的时候,也就是栈内被清空的时候,被读取到栈中等待主线程执行。进入任务队列后,按照宏任务和微任务进行划分,划分完毕后,执行下一步。

  2. 如果微任务队列不为空,则依次取出微任务来执行,直到微任务队列为空(即当前 loop 所有微任务执行完),执行下一步。

  3. 进入下一轮 loop 或更新 UI 渲染。

# js 任务

  • 宏任务(macro)task 主要包含:
  1. script( 整体代码)、

  2. setTimeout、

  3. setInterval、

  4. I/O、UI 交互事件、

  5. setImmediate(Node.js 环境或 IE10)

  • 微任务 microtask 主要包含:
  1. Promise 的 then 或 catch,

  2. MutationObserver.

  3. process.nextTick(Node.js 环境)

  4. Object.observer(已废弃),

宏任务优先级,主代码块 > setImmediate > MessageChannel > setTimeout / setInterval

微任务优先级,process.nextTick > Promise > MutationObserver

# 消息队列

异步过程中,工作线程在异步操作完成后需要通知主线程,那么通知机制是怎么样的呢?就是利用消息队列和事件循环。

工作线程将消息放到消息队列,主线程通过事件循环过程去取消息。

消息队列: 消息队列是一个先进先出的队列,里面放着各种各样的消息;

事件循环: 事件循环是指主线程重复从消息队列中取消息,执行的过程。

实际上,主线程只会做一件事情,就是从消息队列里面取消息、执行消息,再取消息、再执行。当消息队列为空时,就会等待直到消息队列变成非空。而且主线程只有在将当前的消息执行完成后,才会去取下一个消息。这种机制就叫做事件循环机制,取一个消息并执行的过程叫做一次循环。

事件循环用代码表示大概是这样的:

while (true) {
  var message = queue.get();
  execute(message);
}
1
2
3
4

那么,消息队列中放的消息具体是什么东西?消息的具体结构当然跟具体的实现有关,但是为了简单起见,我们可以认为:

消息就是注册异步任务时添加的回调函数。 再次以异步 AJAX 为例,假设存在如下的代码:

\$.ajax("http://segmentfault.com", function(resp) {
  console.log("我是响应:", resp);
});
1
2
3

主线程在发起 AJAX 请求后,会继续执行其他代码。AJAX 线程负责请求 segmentfault.com,拿到响应后,它会把响应封装成一个 JavaScript 对象,然后构造一条消息:

// 消息队列中的消息就长这个样子

var message = function() {
  callbackFn(response);
};
1
2
3

// 其中的 callbackFn 就是前面代码中得到成功响应时的回调函数。 主线程在执行完当前循环中的所有代码后,就会到消息队列取出这条消息(也就是 message 函数),并执行它。到此为止,就完成了工作线程对主线程的通知,回调函数也就得到了执行。如果一开始主线程就没有提供回调函数,AJAX 线程在收到 HTTP 响应后,也就没必要通知主线程,从而也没必要往消息队列放消息。

# JS 事件的操作

# 添加监听事件和删除事件

DOM 2 级事件定义了两方法:用于处理添加事件和删除事件的操作:

  1. 添加事件 addEventListener()

  2. 删除事件 removeEventListener()

所有 DOM 节点中都包含这两个方法,并且他们都包含 3 个参数:

  1. 要处理的事件方式(例如:click,mouseover,dbclick.....)

  2. 事件处理的函数,可以为匿名函数,也可以为命名函数(但如果需要删除事件,必须是命名函数)

  3. 一个布尔值,(默认值为 false)代表是处于事件冒泡阶段处理还是事件捕获阶段(true:表示在捕获阶段调用事件处理程序;false:表示在冒泡阶段调用事件处理程序)

# JS 阻止事件冒泡

w3c 的方法是e.stopPropagation(),IE 则是使用e.cancelBubble = true

阻止冒泡代码:

function stopBubble(e) {
  //如果提供了事件对象,则这是一个非IE浏览器
  if (e && e.stopPropagation)
    //因此它支持W3C的stopPropagation()方法
    e.stopPropagation();
  //否则,我们需要使用IE的方式来取消事件冒泡
  else {
    window.event ? (window.event.cancelBubble = true) : "";
  }
}
1
2
3
4
5
6
7
8
9
10

# 取消默认事件

w3c 的方法是e.preventDefault(),IE 则是使用e.returnValue = false;

javascript 的return false只会阻止默认行为,而是用 jQuery 的话则既阻止默认行为又防止对象冒泡。

例子:阻止 a 标签的跳转

var a = document.getElementById("testA");
a.onclick = function(e) {
  if (e.preventDefault) {
    e.preventDefault();
  } else {
    window.event.returnValue == false;
  }
};
1
2
3
4
5
6
7
8

# 监听滚动条事件

window.addEventListener("scroll", winScroll, false);

function winScroll() {
  var scrollTop = document.documentElement.scrollTop;
  var clientHeight = document.documentElement.clientHeight;
  var scrollHeight = document.documentElement.scrollHeight;

  if (scrollHeight >= clientHeight + scrollTop) {
    // 到底部了
  }
}
1
2
3
4
5
6
7
8
9
10
11

# js 事件问题汇总

# 1.mouseover 和 mouseenter 区别

# 2.事件绑定与事件监听的区别

事件绑定:同一个对象,进行多次事件绑定,则后绑定的事件会覆盖之前绑定的事件,总之只能执行最后一个事件. -------- onclick

事件监听:同一个对象,进行多次事件监听,都会执行 -------addEventListener

# 2. javascript 中 script 整体代码属于宏任务怎么理解呢?

请问 script 整体代码是宏任务怎么理解呢,是说像 console.log()这样的同步代码也应该属于宏任务吗?还请大神指点

因为代码不是任务,所以 console.log()这句代码也不是任务,更不是宏任务。

虽然社区有人总喜欢列举 Promise.then、MutationObserver 是微任务(这里没列举完全),但是没捋清事件循环机制的前提下,死记硬背这些个“微任务”的话,面试官很容易借此挖坑请你跳。

所谓任务,浅显来说就是代码块开始执行的入口(确切地说,是函数栈的入口,但是栈的概念较为复杂,不表)。而在 JS 里,除了“script 整体代码块”之外,所有代码块的入口都是“回调函数”,回调函数被注册到事件后不会马上被执行,而是保存在一个神秘的的地方,保存起来待执行的才能算“任务”,然后才有宏/微任务之分。

“script 整体代码块”的特殊之处,在于它的入口不是回调函数,但是我们可以想象它被装在一个隐形的函数里,作为回调函数被注册到某个事件里(大概是它解析完成之后会触发的一个事件),这时候这个隐形的函数就成为了一个任务。

这只是一个浅显的说法,事件循环相关的知识请自行补全。

# 3. e.target 和 e.currentTarget 的区别

target 触发事件的源事件

currentTarget 事件触发的当前事件(当前事件,可能是触发事件的源组件,可能是触发的事件组件(即触发事件源组件的子元素)),

1、如果绑定的事件所在组件没有子元素,则用 e.target===e.currentTarget 一样;

2、如果事件绑定在父元素中,且该父元素有子元素,

 当用e.currentTarget时,不管点击父元素所在区域还是子元素(当前事件),都正确执行,

 若用e.target时,点击父元素所在区域无错,点击子元素区域,执行报错-------》

 报错的原因是事件没绑定在子元素上,是在父元素上,子元素要用e.currentTarget才正确!

# js 单线程问题

# 1、什么是单线程?

单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。简单来说,即同一时间只能做一件事件。

# 2、Js 为什么是单线程?

Js 是一种运行在网页的简单的脚本语言,由于设计的初衷是作为浏览器脚本语言,用于与用户互动,以及操作 DOM。这决定它是单线程的。

# 3、单线程带来的问题?

单线程就意味着,所有任务都需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就需要一直等着。这就会导致 IO 操作(耗时但 cpu 闲置)时造成性能浪费的问题。

# 4、如何解决单线程的性能问题?

采用异步可以解决。主线程完全可以不管 IO 操作,暂时挂起处于等待中的任务,先运行排在后面的任务。等到 IO 操作返回了结果,再回过头,把挂起的任务继续执行下去。于是,所有任务可以分成两种,一种是同步任务,另一种是异步任务。

# JS 中自定义事件的使用与触发

# 1. 事件的创建

JS 中,最简单的创建事件方法,是使用 Event 构造器: var myEvent = new Event('event_name'); 但是为了能够传递数据,就需要使用 CustomEvent 构造器:

var myEvent = new CustomEvent("event_name", {
  detail: {
    // 将需要传递的数据写在 detail 中,以便在 EventListener 中获取
    // 数据将会在 event.detail 中得到
  },
});
1
2
3
4
5
6

# 2. 事件的监听

JS 的 EventListener 是根据事件的名称来进行监听的,比如我们在上文中已经创建了一个名称为‘event_name’ 的事件,那么当某个元素需要监听它的时候,就需要创建相应的监听器:

复制代码 //假设 listener 注册在 window 对象上

window.addEventListener("event_name", function(event) {
  // 如果是 CustomEvent,传入的数据在 event.detail 中
  console.log("得到数据为:", event.detail);

  // ...后续相关操作
});
1
2
3
4
5
6

至此,window 对象上就有了对‘event_name’ 这个事件的监听器,当 window 上触发这个事件的时候,相关的 callback 就会执行。

# 3. 事件的触发

对于一些内置(built-in)的事件,通常都是有一些操作去做触发,比如鼠标单击对应 MouseEvent 的 click 事件,利用鼠标(ctrl+滚轮上下)去放大缩小页面对应 WheelEvent 的 resize 事件。 然而,自定义的事件由于不是 JS 内置的事件,所以我们需要在 JS 代码中去显式地触发它。方法是使用 dispatchEvent 去触发(IE8 低版本兼容,使用 fireEvent): // 首先需要提前定义好事件,并且注册相关的 EventListener

var myEvent = new CustomEvent("event_name", {
  detail: { title: "This is title!" },
});
window.addEventListener("event_name", function(event) {
  console.log("得到标题为:", event.detail.title);
});
// 随后在对应的元素上触发该事件
if (window.dispatchEvent) {
  window.dispatchEvent(myEvent);
} else {
  window.fireEvent(myEvent);
}
1
2
3
4
5
6
7
8
9
10
11
12

// 根据 listener 中的 callback 函数定义,应当会在 console 中输出 "得到标题为: This is title!" 需要特别注意的是,当一个事件触发的时候,如果相应的 element 及其上级元素没有对应的 EventListener,就不会有任何回调操作。 对于子元素的监听,可以对父元素添加事件托管,让事件在事件冒泡阶段被监听器捕获并执行。这时候,使用 event.target 就可以获取到具体触发事件的元素。

更新时间: 2/10/2022, 7:21:32 PM
js 函数
js 循环

← js 函数 js 循环→

最近更新
01
前端权限管理
02-24
02
vue2指令
02-24
03
vue2 hook
02-24
更多文章>
Theme by Vdoing | Copyright © 2019-2022 放肆青春
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式