zoukankan      html  css  js  c++  java
  • 小白科普之JavaScript的DOM模型

      微信公众号“前端大全”推送了一篇名为“通俗易懂的来讲讲DOM”的文章,把javascript原生DOM相关内容讲解的很详细。仔细读了一遍,觉得整理总结的不错,对自己也很使用,所以把内容整理过来,并根据自己的理解稍作改动。


      DOM是所有前端开发每天打交道的东西,但是随着jQuery等库的出现,大大简化了DOM操作,导致大家慢慢的“遗忘”了它的本来面貌。不过,要想深入学习前端知识,对DOM的了解是不可或缺的,所以本文力图系统的讲解下DOM的相关知识,如有遗漏或错误,还请大家指出一起讨论^ ^。

    一、DOM是什么?

      DOM(文档对象模型)是针对HTML和XML文档的一个API,通过DOM可以去改变文档。这个说法很官方,大家肯定还是不明白。举个例子:我们有一段HTML,那么如何访问第二层第一个节点呢,如何把最后一个节点移动到第一个节点上面去呢?

      DOM就是定义了如果做类似操作,那么应该怎么做的标准。比如用getElementById来访问节点,用insertBefore来插入节点。当浏览器载入HTML时,会生成相应的DOM树。简而言之,DOM可以理解为一个访问或操作HTML各种标签的实现标准。

      对于一个HTML来说,文档节点Document(看不到的)是它的根节点,对应的对象便是document对象(严格讲是子类HTMLDocument对象,下面单独介绍Document类型时会指出)。换句话说存在一个文档节点Document,然后它有子节点,比如通过document.getElementsByTagName(“html”),得到类型为元素节点的Element html。每一段HTML标记都可以用相应的节点表示,例如:

      HTML元素通过元素节点表示,注释通过注释节点表示,文档类型通过文档类型节点表示等。

      一共定义了12种节点类型,而这些类型又都继承自Node类型。所以我们首先讲Node类型,因为这个类型的方法是所有节点都会继承的。

    二、Node类型(基类,所有节点都继承了它的方法)

      Node是所有节点的基类型,所有节点都继承自它,所以所有节点都有一些共同的方法和属性。先讲Node类型的属性:

      首先是nodeType属性,用来表明节点类型的,例如:

    document.nodeType;    // 返回 9 ,其中document对象为文档节点Document的实例

      这里面,9代表的就是DOCUMENT_NODE节点的意思,可以通过Node.DOCUMENT_NODE查看节点对应的数字。

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

      至于一共有哪些节点,每个节点对应的数字又是多少,这个可以问谷歌就知道了。反正常用的就是元素节点Element(对应数字为1)和文本节点Text(对应数字为3)。

      然后常用的还有nodeName和nodeValue

       对于元素节点 nodeName就是标签名,nodeValue就是null,对于文本节点 nodeName为”#text”(chrome里面测试的),nodeValue就是实际的值。每个节点还有childNodes属性,这是个十分重要的属性,它保存了这个节点所有直接子元素。调用childNodes返回的是一个NodeList对象,它极其像数组,但是有一个最关键的地方,它是动态查询的,也就是说每次调用它都会对DOM结构查询,所以对它的使用需要慎重,注意性能。访问childNodes可以使用数组下标或者item方法,然后各个节点还存在各种属性让它们可以相互访问,下图很好的总结了:

      比较有用的方法和属性: 

      1. hasChildNodes()

      如果包含子节点就返回true,比查询childNodes的length来的简单。

      2. ownerDocument

      返回文档节点的引用(在html里面也就是document对象)

      再介绍下Node类型常用的方法

      appendChild()方法可以在节点的childNodes的末尾增加一个节点,值得注意的是如果这个节点是已经存在在文档中的,那么便会删除原节点,感觉上就像是移动节点一样。

      insertBefore()方法接受两个参数,一个是插入的节点,另外一个是参照的节点。如果第二个参数为null,则insertBefore()和appendChild()效果一样,否则便会把节点插入到参照节点之前。这里要注意的是,如果第二个参数不为null,那么插入的节点不能是已经存在的节点。

       replaceChild()方法可以替换节点,接受两个参数,需要插入的节点和需要替换的节点。返回被替换掉的节点。

       removeChild()移除节点。这里有个常见需求,比如我有一个节点 #waste-node ,那么如何移除它呢?

    var wasteNode =  document.getElementById("waste-node");
    wasteNode.parentNode.removeClhid(wasteNode);    // 先拿到父节点,再调用removeClild删除自己

      这里先暂停一下,不知道大家注意到没有,以上的几个方法都是操作某个节点的子节点,也就是说,操作前必须找到父节点(通过parentNode来找)。

      接下来说下复制节点的方法:

      cloneNode():复制节点,接受一个参数 true或者false表示是否深入复制。如果true就是复制那个节点和它的子节点。如果是false,就是复制节点本身(复制出来的节点就会没有任何子元素)。这个方法返回复制的节点不属于文档树,它的parentNode属性为 null。当复制的是 Element 节点时,它的所有属性都将被复制。但要注意,当前节点上注册的事件监听器函数不会被复制。

      至此,Node类型的常见属性和方法都介绍完了。结合开头讲的,所有节点类型都继承自Node类型,所以这些方法是所有节点都有的。

    三、Document类型

      最开始讲DOM是什么的时候提到了Document类型。其实关于这个类型最重要的是它的一个子类HTMLDocument有一个实例对象document。而这个document对象是我们最常用的一个对象了。 document对象又挂载在window对象上,所以在浏览器就可以直接访问document了。

      老规矩,先讲讲document对象的属性,等会讲讲它的方法。

    3.1 document对象上的一些属性

       document.childNodes 继承自上面讲的Node类型,可以返回文档的直接子节点(通常包括文档声明和html节点)

      document.documentElement 可以直接拿到html节点的引用(等价于document.getElementsByTagName(“html”)[0])

      document.body body节点的引用

      document.title 页面的title,可以修改,会改变浏览器标签上的名字

      document.URL 页面的url

      document.referrer 取得referrer,也就是打开这个页面的那个页面的地址,做来源统计时候比较有用

      document.domain 取得域名,可以设置,但是通常只能设置为不包含子域名的情况,在一些子域名跨域情况下有效

    3.2 接下来介绍两个熟悉的方法:getElementById 和 getElementsByTagName

      getElementById,传入id,得到元素节点。里面的参数区分大小写(IE8-不区分)。注意:如果有多个id相同的元素,则返回第一个IE7-里面表单元素的name也会被当做id来使用。

      getElementsByTagName 根据标签取得元素,得到的是HTMLCollection类型。如果传入的是 "*" ,则可以取得全部元素。

      还有一个是只有HTMLDocument类型(也就是document对象)才有的方法 getElementsByName 顾名思义,根据name返回元素。

    3.3 document对象集合

      document对象还有一些集合,例如document.forms 可以返回所有的form表单。类型也是HTMLCollection。说到HTMLCollection,就再说说它。

      HTMLCollection就是一个包含一个或多个元素的集合,和上面讲的NodeList还挺像的。HTMLCollection这个类型有两个方法,一个是通过下标(或者item())得到具体元素,还有就是通过['name'](或者.namedItem())获得具体元素。

    3.4 document对象的方法

      最后,关于document对象还有一套重要的方法,那便是write() writeln() open() close()

      open和close分别是打开和关闭网页的输出流,在页面加载过程中,就相当于open状态。这两个方法一般不会去用它。然后重要的方法就是write和writeln,它们都是向页面写入东西,区别就是后者会多加入一个换行符。

      注意的是:在页面加载的过程中,可以使用这两个方法向页面添加内容。如果页面已经加载完了,再调用write,会重写整个页面。

      还有一点,如果要动态写入脚本 例如 <script>xxx</script>这样的 ,那么要注意把</script>分开来拼装下,否则会被误以为是脚本结束的标志,导致这个结束符匹配到上面一个开始符。可以这样写"<scr" + "ipt>";

    四、Element类型

      接下来讲讲最重要也是最常见的一个类型,Element类型。我们日常所操作的都是Element类型(实质是HTMLElement,这里为了方便理解,就简单这么说),比如:

    document.getElementById("test") 

      返回的就是Element类型。我们日常所说的“DOM对象”,通常也就是指Element类型的对象。

    4.1 说说这个类型的常见属性

      最开始说的Node类型上的那些属性方法它都有,这个就不再重复了,主要说说它自己独有的。

      首先是tagName,这个和继承自Node类型的nodeName一样。都是返回标签名,通常是大写,结果取决于浏览器。所以在做比较的时候最好是调用下类似toLowerCase()这种方法再做比较。

       说说上面提到过的HTMLElement类型

      HTMLElement类型继承自Element类型,也是HTML元素的实际类型,我们在浏览器里用的元素都是这个类型。这个类型都具有一些标准属性,比如:

      id 元素的唯一标识

      title 通常是鼠标移上去时候会显示的信息

      className 类名

    等等,这几个属性是可读写的,也就是说你改变他们会得到相应的效果。

    4.2 除了属性外,还有几个重要的方法

    4.2.1 首先说说操作节点属性的方法

      getAttribute() 、setAttribute() 、removeAttribute()这3个方法,这些是操作属性最常用的方法了,怎么用就不说了,很简单,顾名思义。

    4.2.2 还有一个attributes属性,保存了元素的全部属性

      这里停下来,出个问题,ele.className 和 ele.getAttribute("class")返回的结果是不是同一个东西?

      解答这个问题,我要说一个重要知识点,一个元素的属性结构是这么来的,比如一个inpnt元素

    <input id="test" checked="checked">

      那么这个元素的属性被包含在 input.attributes里面,比如你在html元素上看到的class、id或者你自己定义的data-test这种属性。

      然后 getAttribute 、setAttribute 、removeAttribute这3个方法可以认为是快捷的取attributes集合的方法。而直接input.id或者input.className都是直接挂在input下的属性,和attributes是同级的。所以返回的东西也许看过去一样,实际是不一样的,不信你可以试试input.checked和input.getAttribute("checked")试试。

    var input = document.getElementById("test");
    alert(input.checked); // true
    alert(input.getAttribute("checked")); // checked 

      总得来说,这3个方法通常用了处理自定义的属性,而不是id、class等这种“公认特性”。

    4.3 接下来说说创建元素

      document.createElement()可以创建一个元素,比如:

    document.createElement("div");

      一般之后可以为元素设置属性,两种方法,一种是直接node.property,还可以node.setAttribute("propertyName","value")。但是做完这些之后,这个元素还是没有在页面中,所以你还得通过最上面讲的类似appendChild这些方法把元素添加到页面里面。在IE中,还可以直接穿整个HTML字符串进去,来创建元素,比如:

    document.createElement("<div>test</div>");

      最后,元素节点也支持HTMLDocument类型的那些查找方法,比如getElementsByTagName。不过它只会找自己后代的节点。所以可以这么写代码:

    document.getElementById("test").getElementsByTagName("div");    // 找到id为test元素下的所有div节点

    五、Text类型

      这个类型很特殊,也是第三常见类型(第一第二分别就是Document和Element)。这个节点简单来说就是一段字符串。有个很重要的特征就是,它没有子元素(不过这个仔细想想也知道= =)。访问text节点的文本内容,可以通过nodeValue或者data属性。

    5.1 下面简单说说它提供的一些方法

    appendData();    // 在text末尾加内容
    deleteData(offset, count);    // 从offset指定的位置开始删除count个字符

      还有insertDate、replaceData、splitText等方法,就不一一说了,用的机会很少,可以用的时候再查阅。然后它还有一个lenght属性,返回字符长度的。

      这里说一个常见的坑。比如下面这个html结构:

    <ul>
         <li></li>
         <li></li>
    </ul>

      这里,ul的第一个子节点(firstChild)是什么呢?第一眼看过去,肯定认为是li了,但是实际上,你会发现不是li,而是一个文本节点!这是因为浏览器认为ul和第一个li之间有空白字符,所以就有文本节点了。这里一个常见的问题就是遍历ul的childNodes的时候,遍历的元素一定要判断下nodeType是不是等于1(等于1就代表是元素节点),这样才能跳过这个坑。否则你也可以删除所有的空格和换行符。

      创建文本节点的方法是document.createTextNode

      然后接下来和操作Element类型一样,就是再插入到元素中,浏览器就可以看到了。

    六、其他的一些类型 Comment、DocumentType和DocumentFragment

      Comment是注释节点

      DocumentType就是doctype节点,通过docment.doctype来访问

      DocumentFragment这个节点是一个文档片段,偶尔会用到。

      比如一种常见的用法是,在一个ul中插入3个li。如果你循环插入3次,那么浏览器就要渲染3次,对性能有蛮大的影响。所以大家一般这么做先:

    var fragment = document.createDocumentFragment();

      然后循环把li,用appendChild插入到fragment里面,最后在一次把fragment插入到ul里面,这样就会很快。

    七、DOM扩展

      讲过上面讲的这么多节点类型,想必大家对DOM节点已经有了很深的了解,下面讲一讲DOM扩展的一些东西。

      浏览器为了方便开发者,扩展了一些DOM功能。因为是浏览器自己扩展的,所以使用前兼容性问题一定要注意!

    7.1 判断“标准模式”和“混杂模式”

      通过 document.compatMode和新的document.documentMode判断浏览器模式。

      上面不是说了一个文本节点作为第一子元素的坑吗,所以浏览器又实现了一个children属性,这个属性只包含元素节点。

       为了方便判断A节点是不是B节点的子节点,引入了contains方法,比如:

    B.contains(A);    // true就代表是,false就代表不是

    这个方法有兼容性问题,使用前可以谷歌解决方法。

    7.2 访问元素的属性

      针对访问元素,又提供了4个方法innerText/innerHTML/outerTEXT/outerHTML属性。通过这些属性,可以读和写元素。其中,

      *TEXT是返回文本内容 

      *HTML是返回html文本。

      outer*则是代表是否包含元素本身。

      实际使用来看,在读内容的时候 inner*和outer*没有区别。在把内容写入元素的时候,就是是否包含元素本身的区别。重要的是,这几个方法有性能问题,比如在IE中,通过inner*删除的节点,其绑定的事件依然在内存中,就很容易消耗大量内存。

       还有一个技巧是,插入大量的html代码,用innerHTML是非常快的,建议使用。

    八、MORE

      DOM知识远不止这些,接下来会简单说说DOM的一些其余接口。
    9.1 首先就是一个通过document查找到窗口(也就是window)的方法
    var parentWindow = document.defaultView || document.parentWindow;
      defaultView是标准方法,parentWindow是兼容IE用的。
    9.2 Node类型新增了 isSameNode()和isEqualNode()
      它们的区别就是:same是指同一个节点,equal是指“看过去”一模一样的两个节点。
    9.3 提供了一个访问iframe方法
      标准的是通过ele.contentDocument 这个会返回iframe里面的document对象,但是有兼容性问题,ie不支持,但是ie支持contentWindow,相当于拿到window对象,那么document对象也很容易取到,所以一般这么写。
    var iframe = document.getElementById("myIframe");
    var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
    9.4 接下来说说处理样式
      设置样式的基本的方式就是 ele.style.width = "20px";这个大家估计很常用。 使用类似这种方法来处理要注意的点就是记得加单位。快捷一点的方法就是 ele.style.cssText = " 20px;height:20px;";这个可以批量设置
      上面说的是设置,那么获取元素所使用的样式,一般而言会用 document.defaultView.getComputedStyle(ele, null);方法来实现。这个方法会返回这个一个CSSStyleDeclaration对象(就和ele.style返回的一样),但是会包含这个元素的全部样式信息,然后直接返回值.width或者别的属性名就可以得到样式了。不过IE不支持这种方式,IE更简单,直接ele.currentStyle就可以得到和 document.defaultView.getComputedStyle(ele, null)一样的返回值了。 

    9.5 最后说说元素的大小

      取得元素的宽高就使用 offsetHeight/offsetWidth,注意,不包括margin部分

      得到元素的偏移值就复杂了一点,有两个方法offsetTop/offsetLeft,但是这两个方法是指元素针对与它的offsetParent对象而言的,所以,如果你想得到元素距离视口的位置,那么还需要找到offsetParent,计算他们的offsetTop/offsetLeft,在找offsetParent的offsetParent,如此循环直到offsetParent为null。注意,这个计算的位置也不包括margin部分。

      上面说的是不包括margin,然后还有一组属性clientWidth/clientHeight,它们也可以得到宽高,但是它们是不包括margin和border,也不计算滚动条。

      scrollWidth和scrollHeight是元素的内容区域加上它的内边距再加上任何溢出内容的尺寸。scrollLeft和scrollTop指定元素滚动条的位置。最后,滚动的位置与距离,这个很难说清,大家直接看图比较清楚明了。

     九、脚本化CSS

    9.1 内联样式

         style属性代表元素的内联样式,它会覆盖所有的样式表。style属性的值并不和其他属性一样是字符串,而是CSSStyleDeclaration对象。
         名字约定:如果一个CSS属性名包含一个或多个连字符,CSSStyleDeclaration属性名的格式应该是移除连字符,将每个连字符后面紧跟着的字母大写。例如:
         e.style.fontFamily = "sans-serif";
    另外,当一个CSS属性在JavaScript中对应的名字是保留字时,在之前加“css”前缀来创建合法的CSSStyleDeclaration名字。
         注意事项:所有的定位属性都需要包含单位,例如:e.style.left = "30px";

    9.2 计算样式

         元素的计算样式是一组属性值,它由浏览器通过把内联样式结合所有链接样式表中所有可应用的样式规则后导出(或计算)得到的:它是一组在显示元素时实际使用的属性值。计算样式也是CSSStyleDeclaration对象表示的,是只读的。但为元素计算出的CSSStyleDeclaration对象确切地决定了浏览器在渲染元素时使用的样式属性值。
     
    getComputedStyle()获取一个元素的计算样式
    参数:
         第一个参数,要获取其计算样式的元素
         第二个参数,必需的,通常是null或空字符串,但也可以是命名CSS伪对象的字符串,如":before"、":after"、":first-line"、":first-letter"
    返回值:
         一个CSSStyleDeclaration对象,代表了应用在指定元素(或伪对象)上的所有样式。
      var title = document.getElementById("selection1title");
      var titlestyle = window.getComputedStyle(title, null);
     
    表示计算样式的CSSStyleDeclaration对象与内联样式的对象之间的区别
    1. 计算样式的属性是只读的;
    2. 计算样式的值时绝对值:类似百分比和点之类相对的单位将全部转换为绝对值。所有指定尺寸(例如外边距大小和字体大小)的属性都有一个以像素为度量单位的值。该值将是一个冠以“px”后缀的字符串,使用时仍然需要解析它,但是不要担心单位的解析或转换。其值是颜色的属性将以“rgb(#,#,#)”或“rgba(#,#,#,#)”的格式返回。
    3. 不计算复合属性,它们是基于最基础的属性。例如,不要查询margin属性,应该使用marginLeft和marginTop等。
    4. 计算样式的cssText属性未定义。
         计算样式和内联样式可以同时使用。
     
         getComputedStyle()在IE8或更早的版本中没有实现,在IE中,每个HTML元素有自己的currentStyle属性,它的值是CSSStyleDeclaration对象。IE的currentStyle组合了内联样式和样式表,但它不是真正的计算样式,因为那些相对值都没有转化成绝对值。查询IE的当前样式属性会返回带相对性单位(如%或em)的尺寸或者非精确的颜色值(如“red”)。
     
  • 相关阅读:
    Java总结篇系列:Java多线程(一)
    path方法总结
    Ember模板中的操作指向
    EmberJS路由详解
    观察器observes与对象初始化
    emberjs重写补充类之reopen方法和reopenClass方法
    emberjs创建类
    2014Ember带来怎样的变化?
    创建应用和模型和控制器
    自定义指令
  • 原文地址:https://www.cnblogs.com/borage/p/4334449.html
Copyright © 2011-2022 走看看