JavaScript学习笔记--BOM&&DOM(JavaScript高级程序设计第4版)

BOM

window对象

window对象代表浏览器实例。window对象在浏览器中有两重身份,一个是ECMAScript中的Global对象,另一个就是浏览器窗口的JavaScript接口。这意味着网页中定义的所有对象、变量和函数都以window作为其Global对象

Global对象作用域

  • 通过var声明的所有全局变量和函数都会变成window对象的属性和方法。如果在这里使用let或const替代var,则不会把变量添加给全局对象。
  • 可以在window对象上查询是否存在可能未声明的变量。
  • js中有很多对象暴露在全局作用域内,作为window的属性,例如locationhistory

窗口关系

  1. top指向最外层窗口,即浏览器窗口本身
  2. parent对象指向当前窗口的父窗口,如果是最外层就等于top
  3. shelf对象始终指向window

窗口位置与像素比

现代浏览器提供了screenLeftscreenTop属性,用于表示窗口相对于屏幕左侧顶部的位置,返回值的单位是CSS像素

窗口大小

  • innerWidth、innerHeight、outerWidth和outerHeight。
  • outerWidth和outerHeight返回浏览器窗口自身的大小(不管是在最外层window上使用,还是在窗格<frame>中使用)。
  • innerWidth和innerHeight返回浏览器窗口中页面视口的大小(不包含浏览器边框和工具栏)。
  • document.documentElement.clientWidth和document.documentElement.clientHeight返回页面视口的宽度和高度

视口位置

  • 度量文档相对于视口滚动距离的属性有两对,返回相等的值:window.pageXoffset/window. scrollX和window.pageYoffset/window.scrollY。
  • 可以使用scroll()、scrollTo()和scrollBy()方法滚动页面。

导航与打开新窗口

window.open()方法可以用于导航到指定URL,也可以用于打开新浏览器窗口。这个方法接收4个参数:要加载的URL、目标窗口、特性字符串和表示新窗口在浏览器历史记录中是否替代当前加载页面的布尔值。

  • 如果window.open()的第二个参数不是已有窗口,则会打开一个新窗口或标签页。
  • 第三个参数,即特性字符串,用于指定新窗口的配置。特性字符串是一个逗号分隔的设置字符串,如果没有传第三个参数,则新窗口(或标签页)会带有所有默认的浏览器特性(工具栏、地址栏、状态栏等都是默认配置)。
  • window.open(“http://www.wrox.com/”,“wroxWindow”,“height=400, width=400, top=10,left=10, resizable=yes”);
  • window.open()方法返回一个对新建窗口的引用。

定时器

JavaScript在浏览器中是单线程执行的,但允许使用定时器指定在某个时间之后每隔一段时间就执行相应的代码。setTimeout()用于指定在一定时间后执行某些代码,而setInterval()用于指定每隔一段时间执行某些代码。

setTimeout()
  • setTimeout()方法通常接收两个参数:要执行的代码在执行回调函数前等待的时间(毫秒)
  • 不通常的:setTimeout接受从第三个参数之后的所有参数并且传递作为函数入参传递给第一个参数(如果第一个参数是函数)
  • this指针。所有超时执行的代码(函数)都会在全局作用域中的一个匿名函数中运行,因此函数中的this值在非严格模式下始终指向window,而在严格模式下是undefined。如果给setTimeout()提供了一个箭头函数,那么this会保留为定义它时所在的词汇作用域。

第二个参数是要等待的毫秒数,而不是要执行代码的确切时间。
JavaScript是单线程的,所以每次只能执行一段代码。为了调度不同代码的执行,JavaScript维护了一个任务队列。其中的任务会按照添加到队列的先后顺序执行。setTimeout()的第二个参数只是告诉JavaScript引擎在指定的毫秒数过后把任务添加到这个队列。如果队列是空的,则会立即执行该代码。如果队列不是空的,则代码必须等待前面的任务执行完才能执行。
调用setTimeout()时,会返回一个表示该超时排期的数值ID。这个超时ID被排期执行代码的唯一标识符可用于取消该任务。要取消等待中的排期任务,可以调用clearTimeout()方法传入超时id

let timeoutId = setTimeout(() => alert("Hello world! "), 1000);
// 取消超时任务
clearTimeout(timeoutId);

location对象

location是最有用的BOM对象之一,提供了当前窗口中加载文档的信息,以及通常的导航功能。这个对象独特的地方在于,它既是window的属性也是document的属性。也就是说,window.location和document.location指向同一个对象。
假设浏览器当前加载的URL是http://foouser:barpassword@www.wrox.com:80/WileyCDA/?q=javascript#contents, location对象的内容如下表所示

属性意义
location.hash“#contents ”URL散列值,#号后面跟0或多个字符串,如果没有则为空字符串
location.hostwww.wrong.com:80服务器名及端口号
location.hostnamewww.wrong.com服务器名
location.hrefhttp://foouser:barpassword@www.wrox.com:80/WileyCDA/?q=javascript#contents当前加载页面的完整URL。location的toString方法返回这个值
location.pathname/WileyCDA/资源路径名
location.port80端口号
location.protocolhttp协议
location.search?q=javascript查询字符串
location.usernamefoouser域前用户名
location.passwordbarpassword域前指定密码
location.originhttp://www.wrong.comURL源地址

查询字符串

location.search返回了从问号开始直到URL末尾的所有内容,

let getQueryStringArgs = function() {
    // 取得没有开头问号的查询字符串      
    let qs = (location.search.length > 0 ? location.search.substring(1) : ""),
    // 保存数据的对象        
    args = {};      
    // 把每个参数添加到args对象      
    for (let item of qs.split("&").map(kv => kv.split("="))) {        
        let name = decodeURIComponent(item[0]),          
        value = decodeURIComponent(item[1]);       
        if (name.length) {          
            args[name] = value;       
        }      
    }      
    return args;    
}

操作地址

  • 可以通过修改location对象修改浏览器的地址。首先,最常见的是使用assign()方法并传入一个URL,location.assign(“http://www.wrox.com”);这行代码会立即启动导航到新URL的操作,同时在浏览器历史记录中增加一条记录。如果给location.href或window.location设置一个URL,也会以同一个URL值调用assign()方法。
  • 修改location对象的属性也会修改当前加载的页面。其中,hash、search、hostname、pathname和port属性被设置为新值之后都会修改当前URL除了hash之外,只要修改location的一个属性,就会导致页面重新加载新URL。注意 修改hash的值会在浏览器历史中增加一条新记录
  • 如果不希望增加历史记录,可以使用replace()方法。这个方法接收一个URL参数,但重新加载后不会增加历史记录。调用replace()之后,用户不能回到前一页
  • 最后一个修改地址的方法是reload(),它能重新加载当前显示的页面。调用reload()而不传参数,页面会以最有效的方式重新加载。如果页面自上次请求以来没有修改过,浏览器可能会从缓存中加载页面。如果想强制从服务器重新加载,可以给reload()传个true

navigator对象

只要浏览器启用JavaScript, navigator对象就一定存在。但是与其他BOM对象一样,每个浏览器都支持自己的属性。navigator对象的属性通常用于确定浏览器的类型。

检测插件

navigator.plugins

操作系统navigator.platform

navigator.platform属性是一个字符串,通常表示浏览器所在的操作系统。

screen对象

window的另一个属性screen对象,是为数不多的几个在编程中很少用的JavaScript对象。这个对象中保存的纯粹是客户端能力信息,也就是浏览器窗口外面的客户端显示器的信息,比如像素宽度和像素高度。

history对象

history对象表示当前窗口首次使用以来用户的导航历史记录。因为history是window的属性,所以每个window都有自己的history对象。

导航

  • go()方法可以在用户历史记录中沿任何方向导航,可以前进也可以后退。这个方法只接收一个参数,这个参数可以是一个整数,表示前进或后退多少步。负值表示在历史记录中后退(类似点击浏览器的“后退”按钮),而正值表示在历史记录中前进(类似点击浏览器的“前进”按钮)
  • go()有两个简写方法:back()和forward()。顾名思义,这两个方法模拟了浏览器的后退按钮和前进按钮:

length属性

  • history对象还有一个length属性,表示历史记录中有多个条目。这个属性反映了历史记录的数量,包括可以前进和后退的页面。
  • 注意如果页面URL发生变化,则会在历史记录中生成一个新条目。对于2009年以来发布的主流浏览器,这包括改变URL的散列值(因此,把location.hash设置为一个新值会在这些浏览器的历史记录中增加一条记录)。这个行为常被单页应用程序框架用来模拟前进和后退,这样做是为了不会因导航而触发页面刷新

历史状态管理

state

小结

浏览器对象模型(BOM, Browser Object Model)是以window对象为基础的,这个对象代表了浏览器窗口和页面可见的区域

  1. 要引用其他window对象,可以使用几个不同的窗口指针。
  2. 通过location对象可以以编程方式操纵浏览器的导航系统。通过设置这个对象上的属性,可以改变浏览器URL中的某一部分或全部。
  3. 使用replace()方法可以替换浏览器历史记录中当前显示的页面,并导航到新URL。
  4. navigator对象提供关于浏览器的信息。提供的信息类型取决于浏览器,不过有些属性如userAgent是所有浏览器都支持的。
  5. BOM中的另外两个对象也提供了一些功能。screen对象中保存着客户端显示器的信息。这些信息通常用于评估浏览网站的设备信息。history对象提供了操纵浏览器历史记录的能力,开发者可以确定历史记录中包含多少个条目,并以编程方式实现在历史记录中导航,而且也可以修改历史记录。

客户端检测

能力检测

能力检测(又称特性检测)即在JavaScript运行时中使用一套简单的检测逻辑,测试浏览器是否支持某种特性。能力检测最适合用于决定下一步该怎么做,而不一定能够作为辨识浏览器的标志

用户代理检测

用户代理检测通过浏览器的用户代理字符串确定使用的是什么浏览器。用户代理字符串包含在每个HTTP请求的头部,在JavaScript中可以通过navigator.userAgent访问。

用户代理的历史

HTTP规范(1.0和1.1)要求浏览器应该向服务器发送包含浏览器名称和版本信息的简短字符串。

  1. Mosaic是早期Web浏览器的代表,其用户代理字符串相当简单,类似于:Mosaic/0.9
  2. Netscape Navigator 3和IE3。1996年,Netscape Navigator 3发布之后超过Mosaic成为最受欢迎的浏览器。Mozilla/Version(Platform; Encryption[; OS-or-CPUdescription])
  3. 3.Netscape Communicator 4和IE4~8
  4. Gecko。Gecko渲染引擎是Firefox的核心。Gecko最初是作为通用Mozilla浏览器(即后来的Netscape 6)的一部分开发的。有一个针对Netscape 6的用户代理字符串规范,规定了未来的版本应该如何构造这个字符串。
  5. WebKit。2003年,苹果宣布将发布自己的浏览器Safari。Safari的渲染引擎叫WebKit,是基于Linux平台浏览器Konqueror使用的渲染引擎KHTML开发的。
  6. Chrome。谷歌的Chrome浏览器使用Blink作为渲染引擎,使用V8作为JavaScript引擎。Chrome的用户代理字符串包含所有WebKit的信息,另外又加上了Chrome及其版本的信息。

浏览器分析

想要知道自己代码运行在什么浏览器上,大部分开发者会分析window.navigator.userAgent返回的字符串值。所有浏览器都会提供这个值,如果相信这些返回值并基于给定的一组浏览器检测这个字符串,最终会得到关于浏览器和操作系统的比较精确的结果。相比于能力检测,用户代理检测还是有一定优势的。能力检测可以保证脚本不必理会浏览器而正常执行。现代浏览器用户代理字符串使我们能够利用它们准确识别浏览器

小结

  • 能力检测,在使用之前先测试浏览器的特定能力。例如,脚本可以在调用某个函数之前先检查它是否存在。这种客户端检测方式可以让开发者不必考虑特定的浏览器或版本,而只需关注某些能力是否存在。能力检测不能精确地反映特定的浏览器或版本。
  • 用户代理检测,通过用户代理字符串确定浏览器。用户代理字符串包含关于浏览器的很多信息,通常包括浏览器、平台、操作系统和浏览器版本。用户代理字符串有一个相当长的发展史,很多浏览器都试图欺骗网站相信自己是别的浏览器。用户代理检测也比较麻烦,特别是涉及Opera会在代理字符串中隐藏自己信息的时候。即使如此,用户代理字符串也可以用来确定浏览器使用的渲染引擎以及平台,包括移动设备和游戏机。
  • 选择客户端检测方法时,首选是使用能力检测。特殊能力检测要放在次要位置,作为决定代码逻辑的参考。用户代理检测是最后一个选择,因为它过于依赖用户代理字符串。

DOM

文档对象模型(DOM, Document Object Model)是HTML和XML文档的编程接口。它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档。DOM表示由多层节点构成的文档,通过它开发者可以添加、删除和修改页面的各个部分。

节点层级

document节点表示每个文档的根节点。在HTML中,根节点的唯一子节点<html>元素,我们称之为文档元素(documentElement)。文档元素是文档最外层的元素,所有其他元素都存在于这个元素之内。每个文档只能有一个文档元素。在HTML页面中,文档元素始终是<html>元素

HTML中的每段标记都可以表示为这个树形结构中的一个节点元素节点表示HTML元素属性节点表示属性文档类型节点表示文档类型注释节点表示注释。DOM中总共有12种节点类型,这些类型都继承一种基本类型

Node类型

Node接口是所有DOM节点类型都必须实现的。Node接口在JavaScript中被实现为Node类型,JavaScript中,所有节点类型都继承Node类型,因此所有类型都共享相同的基本属性和方法。

值属性

  • nodetype属性,表示节点的类型,定义了12个常量(如Node.ELEMENT_NODE(1)、Node.ATTRIBUTE_NODE(2)…),结点类型可以通过与这些常量比较来确定
  • nodeName属性,节点的标签名字
  • nodeValue属性,对于一个元素来说,nodeName是标签名,nodeValue是null。

关系属性(关系指针)和方法

  • childNodes属性,其中包含一个NodeList实例,是一个类数组对象,可以用中括号访问,也可以用item()访问,具有length属性。NodeLisy对象独特的地方在于它其实是对一个DOM结构的查询,因此DON结构的变化会自动地在NodeList中反映出来,我们通常说NodeList是实时的活动对象,而不是第一次访问时所获得的内容的快照
    let firstChild = someNode.childNodes[0];
    let secondChild = someNode.childNodes.item(1);
    let count = someNode.childNodes.length;
  • parentNode属性,指向DOM树中的父元素childNodes中所有的节点都有同一个父元素,因此它们的parentNode属性都指向同一个节点。childNodes列表中的每个节点都是同一列表中其他节点的同胞节点。而使用previousSiblingnextSibling可以在这个列表的节点间导航
  • firstChildlastChild属性。父节点它的第一个及最后一个子节点也有专门属性:firstChild和lastChild分别指向childNodes中的第一个和最后一个子节点someNode.firstChild的值始终等于someNode.childNodes[0],而someNode.lastChild的值始终等于someNode.childNodes[someNode.childNodes.length-1]。如果只有一个子节点,则firstChild和lastChild指向同一个节点。如果没有子节点,则firstChild和lastChild都是null
  • hasChildNodes(),这个方法如果返回true则说明节点有一个或多个子节点。比使用length更方便
  • ownerDocument属性是一个指向代表整个文档的文档节点的指针。即返回document对象

操纵节点

  • appendChild(),用于在childNodes列表末尾添加节点。添加新节点会更新相关的关系指针,包括父节点和之前的最后一个子节点。appendChild()方法返回新添加的节点,如下所示:如果把文档中已经存在的节点传给appendChild(),则这个节点会从之前的位置被转移到新位置,一个节点不会在文档中同时出现两个或者更多地方,如果调用该方法传入父元素的第一个子节点,则该结点会成为父元素的最后一个节点。
    let returnedNode = someNode.appendChild(someNode.firstChild);   
    alert(returnedNode == someNode.firstChild);          // false 
    alert(returnedNode == someNode.lastChild);   // true
  • insertBefore()方法。这个方法接收两个参数:要插入的节点参照节点。插入的节点会变成参照节点的前一个同胞节点,并被返回。appendChild()和insertBefore()在插入节点时不会删除任何已有节点
  • replaceChild()方法接收两个参数:要插入的节点要替换的节点要替换的节点被返回从文档树中完全移除要插入的节点取而代之。使用replaceChild()插入一个节点后,所有关系指针都会从被替换的节点复制过来。
    // 替换第一个子节点
    let returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
    // 替换最后一个子节点
    returnedNode = someNode.replaceChild(newNode, someNode.lastChild);
  • removeChild()方法。这个方法接收一个参数,即要移除的节点。被移除的节点会被返回。
    上面介绍的4个方法都用于操纵某个节点的子元素,也就是说使用它们之前必须先取得父节点(使用前面介绍的parentNode属性)。

其他方法

  • cloneNode(),会返回与调用它的节点一模一样的节点。cloneNode()方法接收一个布尔值参数,表示是否深复制。在传入true参数时,会进行深复制,即复制节点及其整个子DOM树。如果传入false,则只会复制调用该方法的节点。复制返回的节点属于文档所有,但尚未指定父节点,所以可称为孤儿节点(orphan)。可以通过appendChild()、insertBefore()或replaceChild()方法把孤儿节点添加到文档中。cloneNode()方法不会复制添加到DOM节点的JavaScript属性,比如事件处理程序。这个方法只复制HTML属性,以及可选地复制子节点。
  • normalize()。这个方法唯一的任务就是处理文档子树中的文本节点,处理不包含文本的文本节点

document类型

Document类型是JavaScript中表示文档节点的类型。document是window对象的属性,因此是一个全局对象。document对象可用于获取关于页面的信息以及操纵其外观和底层结构

  • nodeType等于9;
  • nodeName值为"#document";
  • nodeValue值为null;
  • parentNode值为null;
  • ownerDocument值为null;
  • 子节点可以是DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction或Comment类型。

访问文档子节点

  • documentElement属性,始终指向HTML页面中的<html>元素
    let html = document.documentElement;      
    // 取得对<html>的引用    
    alert(html === document.childNodes[0]);   // true
    alert(html === document.firstChild);      // true
  • body属性,直接指向<body>元素document.body
    let body = document.body;
  • doctype属性<! doctype>标签是文档中独立的部分,其信息可以通过doctype属性(在浏览器中是document.doctype)来访问

文档信息

  • title,包含<title>元素中的文本,通常显示在浏览器窗口标签页的标题栏。通过这个属性可以读写页面的标题,修改后的标题也会反映在浏览器标题栏上。
// 修改文档标题 
document.title = "New page title";
  • URL,包含当前页面的完整URL(地址栏中的URL)
  • domain,包含页面的域名
  • referrer,包含链接到当前页面的那个页面的URL。如果当前页面没有来源,则referrer属性包含空字符串。
    所有这些信息都可以在请求的HTTP头部信息中获取,只是在JavaScript中通过这几个属性暴露出来而已
    // 取得完整的URL
    let url = document.URL;
    // 取得域名
    let domain = document.domain;    
    // 取得来源
    let referrer = document.referrer;

URL域名是相关的。比如,如果document.URLhttp://www.wrox.com/WileyCDA/,则document.domain就是www.wrox.com。这些属性中只有域名domain是可以修改,可以将域名修改为原域名中的子域名,不能给他设置为原域名中不包含的值,且一旦缩紧就不能够放松

定位(获取)元素

使用DOM最常见的情形可能就是获取某个或某组元素的引用,然后对它们执行某些操作。

  • getElementById()方法接收一个参数,即要获取元素的ID,如果找到了则返回这个元素,如果没找到则返回null。参数ID必须跟元素在页面中的id属性值完全匹配,包括大小写。如果页面中存在多个具有相同ID的元素,则getElementById()返回在文档中出现的第一个元素
  • getElementsByTagName()是另一个常用来获取元素引用的方法。这个方法接收一个参数,即要获取元素的标签名,返回包含零个或多个元素的NodeList。这个方法返回一个HTMLCollection对象,与NodeList相似,他还有一个namedItem()方法来通过标签的name属性取得某一项的引用
  • 要取得文档中的所有元素,可以给getElementsByTagName()传入*。在JavaScript和CSS中,*一般被认为是匹配一切的字符。
  • getElementsByName()。顾名思义,这个方法会返回具有给定name属性的所有元素。getElementsByName()方法最常用于单选按钮,因为同一字段的单选按钮必须具有相同的name属性才能确保把正确的值发送给服务器,

DOM兼容性检测

document.implementation属性是一个对象,其中提供了与浏览器DOM实现相关的信息和能力。DOM Level 1在document.implementation上只定义了一个方法,即hasFeature()。这个方法接收两个参数:特性名称和DOM版本。如果浏览器支持指定的特性和版本,则hasFeature()方法返回true,如下面的例子所示:

let hasXmlDom = document.implementation.hasFeature("XML", "1.0");

文档写入

网页输出流中写入内容。这个能力对应4个方法:

  • write()
  • writeln()
  • open()
  • close()
    其中,write()和writeln()方法都接收一个字符串参数,可以将这个字符串写入网页中。write()简单地写入文本,而writeln()还会在字符串末尾追加一个换行符(\n)。这两个方法可以用来在页面加载期间页面动态添加内容
document.write("<strong>" + (new Date()).toString() + "</strong>");

write()和writeln()方法经常用于动态包含外部资源,如JavaScript文件。在包含JavaScript文件时,记住不能这样直接包含字符串"</script>",因为这个字符串会被解释为脚本块的结尾,

Element类型

除了Document类型,Element类型就是Web开发中最常用的类型了。Element表示XMLHTML元素,对外暴露出访问元素标签名、子节点和属性的能力。

  • nodeType等于1;
  • nodeName值为元素的标签名;
  • nodeValue值为null;
  • parentNode值为Document或Element对象;
  • 子节点可以是Element、Text、Comment、ProcessingInstruction、CDATASection、EntityReference类型。
    可以通过nodeName或tagName属性来获取元素的标签名。这两个属性返回同样的值。先把标签名转换为全部小写后再比较。

HTML元素

继承的属性
HTMLElement直接继承Element并增加了一些属性。

  • id,元素在文档中的唯一标识符;
  • title,包含元素的额外信息,通常以提示条形式展示;
  • lang,元素内容的语言代码(很少用);
  • dir,语言的书写方向("ltr"表示从左到右,"rtl"表示从右到左,同样很少用);
  • className,相当于class属性,用于指定元素的CSS类(因为class是ECMAScript关键字,所以不能直接用这个名字)。
    let div = document.getElementById("myDiv");
    alert(div.id); 
    // "myDiv"    
    alert(div.className);   
    // "bd"   
    alert(div.title);        
    // "Body text"    
    alert(div.lang);         
    // "en"    
    alert(div.dir);          
    // "ltr"`

取得属性

每个元素都有零个或多个属性,通常用于为元素或其内容附加更多信息。与属性相关的DOM方法主要有3个:getAttribute()、setAttribute()和removeAttribute()。这些方法主要用于操纵属性,包括在HTMLElement类型上定义的属性

    let div = document.getElementById("myDiv");    
    alert(div.getAttribute("id"));      
    // "myDiv"    
    alert(div.getAttribute("class"));   
    // "bd"    
    alert(div.getAttribute("title"));   
    // "Body text"    
    alert(div.getAttribute("lang"));    
    // "en"    
    alert(div.getAttribute("dir"));     
    // "ltr"

通过DOM对象访问的属性中有两个返回的值跟使用getAttribute()取得的值不一样。首先是style属性,这个属性用于为元素设定CSS样式。在使用getAttribute()访问style属性时,返回的是CSS字符串。而在通过DOM对象的属性访问时,style属性返回的是一个(CSSStyleDeclaration)对象。DOM对象的style属性用于以编程方式读写元素样式,因此不会直接映射为元素中style属性的字符串值。

第二个属性其实是一类,即事件处理程序(或者事件属性),比如onclick。在元素上使用事件属性时(比如onclick),属性的值一段JavaScript代码。如果使用getAttribute()访问事件属性,则返回的是字符串形式的源代码。而通过DOM对象的属性访问事件属性时返回的则是一个JavaScript函数(未指定该属性则返回null)。这是因为onclick及其他事件属性是可以接受函数作为值的。

考虑到以上差异,开发者在进行DOM编程时通常会放弃使用getAttribute()而只使用对象属性。getAttribute()主要用于取得自定义属性的值。

setAttribute(),这个方法接收两个参数:要设置的属性名和属性的值。如果属性已经存在,则setAttribute()会以指定的值替换原来的值;如果属性不存在,则setAttribute()会以指定的值创建该属性

attributes属性

Element类型是唯一使用attributes属性的DOM节点类型。attributes属性包含一个NamedNodeMap实例,是一个类似NodeList的“实时”集合。元素的每个属性都表示为一个Attr节点,并保存在这个NamedNodeMap对象中

创建元素

可以使用document.createElement()方法创建新元素。这个方法接收一个参数,即要创建元素的标签名

    let div = document.createElement("div");

使用createElement()方法创建新元素的同时也会将其ownerDocument属性设置为document。此时,可以再为其添加属性、添加更多子元素。比如:

    div.id = "myNewDiv";
    div.className = "box";

在新元素上设置这些属性只会附加信息。因为这个元素还没有添加到文档树,所以不会影响浏览器显示。要把元素添加到文档树,可以使用appendChild()、insertBefore()或replaceChild()。比如,以下代码会把刚才创建的元素添加到文档的<body>元素中:

    document.body.appendChild(div);

元素被添加到文档树之后,浏览器会立即将其渲染出来。之后再对这个元素所做的任何修改,都会立即在浏览器中反映出来。

元素后代

childNodes属性包含元素所有的子节点,这些子节点可能是其他元素、文本节点、注释或处理指令。考虑到这种情况,通常在执行某个操作之后需要先检测一下节点的nodeType,如下所示:

    for (let i = 0, len = element.childNodes.length; i < len; ++i) {
        if (element.childNodes[i].nodeType == 1) {
            // 执行某个操作
        }
    }

以上代码会遍历某个元素的子节点,并且只在nodeType等于1(即Element节点)时执行某个操作。

要取得某个元素的子节点和其他后代节点,可以使用元素的getElementsByTagName()方法。在元素上调用这个方法与在文档上调用是一样的,只不过搜索范围限制在当前元素之内,即只会返回当前元素的后代。对于前面<ul>的例子,可以像下面这样取得其所有的<li>元素

    let ul = document.getElementById("myList");    
    let items = ul.getElementsByTagName("li");

这里例子中的<ul>元素只有一级子节点,如果它包含更多层级,则所有层级中的<li>元素都会返回。

Text类型

Text节点由Text类型表示,包含按字面解释的纯文本,Text类型的节点具有以下特征:

  • nodeType等于3;
  • nodeName值为"#text";
  • nodeValue值为节点中包含的文本;
  • parentNode值为Element对象;
  • 不支持子节点。
    Text节点中包含的文本可以通过nodeValue属性访问,也可以通过data属性访问,

DocumentType类型

DocumentType类型的节点包含文档的文档类型(doctype)信息,具有以下特征:

  • nodeType等于10;
  • nodeName值为文档类型的名称;
  • nodeValue值为null;
  • parentNode值为Document对象;
  • 不支持子节点。
    DocumentType对象保存在document.doctype属性中。

DOM编程

动态脚本

<script>元素用于向网页中插入JS代码,可以是arc属性包含的外部文件,也可以是作为该元素内容的源代码。注意通过innerHTML属性创建的<script>元素永远不会执行。

动态样式

css样式在HTML页面中可以通过两个元素加载,<link>元素用于包含CSS外部文件,<style>元素用于添加嵌入样式。动态样式也是页面初始加载时并不存在,而是在之后才添加到页面中的

使用NodeList

理解NodeList对象和相关的NamedNodeMap、HTMLCollection是理解DOM编程的关键。这三个集合类型都是实时的,意味着文档结构的变化会实时的在他们身上反映出来,他们的值始终代表最新的状态。因此最好限制操作NodeList的次数,因为每次查询都会搜索整个文档,最好把查询到的NodeList缓存起来。

MutationObserver接口

MutationObserver接口可以在DOM被修改时异步执行回调,使用它可以观察整个文档、DOM树的一部分

observer()方法
    //创建一个观察者并配置它观察body元素上的属性变化
    let observer = new MutationObserver(() => console.log('<body> attributes changed'));
    observer.observe(document.body,{attributes : true});

首先通过调用构造函数传入一个回调函数创建MutationObserver实例,该实例还不会关联DOM的任何部分,要把它与DOM关联起来,需要使用observe()方法,接收两个参数要观察其变化的DOM结点以及一个MutationObserverInit对象,MutationObserverInit对象用于控制观察哪些方面的变化,是一个键值对形式配置选项的字典。

执行上述代码后,body元素上任何属性发生变化都会被这个MutationObserver实例发现。需要注意的是,回调中的console.log是后执行的,表明回调并非与实际的DOM变化同步执行

小结

DOM由一系列节点类型构成,主要包括以下几种。

  • Node是基准节点类型,是文档一个部分的抽象表示,所有其他类型都继承Node。
  • Document类型表示整个文档,对应树形结构的根节点。在JavaScript中,document对象是Document的实例,拥有查询和获取节点的很多方法。
  • Element节点表示文档中所有HTML或XML元素,可以用来操作它们的内容和属性。
  • 其他节点类型分别表示文本内容、注释、文档类型、CDATA区块和文档片段。
    DOM编程在多数情况下没什么问题,在涉及<script>和<style>元素时会有一点兼容性问题。因为这些元素分别包含脚本样式信息,所以浏览器会将它们与其他元素区别对待。

    要理解DOM,最关键的一点是知道影响其性能的问题所在。DOM操作JavaScript代码中代价比较高的,NodeList对象尤其需要注意。NodeList对象是“实时更新”的,这意味着每次访问它都会执行一次新的查询。考虑到这些问题,实践中要尽量减少DOM操作的数量。

DOM操作的扩展

根据CSS选择符的模式匹配DOM元素。比如,jQuery就完全以CSS选择符查询DOM获取元素引用,而不是使用getElementById()和getElementByTagName()。

querySelector()

querySelector()方法接收CSS选择符参数,返回匹配该模式的第一个后代元素,如果没有匹配项则返回null。下面是一些例子:

    // 取得<body>元素
    let body = document.querySelector("body");
    // 取得ID为"myDiv"的元素    
    let myDiv = document.querySelector("#myDiv");    
    // 取得类名为"selected"的第一个元素    
    let selected = document.querySelector(".selected");    
    // 取得类名为"button"的图片    
    let img = document.body.querySelector("img.button");

querySelectorAll()

接收一个用于查询的参数,但它会返回所有匹配的节点,而不止一个。这个方法返回的是一个NodeList的静态实例
再强调一次,querySelectorAll()返回的NodeList实例一个属性和方法都不缺,但它是一个静态的“快照”,而非“实时”的查询。这样的底层实现避免了使用NodeList对象可能造成的性能问题。平常的NodeList是对元素的动态查询,如果进行新增Element可能会导致死循环。返回的NodeList对象可以通过for-of循环、item()方法或者中括号法取得个别元素

HTML5

  1. 包含了与标记相关的大量JavaScript API定义
  2. HTML5增加了一些特性以方便使用CSS,getElementsByClassName()
  3. HTML5增加了辅助DOM焦点管理的功能。首先是document.activeElement,始终包含当前拥有焦点的DOM元素
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值