JS基础知识储备(DOM与BOM)

一、背景

1、浏览器组成

JAVAScript 有三部分构成,ECMAScript,DOM和BOM,根据浏览器的不同,具体的表现形式也不尽相同。

  1. DOM是 W3C 的标准; [所有浏览器公共遵守的标准]
  2. BOM 是 各个浏览器厂商根据 DOM在各自浏览器上的实现;[表现为不同浏览器定义有差别,实现方式不同]
  3. window 是 BOM 对象,而非 js 对象;

2、DOM和BOM

  • DOM:DOM(文档对象模型)是 HTML 和 XML 的应用程序接口(API)。
  • BOM:BOM 主要处理浏览器窗口和框架,不过通常浏览器特定的 JavaScript 扩展都被看做 BOM 的一部分。而这些扩展则包括:
    (1)弹出新的浏览器窗口
    (2)移动、关闭浏览器窗口以及调整窗口大小
    (3)提供 Web 浏览器详细信息的定位对象
    (4)提供用户屏幕分辨率详细信息的屏幕对象
    (5)对 cookie 的支持
    (6)IE 扩展了 BOM,加入了 ActiveXObject 类,可以通过 JavaScript 实例化 ActiveX 对象

3、两者联系

javacsript是通过访问BOM(Browser Object Model)对象来访问、控制、修改客户端(浏览器),由于BOM的window包含了document,window对象的属性和方法是直接可以使用而且被感知的,因此可以直接使用window对象的document属性,通过document属性就可以访问、检索、修改XHTML文档内容与结构。因为document对象又是DOM(Document Object Model)模型的根节点。可以说,BOM包含了DOM(对象),浏览器提供出来给予访问的是BOM对象,从BOM对象再访问到DOM对象,从而js可以操作浏览器以及浏览器读取到的文档

网上抠了两张图(哈哈哈):


image
image

二、DOM基本操作

image

接下来我们来具体学习一下DOM的基本操作

(一)定义

DOM:Document Object Model,即文档对象模型。DOM 里边定义了一系列方法,是用 来操作 html 和 xml 功能的一类对象的集合,也有人称 DOM 是 html 和 xml 的标准编程 接口。

注意:我们所说的改变不了 css 指的是改变不了 css 样式表,但是我们可以改变 HTML 的行间样式,也就是说我们可以通过间接地改变行间样式来改变他

(二)DOM选择器、节点类型和属性

1、DOM选择器(方法类操作)

(1) document 代表整个文档

document 是一个对象,这个对象上边有一些属性和方法,单独的 document 就代表的是整个文档在 js 里的显示形式,我们现在所说的整个文档最顶级的标签看上 去好像是 html 标签,但是如果在 html 标签外边再套一个标签的话,这个标签就是 document,就是说 document 才是真正的代表整个文档,html 只是他下面的一个根标签。

(2)id 选择器

这个选择器和 css 里边讲的极其类似,比如说你在 html 里边写上一个

123
,然后在 js 里 var div = document.getElementById("only")

(3)标签选择器

比如说你在 HTML 里边写一个

123
,然后在 js 里 var div = document.getElementsByTagName("div")

注意:document.getElementsByTagName("*"),里边可以写 *,和通配符一样,选中了所有标签

(4)class选择器

他和这个标签选择器差不多,选出来也是一组,你先

123
, 然后 var div = document.getElementsByClassName("demo")[0],此时访问 div 就得 到
123

注意:class 选择器在 ie8 和 ie8 以下的浏览器中没有,但是新版本是可以的, 所以在 js 里,class 选择器并没有标签选择器那么常用,标签选择器 getElementsByTagName 在任意一个浏览器里都好使。

(5)name选择器

比如,然后 var input = document.getElementsByName("abc")[0], 访问 input 就得到。需注意这个 name 属性只对部分元素生效,如 表单、表单元素、img 等,name 选择器很少用。

(6)css选择器

css 选择器能让我们在 js 里选择元素的时候和 css 里一样灵活,比如说:

然后在 js 里写上 var strong = document.querySelector("div span strong");括号 里写的东西就和 css 里选择标签的方法是一样的,此时访问 strong 就得到 ,这选的是一个,还有一个 var strong = document.querySelectorAll("div span strong");他选出来是一组,你再访问 strong 就得到 NodeList[strong]

注意:在 ie7 及以下的版本没有,这个对我们并没有什么影响,还有一个致命的问题,就是 这个 querySelector 和 querySelectorAll 选出来的东西不是实时的,在用法上就及其受局限,除非极特殊情况,你就想选他的副本保存起来才会用这个,否则的话我 们不用。

2、遍历节点树(非方法类操作)

(1)parentNode:父节点

比如说:

现在访问 strong.parentNode 就是他的父节点,得到

...
,继续 strong.parentNode.parentNode 就是 div 的父亲,即...,继续就是 body 的父级,即..., 再就是 HTML 的父级,即 #document,再继续就得到 null,这就说明 document 就到顶层了,他是顶层的父级节点,代表整个文档

(2)childNodes 子节点们

遍历的是节点树,并不是只有元素节点算节点,节点的类型有很多,它包括文本节点、元素节点、属性节点、注释节点 等
比如:

访问 div.childNodes 就得到 odeList(7)[text, comment, text, strong, text, span, text],可见他有七个节点:依次是文本节点、注释节点、文本节点、元素节点、 文本节点、元素节点、文本节点。(空格文本和文本和回车文本都写在一起,就是一个文本 节点

(3)firstChild/lastChild 第一个子节点/最后一个子节点

例如上边的,现在 div.firstChild 就是文本 123 和回车,div.lastChild 就是#text, 还是文本节点,只不过是空的,他就那样显示了。

(4)nextSibling/previousSibling 后一个兄弟节点/前一个兄弟节点

还是上边的,var strong = document.getElementsByTagName("strong")[0];,然后 strong.nextSibling 就得到#text

3、遍历元素节点树

这几种方法只遍历的是元素节点,其他的都不掺杂了。比如说:

123

(1)parentElement 元素父节点

在 js 里 var div = document.getElementsByTagName("div")[0]; 然 后 div .parentElement 他 的 元 素 父 节 点 就 是 ... , 然 后 div.parentElement.parentElement,就是 body 的元素父节点,即..., 再父节点就是 null,因为 document 不叫 元素节点,他自称为一个节点,所以 parentNode 和 parentElement 的区别就在于有没有document。

(2)children 元素子节点

现在 div.children 就得到 HTMLCollection(2)[strong,span],只有两个。

(3)childElementCount === children.length 元素子节点的个数

比如说 div.childElementCount 就得到 2,你 div.children.length 也是 2,两者用法一样

(4)firstElementChild/lastElementChild 第一个元素子节点/最后一个元素子节 点

现在 div.firstElementChild 就是,div.lastElementChild 就是

(5)nextElementSibling/previousElementSibling 后一个兄弟元素节点/前一个兄 弟元素节点

strong.nextElementSibling 就得到

备注:以上遍历节点树的方法所有浏览器都兼容,但是遍历元素节点树的方法除 children 以外,ie9 及以下的浏览器都不兼容。

4、节点的四个属性

123

(1)nodeName 元素的标签名,只读

var div = document.getElementsByTagName("div")[0];
你访问 document.nodeName 就得到 "#document",
访问 div.firstChild.nodeName,他是一个文本节点,就得到"#text",
继续 div.childNodes[1].nodeName,第二个子节点是注释,就得到"#comment", div.childNodes[3].nodeName 是元素节点,就得到"STRONG"。返回的是一个字符串, 只读就是只能读取不能写入,比如说我把 div.childNodes[3].nodeName = "abc",再 访问的话还是"STRONG"。

(2)nodeValue text节点或comment节点的文本内容,可读写

这个属性只有文本节点和注释节点才有,
访问 div.childNodes[0].nodeValue 就得到 文本的回车和 123,这个可以改,你 div.childNodes[0].nodeValue = "234",
再访问 的话就是"234",再比如访问 div.childNodes[1].nodeValue 就得到" this is comment! ",如果 div.childNodes[1].nodeValue = " that is comment! ",再访问 div.childNodes[1]就是。

(3)nodeType 该节点的类型,只读

比如说现在有一个节点,我也不知道里边是什么节点,就有他来分辨,每一个节点都 有这个 nodeType 属性,你调用他返回的是一个具体的值:
元素节点是 1,
属性节点是 2,
文本节点是 3,
注释节点是 8,
document 节点是 9,
DocumentFragment(文档碎片节点)是 11

现在 document.nodeType 就是 9,div.childNodes[1].nodeType 第二个是 注释节点就返回 8,div.childNodes[0].nodeType 文本节点返回 3, div.childNodes[3].nodeType 元素节点就是 1。

练习题
还是上边的例子,现在封装一个方法,要求返回 div 的直接子元素节点,但是 不允许用 children。

function retElementChild(node) { 
  var temp = {
              length: 0,
              push: Array.prototype.push, 
              splice: Array.prototype.splice
              },
      child = node.childNodes, len = child.length;
      for (var i = 0; i < len; i++) { 
        if (child[i].nodeType === 1) {
            temp.push(child[i]); 
            }
        }
    return temp;
 }

(4)attributes 元素节点的属性集合

这个属性就是访问元素的属性节点的,


然后继续把 div 选出来:var div = document.getElementsByTagName("div")[0];现 在 div.attributes 就得到 NamedNodeMap{0: id, 1: class, id: id, class: class, length: 2},
访问 div.attributes[0]就得到 id="only",
访问 div.attributes[1]就 是 class="demo"。
div.attributes[1].nodeType 就得到 2. 你也可以把他的属性名和属性值都取出来,
div.attributes[0].name 就得到"id",
div.attributes[0].value 就得到"only"。
属性名是只读的,属性值可读可写。

5、节点的一个方法 Node.hasChildNodes()

这个方法就是判断这个元素有没有子节点,现在 div.hasChildNodes()就返回 true, 因为空格回车文本也算文本节点,除非

的话, 访问 div.hasChildNodes()是 false,中间啥都没有(属性节点不算,他是 div 自己的, 不能算子节点)才能返回 false。

(三)DOM 继承树及基本操作

1、DOM继承树

image
  • 首先我们知道 document 代表的是整个文档,访问就得到#document,但是我访问 Document 就得到ƒ Document() { [native code] },是一个构造函数。假如说你给 Document 的原型上加一些属 性的话,比如 Document.prototype.abc = "abc",那么 document 就能继承他的属性, 你访问 document.abc 就得到"abc",上边的结构树就表示这一系列继承的关系。
  • 第二个 CharacterData 下边有 Text 和 Comment,这就说明文本节点能用的属性和 方法全部继承自 Text.prototype,注释节点的方法则全部继承自 Comment.prototype, 然后一层一层向上继承。
  • 第三个最长,Element 下边有一个 HTMLElement(那下边就肯定还有一个 XMLElement, 只不过没写),然后在下边有一堆东西,最后还没写完,但是你会发现那一堆东西都是 一些标签能用的一些属性和方法
  • 现在 document.__ proto__得到 HTMLDocument{...},
    document.__ proto__.__ proto__就得到 Document{...},
    document.__ proto__.__ proto__.__ proto__得到 Node{...},
    document.__ proto__.__ proto__.__ proto__.__ proto__得到 EventTarget{...}
    document.__ proto__.__ proto__.__ proto__.__ proto__.__ proto__就得到 Object{}.
    这就说明 DOM 对象最终也继承自 Object.prototype,比如你访问 document.toString() 就得到"[object HTMLDocument]",这个 Object.prototype 是所有对象原型链上的终端。

2、DOM 结构树的应用

  1. getElementById 方法定义在 Document.prototype 上,即 Element 节点上不能使用
  2. getElementsByName 方法定义在 HTMLDocument.prototype 上,即非 html 中的 document 不能使用(xml 的 document 和 Element 不能用)。
  3. getElementsByTagName 方法定义在 Document.prototype 和 Element.prototype 上。
    解释:这个方法在两个地方都定义了,就都能用,比如说
1
  1. HTMLDocument.prototype 定义了一些常用的属性,body,head,分别指代 HTML 文 档中的和标签。
    解释:要选 head 标签或者 body 标签的话就直接 document.head 或者 document.body 就可以了。
  2. Document.prototype 上定义了 documentElement 属性,指代文档的根元素,在 HTML 文档中,他总是指代元素。
  3. getElementsByClassName、querySelectorAll、querySelector 这几个方法在 Document.prototype 和 Element.prototype 类中均有定义。

练习题1:遍历元素节点树(在原型链上编程)


    

练习题2:封装函数,返回元素 a 的第 n 层祖先元素节点


    

练习题3:编辑函数,封装 myChildren 功能,解决以前部分浏览器的兼容性问题。


    
蜡笔小新

练习题4:自己封装 hasChildren()方法,不可用 children 属性。


    
蜡笔小新

练习题5:封装函数,返回元素 e 的第 n 个兄弟元素节点,n 为正,返回后面的兄弟元素 节点,n 为负,返回前面的,n 为 0,返回自己。


    

3、DOM基本操作

上边讲的所有方法都是查看操作,下边讲其他的几个操作。

(1)增

  • 创建元素节点(即创建标签):document.createElement()
  • 创建文本节点:document.createTextNode()
  • 创建注释节点:document.createComment()
  • 创建文档碎片节点:document.createDocumentFragment()

(2)插

  • appendChild():每个元素都有 appendChild 方法,这个方法就跟 push 方法一样。
    注意:appendChild 进行的是剪切操作
  • 父级.insertBefore(a, b):父级调用,里边传两个参数,意思是在 b 之前插入 a

(3)删


    
  • parentNode.removeChild(a) 父级删除自己的子节点
    div.removeChild(i)后其实是把 i 标签剪切出来了,比如说上边的代码刷新后 var ii = div.removeChild(i), 你在访问 ii 就是
  • a.remove()
    比如上边代码刷新后 i.remove();strong.remove();div 里边就只剩下 span 了,remove 是真正的删除,删掉后就啥都没有了。

(4)替换

  • parentNode.replaceChild(new, origin):这个也是父级调用,里边传入两个参数,第一个是 new 就是新的,第二个是目标

4、Element 节点的一些属性

(1)innerHTML

这个属性可以改变 html 里的内容,这个 innerHTML 取得是 HTML 结构,是可读写的,所以你写进去什么东西他都会识别成 HTML 结构。

(2)innerText

这个赋值的话里边的东西就全部被覆盖了,所以在用这个方法的时候需要谨慎, 如果标签底下有其他子标签,最好在赋值的时候不要用这个。
还有这个 innerText 老 版本的火狐浏览器不兼容,当时火狐有一个属性 textContent 和这个作用是一样的,但是火狐这个方法老版本的 ie 不好使(都是老版本的兼容性问题,现在的新版本不存 在不兼容的)。

5、Element 节点的一些方法


    

(1)元素.setAttribute() 添加属性

现在 div.setAttribute("class","demo"),括号里第一个是属性名,第二个是属性值, 在访问 div 就得到

,继续 div.setAttribute("id","only"), 在访问 div 就是

(2)元素.getAttribute() 查看属性

接着上边的,现在 div.getAttribute("id")就得到"only",div.getAttribute("class") 就得到"demo"。
有了这些操作,就更灵活了,比如说我在 css 里定义了一个 class 样式,然后可以再 js 里动态的添加 class 属性,让这个样式作用在对应的元素上。

练习题1:封装函数 insertAfter(),功能类似 insertBefore(). 提示:可忽略老版本浏览器,直接在 Element.prototype 上编程。


    

练习题2:将目标节点的内部节点逆序


    

三、获取窗口属性、获取 DOM 尺寸、脚本化 CSS

1、查看滚动条的滚动距离

  • 标准方法 : window.pageXOffset window.pageYOffset

但是以上两种方法 ie8 及 ie8 以下浏览器不兼容,这些浏览器提供了两种方法:

  • document.body.scrollLeft(和 window.pageXOffset 效果一样)
    document.body.scrollTop(和 window.pageYOffset 效果一样)
  • document.documentElement.scrollLeft
    document.documentElement.scrollTop

注意:以上两种方法兼容性比较混乱,就是 ie8 及 ie8 以下的浏览器有的浏览器版本第一个 方法好使,有的版本第二个方法好使,但是任何一个浏览器版本只要一种方法好使, 返回的有值,另一种不好使的方法返回的一定是 0,所以咱们在 ie8 及 ie8 以下的浏览 器不管哪个版本直接把两个值相加就行了。

练习题:封装兼容性方法,求滚动条滚动距离 getScrollOffset()

function getScrollOffset() {
            if (window.pageXOffset) {
                return {
                    x: window.pageXOffset,
                    y: window.pageYOffset
                }
            } else {
                return {
                    x: document.body.scrollLeft + document.documentElement.scrollLeft,
                    y: document.body.scrollTop + document.documentElement.scrollTop
                }
            }
        }

2、查看可视区窗口尺寸

可视区窗口就是咱们编写的 html 文档能看到的部分,不包括菜单栏、地址栏和控制台。

  • 标准方法:window.innerWidth window.innerHeight

注意:以上两个方法还是 ie8 及 ie8 以下版本不兼容。这些浏览器版本提供了 两种方法,一种是在标准模式下用的,一种是在怪异模式下用的。

备注:什么是怪异模式?比如说在很久以前 ie7 还没有诞生,我写了一个页面,语法 全部用的是 ie6 的语法,但是一年之后 ie7 诞生了,人们都开始用新的浏览器,那么 我之前写的那个页面的部分语法就不能用了,因为有冲突,重写的话又太浪费时间, 后来人们研究了一种新的渲染模式叫怪异模式,比如说现在我启动了怪异模式,在这 个模式下即使人们用的是 ie7 浏览器,他也能根据 ie6 的语法把这个页面渲染出来, 这个怪异模式也叫混杂模式,这个模式一经启动他识别的就不是现在的语法而是之前 的语法,起到了一个向后兼容的作用,兼容之前的语法。那么怎么启用怪异模式?其 实我们在讲 html 的时候他第一行应该是,这个我们一直没说,其实 有这一行就是标准模式,要想启动怪异模式,直接把这一行删掉即可。

  • 第一种:标准模式下,任意浏览器都能兼容
    document.documentElement.clientWidth
    document.documentElement.clientHeight
  • 第二种:适用于怪异模式下的浏览器
    document.body.clientWidth
    document.body.clientHeight

怎么区分标准模式和怪异模式?
document 上有个属性 compatMode,在标准模式下访问 document.compatMode 得"CSS1Compat",在怪异模式下访问 document.compatMode 得 "BackCompat"。

练习题:封装兼容性方法,返回浏览器视口尺寸 getViewportOffset()

function getViewportOffset() {
            if (window.innerWidth) {
                return {
                    w: window.innerWidth,
                    h: window.innerHeight
                }
            } else {
                if (document.compatMode === "BackCompat") {
                    return {
                        w: document.body.clientWidth,
                        h: document.body.clientHeight
                    }
                } else {
                    return {
                        w: document.documentElement.clientWidth,
                        h: document.documentElement.clientHeight
                    }
                }
            }
        }

3、查看元素几何尺寸

  • dom.getBoundingClientRect()

返回的是一个对象,对象里边有 left、top、right、bottom 等属性,left 和 top 代表该元素左上角的 X 和 Y 坐标,right 和 buttom 代表元素右下角的 X 和 Y 坐标, 这个方法兼容性很好,但是老版本的 ie 里并没有 height 和 width,所以我们在老版本 的 ie 只能计算来求宽高,而且这个方法返回的结果并不是实时的,比如说我 var box = div. getBoundingClientRect();div.style.width = "200px",再访问 box 的话里边 的 width 还是 100px,所以他不是实时的。

4、查看元素的尺寸

  • dom.offsetWidth dom.offsetHeight

5、查看元素的位置

  • dom.offsetLeft dom.offsetTop

6、滚动条滚动

window上有三个方法:

  • window.scroll()
  • window.scrollTo()
  • window.scrollBy()

用法都是将 x、y 坐标传入,即实现让滚动条滚动到当前位置。
window.scroll()和 window.scrollTo()两个方法完全一样,兼容性也一样,比如说 window.scroll(0,100)他就能让 y 方向的滚动条滚动到 100 像素这个位置,你继续 window.scroll(0,100)他是不变的,说明他是让滚动条滚动到当前位置。
而 window.scrollBy()是累加滚动当前距离,比如说 window.scrollBy(0,10)他是让滚 动条向下滚动 10 像素,继续 window.scrollBy(0,10)就继续向下滚动 10 像素,继续 window.scrollBy(0,-10)就又向上滚动 10 像素。

练习题:模仿手机阅读器,做一个自动阅读的功能


    
start
stop

7、脚本化css

(1)读写元素css属性 dom.style

任何一个 dom 元素都会有 style 属性,我们在控制台访问 div.style 得到,这个 dom.style 没有任何兼容性问题,但是注意碰到 float 这样的保留字属性,前边 应该加上 css,如 div.style.cssFloat = "right"。还有就是复合属性(如 border) 最好把他拆解开设置,但是现在写在一起也是可以的,最好把他拆解开。

(2)查询计算样式

  • window.getComputedStyle(dom,null)

这个方法需要传入两个参数,第一个是 dom 元素,第二个是 null,返回的也是一个样式表,但是和上边的不一样,style 里读的只是行间里的样式,假如说你在行间没有设置的话他就没有值,但是这个方法返回的属性里即使你没有设置他也是有值的,是默认值,这个方法获取的是当前元素所展示的一切 css 的显示值, 就是假如说你通过多个选择器给一个元素设置了一个属性,那么只有权重最高的那个 起作用,而这个方法获取的只是那个起作用的也就是显示的那个值和一些默认值。这个方法 ie8 及 ie8 以下不兼容。

这个方法第二个参数是干嘛的吗?为啥要传 null 呢?第二个参数传对了,可以获取伪元素的样式

(3)查询样式

  • dom.currentStyle (ie 独有的属性)

这个是 ie 独有的属性,他也能返回一个样式表,和 window.getComputedStyle()方法 类似,也是只能读取不能写入,他获取的也是最终展示的那个值,但是他返回的计算 样式的值不是经过转换的绝对值,写啥就展示啥。

练习题:做一个小木块运动。


    

你可能感兴趣的:(JS基础知识储备(DOM与BOM))