在上一篇的分享当中,我们简单介绍了BOM 与DOM,也了解到JavaScript 是怎么通过它们提供的方法来与浏览器进行沟通。

当一个网页被载入到浏览器时,浏览器会首先分析这个HTML 文档,然后会依照这份HTML 的内容解析成DOM (Document Object Model,即文件对象模型)。

而DOM 是W3C 制定的一个规范,它是独立于平台与语言的标准。换言之,只要遵守这样的规范,不管是什么平台或者是什么语言开发,都可以通过DOM 提供的API 来操作DOM 的内容、结构与样式。

所以说,DOM 是网页的根本,懂得控制DOM就可以控制整个网页,做出良好的互动体验

那么在今天的分享中,我们就继续来介绍DOM API 查找节点的方法吧。

前言:<script>标签放哪里有区别吗?

针对<script>标签放哪里,这个题目其实没有标准答案,一般你会看到有两种版本:

  • 放在<head> ... </head>之间
  • 放在</body>之前

还有人会说为什么放在<head> ... </head>里面的JavaScript没有作用?我觉得这说的有出入,这里我们简单来讲一下问题所在。

那么我们来试试上一篇介绍过的,先以document.querySelector取得id="hello"的节点,然后通过textContent来修改内容。

先来试试把<script>标签放在</body>之前。在jsbin里面马上执行看看,看起来似乎很ok呢!

接着,我们试着把<script>标签移到<head> ... </head>之间:

咦?怎么什么都没有呢?而且也没有错误信息,JavaScript真的如大家说的一样,很垃圾吗?

冷静一下,容我解释一下。

前面说过,当一个网页被载入到浏览器时,浏览器会先分析这个HTML 文档,由上而下依序来读取解析:

所以上面jsbin例子中,当浏览器在<head> ... </head>之间遇到<script>标签时,就会暂停解析网页,并且立即执行<script>里的内容,直到script执行完毕后再继续解析网页。

<head> ... </head>里的<script>想要尝试去寻找<div id="hello">这个标签,但因为还没解析到网页本体,所以也无从取得。

不是浏览器坏掉,也不是JavaScript太渣,而是因为我们不理解浏览器执行的原理所造成的误会

这里是浏览器加载一个有 <script> 标签的网站所发生的事情:

  1. 拉取 HTML 页面
  2. 开始解析 HTML
  3. 解析到 <script> 标签之后准备获取 script 文件.
  4. 浏览器获取script文件。同时,html 解析中断并且阻断页面上其他html的解析。
  5. 一段时间后,script下载完成并且执行
  6. 继续解析HTML文档的其他部分(解析script之后的html代码)

第4步导致了很不好的用户体验,直到script文件全部下载完成之前HTML都不能得到解析。

那么,当我们把<script>标签放在</body>结束之前,由于DOM已经解析完成,所以document.querySelector就可以顺利取得id="hello"的节点,并且把'HELLO'的字串放在网页里啦!

这样说起来,<script>标签是不是就不适合放在<head> ... </head>之间呢?

也不能这么说,这点认真要讲的话之后或许可以用一整篇来说明这个。

DOM 节点的选取

上一篇文章说过,document对象是DOM tree的根节点,所以当我们要存取HTML时,都从document对象开始。而DOM的节点类型除了HTML元素节点(element nodes)外,还有文字节点(text nodes)、注释节点(comment nodes)等。

而常见的DOM 选取方法有下列这些:

// 根据传入的值,找到 DOM 中 id 为 'xxx' 的元素。
document.getElementById('xxx');// 针对给定的 tag 名称,返回所有符合条件的 NodeList 对象(节点的集合)
document.getElementsByTagName('xxx');// 针对给定的 class 名称,返回所有符合条件的节点集合
document.getElementsByClassName('xxx');// 针对给定的 Selector 条件,返回第一个 或 所有符合条件的节点集合
document.querySelector('xxx');
document.querySelectorAll('xxx');

DOM 节点的类型

DOM 常用的节点类型有下面几种:

可以通过节点类型常数或是对应数值来判断:

document.nodeType === Node.DOCUMENT_NODE;   //true
document.nodeType === 9;   //true

其他不常用或是已经废弃的部分可以参考:MDN Node.nodeType一节。

DOM 节点间的查找遍历(Traversing)

由于DOM 节点有分层的概念,于是节点与节点之间的关系,我们大致上可以分成以下两种:

  • 父子关系
    除了document之外,每一个节点都会有个上层的节点,我们通常称之为「父节点」 (Parent node),而相对地,从属于自己下层的节点,就会称为「子节点」 (Child node)。
  • 兄弟关系:有同一个「父节点」的节点,那么他们彼此之间就是「兄弟节点」(Siblings node)。

而隔层的节点基本上没有直接关系。

上图中水平方向的邻层节点为父子关系,垂直方向的同层节点为兄弟关系。

Node.childNodes

所有的DOM节点对象都有childNodes属性,且此种属性无法修改。

我们可以通过Node.hasChildNodes()来检查某个DOM节点是否有子节点。

var node = document.querySelector('#hello');// 如果 node 內有子元素
if( node.hasChildNodes() ) {// 可以通过 node.childNodes[n] (n 为数字索引) 取得对应的节点// 注意,NodeList 对象內容为即时更新的集合for (var i = 0; i < node.childNodes[i].length; i++) {// ...     };
}

Node.childNodes返回的可能会有这几种:

  • HTML 元素节点(element nodes)
  • 文字节点(text nodes),包含空格
  • 注释节点(comment nodes)

Node.firstChild

Node.firstChild可以取得Node节点的第一个子节点,如果没有子节点则返回null

要注意的是,子节点包括空白节点,如下面例子:

<p><span>span 1</span><span>span 2</span><span>span 3</span>
</p><script>var p = document.querySelector('p');    // tagName 属性可以取得 node 的标签名称console.log(p.firstChild.tagName);      // undefined
</script>

因为取得的是<p>与第一个<span>中间的换行字元,所以p.firstChild.tagName会得到undefined。所以改成这样:

<p><span>span 1</span><span>span 2</span><span>span 3</span></p><script>var p = document.querySelector('p');    // tagName 属性可以取得 node 的标签名称console.log(p.firstChild.tagName);      // "SPAN"
</script>

把中间的换行与空白移除,就会得到预期中的"SPAN"了。

Node.lastChild

Node.lastChild可以取得Node节点的最后一个子节点,如果没有子节点则返回null

Node.firstChild一样的是,子节点也包括空白节点,所以像这样:

<p><span>span 1</span><span>span 2</span><span>span 3</span>
</p><script>var p = document.querySelector('p');    // textContent 属性可以取得节点内的文字内容console.log(p.lastChild.textContent);      // "" (换行字元)
</script>

得到的会是一个换行字元的空字符串。

移除节点之间多余的空白后:

<p><span>span 1</span><span>span 2</span><span>span 3</span></p><script>var p = document.querySelector('p');    // textContent 属性可以取得节点内的文字内容console.log(p.lastChild.textContent);      // "span 3"
</script>

输出的就会是正确的"span 3" 啦。

Node.parentNode

那么相较于Child系列,parentNode就单纯一些。

通过Node.parentNode可以用来取得父元素,返回值可能会是一个元素节点(Element node)、根节点(Document node)或DocumentFragment节点。

<p><span>span 1</span><span>span 2</span><span>span 3</span></p><script>var el = document.querySelector('span');   console.log( el.parentNode.nodeName );    // "P"
</script>

Node.previousSibling

看完了DOM父与子之后,接着来看看兄弟节点。

通过Node.previousSibling可以取得同层之间的前一个节点,如果node已经是第一个节点且前面无节点,则返回null

<p><span>span 1</span><span>span 2</span><span>span 3</span></p><script>var el = document.querySelector('span');   console.log( el.previousSibling );    // null// document.querySelectorAll 会取得所有符合条件的集合,// 而 document.querySelectorAll('span')[2] 指的是「第三个」符合条件的元素。var el2 = document.querySelectorAll('span')[2];   console.log( el2.previousSibling.textContent );    // "span 2"
</script>

Node.nextSibling

Node.previousSibling类似,通过Node.nextSibling可以取得同层之间的下一个节点,如果node已经是最后一个节点,则返回null

<p><span>span 1</span><span>span 2</span><span>span 3</span></p><script>// document.querySelector 会取得第一个符合条件的元素var el = document.querySelector('span');console.log( el.nextSibling.textContent );    // "span 2"
</script>

document.getElementsBy**与document.querySelector/ document.querySelectorAll的差异

今天分享了很多关于DOM的选取以及查找遍历的方式,其中,像是document.getElementById以及document.querySelector因为取得的一定只会有一个元素/节点,所以不会有index与length属性。

document.getElementsBy**(注意,这里有个s)以及document.querySelectorAll则分别返回HTMLCollection与NodeList。

这两者其实是有点差别的,HTMLCollection只收集HTML element 节点,而NodeList除了HTML element 节点,也包含文字节点、属性节点等。当然两者也有类似的地方,虽然不能使用数组的method,但这两种都可以用数组索引的方式来存取内容,也就是伪数组。

另一个需要注意的地方是,HTMLCollection/NodeList在大部分情况下是即时更新的,但通过document.querySelector/document.querySelectorAll取得的NodeList是静态的。

啥意思呢?举个例子:

<div id="outer"><div id="inner">inner</div>
</div><script>// <div id="outer">var outerDiv = document.getElementById('outer');    // 所有的 <div> 标签var allDivs = document.getElementsByTagName('div');    console.log(allDivs.length);    // 2// 清空 <div id="outer"> 下的节点   outerDiv.innerHTML = '';    // 因为清空了<div id="outer"> 下的节点,所以只剩下 outerconsole.log(allDivs.length);    // 1
</script>

如果改成document.querySelector的写法:

<div id="outer"><div id="inner">inner</div>
</div><script>// <div id="outer">var outerDiv = document.getElementById('outer');    // 所有的 <div> 标签var allDivs = document.querySelectorAll('div');    console.log(allDivs.length);    // 2// 清空 <div id="outer"> 下的节点   outerDiv.innerHTML = '';    // document.querySelector 返回的是静态的 NodeList,不受 outerDiv 更新影响console.log(allDivs.length);    // 2
</script>

那么以上就是今天所要介绍的内容啦。

在后续的文章会再继续说明DOM API新增/删除/修改节点的部分,欢迎持续关注。

如果觉得文章对你有些许帮助,欢迎在我的GitHub博客点赞和关注,感激不尽!

easyui的tree获取父节点_通过DOM API 查找节点相关推荐

  1. easyui的tree获取父节点_力扣 1519——子数中标签相同的节点数

    本题主要在于对树这种数据结构的考察,以及深度优先遍历的使用,优化时可以采取空间换时间的策略. 原题 给你一棵树(即,一个连通的无环无向图),这棵树由编号从 0 到 n - 1 的 n 个节点组成,且恰 ...

  2. elementui tree获取父节点_vue_elementUI_ tree树形控件 获取选中的父节点ID

    一,  vue_elementUI_ tree树形控件 1.1默认点击tree节点的第一个(注意不是checked选中) :expand-on-click-node="false" ...

  3. EasyUI中tree选中父节点自动选中子节点,取消子节点自动取消父节点,子节点勾选完毕自动勾选父节点

    今天有需求,将树设为具有全选功能 选中父节点自动全选子节点 全选状态下,当用户取消某一个子节点,自动取消父节点的勾选 未全选状态下,用户勾选全部子节点,自动勾选对应的父节点 感觉挺简单,实际写的时候才 ...

  4. 一致性hash算法虚拟节点_一致性哈希虚拟节点

    采用固定哈希算法平衡负载 在大规模的缓存应用中,应运而生了分布式缓存系统.key-value如何均匀的分散到集群中?最常规的方式莫过于hash取模的方式.比如集群中可用机器适量为N,那么key值为K的 ...

  5. 一致性hash算法虚拟节点_一致性hash和虚拟节点

    consistent hashing 算法的原理 consistent hashing 是一种 hash 算法,简单的说,在移除 / 添加一个 cache 时,它能够尽可能小的改变已存在key 映射关 ...

  6. elementui tree获取父节点_elementUI 树状图 点击子节点获取父节点

    权限使用elementUI tree 组件,点击子节点获取对应多级的父节点 这是应用的组件 :data="hovePermissData" :default-checked- ke ...

  7. easyui 扩展tree 获取选中节点的级数

    2019独角兽企业重金招聘Python工程师标准>>> //扩展tree, 增加getLevel方法$.extend($.fn.tree.methods, {getLevel:fun ...

  8. class 原生js获取父元素_原生js获取class

    //使用原生js时,通过class名称就可以得到相应的class名称标签组封装的函数 //定义一个函数getClass(oParent,aClass); function getClass(oPare ...

  9. 接口获取行政区划代码_行政区域查询-API文档-开发指南-Web服务 API | 高德地图API...

    产品介绍 行政区域查询是一类简单的HTTP接口,根据用户输入的搜索条件可以帮助用户快速的查找特定的行政区域信息. 使用API前您需先申请Key,若无高德地图API账号需要先申请账号. 例如:中国> ...

  10. java json删除节点_指定json的某个节点进行增、删、改

    有时候我们需要对json结构的数据进行更新,或增,或改,或删. 当json层级比较复杂时操作起来是比较麻烦的,得一层层找下去找到要更新的节点才能操作它. 我用python语言封装了一个类,提供三个函数 ...

最新文章

  1. 【 Verilog 】always@()的敏感源中为什么不能双边沿触发?为什么不能双时钟触发?
  2. Android之一窥究竟Activity间的数据传递以及Intent的用处
  3. 下列不属于计算机图形学的应用的是,《数字图形设计》题目与答案3
  4. 解密NTFS下经EFS加密的文件
  5. C和指针之函数之实现简单的printf函数(支持%d, %f, %c, %s)
  6. redis终端简单命令
  7. Mcad学习笔记之异步编程(AsyncCallback委托,IAsyncResult接口,BeginInvoke方法,EndInvoke方法的使用小总结)...
  8. struts2进阶篇(3)
  9. MATLAB画演化博弈图,演化博弈matlab程序与作图
  10. 2021年中国图书出版行业经营现状及重点企业对比分析:凤凰传媒优势明显[图]
  11. CodecContext-gop_size 是什么
  12. 102 613 SWP协议学习笔记--SHDLC
  13. php 表示每月一号,关于适合每月一号发的说说
  14. html用于排版标题标签,HTML常用标签(示例代码)
  15. 【图形学】18 光照模型(三、镜面反射的Shader实现)
  16. maven的下载安装,setting.xml配置教程,Idea 配置maven
  17. 电池供电的电容麦_区别真不小 动圈麦和电容麦你了解吗?
  18. FFmpeg命令(二)、 从视频中提取音频
  19. 地铁译:Spark for python developers ---Spark处理后的数据可视化
  20. 自动登录校园网代码加入wifi自动搜寻并主动连接部分

热门文章

  1. SQL点滴18—SqlServer中的merge操作,相当地风骚
  2. 在春天,我用秋来诱惑你
  3. 关于SqlDataReader类型的变量传值问题
  4. gcc与g++编译器介绍
  5. 51nod 1833 状压dp加一点图论
  6. Fiddler内置命令
  7. C/C++怎样传递二维数组,转载自CSDN
  8. 收集WebDriver的执行命令和参数信息
  9. AE+C#实现:在SceneControl里打开和保存
  10. C#问题——interface class