《JavaScript DOM 编程艺术》读书笔记

Author Avatar
wshunli 10月 22, 2017
  • 在其它设备中阅读本文章

《JavaScript DOM 编程艺术》读书笔记

本科的时候也有看前端的内容但是没有系统地学习,现在有 WebGIS 相关的项目,并且发现 Android 开发也发生了一些变化: 很多应用不再单单是一个简单的原生 Android 应用,用到了跨平台技术,比如说 React Native 、 Ionic 等等,这都需要 Javascript 基础。所以打算系统地学习下 Javascript 。

其实有这个想法很久了,包括暑假来到也有意学习前端技术,最开始是在慕课网看 前端基础 相关视频,然后也简单地在 菜鸟教程 看了相关文字内容,最后在廖雪峰的网站看 JavaScript全栈教程,这部分对后来 Node.js 后端开发很有帮助。

但是现在发现 Javascript 水品还不行,没有接近实战的水平,所以打算再系统学习下。
按照知乎 如何循序渐进、有效地学习JavaScript? 问题的回答,决定先学习 《JavaScript DOM 编程艺术》(第二版),后面再学习 《JavaScript高级程序设计》,最后再刷 《ECMAScript 6 入门》 学习 ES6 。

第1章 JavaScript 简史

本章主要介绍了 JavaScript 的起源、浏览器之间的战争、DOM 的演变史。

DOM (Document Object Model,文档对象模型) 是一套对文档的内容进行抽象和概念化的方法。
感觉和类的说法很类似。

第2章 JavaScript 语法

程序设计语言分为解释型和编译型两大类。Java或者C++等语言需要一个编译器,把用Java等高级语言编写出来的源代码翻译为计算机能直接执行的文件。
解释型语言不需要编译器—它们仅需要解释器,对于JavaScript而言,Web浏览器负责完成有关解释和执行工作。
浏览器的JavaScript解释器将直接读取源代码并执行,相关错误也只能在此时才能发现。

语法

JavaScript 语法基本上和 Java 或者 C++ 类似,下面主要介绍不同点。

JavaScript 是一种弱类型语言,变量使用前不需要进行类型声明,但不建议这样做。

数据类型

JavaScript 中的数据类型主要包括 字符串、数值、布尔值 三种。

  • 字符串:单引号双引号都可以,最好根据字符内容选择。
  • 数值:不单单是整数,允许任意位小数。
  • 布尔值:true 或 false 。

数组

JavaScript 数组声明不必指出数组长度:

var car = Array(5);
var car = Array();
var car = [];

声明数组的同时也可以进行填充(向数组添加元素):

var cars = new Array("Saab","Volvo","BMW");
var cars = ["Saab","Volvo","BMW"];

数组元素类型不必相同,甚至可以是数组或者对象:

var cars = new Array("Saab",2017,true);
var beatles = [];
beatles[0] = cars;
// beatles[0][1] 的值为 2017 。

对象简介

对象的声明使用 Object 关键字:

var car = new Object();
var car = {};
var person = {firstname:"John", lastname:"Doe", id:5566};

对象属性取值赋值方法:

var person = {firstname:"John", lastname:"Doe", id:5566};
person.lastname = "Wang";
var name = person.lastname;
var name = person["lastname"];

算数操作符、条件语句、循环语句

和 Java、C++ 基本一致,不再赘述。

比较操作符不太一样:
== : 表示类型转换后值是否相等;
=== :严格相等,类型必须相同。

函数

函数声明及调用方法:

function myFunction(a, b) {
    return a * b;
}
// 调用函数
myFunction(2,5);

变量的作用域

全局变量:可以在脚本的任意位置引用,包括函数内部。
局部变量:在函数内部声明,只在函数内部有效。

对象

对象是自包含的数据集合,包含在对象里的数据可通过 属性(property) 和 方法(method) 访问。

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

对象分类:

  • 自定义对象:利用 JavaScript 创建的自己的对象;
  • 内建对象:JavaScript提供的一系列预先定义好的对象。数组也可以看做是 JavaScript 的内建对象的一种。常见的还有Data对象;
  • 宿主对象:由浏览器提供的预定义对象。常见的有 windows,document 等。

第3章 DOM

DOM 三个字母的具体含义:
D 是基础,没有文档(Document)DOM 也就无从谈起;
O 是对象(Object),JavaScript 本身就可以看做是由对象构成的语言,其重要性不言而喻;
M 是模型(Model),其含义是某种事物的表现形式。
具体的说 DOM 把文档表示成了一颗家谱树(DOM 使用 parent、child,sibling 等记号来表明家庭成员之间的关系。

节点

DOM 由许多不同的节点(node)组成,节点可分为三类:
元素节点:DOM 的原子是元素节点,可以包含其他元素。
文本节点:元素节点的内容。
属性节点:元素节点的描述。

<p title="Paragraph">This is a paragraph.</p>
/**
* 元素节点:p
* 文本节点:This is a paragraph.
* 属性节点:title="Paragraph"
*/

获取元素

有三种方法可以获取元素节点,分别通过 id、标签名、class 。

// document 特有函数,返回一个元素
var x = document.getElementById("intro");

// getElementsByTagName、getElementsByClassName 返回元素数组

var y = document.getElementsByTagName("p");
var y = x.getElementsByTagName("p");
// 允许使用通配符
var y = x.getElementsByTagName("*");

var z = document.getElementsByClassName("intro");
var z = x.getElementsByClassName("intro");
// 允许查找带有多个类名的元素,并且类名顺序不重要
var z = x.getElementsByClassName("import intro");

获取和设置属性

获取和设置属性的方法如下:
getAttribute():该方法只能通过元素节点对象调用;
setAttribute():该方法允许我们对属性节点的值做出修改。

var img = document.getElementById("image");
img.getAttribute("src");
img.src;

img.setAttribute("src","landscape.jpg");
img.src = "landscape.jpg";

document.getElementById("image").src="landscape.jpg";

通过浏览器查看源代码,其属性并不会改变,也就是说 setAttribute 做出的修改不会反映到文档本身的源码里。这种“表里不一”的的现象源自 DOM 的工作模式:
先加载文档的静态内容,再动态刷新,动态刷新不影响文档的静态内容。

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

事件处理函数

事件处理函数的作用是,在特定事件发生时调用特定的代码。

 <a href="images/fireworks.jpg" title="A fireworks display" onclick="showPic(this); return false;">Fireworks</a>

事件处理函数的工作机制:在给某元素添加事件处理函数后,一旦事件发生相应的 JavaScript 代码就会执行。
被调用的 JavaScript 代码可以返回一个值,这个值将被传递至事件处理函数。

例如:我们给某链接添加一个 onclick 事件处理函数,并把这个事件处理函数所触发的 JavaScript 代码返回 true 或 false 。
这样一来,当这个链接被点击时,JavaScript 代码返回值是 true ,onclick 事件处理函数就认为 这个链接被点击了;反之,认为没有被点击。

所以在 onclick 事件处理函数所触发的 JavaScript 代码里增加一条 return false; 语句,屏蔽掉链接的默认行为。

对函数进行拓展

childNodes 属性

childNodes 属性可以获取任一元素的所有子元素。

var body_element = document.getElementById("body")[0];
// 得到 body 的所有子元素、数组
body_element.childNodes;

nodeType 属性

childNodes 属性返回的数组包括所有类型的节点,而不仅仅是元素节点。
事实上,文档里的每样东西都是一个节点,设置连空格和换行符都被解释为节点。

可通过节点的 nodeType 属性区分不同的节点。

node.nodeType

返回值是一个数字。

nodeType 共有 12 种取值,其中仅 3 种具有实用价值。

元素节点:1.
属性节点:2.
文本节点:2.

nodeValue 属性

若改变 文本节点 的值,可通过 DOM 提供的 nodeValue 属性。

node.nodeValue

注意:对于元素节点而言,element.nodValue 得到的值并不是元素的文本值。

正确的做法的先得到元素节点的文本节点,再取 nodeValue 属性:

element.childNodes[0].nodeValue

firstChild 和 lastChild 属性

firstChild:元素的第一个子元素。
lastChild:元素的最后一个元素。

function showPic(whichPic) {
    var source = whichPic.getAttribute("href");//获取资源路径
    var placeholder = document.getElementById("placeholder");
    placeholder.setAttribute("src",source);

    var text = whichPic.getAttribute("title");//获取内容
    var description = document.getElementById("description");
    description.firstChild.nodeValue = text;
}

第5章 最佳实践

1、在使用任何一句JavaScript代码时,都应该想想,对这个网页是否有用;
2、平稳退化(graceful degradation):如果正确使用了 JavaScript 脚本,可以让访问者在他们的浏览器不支持 JavaScript 的情况下仍能顺利地浏览你网站。
虽然某些功能无法使用,但是最基本的操作仍能顺利完成;
3、渐进加强:用额外的信息层去包裹原始数据;使 CSS 代码负责提供关于“表示”的信息,JavaScript 代码负责提供关于“行为”的信息。
4、分离 JavaScript:在 HTML 文档中使用诸如 onclick 之类的属性也是一种没有效率又容易引发问题的做法。
如果利用像 CSS 中的 class 和 id 属性那样,把JavaScript 代码调用行为与 HTML 文档内容和结构分离,网页就会健壮不少。
5、向后兼容:对象检测:检测浏览器对 JavaScript 的支持程度。
用一个 if 语句的条件表达式看求值结果是 true 还是 false 来采取不同的行动。
如在代码前加上 if(!getElementById) return false;
6、性能考虑:
尽量少访问DOM和尽量减少标记,不管什么时候只要是查询DOM中的某些元素,浏览器就会搜索整个DOM树,从中查找可能匹配的元素。
在多个函数都会取得一组类似元素的情况下,可以考虑重新构建代码,把搜索结果保存在一个全局变量里,或者把一组元素以参数形式传递给函数。
减少标记数量的目的在于,过多的不必要的元素只会增加DOM树的规模。
7、合并和放置脚本:减少请求数量是在性能优化时首先要考虑的;
把所以的<script>标签都放在文档的末尾,</body>标记之前,就可以让页面变得更快。
8、压缩脚本:指的是把脚本文件中的不必要的字节,比如空格和注释,统统删除,从而达到“压缩”文件的目的;
多数情况下应该有两个版本,一个是工作副本,可以修改代码并添加注释,另一个是精简副本,用于放在站点上,通常在精简副本的文件名上加上 min 字样。

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

本章主要是第5章内容在 图片库 上的实践。

共享onload事件:

假如有两个函数 firstFunction 和 secondFunction 需要在页面加载时执行:

window.onload = firstFunction;
window.onload = secondFunction;

这样做的话实际上只有后一个可以执行。

window.onload = function()
{
  firstFunction();
  secondFunction();
}

这样做创建了一个匿名函数,在需要绑定的函数不是很多的场合的确很实用.

最佳解决方案,使用 addLoadEvent 函数。
这个方案需要额外添加一些代码,但一旦有了这些代码,绑定函数到 onload 就很方便。

function addLoadEvent(func){
   //将现有的 window.onload 处理函数保存到 oldonload
  var oldonload = window.onload;
    //如果现有的 window.onload 上没有处理函数,将 func 添加给它
  if(typeof oldonload != 'function')
  {
    window.onload = func;
  }
  else{
    window.onload function()
    {
      oldload();
      func();
    }
  }
}

第7章 动态创建标记

JavaScript 可以通过创建新元素和修改现有元素改变网页的结构。

传统方法

document.write

document 的 write() 方法可以方便快速地将字符串插入到文档内。

<body>
  <script type="text/javascript">
    document.write("<p>This is inserted.</p>");
  </script>
</body>

缺点就是违背了”行为与表现分离的原则”,
即使把这句语句挪到外部,还是需要在<body>里边添加<script>标签才可以调用。

innerHTML 属性

innerHTML 属性可以用来读写某给定元素里地 HTML 元素。

<body>
  <div id="testdiv"></div>
</body>

window.onload = function(){
  var testdiv = document.getElementById("testdiv");
  testdiv.innerHTML="<p>I inserted <em>this</em> content.</p>";
}

利用此技术无法区分“插入一段内容”还是“替换一段内容”。

DOM 方法

createElement 方法

var para = document.createElement("p");

创建元素节点,只创建会出现一个文档碎片(document fragment)。
本身并不影响页面表现,它是游荡在JavaScript世界里的一个孤儿。
但是它已经有 nodeType 和 nodeName 属性。

appendChild 方法

把新创建的节点插入文档的节点树最简单方法是:让其成为某个现有节点的一个子节点。

var para = document.createElement("p");
var testdiv = document.getElementById("testdiv");
testdiv.appendChild(para);

createTextNode 方法

创建文本节点填充元素节点的内容。
把文本节点插入为元素节点的子节点。

var para = document.createElement("p");
var testdiv = document.getElementById("testdiv");
testdiv.appendChild(para);
var txt = document.createTextNode("Hello World");
para.appendChild(txt);

改变顺序,二者结果相同。

var para = document.createElement("p");
var txt = document.createTextNode("Hello World");
para.appendChild(txt);
var testdiv = document.getElementById("testdiv");
testdiv.appendChild(para);

重回图片库

在已有元素前插入元素

DOM 提供了 insertBefore() 方法,把一个元素插入到现有元素之前。

parentElement.insertBefore(newElement,targetElement);

其中:
parentElement:目标元素的父元素,
newElement:想插入的元素,
targetElement:想插入哪个元素之前。

var gallery = document.getElementById("imagegallery");
gallery.parentNode.insertBefore(placeholder,gallery);

在已有元素后插入元素

DOM 并没有提供了 insertAfter() 方法,下面编写:

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

var gallery = document.getElementById("imagegallery");
insertAfter(placeholder,gallery);

Ajax

Ajax 可以做到只更新页面中的一小部分,其它内容不用重新加载。
Ajax 的主要优势是对页面的请求以异步方式发送到服务器。

XMLHttpRequest 对象

Ajax 的核心是 XMLHttpRequest 对象,XMLHttpRequest 充当浏览器脚本与服务器之间的中间人的角色。
JavaScript 可以通过这个对象自己发送请求,同时自己处理响应。

function getnewContent (){
  var request = new XMLHttpRequest();
  if(request){
    request.open("GET","ajax_info.txt",true);
    request.onreadystatechange = function(){
      if(request.readyState == 4){
        var para = document.createElement("p");
        var txt = document.createTextNode(request.responseText);
        para.appendChild(txt);
        var testdiv = document.getElementById("testdiv");
        testdiv.appendChild(para);
      }
    };
    request.send(null);
  }
}

其中 readyState 属性的值,有5个可能值:
0 表示未初始化
1 表示正在加载
2 表示加载完毕
3 表示正在交互
4 表示完成

访问服务器返回的数据要通过两个属性完成。
responseText:保存文本字符串形式的数据。
responseXML:保存 Content-Type 头部指定为 “text/xml” 的数据。

注意 异步请求,脚本在发送 XMLHttpRequest 请求之后仍然会继续执行,不会等待响应返回。

HIjax

HIjax 意思是渐进增强地使用 Ajax 。

第8章 充实文档内容

JavaScript 脚本只应该用来充实文档内容,要避免使用 DOM 技术来创建核心内容。

function displayAbbreviations() {
  if (!document.getElementsByTagName || !document.createElement || !document.createTextNode) return false;
// 得到所有链接
  var abbreviations = document.getElementsByTagName("abbr");
  if (abbreviations.length < 1) return false;
  var defs = new Array();
// 遍历链接
  for (var i=0; i<abbreviations.length; i++) {
    var current_abbr = abbreviations[i];
    if (current_abbr.childNodes.length < 1) continue;
    var definition = current_abbr.getAttribute("title");
    var key = current_abbr.lastChild.nodeValue;
    defs[key] = definition;
  }
// 创建列表
  var dlist = document.createElement("dl");
// 遍历访问键
  for (key in defs) {
    var definition = defs[key];
    var dtitle = document.createElement("dt");
    var dtitle_text = document.createTextNode(key);
    dtitle.appendChild(dtitle_text);
    var ddesc = document.createElement("dd");
    var ddesc_text = document.createTextNode(definition);
    ddesc.appendChild(ddesc_text);
// 添加列表项到列表中
    dlist.appendChild(dtitle);
    dlist.appendChild(ddesc);
  }
  if (dlist.childNodes.length < 1) return false;
// 创建标题
  var header = document.createElement("h2");
  var header_text = document.createTextNode("Abbreviations");
  header.appendChild(header_text);
// 把标题添加到页面主体
  document.body.appendChild(header);
// 把列表添加到页面主体
  document.body.appendChild(dlist);
}
addLoadEvent(displayAbbreviations);

我的理解就是把原本 HTML 的内容通过 JavaScript 操作提取出来摘要,再加到 HTML 中充实文档内容。

第9章 CSS-DOM

我们在浏览器看到的网页其实有三部分构成:

  • 结构层(structural layer) 由 XHTML 或者 HTML 等标记语言创建。
  • 表示层(presentation layer) 由 CSS 负责创建。
  • 行为层(behavior layer) 负责内容应该如何响应事件这一动作,这主要是由 javaScript 和 DOM 负责。

style 属性

在文档中每个人元素都是一个对象,每个元素都有一个 style 属性,他们也是一个对象。

获取样式

element.style.color;
// 中间带连字符的 CSS 属性要使用驼峰写法
element.style.fontFamily;

获取样式属性的返回值与设置值采用同样的单位。
如我们在 CSS font-size 属性时以 em 为单位,相应的 DOM fontSize 属性也以 em 为单位。

注意 通过 style 获取属性的局限性,即只能返回 内联样式

<p style="color:sienna;margin-left:20px">这是一个段落。</p>

通过 link 元素引入的 CSS 文件样式不能用 DOM style 属性检索出来。

<link rel="stylesheet" type="text/css" href="mystyle.css">

通过 <head> 部分引入的 <style> 标签里也不能用 DOM style 属性检索出来。

<head>
  <style>
  hr {color:sienna;}
  p {margin-left:20px;}
  </style>
</head>

设置样式

style 对象的各个属性都是可读写的,可以通过元素的 style 属性获取样式,也可以通过它更新样式。

element.style.property = value;
// 例如:
para.style.color = "black";

何时该用 DOM 脚本设置样式

在绝大多数场合还是应该使用 CSS 声明样式。
在使用 CSS 不方便的场合,可以利用 DOM 对文档的样式做一些小的增强。

通过 CSS 设置样式方式:

// 1.通过标签元素
p {
  font-size: 1em;
}
// 2.通过class属性
.fineorint {
  font-size: 1em;
}
// 3.通过id属性
#fineorint {
  font-size: 1em;
}

通过 DOM 脚本设置样式的情况:

  • 根据元素在节点数的位置来设置样式
  • 根据某种条件反复设置某种样式
  • 响应事件,即事件发生时设置有关元素的样式

className 属性

前面一直在使用 DOM 直接设置或者修改元素的样式,这种让行为层干表示层的活,并不是理想的工作方式。

与其使用 DOM 直接改变某个元素的样式,倒不如通过 JavaScript 代码修改元素的 class 属性。

通过修改 DOM 直接改变元素样式:

function styleHeaderSiblings() {
  if (!document.getElementsByTagName) return false;
  var headers = document.getElementsByTagName("h1");
  for (var i=0; i<headers.length; i++) {
    var elem = getNextElement(headers[i].nextSibling);
    elem.style.fontWeight = "bold";
    elem.style.fontSize = "1.2em";
  }
}

通过修改 class 属性:

function styleHeaderSiblings() {
  if (!document.getElementsByTagName) return false;
  var headers = document.getElementsByTagName("h1");
  for (var i=0; i<headers.length; i++) {
    var elem = getNextElement(headers[i].nextSibling);
    elem.setAttribute("class","intro");
  }
}

当然需要提前引入 CSS 样式:

.intro {
  font-weight: bold;
  font-size: 1.2em;
}

更简单的是通过 className 属性。
className 属性是一个可读/可写的属性,凡是元素节点都有这个属性。

获得元素的 class 属性:
element.className
修改元素的 class 属性:
element.className = value

通过 className 属性修改样式:

function styleHeaderSiblings() {
  if (!document.getElementsByTagName) return false;
  var headers = document.getElementsByTagName("h1");
  for (var i=0; i<headers.length; i++) {
    var elem = getNextElement(headers[i].nextSibling);
    elem.className = "intro";
  }
}

缺点:通过 className 属性设置元素的 class 属性将替换原有 class 设置。

可以通过 字符串拼接 解决(注意 intro 前的空格):

elem.className += " intro";

第10章 用JavaScript实现动画效果

JavaScript 能够按照预定的时间间隔重复调用一个函数,而这意味着我们可以随着时间的推移而不断地改变某个元素地样式。

位置

位置通常是由 CSS 负责设置的:

element {
  position: absolute;
  top: 50px;
  left: 100px;
}
  • static: 默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。
  • relative: 生成相对定位的元素,相对于其正常位置进行定位。因此,”left:20” 会向元素的 LEFT 位置添加 20 像素。
  • absolute: 生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。
  • fixed: 生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。
  • inherit: 规定应该从父元素继承 position 属性的值。

时间

JavaScript 函数 setTimeout 能让某个函数在经过一段预定的时间后才开始执行。

variable = setTimeout("function",interval);

第一个参数为函数名字,第二个参数为间隔时间。若想取消执行:

clearTimeout(variable)

本章主要是定义的一个位置随时间运动的函数:

function moveElement(elementID,final_x,final_y,interval) {
  if (!document.getElementById) return false;
  if (!document.getElementById(elementID)) return false;
  var elem = document.getElementById(elementID);
  var xpos = parseInt(elem.style.left);
  var ypos = parseInt(elem.style.top);
  if (xpos == final_x && ypos == final_y) {
    return true;
  }
  if (xpos < final_x) {
    xpos++;
  }
  if (xpos > final_x) {
    xpos--;
  }
  if (ypos < final_y) {
    ypos++;
  }
  if (ypos > final_y) {
    ypos--;
  }
  elem.style.left = xpos + "px";
  elem.style.top = ypos + "px";
  var repeat = "moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")";
  movement = setTimeout(repeat,interval);
}

第11章 HTML5

本章介绍了 HTML5 的新特性,包括 <canvas><audio><video> 元素及新的表单控件。

第12章 综合示例

结合前面的知识实现了一个综合示例,对前面的知识进行了很好的回顾。

这本书看下来,感受最大的是 HTML 和 CSS 基础太差,JavaScript 部分还好。
原来计划上个周末看完呢,中间又催文档啥的,这个周末(2017.10.29)才看完。

如果本文对您有所帮助,且您手头还很宽裕,欢迎打赏赞助我,以支付网站服务器和域名费用。 https://paypal.me/wshunli 您的鼓励与支持是我更新的最大动力,我会铭记于心,倾于博客。
本文链接:https://www.wshunli.com/posts/25be2399.html