如何理解JS中的 WebAPI、 DOM和BOM

2,990 阅读17分钟

  最近在重新过一遍前端的基础知识,WebAPI、 DOM和BOM是前端中非常重要的概念,但是很多刚刚接触前端的小伙伴不知道WebAPI、 DOM和BOM是什么,以及它们之间的关系,本文就把笔者对WebAPI、 DOM和BOM相关知识的总结,分享给各位小伙伴,希望能对大家有所启发。

JS的三大组成部分

  说到JS中的WebAPI、DOM和BOM,就不得不先说js的基本组成了。JS由ECMAScript基本语法(简称ES)、DOM(文档对象模型)BOM(浏览器对象模型),图示如下:

WebAPI

什么是API和WebAPI ?

👉:API——Application Programming Interface(应用程序接口),简单来说,就是一些程序(比如操作系统)提前封装好了一些函数和方法,提供给我们,我们不需要了解它是怎么封装的,只要调用,就可以实现相应的功能。

👉:WebAPI——就是浏览器用js语法提前封装好了一些函数和方法,给我们来调用。主要是针对BOMDOM 封装的一些方法。

为什么要使用WebAPI?

👉:最主要是可以提高开发效率

BOM和DOM的关系

👉:BOM——Browser Object Model (浏览器对象模型),其实就是把浏览器当做一个对象来进行操作,比如前进、后退、页面跳转、刷新等

👉:DOM——Document Object Model(文档对象模型),Document是文档,即整个WEB页面,所有的Dom元素都在Document整个文档里。简单来说,DOM就是把整个文档页面当做一个对象进行操作,其核心思路就是把网页上的任何内容都当做一个对象来处理

👉:二者的关系简单理解,就是BOM包含DOM,图示如下:

  • 浏览器打开一个页面就会 自动 创建 一套 BOM 对象(window/document/.......)
  • 其中 document 下 包含了 根据 html 创建Dom 对象,这个DOM对象,以树形结构展示,即DOM树

DOM对象的核心

  作为一个对象,DOM对象内,有数百个属性和方法。DOM对象处理的核心,就是对DOM中的元素节点(即标签)进行一些增、删、改、查、事件操作、属性操作等一系列操作。

DOM核心操作思维导图:

DOM之创建元素(三种方法的对比)

​ 先引入一个概念:文档流

👉文档流

  浏览器在打开页面时会开启一个文档流,来渲染每个标签,从上到下执行完了后会关闭文档流。

  • document.write

    • 特点是,如果document.write是在文档流关闭后才来调用,那么浏览器会开启一个新的文档流,新的文档流就会把原来的内容全部覆
    • 此外,document.write创建元素时,只能创建到body里
    • 因此, 在实际工作中,创建元素时不会用document.write,因为它不好用,而且可能会把原来网页内所有的内容都覆盖掉
  • innerHTML

    • 如果直接用等号(=),也会覆盖原来的值
    • 如果用加等(+=),从视觉角度没有覆盖,就是增加新元素
    • 但是从原理角度:本质上是把原来的元素取出来,再和新的元素一起添加进去,其实也是覆盖了原来的内容,它会让原来的元素事件失效
    • 就像正品和高仿,就算再像,也不是同一个
  • createElement

    • 就是单纯的新增一个元素,不会影响原来的内容,最好用

DOM之增:就是新增元素节点

简单到令人发指:

1、利用appendChild和insertBefore添加

  • appendChild增到到最后面,即追加:父元素.appendChild(标签名) 注意:这个标签名是变量,不能加引号

  • insertBefore增加到某一子元素前面,即插入:父元素.insertBefore(标签名,在哪个元素之前插入)

  • 细节

    • 以上两种方式,如果添加的是新创建的元素,就是添加;
    • 如果添加的是已经存在的元素,那么都相当于是移动;

2、以克隆方式新增:cloneNode(),分浅克隆和深克隆

  • 浅克隆:cloneNode(),就像山寨,只克隆样式(即标签),连内容都没有,一看就是假的

  • 深克隆:cloneNode(true),就像高仿,虽然克隆了样式和内容,但是没有方法,仔细看还是能看出来是假的

  • 两种方式的克隆,都不包含事件

DOM之删:就是删除元素节点

  • 父元素.removeChild(要被删除的标签名)

DOM之查:就是获取页面的元素节点

​ 常见的方法有2个:

1、传统方法获取页面元素:

​ 就是DOM提供的API方法,现在不常用

  • 获取单个DOM对象:document.getElementById('id')
    • 要传入的是字符串,直接写id名不用加#
    • document不能换成其他标签,因为ID具有唯一性
  • 获取DOM伪数组
    • document.getElementsByTagName('标签名') // 要传入的是字符串
    • document.getElementsByClassName('类名') // 直接写类名不用加.
    • 这两个document都可以换成其他的标签

2、H5新方法: 推荐使用

  • 获取单个DOM对象:

    • document.querySelector('选择器名')// ID和类选择器要加#或. 如果有多个,只能找到第一个
    • document.body //获取body
    • document.head //获取head
    • document.documentElement //获取html
  • 获取DOM伪数组:

    • document.quertSelectorAll ('选择器名') // ID和类选择器要加#或.

3、利用节点操作获取元素

  • 获取父节点(父元素):子元素.parentNode

    • 只能得到1个元素节点,一是因为,文本节点不能做父亲,二是因为,亲爸爸只有一个,因此只能获取到一个父元素
  • 获取子元素:父元素.children

    • 默认得到伪数组
    • 如果想找第几个孩子,可以像找数组元素一样,用下标找,即父元素.children[下标]

DOM之改:就是修改页面的元素属性

1、访问和修改的方法

​ 由于获取的页面元素,本质上都是对象,因此用访问和修改对象属性的方法,来访问和修改页面元素的属性

  • 通过点语法:对象.属性名=‘属性值’

     img.src = './images/lyf.jpg'
    
  • 通过中括号: 对象['属性名']=‘属性值’

    img['title'] = '我是李易峰'
    

2、常被修改的元素属性:src、href、tittle等

3、修改普通元素内容:innerText和innerHTML

  • 相同点
    • 都能获取双标签元素的内容
    • 都能给双标签元素添加内容
  • 不同点
    • 获取内容时:innerText只能获取文本内容,innerHTML既能获取文本,也能获取标签
    • 添加内容时:如果内容中有标签,innerText会把标签当做文本一起添加进去,而innerHTML会把标签解析出来,比如h3,innerHTML会把标签内容解析为h3的格式

4、修改表单的属性:

  • 获取表单元素(单标签)的文本:对象名.value
  • 一些表单属性:disabled(是否禁用)、checked(单复选框是否选中)、selected(下拉菜单是否选中),这些属性,在js中是写上就代表使用了,因此修改的时候,赋值为true 或false即可

5、修改元素样式

  • document.style.样式名,如box.style.width='300px'

6、修改元素的类名

6.1 传统方法修改类名
  • className,如box.className=‘.red’
6.2 H5新增的操作元素类的方法——classList
  • 为什么有这个新增的方法?

    • 传统的className方法,添加和删除的操作都非常不方便

    • 添加的时候,如果直接用等号,会覆盖原来的样式;如果用加等,可以一直赋值相同的类名,但样式没有任何变化,如下图:

    • 删除的时候,如果用赋值为原值的方法进行删除,会把后来添加的所有样式,都删除;如果用replaca替换的方法进行删除,在行内样式中会产生很多空格,而且,如果有重复的样式名,只能删除一个,如图:

    • 因此,传统的className操作元素类的方法,非常之不方便

  • classList的一些方法:classList是一个伪数组,它保存当前这个元素所有的类

    • add()
      • 添加一个类
      • 如果要添加多个类,用逗号隔开,每个类都是独立的一个参数
    • remove()
      • 删除一个类
      • 如果要删除多个类,用逗号隔开,每个类都是独立的一个参数
    • contains()
      • 判断是否有某个类
      • 如果有得到true,如果没有得到false
    • toggle()
      • 切换一个类,原来有这个类就删除,原来没有这个类就添加
    • replace()
      • 替换一个类
      • 参数1:被替换的类
      • 参数2:要替换的新类

DOM之属性操作:主要是对自定义的属性进行操作

1、传统操作自定义属性的方法

  • 新增或设置DOM的属性值:setAttribute()
  • 获取DOM的属性值:getAttibute()
  • 移除DOM的属性值(自定义和非自定义都可以移除):removeAttibute()
  • 点语法只能拿到html中自带的属性,不能拿到自定义属性
  • 以上方法,只能操作单个属性,如果想将自定义属性,全部取出,就很麻烦,因此,H5引入了新的操作自定义属性的方法,即dateset()

2、H5新增的操作自定义属性的方法

  • 自定义属性写法规范:以用data-开头,方便识别哪些是自定义的属性
  • 方法:元素.dataset
    • 它是一个对象,可以获取所有以data-开头的自定义的属性
    • 获取单个属性,用元素.dataset.属性名
    • 设置元素属性,元素.dataset.属性名 = 数据

事件操作

👉事件:就是用户与页面的交互,用户做了什么,页面做了什么回应

1、什么是事件对象?

  • 其实就是一个对象。里面包含了事件触发时的一些信息,比如,是否按了alt键、鼠标点击的位置等

2、事件分类:0级事件和2级事件

  • 0级事件:
    • 用on开头,比如onclick;0级事件是DOM初稿中的方法
    • 删除事件:对象.onclick=null
  • 2级事件:第二版DOM中的新方法
    • 添加事件:对象.addEventListener('事件名',function(){})
    • 删除事件:对象.removeEventListener('事件名', function(){})
  • 删除时,用哪种方法添加的事件,就用哪种方式删除
  • 匿名函数添加的事件,不能删除原因详见《为什么匿名函数添加的事件,不能进行删除操作?》

3、事件三要素

  • 事件源:真正触发事件的元素

    • 获取事件源:e.target
  • 事件类型

    • 鼠标事件:

      • 鼠标点击:click
      • 鼠标双击:dblclick
      • 鼠标移入:mouseover
      • 鼠标移出:mouseout
    • 鼠标按下:mousedown

      • 鼠标弹起:mouseup
    • H5中的鼠标拖拽事件

      • 元素默认不可拖拽,要加拖拽属性,才能拖拽,即draggable='true'
      • 拖拽事件:
        • 给被拖拽的元素加的事件:dragstart(拖拽开始)、drag(拖拽中)、dragend(拖拽结束事件)
        • 给容器检测添加的事件:dragenter(拖拽进入事件)、dragleave(拖拽离开事件)
      • 拖拽悬停事件:dragover
        • 在容器范围内,鼠标拖拽并悬停,就会一直触发
        • 它的默认行为是禁止被拖放进来
      • 拖放事件:drop
        • 在容器范围内,且松手
        • 默认不触发,想触发,要先用dragover,阻止拖拽悬停的默认行为
    • 焦点事件:

      • 获得焦点:focus
      • 失去焦点:blur
    • 键盘事件:

      • 键盘按下:keydown和keypress,常用keydown
      • 键盘弹起:keyup,与keydown是一对,不区分大小写,不过滤功能键
  • 响应程序:就是做了什么回应,一般是个函数

4、事件流

👉定义:事件触发时会经历从上到下,再从下到上的流动过程

  • 事件流的三个阶段:
    • 捕获阶段:从上到下,需要写代码调用
    • 目标阶段:当前被触发的事件
    • 冒泡阶段:从下往上,默认存在

5、事件流的应用——事件委托

  • 什么是事件委托?

👉就是把事件交给其他元素来处理,即把事件绑定给其他元素,一般是父元素

  • 为什么要委托?

👉主要是为了减少事件的绑定的次数,提高运行性能。

  比如说,一个ul中有很多li,如果想点击每个li,就弹出这个li的内容,那传统的方法是,要遍历这个ul,找到每个li,给每个li添加点击事件,这样一方面会绑定很多事件,另一方面会降低运行性能;另外,如果后续这个ul中添加了很多新的li,那新增的li,是不会有点击事件的,因为,在给每个li添加事件时,获取的是当时ul中所有的li,后来新增的,不包含在内。 

  这种情况,就可以采用事件委托的方法,把这个点击事件,绑定给li的父亲ul。借用事件冒泡的优势,点击li的时候,li没有事件,会冒泡给父亲ul,ul再执行事件,显示每个事件源的innerText即可。

  • 事件委托的好处?

    由此,可以看出,事件委托主要有两大好处:

    • 减少事件的绑定的次数
    • 让不管是老元素,还是后续新增的元素,同样具有事件

6、事件中的常用方法:详见思维导图

7、关于坐标系的使用场景

  很多小伙伴在学习JS的时候,经常将e.pageX、e.screenX、e.clientX等概念,与三大家族混淆,不知道它们的区别及具体使用场景,在此做一个简要小结:

7.1、事件里的坐标系:e.pageX、e.screenX、e.clientX

  顾名思义,这些坐标系是用在事件里的,获取的是事件中鼠标的位置(坐标)。我们都知道,事件是用户在页面上做的一些操作(如鼠标点击、键盘按下等)后,页面给出的一些响应程序。事件中包含事件对象,即保存了事件触发时的一些信息。

  想要获取事件里的信息,需要在事件触发的函数(如onclick)内写一个形参,一般用e、ev、event等,这样,再触发的事件里,就可以用e.来获取事件触发时的一些信息。

  因此,e.pageX、e.screenX、e.clientX这些,其实都是事件对象中的方法, 主要作用是,获取事件内,鼠标的一些位置信息,如点击的时候,鼠标距离【页面】的距离(e.pageX、e.pageY)、鼠标距离【可视区】的距离(e.clientX、e.clientY)、鼠标距离【屏幕】的距离(e.screenX、e.screenY)

7.2、给元素使用的坐标系:三大家族

  与上述事件内的坐标系不同,三大家族系列,不是给事件对象用的,而是给元素自己用的,获取的是元素自己相对于页面(父盒子)、可视区和被卷去的距离,具体区别详见下图:

BOM对象的核心

  前文提到过,DOM是把整个文档页面当做一个对象进行操作,而BOM是,把整个浏览器当做一个对象来进行操作,比如前进、后退、页面跳转、刷新等。因此,BOM的核心操作,其实是对BOM中除Document以外的其他Window、Location、History等对象的操作。

Window

什么是Window?

👉 Window: 代表整个浏览器,它是浏览器的顶级事件,具体含义包括两个方面:

  • 页面上在全局作用域中用var声明的变量以及函数,都是window的属性方法,window可以省略
  • BOM和DOM都在window里

Window中的一些事件

  window本身,可以被看成一个对象,因此,它也有属性方法,也可以添加事件

1. 加载相关事件
  • load加载事件

    • 页面加载完成之后(DOM树创建完毕,内外部资源如图片等加载完毕),里面的代码才执行
    • 用法:window.onload 或者 window.addEventListener('load', 回调函数)
  • beforeunload :页面关闭之前执行的事件

  • unload:页面关闭时执行的事件,可以保存用户信息

2. resize尺寸改变事件
  • 用法:window.onresize
  • 动态监听/获取浏览器的【可视区】的尺寸
  • 应用场景:可实现响应式布局

window的一些属性

1. name属性
  • window.name
  • 特点是:不管给name赋什么值,最后都会转成【字符串】
2. innerWidth和innerHeight属性
  • window.innerWidth,获取整个浏览器【能显示内容】的宽
  • window.innerHeight,获取整个浏览器【能显示内容】的高
3. pageXOffset和pageYOffset属性
  • window.pageXOffset,获取浏览器【往左】滚出去的距离
  • window.pageYOffset,获取浏览器【往上】滚出去的距离

window的一些方法

  • open():打开一个新页面
  • close():关闭当前页面
  • scroll(x,y):设置页面滚动距离
  • 定时器
    • 开启:setInterval(回调函数, 间隔时间)、setTimeout(回调函数, 间隔时间)
    • 关闭:clearInterval( 定时器的id )、clearTimeout(( 定时器的id )
    • setInterval和setTimeout的用法一样,只不过setInterval是每隔一段时间,调用一次,有循环的作用,只要不手动关闭,会一直执行;而setTimeout执行一次就结束,因此主要用来设置延迟执行事件

Location对象

什么是Location对象?

👉 : 保存了浏览器的一些地址相关信息的对象,本质上也是一个对象,有自己的属性和方法

👉 : 对Location对象的操作,可以获得浏览器地址相关的一些信息

Location对象的一些属性

  • location.href, 获取完整网址,可设置,设置就是跳转
  • location.search ,获取问号及之后的信息,即用户输入或提交的信息
  • location.hash, 获取#号及之后的信息

Location对象的一些方法

  • assign(),等同于给location.href赋值,实现跳转功能

  • reload(),刷新

  • replace(),替换当前页面

History对象

什么是History对象?

👉 : 保存了浏览器的一些历史相关信息的对象,本质上也是一个对象,有自己的属性和方法

History对象的一些方法

  • history.back() 去上一个页面
  • history.go() 去后退几个数字的页面
  • history.forward() 前进一个页面

Navigator对象

👉 : 保存了浏览器关于的定位相关信息的对象,获取你所在位置的经纬度

浏览器本地存储

  • 本地存储、浏览器存储、浏览器缓存,都是一个意思,都是只讲数据存储在浏览器本地。
  • 浏览器本地存储有三种方法,分别是:localStorage、sessionStorage和cookie

1、LocalStorage

  • 特点:默认看不到数据,要想看,去控制台的Application——localStorage找
  • 方法
    • localStorage.setItem('键','值')——提交设置、修改存储数据,参数1是属性名,参数2是属性值
    • localStorage.setItem('键')——获取存储的数据,只有一个参数,即属性名,得到结果是属性值
    • localStorage.removeItem('键')——删除存储的数据,只有一个参数,即通过属性名,一个一个删除整条数据
    • localStorage.clear()——清除存储的数据,没有参数,调用后立即清空所有存储的数据

2、SessionStorage

  • 特点:默认看不到数据,要想看,去控制台的Application——sessionStorage找

  • 方法:跟localStorage一模一样

    • sessionStorage.setItem('键','值')——提交设置、修改存储数据,参数1是属性名,参数2是属性值
    • sessionStorage.setItem('键')——获取存储的数据,只有一个参数,即属性名,得到结果是属性值
    • sessionStorage.removeItem('键')——删除存储的数据,只有一个参数,即通过属性名,一个一个删除整条数据
    • sessionStorage.clear()——清除存储的数据,没有参数,调用后立即清空所有存储的数据
  • localStorage和sessionStorage应用实例代码:

<body>
  <button id="btn1">保存</button>
  <button id="btn2">获取</button>
  <button id="btn3">删除</button>
  <button id="btn4">清除</button>

  <script>
    // 保存数据到浏览器
    document.querySelector('#btn1').onclick = function () {

      // 参数1:key,相当于给数据起一个别名(以后就通过这个别名找到数据)
      // 参数2:value(真正存储的数据)
      localStorage.setItem('name', 'andy')
      localStorage.setItem('age', 16)
      localStorage.setItem('sex', '男')

      // 只能存字符串,如果你强行传入别的数据,它会调用toString方法转成字符串
      // 再来存储,这个时候再取出来无法恢复成原来的数据
      localStorage.setItem('array', [10, 20, 30])
      localStorage.setItem('obj', {name:'jack', age:16} )

      console.log([10,20,30].toString())
      console.log({name:'jack', age:16}.toString())
    }

    // 获取
    document.querySelector('#btn2').onclick = function () {

      // 传入key,它就根据这个key取出对应的值
      let res = localStorage.getItem('name')
      alert(res)

      console.log(localStorage.getItem('age'))
      console.log(localStorage.getItem('sex'))

      // 如果访问不存在的数据,得到null
      console.log(localStorage.getItem('shengao'))

      console.log(localStorage.getItem('array'))
      console.log(localStorage.getItem('obj'))
    }


    // 删除
    document.querySelector('#btn3').onclick = function () {

      // 删除name对应的数据
      localStorage.removeItem('name')
      localStorage.removeItem('age')
      localStorage.removeItem('sex')
    }

    // 清除
    document.querySelector('#btn4').onclick = function () {

      localStorage.clear()
    }
  </script>

3、localStorage和sessionStorage的异同点

  • 相同点

    • 都是按域名保存起来的,如果多个网页,保存在同一个域名内,那网页之间,可以访问这些数据;但不同域名内的网页,不能访问
    • 保存数据默认都是字符串的形式,所以,默认取出来的也是字符串
    • 因此,如果要保存非字符串的数据,可以先把数据用JSON.stringify转换成JSON字符串,然后进行保存
    • 取出时,再用JSON.parse转成JS相应的数据
  • 不同点

    • localStorage:只要不手动删除,会永久保存
    • sessionStorage:只要关闭当前的网页,存储的内容就会被清空,因此也可以叫临时存储会话存储