JavaScript DOM编程艺术--笔记

278 阅读11分钟

第一章 JavaScript简史

1.1 JavaScript的起源

JavaScript是Netscape公司与Sun公司合作开发的。在JavaScript出现之前,Web浏览器只是一个能够显示超文本文档的简单软件。在JavaScript出现之后,网页内容不再局限枯燥的文本,他们的可交互性得到显著改善。

  • JavaScript 1.0发布时,Netscape Navigator主宰浏览器市场;
  • 微软在IE 3 发布自己的VBScript语言,同时以JScript为名发布了JavaScript的一个版本;
  • 面对微软公司竞争,Netscape和Sun联合ECMA(欧洲计算机制造商协会)对JavaScript语言进行标准化。出现了ECMAScript语言,也就是JavaScript。

1.2 DOM

DOM是一套对文档内容进行抽象和概念化的方法。

1.3 浏览器战争

NetScape Navigator和IE两种浏览器都对他们的早期版本进行改进,大幅度扩展DOM,各自为营,使得DOM并不兼容。

1.4 制定标准

在浏览器制造商以DOM为武器展开营销大战同时,W3C推出了一个标准化的DOM。

1.4.1 浏览器以外的考虑

DOM是一种API。W3C对DOM的定义是:一个与平台和编程语言无关的接口,程序和脚本可以通过这个接口动态地访问和修改文档内容、结构和样式。W3C推出标准化DOM,在独立性和适用范围等诸多方面,都远远超出了各自为战的浏览器制造商们推出的各种专有DOM。

第二章 JavaScript语法

基础语法笔记省略,只记录对象部分语法内容;

对象

对象是一种非常重要的数据类型,它是自包含的数据集合,包含在对象里的数据可以通过两种形式访问--属性和方法;

  • 属性是隶属于某个特定对象的变量;
  • 方法是只有某个特定对象才能调用的函数;

对象就是一些属性和方法组合在一起而构成的一个数据实体。

内建对象

内建在JavaScript语言里的对象,如Array,Math,Date;在编写JavaScript脚本时,内建对象可以帮助我们快速、简单的完成许多任务。

宿主对象

由浏览器提供的预定义对象被称为宿主对象。

宿主对象包括Form、Image、Element等,我们可以通过这些对象获得关于网页上表单、图像和各种表单元素等信息。

第三章 DOM

3.1 文档:DOM中的“D”(document)

当创建一个网页并把它加载到web浏览器中时,就会把网页文档转换为文档对象。

3.2 对象:DOM中的“O”

JavaScript中的对象可以分为三种类型;

  • 用户定义对象:由程序员自行创建的对象;
  • 内建对象:内建在JavaScript语言里的对象,如Array、Math和Date等;
  • 宿主对象:由浏览器提供的对象;

3.3 模型:DOM中的“M”(Model)

DOM把一份文档表示为一棵树模型,我们可以通过JavaScript去读取这个模型。

3.4 节点

DOM中包含三种类型节点

  • 元素节点
  • 文本节点
  • 属性节点

元素节点

DOM的原子是元素节点。比如,段落元素p,无序清单ul等。

文本节点

p元素中包含一些文本,它就是一个文本节点。文本节点总是被包含在元素节点的内部,但并非所有元素节点都包含文本节点。

属性节点

属性节点用来对元素做出更具体得描述。比如title,class属性。

获取元素

有3种DOM方法可以获取元素节点,分别是通过元素ID,通过标签名字和通过类名字;

  1. getElementById(): 返回一个对象
  2. getElementsByTagName():返回对象数组
  3. getElementsByClassName():返回对象数组

3.5 获取和设置属性

  1. getAttribute()
  2. setAttribute()

getAttribute()方法不属于document对象,所以不能通过document对象调用,它只能通过元素节点对象调用。


//Demo
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>DOM API</title>
</head>
<body>
<h1>What to bug</h1>
<p title="a gentle">Don't forget to buy this stuff.</p>
<ul id="purchase">
    <li>A tin of beans</li>
    <li class="sale">Cheese</li>
    <li class="sale important">Milk</li>
</ul>

<script>
    let purchase = document.getElementById("purchase")
    console.log(purchase)
    console.log("----")

    let res = document.getElementsByTagName('li');  //返回一个集合
    console.log(res)
    console.log("---")

    let resp = document.getElementsByClassName('important'); //返回一个集合
    console.log(resp)
    resp[0].setAttribute('title',"new brand")

</script>
</body>
</html>

第四章 案例研究:JavaScript图片库

第五章 最佳实践

5.1 过去的错误

5.2 平稳退化

确保网页在没有JavaScript的情况下也能正常工作。

5.3 向CSS学习

5.3.1 结构与样式的分离

把文档的结构和样式分离为两部分的CSS技术给每个人都带来了方便,作为CSS技术的突出优点,文档结构与文档样式的分离可以确保网页都能平稳退化。

5.3.2 渐进增强

所谓“渐进增强”就是用一些额外的信息层去包裹原始数据,按照“渐进增强”原则创造出来得网页几乎都符合“平稳退化”的原则。

5.4 分离JavaScript

把网页的结构和内容与JavaScript脚本的动作行为分开。

5.5 向后兼容

确保老版本的浏览器不会因为你的JavaScript脚本而死掉。

5.6 性能考虑

5.6.1 尽量少访问DOM和尽量减少标记

查询DOM,浏览器都会搜索整个DOM树,比较好的方法是在搜索出DOM元素时,存到一个变量中。

5.6.1 合并和放置脚本

目的是减少页面加载时发送请求的数量。

5.6.2 压缩脚本

减少ROM空间。

第六章 案例研究:图片库改进版

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Image Gallery</title>
</head>
<body>
<h1>Snapshots</h1>
<ul>
    <li>
        <a href="img/scene_1.jpg" title="Scene A display" onclick="showPic(this);return false">Fireworks</a>
    </li>

    <li>
        <a href="img/scene_2.jpg" title="Scene B display" onclick="showPic(this);return false">Coffee</a>
    </li>

    <li>
        <a href="img/scene_3.jpg" title="Scene C display" onclick="showPic(this);return false">Rose</a>
    </li>
</ul>

<img id="placeHolder" src="" alt="my image gallery" style="width: 300px; height: 150px"/>
<p id="description">Choose an image.</p>

<script>
    function showPic(whichPic) {
        let source = whichPic.getAttribute("href");
        let placeHolder = document.getElementById("placeHolder");
        placeHolder.setAttribute('src', source);
        let text = whichPic.getAttribute("title");
        let desc = document.getElementById("description");
        desc.firstChild.nodeValue = text;

    }

    window.onload = function (){
        let ele = document.getElementsByTagName('body')[0]
        console.log(ele.childNodes);
    }
</script>
</body>
</html>

第七章 动态创建标记

7.1 传统方法

1. document.write()

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>DEMO write</title>
</head>
<body>
<script>
    document.write("<p>This is inserted content.</p>");
</script>
</body>
</html>

该方法最大缺点是它违背了“行为应该与表现分离”的原则。

把JavaScript和HTML代码混杂在一起是一种很不好的做法,这样的标记既不容易阅读和编辑,也无法享受到吧行为和结构分离开来得好处。

2. innerHTML属性

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>DEMO write</title>
</head>
<body>
<div id="app"></div>
<script>
    window.onload = function (){
        let app = document.getElementById("app");
        app.innerHTML = "<p>I insert this content.</p>";
    }
</script>
</body>
</html>

与标准化DOM相比,innerHTML属性用起来很粗犷;利用这个技术无法区分“插入一段HTML内容”和“替换一段HTML内容”。id为app的div元素里有没有HTML内容无关紧要,一旦使用了innerHTML属性,他的全部内容都将被替换。

innerHTML属性也是HTML专有属性,但并不是W3C DOM标准的组成部分。

7.2 DOM方法

DOM是文档的表示。DOM所包含的信息与文档里的信息一一对应。

DOM是一条双向车道,不仅可以获取文档的内容,还可以更新文档的内容。以动态方式实时创建标记其实是在改变DOM节点树。

在DOM看来,一个文档就是一棵节点树,如果想在节点树上添加内容,就必须插入新的节点,如果想添加一些标记到文档,就必须插入元素节点。

7.2.1 createElement()

该方法用来创建元素节点。

let para = document.createElement("p");
console.log("name:", para.nodeName);  //name: P
console.log("type:", para.nodeType);  //type: 1

变量para现在包含着一个指向p元素的引用,虽然这个p元素已经存在,但它还不是任何一棵DOM节点树的组成部分。不过,它已经像任何其他的节点那样,有了自己的DOM属性(nodeType,nodeName)。

7.2.2 appendChild()

将某节点插入到文档节点树。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>DEMO write</title>
</head>
<body>
<div id="app"></div>
<script>
    window.onload = function () {
        let app = document.getElementById("app");
        let para = document.createElement("p");
        app.appendChild(para);
    }
</script>
</body>
</html>

7.2.3 createTextNode()

前面已经创建出了一个元素节点,并把它插入到了文档的节点树,但是这个节点是空白的p元素。

当想把一些文本放入到p元素时,createElement方法帮不上忙,它只能创建元素节点,这里需要一个文本节点,需要使用createTextNode方法来创建。

创建后,同样使用appendChild方法把文本节点插入到现有元素的子节点。

<script>
    window.onload = function () {
        let app = document.getElementById("app");
        let para = document.createElement("p");
        let text = document.createTextNode("Hello World");
        app.appendChild(para);
        para.appendChild(text);
    }
</script>

7.3 实例

7.3.1 在已有元素前插入一个新元素

DOM提供了名为insertBefore() 方法,这个方法把一个新元素插入到一个现有元素的前面。

  • 新元素:你想插入的元素(newElement);
  • 目标元素:你想把这个新元素插入到哪个元素(targetElement)之前;
  • 父元素:目标元素的父元素(parentElement);
parentElement.insertBefore(newElement,targetElement);
<body>
<h1>Snapshots</h1>

<div id="image-gallery">Gallery</div>
<div id="place-holder">Place Holder</div>

<script>
    let gallery = document.getElementById("image-gallery");
    let placeHolder = document.getElementById("place-holder");
    gallery.parentNode.insertBefore(placeHolder,gallery);

</script>
</body>

7.3.2 在现有元素后插入一个新元素

DOM中并没有提供insertAfter()方法,我们可以自己实现一个。

function insertAfter(newElement, targetElement) {
        let parent = targetElement.parentNode;
        if (parent.lastChild === targetElement) {
            parent.appendChild(newElement);
        } else {
            parent.insertBefore(newElement, targetElement.nextSibling);
        }
}

函数中用到的DOM方法和属性:

  • parentNode:元素父节点;
  • lastChild:元素节点的最后一个子元素节点;
  • appendChild()方法
  • insetBefore()方法
  • nextSibling属性:元素的下一个兄弟元素节点;

第八章 充实文档的内容

本章主要内容为实践,实现以下内容:

  • 为文档创建“缩略语列表”的函数;
  • 为文档创建“文献来源链接”的函数;
  • 为文档创建“快捷键清单”的函数;

8.1 不应该做什么

使用JavaScript把一些重要的内容添加到网页上,是一个坏主意,因为这样一来,JavaScript就没有任何空间去支持平稳退化。缺少JavaScript支持的访问者就没法看到这些重要内容。

两项原则要牢记在心:

  • 渐进增强:根据内容使用标记实现良好的结构,然后再逐步加强这些内容;
  • 平稳退化:缺乏必要CSS和DOM支持的访问者,应该仍能访问到你的核心内容;

8.2 把“不可见”变成“可见”

标签之间除了内容,标签内的属性也包含语义信息,而且绝大多数属性值都是隐藏的。本章实例着眼于DOM技术,为网页添加一些使用小组件。

  1. 得到隐藏在属性里的信息;
  2. 创建标记,封装这些信息;
  3. 把这些标记插入到文档;

8.3 内容

<body>
<h1>What is the Document Object Model?</h1>
<p>
    The <abbr title="World Wide Web Consortium">W3C</abbr> defines
    the <abbr title="Document Object Model">DOM</abbr> as:
</p>
<blockquote cite="http://www.w3.org/DOM/">
    <p>
        A platform- and language-neutral interface that will allow programs
        and scripts to dynamically access and update the
        content, structure and style of documents.
    </p>
</blockquote>

<p>
    It is an <abbr title="Application Programming Interface">API</abbr>
    that can be used to navigate <abbr title="HyperText Markup Language">
    HTML</abbr> and <abbr title="eXtensible Markup Language">XML
</abbr> documents.
</p>

</body>

8.4 显示“缩略语列表”

最终要实现如下效果

<dl>
    <dt>W3C</dt>
    <dd>World Wide Web Consortium</dd>
    
    <dt>DOM</dt>
    <dd>Document Object Model</dd>
    ...
</dl>

8.4.1 编写displayAbbreviations()函数

function displayAbbreviations() {

    let abbrLists = document.getElementsByTagName("abbr");
    if (abbrLists.length < 1) return false; //边界检查
    
    let defs = [];
    for (let i = 0; i < abbrLists.length; i++) {
        let attr = abbrLists[i];
        let definition = attr.getAttribute("title"); //获取title里的属性值
        let key = attr.lastChild.nodeValue;  //获取节点值
        defs[key] = definition;
    }
}

8.4.2 创建标记

  	let dlist = document.createElement("dl");  //创建dl元素节点
    for (const key in defs) {
        let dtitle = document.createElement("dt");  //创建dt元素
        let dtitle_text = document.createTextNode(key);      //创建文本节点
        dtitle.appendChild(dtitle_text);

        let ddesc = document.createElement("dd");
        let ddesc_text = document.createTextNode(defs[key]);
        ddesc.appendChild(ddesc_text);
        dlist.appendChild(dtitle);
        dlist.appendChild(ddesc);
    }

    let header = document.createElement("h2");          //创建标题
    let header_text = document.createTextNode("Abbreviations");     //创建文本内容
    header.appendChild(header_text);

    document.body.appendChild(header);
    document.body.appendChild(dlist);

封装函数,将多个事件添加到window.onload事件中;

addLoadEvent(displayAbbreviations);
function addLoadEvent(func) {
  let oldonload = window.onload;
  if (typeof window.onload != 'function') {
    window.onload = func;
  } else {
    window.onload = function() {
      oldonload();
      func();
    }
  }
}

8.5 显示“文献来源链接表”

blockquote元素包含cite属性,在实践中,浏览器会完全忽视cite属性的存在。利用JavaScript和DOM,我们可以把信息收集起来,并以一种更有意义的方式把他们显示在网页上。

编写步骤如下:

  1. 遍历文档里的所有blockquote元素;
  2. 从blockquote元素里提取出cite属性的值;
  3. 创建一个标识文本是source的链接;
  4. 把这个链接赋值为blockquote元素的cite属性值;
  5. 把这个链接插入到文献节选的末尾;

编写displayCitations函数

function displayCitations(){
    //找出文档中所有blockquote元素节点
    let quotes = document.getElementsByTagName("blockquote");
    
    for (let i = 0; i < quotes.length; i++) {
        //如果没有cite属性,立刻跳入下一次循环
        if (!quotes[i].getAttribute("cite")) continue
        //保存cite属性值
        let url = quotes[i].getAttribute("cite");
        //取得元素中所有元素节点
        let quoteElements = quotes[i].getElementsByTagName("*");
        //如果没有元素节点,继续循环
        if (quoteElements.length<1) continue;
        //取得引用中最后一个元素节点
        let elem = quoteElements[quoteElements.length-1];

        //创建链接
        let link = document.createElement("a");
        let link_text = document.createTextNode("source");
        link.setAttribute("href",url);
        link.appendChild(link_text);

        //使用sup元素,呈现上标的效果
        let superScript = document.createElement("sup");
        superScript.appendChild(link);

        elem.appendChild(superScript);
    }
}

addLoadEvent(displayCitations)

8.6 显示“快捷键菜单”

accesskey属性可以把一个元素与键盘上的某个特定按键关联在一起;但是是否以及如何把快捷键的分配情况显示在页面上需要由网页设计人员来决定。许多网站都会在一个快捷键清单页面上列明该网站都支持哪些快捷键。

<ul id="navigation">
      <li><a href="index.html" accesskey="1">Home</a></li>
      <li><a href="search.html" accesskey="4">Search</a></li>
      <li><a href="contact.html" accesskey="0">Contact</a></li>
</ul>

对于上面这个例子,使用DOM技术,可以动态地创建一份快捷键清单,步骤如下:

  1. 把文档里的所有链接全部提取到一个节点集合里;
  2. 遍历这个节点集合里的所有链接;
  3. 如果链接里有accesskey属性,就把他的值保存起来;
  4. 同时把这个链接在浏览器窗口里的屏显文字也保存起来;
  5. 创建一个清单;
  6. 为拥有快捷键的各个链接分别创建一个列表项(li元素);
  7. 把列表项添加到“快捷键清单”里;
  8. 把“快捷键清单”添加到文档里;
function displayAccesskeys() {
    //取得文档中的所有链接
    let links = document.getElementsByTagName("a");

    let aKeys = [];
    for (let i = 0; i < links.length; i++) {
        //没有accesskey属性继续循环
        if (!links[i].getAttribute("accesskey")) continue;
        //取得accesskey的值
        let key = links[i].getAttribute("accesskey");
        //取得链接文本
        let text = links[i].lastChild.nodeValue;
        aKeys[key] = text;
    }

    //创建列表
    let list = document.createElement("ul");
    for (const key in aKeys) {
        let str = key + ": " + aKeys[key];

        //创建列表项
        let item = document.createElement("li");
        let item_text = document.createTextNode(str);
        item.appendChild(item_text);
        list.appendChild(item);
    }

    //创建标题
    let header = document.createElement("h3");
    let header_text = document.createTextNode("AccessKeys");
    header.appendChild(header_text);

    //把标题添加到页面主体
    document.body.appendChild(header);

    //把列表添加到页面主体
    document.body.appendChild(list);
}

addLoadEvent(displayAccesskeys);

8.7 检索和添加信息

本章编写了几个有用的脚本,都是使用JavaScript函数先把文档结构中的一些现有信息提取出来,再把哪些信息以一种清晰和有意义的方式重新插入到文档中去。

希望大家始终记住:JavaScript脚本只应用来充实文档中的内容,要避免使用DOM技术来创建核心内容。

9.1 三位一体的网页

我们在浏览器看到的网页其实是由以下三层信息构成的一个共同体;

  • 结构层
  • 表示层
  • 行为层

9.1.1 结构层(structural layer)

网页结构层由HTML之类的标记语言负责创建。

9.1.2 表示层(presentation layer)

表示层由CSS负责完成,CSS描述页面内容应该如何去呈现。

9.1.3 行为层(behavior layer)

行为层负责内容如何响应事件,这是JavaScript语言和DOM主宰的领域。

9.1.4 分离

在所有的产品设计活动中,选择最适用的工具去解决问题是最基本的原则。具体到网页设计工作,这意味着:

  • 使用HTML去搭建文档的结构;
  • 使用CSS去设置文档的呈现效果;
  • 使用DOM脚本去实现文档的行为;

9.2 style属性

文档中的每个元素都是一个对象,每个对象又有着各种各样的属性。其中每个元素节点都有一个属性style,style属性包含着元素的样式,查询这个属性将返回一个对象,而不是一个简单的字符串。

9.2.1 获取样式

如获取颜色值:

element.style.color

如果遇到连接符属性,例如font-family,获取属性值时需要使用驼峰命名法;

element.style.fontFamily;

内嵌样式

通过style属性获取样式有很大的局限性,style属性只能返回内嵌样式,即只有把CSS style属性插入到标记里,才可以用DOM style属性去查询那些信息。

在外部样式表里声明的样式不会进入style对象,在文档的部分里声明的样式也是如此。

9.2.2 设置样式

style对象的各个属性都是可读写的,我们不仅可以通过某个元素的style属性获取样式,还可以通过他去更新样式。

9.3 何时该用DOM脚本设置样式

在绝大多数场合,我们还是应该使用CSS去声明样式,就像你不应该利用DOM去创建重要的的内容那样,你也不应该利用DOM为文档设置重要的样式。

9.3.1 根据元素在节点里的位置设置样式

比如,我们可以利用DOM轻而易举的找出文档中的所有h1元素,然后再同样轻而易举找出紧跟每个h1元素后面的那个元素,并把样式添加给它。

9.3.2 根据某种条件反复设置某种样式

在table表格中,使用CSS3很容易设置奇数行和偶数行的样式;

tr:nth-child(odd) {background-color:#ffc;}
tr:nth-child(even) {background-color:#fff;}

如果nth-child不可用,我们就可以使用DOM去更改。

9.3.3 响应事件

CSS提供的:hover等伪类class属性允许我们根据HTML元素的状态来改变样式。DOM也可以通过onmouseover事件来对HTML元素的状态变化做出响应。当伪类选择器不被少数浏览器支持时,我们可以通过DOM去改变样式。

9.4 className属性

这里有一种更为简明的方案:与其改变某个元素的样式,不如通过JavaScript代码去更新元素的class属性。

第十章 用JavaScript实现动画效果

本章主要利用setTimeout()函数固定时间间隔操作style属性来实现动画效果,本章笔记省略。

elem.style.position = "absolute";
elem.style.left = 0;      //改变left值;
elem.style.top = 0;	  //改变top值;

第十一章 HTML5

讲解canvas,audio,video等HTML5新特性,详情略。