JavaScript之Web APIs-DOM

目录

  • DOM获取元素
    • 一、Web API 基本认知
      • 1.1 变量声明
      • 1.2 作用和分类
      • 1.3 DOM树
      • 1.4 DOM对象
    • 二、获取DOM对象
      • 2.1 通过CSS选择器来获取DOM元素
      • 2.2 通过其他方式来获取DOM元素
    • 三、操作元素内容
      • 3.1 元素.innerTest属性
      • 3.2 元素.innerHTML属性
    • 四、操作元素属性
      • 4.1 操作元素常用属性
      • 4.2 操作元素样式属性
      • 4.3 操作表单元素属性
      • 4.4 自定义属性
      • 4.5 定时器-间歇函数
  • DOM事件基础
    • 一、事件监听(绑定)
    • 二、事件类型
    • 三、事件对象
    • 四、环境对象
    • 五、回调函数
  • DOM事件进阶
    • 一、事件流
      • 1.1 事件流和两个阶段说明
      • 1.2 事件捕获(了解)
      • 1.3 事件冒泡
      • 1.4 阻止冒泡
      • 1.5 解绑事件(了解)
    • 二、事件委托
    • 三、其他事件
      • 3.1 页面加载事件
      • 3.2 元素滚动事件
      • 3.3 页面尺寸事件
    • 四、元素的尺寸和位置
  • DOM节点操作
    • 一、日期对象
      • 1.1 实例化
      • 1.2 日期对象方法
      • 1.3 时间戳
    • 二、节点操作
      • 2.1 DOM节点:DOM树每一个内容都称之为节点
      • 2.2 查找节点
      • 2.3 增加节点(重要)
      • 2.4 删除节点
    • 三、M端事件
    • 四、swiper插件

DOM获取元素

一、Web API 基本认知

1.1 变量声明

let const
建议const优先,有了变量先给const,后面要修改的话再改成let
用const声明变量时需要初始化
对于基本数据类型,用const修饰的变量值不能更改
对于引用数据类型,用const修饰的变量值可以更改,但地址值不能更改(即可以对引用数据类型进行增删改查操作)
建议数组和对象用const声明

1.2 作用和分类

作用:使用js操作html和浏览器
分类:DOM(文档对象模型)(操作网页内容)、BOM(浏览器对象模型)

1.3 DOM树

在这里插入图片描述

将HTML文档以树状结构直观的表示出来,体现标签之间的关系

1.4 DOM对象

html里面的标签在js里面叫对象
DOM核心思想:把网页内容当作对象处理
document对象:是DOM里提供的一个对象,它提供的属性和方法都是用来访问和操作网页内容的,网页所有内容都在document里面

二、获取DOM对象

2.1 通过CSS选择器来获取DOM元素

  1. 选择匹配的单个元素
    语法:document.querySelector('css选择器')
    参数:包括一个或多个有效的CSS选择器字符串
    返回值:CSS选择器匹配的第一个元素,一个HTMLElement对象
  2. 选择匹配的多个元素
    语法:document.querySelectorAll('css选择器')
    参数:包括一个或多个有效的CSS选择器字符串
    返回值:CSS选择器匹配的NodeList集合
    得到的是一个伪数组:① 有长度有索引号的数组 ② 没有pop()、push()方法 ③ for遍历得到里面的每一个对象
    【注意】:哪怕只有一个元素,用document.querySelectorAll('css选择器')获取的也是伪数组

2.2 通过其他方式来获取DOM元素

在这里插入图片描述

三、操作元素内容

3.1 元素.innerTest属性

* 将文本内容添加/更新到任意标签位置
* 显示纯文本,**不解析标签**

eg:

const box = document.querySelector('div')
console.log(box.innerText)

3.2 元素.innerHTML属性

* 将文本内容添加/更新到任意标签位置
* 会解析标签,多标签建议使用模板字符

四、操作元素属性

4.1 操作元素常用属性

对象.属性

4.2 操作元素样式属性

通过js设置/修改标签元素的样式属性

  1. 通过style属性操作CSS
    生成的是行内样式表,权重高
    语法:对象.style.样式属性 = 值
    【注意】

    • 修改样式通过style属性引出 eg: div.style.width = '300px'
    • 如果属性有连接符,需要转换为小驼峰命名法
    • 赋值时用单引号括起来,需要的时候要加css单位

    在这里插入图片描述

  2. 操作类名(className) 操作CSS
    适合修改样式较多的情况
    语法:

    // active是一个css类名
    元素.className = 'active'
    

    【注意】:

    • 由于class是关键字,所以使用className代替
    • className是用新值换旧值 ,如果需要添加一个类,需要保留之前的类名
  3. 通过classList操作类控制CSS(重要)
    为了解决className容易覆盖以前的类名,可以通过classList方式追加和删除类名
    语法:

    // 追加一个类
    元素.classList.add('类名')
    // 删除一个类
    元素.classList.remove('类名')
    // 切换一个类
    元素.classList.toggle('类名')
    // 检测是否包含一个类
    元素.classList.contains('类名')
    

4.3 操作表单元素属性

获取:DOM对象.属性名
设置:DOM对象.属性名 = 新值
eg:表单.type = 'password'
表单中要展添加就有效果,移除就没有效果时,一律用布尔值表示,为true表示添加了该属性,false代表移除该属性eg:disabled(禁用,默认false即不禁用)、checked、selected
eg:

<input type = "checkbox"></input>
const ipt = document.querySelector('input')
ipt.checked =  true

4.4 自定义属性

  • 标准属性:标签自带的属性,例如class id title等,可以直接用点语法操作
  • 自定义属性:
    • 在html5中推出的专门的data-自定义属性
    • 在标签开头一律以data-开头
    • 在DOM对象上一律以dataset对象方式获取
      eg:在这里插入图片描述

4.5 定时器-间歇函数

每隔一段时间自动执行一段代码,不需要手动触发,eg:倒计时
定时器函数可以开启和关闭定时器

  1. 开启定时器
    setInterval(函数名,间隔时间)
    【注意】:① 函数可以是匿名函数,也可以是具名函数,使用具名函数时,不要加括号,如果要加括号,必须用引号括起来,eg:'fn()'② 间隔时间单位是毫秒 ③ 定时器返回的是id号,每个定时器的id都不一样
  2. 关闭定时器
    语法:
let 变量名 = setInterval(函数, 间隔时间)
clearInterval(变量名)

eg:

// 获取定时器的id
let n = setInterval(fn, 1000)
// 关闭
clear Interval(n)
// 再次打开时
n = setInterval(fn, 1000)

DOM事件基础

一、事件监听(绑定)

  1. 事件:编程时系统内发生的动作,eg:单击按钮切换图片
  2. 语法:
    元素对象.addEventListener('事件类型', 要执行的函数)
  3. 事件监听三要素:事件源(DOM元素)、事件类型、事件调用的函数
    【注意】:① 事件类型要加引号 ② 函数不会立即执行,触发一次执行一次

二、事件类型

  1. 鼠标事件(鼠标触发)
    • 点击:‘click’
    • 鼠标经过:‘mouseenter’/‘mouseover’
    • 鼠标离开:‘mouseleave’/‘mouseout’
      【补充】:
      ‘mouseover’(鼠标经过)和’mouseout’(鼠标离开)有冒泡效果
      ‘mouseenter’(鼠标经过)和’mouseleave’(鼠标离开)无冒泡效果
  2. 焦点事件(表单获得光标)
    • 获得焦点:‘focus’
    • 失去焦点:‘blur’
  3. 键盘事件(键盘触发)
    • 键盘按下触发:‘Keydown’
    • 键盘抬起触发:‘Keyup’
  4. 文本事件(表单输入触发)
    • 用户输入事件:‘input’
    • 内容发生了变化:‘change’

三、事件对象

事件对象存储了事件触发时的相关信息,eg:判断用户按下哪个键可以进行相应的操作
在事件绑定的回调函数的第一个参数就是事件对象,一般命名为event、ev、e

  1. 获取事件对象
    使用:事件对象.属性
    eg:
    // e就是事件对象
    btn.addEventListener('click', function (e) {})
    
    部分常用属性:
    • type:获取当前的事件类型
    • clientX/clientY:获取光标相对于浏览器可见窗口左上角的位置
    • offsetX/offsetY:获取光标相对于当前DOM元素左上角的位置
    • key:用户按下的键盘的值
    • target:点击的对象

补充: trim方法:去除内容两侧的空格变量名.trim()

四、环境对象

环境对象指的是函数内部特殊的变量this,它代表当前函数运行时所处的环境
作用:弄清楚this的指向,可以让代码更简洁
【注意】:

  1. 函数的调用方式不同this指代的对象也不同
  2. 判断this指向的粗略规则:谁调用,this就是谁
  3. 普通函数里面,this指向的是window

五、回调函数

函数A作为参数传给函数B时,就称A为回调函数

DOM事件进阶

一、事件流

1.1 事件流和两个阶段说明

事件流指事件完整执行过程中的流动路径
触发事件时经历两个阶段:
第一阶段:捕获阶段(从父到子)
第二阶段:冒泡阶段(从子到父)

1.2 事件捕获(了解)

从DOM的根元素开始去执行对应的事件(从外到里)
事件捕获需要对应代码才能看到效果
代码:DOM.addEventListener(事件类型,事件处理函数,是否使用捕获机制)
说明:第三个参数如果是true代表是捕获阶段触发(很少使用),若传入false代表是冒泡阶段触发,默认就是false

1.3 事件冒泡

  • 当一个元素的事件被触发时,会依次向上调用所有父级元素的同名事件
  • 事件冒泡是默认存在的
  • 第三个参数是false(也可以不写第三个参数)

1.4 阻止冒泡

需求:把事件限制在当前元素内
前提:阻止冒泡需要拿到事件对象
语法: 事件对象.stopPropagation()

阻止默认行为:事件对象.preventDefault()

1.5 解绑事件(了解)

用addEventListener方法注册事件时,必须使用:
removeEventListener(事件类型,事件处理函数,[获取捕获或者冒泡阶段])
([]内的参数可写可不写)
eg:

function () {alert('点击了')
}// 绑定事件
btn.addEventListener('click',fn)
// 解绑事件
btn.removeEventListener('click',fn)

【注意】:匿名函数无法解绑

在这里插入图片描述

二、事件委托

事件委托是利用时间流的特征解决一些开发需求的知识技巧
优点:减少注册次数,提高程序性能
原理:事件委托是利用了事件冒泡的特点(事件写到父级上)
实现:事件对象.target.tagName可以获得真正触发事件的元素

三、其他事件

3.1 页面加载事件

加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件

  1. 有时候需要等页面资源全部处理完毕再进行一些操作
    事件名:load
    eg:监听页面所有资源加载完毕:
    // 页面加载事件
    window.addEventListener('load', function () {// 执行的操作
    })
    
    【注意】:不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定load事件
  2. 初始的HTML文档加载和解析完后,无需等待演示表、图像等完全加载
    事件名:DOMContentLoaded
    eg:监听页面DOM加载完毕:给document添加DOMContentLoaded事件

3.2 元素滚动事件

  1. 滚动条再滚动时持续出发的事件
    事件名:scroll
    eg:监听整个页面滚动:
// 页面滚动事件
window.addEventListener('scroll', function () {// 执行的操作
})

【注意】:也可以监听某个元素的内部滚动
2. 页面滚动事件————获取位置
scrollLeft和scrollTop(属性)
- 获取被卷去的大小
- 获取元素内容往左、往上滚出去看不到的距离
- 这两个值时可读写的(可以赋值)
- 如果想知道页面滚动了多少像素:document.documentElement.scrollTop

scrollTo(x,y)也可以控制页面滚动

3.3 页面尺寸事件

  • 会在窗口尺寸改变的时候出发事件
    事件名:resize
    eg:

    window.addEventListener('resize', function () {// 执行的操作
    })
    
  • 获取宽高
    clientWidth和clientHeight
    获取元素可见部分的宽高(不包含边框border、margin、滚动条,包含padding和内容)

    eg:检测屏幕宽度
    window.addEventListener('resize', function () { // w即为页面宽度 let w = document.documentElement.clientWidth })

四、元素的尺寸和位置

通过js的方式获得元素再页面中的位置

  • 获取宽高
    1. offsetWidth和offsetHeight
    2. 获取元素自身的宽高(包含元素自身设置的宽高、border、padding)
    3. 获取出来的是数值,方便计算
    4. 如果盒子是隐藏的,获取的结果是0
  • 获取位置
    1. 获取元素距离自己定位父级元素的左、上距离
    2. offsetLeft和offsetTop只读属性
    3. element.getBoundingClientRect(),该方法返回元素的大小及其相对于视口的位置

【总结】:在这里插入图片描述

综合案例补充:
对页面添加丝滑滚动效果
html { scroll-behavior: smooth; }

DOM节点操作

一、日期对象

1.1 实例化

在代码中发现了new关键字,一般将这个操作成为实例化
创建一个时间对象并获取时间:
获取当前时间:const data = new Date()
获取指定时间:const data = new Date('2024-7-17 08:30:00')

1.2 日期对象方法

在这里插入图片描述

【注意】:月份和星期取值都是从0开始的(0表示星期天)

快速获取时间方法:

  1. date.toLocaleString()使用效果:2024/7/17 15:21:26
  2. date.toDateString()使用效果:Wed Jul 17 2024
  3. date.toLocaleDateString()使用效果:2024/7/17

1.3 时间戳

  1. 使用场景:如果计算倒计时效果,前面的方法无法直接计算,需要借助时间戳完成
  2. 时间戳:是指1970年01月01日00时00分00秒到现在的毫秒数,是一种特殊的时间计量方式
  3. 算法:
    • 将来的时间戳 - 现在的时间戳 = 剩余时间毫秒数
    • 剩余时间毫秒数转换为剩余时间的‘年月日时分秒’就是倒计时时间
      eg:将来的时间戳 2000ms - 现在的时间戳 1000ms = 1000ms,1000ms转换为倒计时就是 0小时0分1秒
  4. 获取时间戳的方法:
    • date.getTime()方法
    • 简写 +new Date()
      无需实例化
      eg:console.log(+new Date())
    • 使用Date.now()
      无需实例化,但这种方法只能获得当前的时间戳,前面两种方法可以返回指定时间的时间戳,即这种方法不能用于时间戳

二、节点操作

2.1 DOM节点:DOM树每一个内容都称之为节点

  1. 节点类型:
    • 元素节点:所有的标签,html是根节点
    • 属性节点:所有的属性,eg:href
    • 文本节点:所有的文本
    • 其他
  2. 节点关系:针对的找亲戚返回的都是对象
    • 父节点
    • 子节点
    • 兄弟节点

2.2 查找节点

  1. 父节点查找
    parentNode属性,返回最近一级的父节点, 找不到则返回none
    语法:子元素.parentNode
  2. 子节点查找
    • childNodes属性
      获得所有子节点,包括文本节点(空格、换行)、注释节点
    • children属性
      仅获得所有元素节点(仅是最近一级的子节点),返回的是一个伪数组
      语法:父元素.children
  3. 兄弟关系查找
    • 下一个兄弟
      nextElementSibling属性
    • 上一个兄弟
      previousElementSibling属性

2.3 增加节点(重要)

  1. 创建节点
    即创造出一个新的网页元素,再添加到网页内,一般先创建节点,然后插入节点
    创建元素节点方法:document.createElement('标签名')
    eg:const div = document.createElement('标签名')
  2. 追加节点
    要想在界面看到,还要插入到某个父元素中
  • 插入到父元素的最后一个子元素:
    父元素.appendChild(要插入的元素)
  • 插入到父元素某个子元素的前面
    父元素.insertBefore(要插入的元素,在哪个元素前面)
    eg:插到最前面ul.insertBefore(li,ul.children[0])
  1. 克隆节点
    特殊情况下,需要新增一个原有的节点
    语法:元素.cloneNode(布尔值)
    cloneNode会克隆出一个跟原标签一样的元素,括号内传入布尔值,若为true则代表克隆时会包含后代节点一起克隆,否则,不包含后代节点,默认为false

2.4 删除节点

删除元素必须通过父元素删除
语法:父元素.removeChild(要删除元素)
【注意】:① 如不存在父子关系则删除不成功 ② 删除节点和隐藏节点(display:none)有区别:隐藏节点还是存在的,但删除是从html中删除节点

三、M端事件

触屏事件 touch
touch对象代表一个触摸点触摸带你可以是一根手指,也可以是一根触屏笔
常见触屏事件:
在这里插入图片描述

四、swiper插件

在这里插入图片描述

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

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

相关文章

图形编辑器基于Paper.js教程09:鼠标拖动画布,以鼠标点为缩放中心进行视图的缩放

如何使用Paper.js实现画布的缩放与拖动功能 在Web开发中&#xff0c;利用Paper.js库进行图形的绘制和交互操作是一种常见的实践。Paper.js是一个强大的矢量图形库&#xff0c;可以让开发者通过简洁的API完成复杂的图形操作。在本文中&#xff0c;我们将详细探讨如何使用Paper.…

昇思25天学习打卡营第29天 | 基于MindSpore通过GPT实现情感分类

基于MindSpore框架通过GPT模型实现情感分类展示了从项目设置、数据预处理到模型训练和评估的详细步骤&#xff0c;提供了一个完整的案例来理解如何在自然语言处理任务中实现情感分析。 首先&#xff0c;环境配置是任何机器学习项目的起点。项目通过安装特定版本的MindSpore和相…

未来已来:生成式 AI 在对话系统与自主代理中的探索

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;日常聊聊 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 一、整体介绍 对话系统&#xff08;Chat&#xff09; 自主代理&#xff08;Agent&#xff09; 二、技术对比 技术差异 优…

安装anaconda后jupyter notebook打不开 闪退

首先&#xff0c;通过清华源安装了最新的anaconda&#xff08;安装在了D盘&#xff09; 尝试打开jupyter&#xff0c;发现小黑框1s后自己关了&#xff0c;根本不打开浏览器 之后尝试按照这个做了一遍https://blog.csdn.net/gary101818/article/details/123560304还是不行。。…

【BUG】已解决:TypeError: Descriptors cannot not be created directly.

已解决&#xff1a;TypeError: Descriptors cannot not be created directly. 目录 已解决&#xff1a;TypeError: Descriptors cannot not be created directly. 【常见模块错误】 【错误原因】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来…

创建和管理大量的数据对象:ScriptableObject

一、创建一个继承自ScriptableObject&#xff0c;名为ItemData的类 1、ItemData.cs using UnityEngine;[CreateAssetMenu(menuName "Items/Item")] public class ItemData : ScriptableObject {public string description;public Sprite thumbnail;public GameObj…

数据挖掘-分类和预测

来自&#x1f96c;&#x1f436;程序员 Truraly | 田园 的博客&#xff0c;最新文章首发于&#xff1a;田园幻想乡 | 原文链接 | github &#xff08;欢迎关注&#xff09; 文章目录 概念KNN 算法决策树ID3 算法缺点 C4.5 算法CART 算法 贝叶斯算法朴素贝叶斯算法贝叶斯信念网络…

如何做好结构化逻辑分析:分析之万能公式

有人问&#xff1a;我看了很多书&#xff0c;可是一到分析问题和解决问题时&#xff0c;还是不知如何下手。你能给我一个万能框架吗&#xff1f;这样我遇到问题就可以马上找到思路、直接套用&#xff0c;再也不用让大脑去神游和不知所措了。 我想了想&#xff0c;总结出了这个…

突破•指针二

听说这是目录哦 复习review❤️野指针&#x1fae7;assert断言&#x1fae7;assert的神奇之处 指针的使用和传址调用&#x1fae7;数组名的理解&#x1fae7;理解整个数组和数组首元素地址的区别 使用指针访问数组&#x1fae7;一维数组传参的本质&#x1fae7;二级指针&#x…

2-41 基于matlab的小车倒立摆系统的控制及GUI动画演示

基于matlab的小车倒立摆系统的控制及GUI动画演示。输入小车及倒立摆的初始参数&#xff0c;位置参数&#xff0c;对仿真时间和步长进行设置&#xff0c;通过LQR计算K值&#xff0c;进行角度、角速度、位置、速度仿真及曲线输出&#xff0c;程序已调通&#xff0c;可直接运行。 …

Linux设置开机启动Nginx

设置开机启动项 systemctl enable nginx 这个命令会创建一个符号链接到/etc/systemd/system/目录下的multi-user.target.wants/目录&#xff0c;从而确保Nginx在系统启动时自动运行。 查看Nginx是否已设置开机启动项 systemctl list-unit-files | grep nginx 删除开机启动 …

数据无忧:2024年高效硬盘数据恢复解决方法

在这个数字化时代&#xff0c;数据已成为我们生活与工作中不可或缺的一部分。手机或者电脑不够存储数据的时候我们最常用的就是采购硬盘来存储。以备不时之需我们来学习一下硬盘数据恢复的一些技巧吧。 1.福、昕数据恢复 这工具是一款简单小巧的数据恢复工具。下载安装在一分…

文心一言 VS 讯飞星火 VS chatgpt (307)-- 算法导论22.2 5题

五、证明&#xff1a;在广度优先搜索算法里&#xff0c;赋给结点 u 的 u.d 值与结点在邻接链表里出现的次序无关。使用图 22-3 作为例子&#xff0c;证明&#xff1a;BFS 所计算出的广度优先树可以因邻接链表中的次序不同而不同。如果要写代码&#xff0c;请用go语言。 文心一…

51单片机(STC8H8K64U/STC8051U34K64)_RA8889驱动TFT大屏_I2C_HW参考代码(v1.3) 硬件I2C方式

本篇介绍单片机使用硬件I2C方式控制RA8889驱动彩屏。 提供STC8H8K64U和STC8051U34K64的参考代码。 【硬件部份】STC8H8K64U/STC8051U34K64 RA8889开发板 7寸TFT 800x480 1. 实物连接图&#xff1a;STC8H8K64URA8889开发板&#xff0c;使用P2口I2C接口&#xff1a; 2.实物连…

2024 年 100 大数据科学面试问答

2024 年 100 大数据科学面试问答 一、说明 数据科学是一个快速发展的领域&#xff0c;它正在改变组织根据数据理解和做出决策的方式。因此&#xff0c;公司越来越多地寻求聘请数据科学家来帮助他们理解数据并推动业务成果。这导致了对数据科学家的高需求&#xff0c;这些职位的…

《Windows API每日一练》13.1 打印基础

在Windows中使用打印机时&#xff0c;在调用一系列与打印相关的GDI绘图函数的背后&#xff0c;实际上启动了一系列模块之间复杂的交互过程&#xff0c;包括 GDI32库模块、打印机设备驱动程序库模块&#xff08;带.DRV后缀的文件&#xff09;、Windows后台打印处理程序&#xff…

(AAAI,2024)Structure-CLIP:利用场景知识图谱增强多模态结构化表征

文章目录 Structure-CLIP: Towards Scene Graph Knowledge to Enhance Multi-modal Structured Representations相关资料摘要引言方法通过场景图进行语义负采样场景图生成选择语义负样本对比学习目标知识增强编码器 Structure-CLIP: Towards Scene Graph Knowledge to Enhance …

go语言Gin框架的学习路线(七)

GORM入门(基于七米老师) 目录 GORM入门 安装 连接数据库 连接MySQL 连接PostgreSQL 连接Sqlite3 连接SQL Server 我们搞一个连接MySQL的例子 创建数据库 GORM操作MySQL GORM是一个流行的Go语言ORM&#xff08;对象关系映射&#xff09;库&#xff0c;它提供了一种方…

maven私服上传jar包 400 Bad Request 错误

文章目录 前言一、直接看报错二、问题处理三 maven 私服配置说明总结 前言 maven仓库的私服,一般会存放公司或者个人封装的jar包,用来共享给二次开发和协作伙伴用,很方便 第一次发布没有问题,但是我第二次发布,开始报错了 一、直接看报错 [外链图片转存失败,源站可能有防盗链…

问题解决实录 | Anaconda | Anaconda Navigator 启动无反应

问题解决实录 | Anaconda | Anaconda Navigator 启动无反应 以管理员身份运行 Anaconda Prompt conda update -n root conda conda update --all如果执行完以上步骤 碰到 AttributeError: module ‘pkgutil’ has no attribute ‘ImpImporter’. Did you mean: ‘zipimporter…