前端开发工程师 - 03.DOM编程艺术 - 第1章.基础篇(上)
Posted FudgeBear
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端开发工程师 - 03.DOM编程艺术 - 第1章.基础篇(上)相关的知识,希望对你有一定的参考价值。
第1章.基础篇(上)
Abstract:文档树、节点操作、属性操作、样式操作、事件
DOM (Document Object Model) - 文档对象模型
以对象的方式来表示对应的html,它有一系列的规范
i.e.
在浏览器中,DOM是通过JS实现的。
DOM:
DOM Core:核心结构、API的定义
DOM HTML: 定义HTML如何转化成对象(HTML对应的对象)-- 操作节点
DOM Style:样式转换成对象 -- 操作样式
DOM Event:事件对象的模型 -- 响应用户的操作
文档树
HTML -> DOM树
节点遍历
node.parentNode
.firstChild
.lastChild
.previousSibling
.nextSibling
.firstElementChild
.lastElementChild
.nextElementSibling
.previousElementSibling
i.e.
p.parentNode是body
p.firstChild是hello,
p.firstElementChild是span
p.lastElementChild是img
p.lastChild是img
p.previousSibling没有,则返回null
p.nextSibling是div
节点类型:
ELEMENT_NODE:元素节点 (如上body, p, div, span, img)
TEXT_NODE:文本节点(如上hello,, 微专业, mooc)
COMMENT_NODE
DOCUMENT_TYPE_NODE
课堂交流:如何实现浏览器兼容版的element.child
element.children能够获取元素的元素子节点,但是低版本的ie不支持,如何在低版本的ie上兼容类似的功能。
http://www.jianshu.com/p/b7e111015c48
节点操作
Abstract: getElementById, getElementsByClassName, getElementsByTagName, querySelector(All), createElement, innerText, appendChild, insertBefore, removeChild, innerHTML
浏览器读取HTML渲染出页面结构以后,还可以通过JS改变页面的结构
获取节点:
通过节点关系可以获取节点(父子关系、兄弟关系)
缺点:可维护性差,如果一个节点的位置发生了变化,则关系也可能会被打乱
所以,一般使用接口来获取节点(获得的是节点对象:
getElementById:
element = document.getElementById(id):id在document中是唯一标识
getElementsByTagName:
collection = element.getElementsByTagName(tagName):通过元素来调用来获取元素内的节点
若tagName为"*", 则会获取指定元素element包含的所有的后代元素节点
注:collection是动态的集合
getElementsByClassName:
collection = element.getElementsByClassName(className)
通过空格分割,可以指定多个类名(无序),获取同时具有多个类名的元素
但是IE 6/7/8不兼容getElementsByClassName
function getElementsByClassName(element, classNames) { if (element.getElementsByClassName) { // 特性侦测,如果兼容则优先使用W3C规范的方式 return element.getElementsByClassName(classNames); } else { var elements = element.getElementsByTagName("*"); // 所有后代元素 var result = []; var element, classNameStr, flag; classNames = classNames.split(\' \'); for (var i = 0; element = elements[i]; i++) { classNameStr = \' \' + element.className + \' \'; flag = true; for (var j = 0, className; className = classNames[j]; j++) { if (classNameStr.indexOf(\' \' + className + \' \') == -1) { flag = false; break; } } if (flag) { result.push(element); } } return result; } }
querySelector:
element = element.querySelector(selector)
返回第一个符合的元素
querySelectorAll:
list = element.querySelectorAll(selector)
i.e.
<div id="users"> <h2>....</h2> <ul> <li class="user">Satoshi</li> <li class="user">春来草青</li> <li class="user last">Kash</li> </ul> </div>
var users = document.querySelector(#users"); // 获取到元素div#users
users.querySelectorAll(".user"); // 获取到 [ li.user li.user li.user.last ]
document.querySelectorAll("#users .user"); // 获取到同上 [ li.user li.user li.user.last ]
注:list和collection不同之处:list静态的,获取到的结果之后就不会自动同步改变了;而collection是动态的
IE6/7不兼容,IE 8部分兼容
若有如下场景:
想要在<ul> ... </ul>的末尾处增加一个<li>节点,应该怎么做
<ul> <li>...</li> ... <li class="user"> <img src="lf.jpg"> <a href="/user/lf">lifeng</a> </li> </ul>
1. 创建li节点;设置li节点的class属性;插入li节点
2. 创建img节点;设置img节点的src属性;插入img节点
3. 创建a节点;设置a节点的hrf属性;设置a节点的内容;插入a节点
var li = document.createElement("li"); li.className = \'user\'; ul.appendChild(li); var img = document.createElement("img"); img.src = \'xxx.jpg\'; li.appendChild(img); var a = document.createElement("a"); a.href = \'/user/xxx\'; a.innerText = "lifeng"; li.appendChild(a);
创建节点
element = document.createElement(tagName);
i.e. var li = document.createElement("li");
修改节点
element.textContent:表示节点及其后代节点的文本内容,但是IE9不支持
i.e. a.textContent = "lifeng";
innerText:不规范,但是经常使用(Firefox不支持)(几乎和textContext一模一样)
i.e. a.innerText = "lifeng";
如果要让Firefox兼容的话(使用textContent)
if(!(\'innerText\' in document.body)) { // 特性侦测 HTMLElement.prototype._defineGetter_("innerText", function() { return this.textContent; }); HTMLElement.prototype._defineSetter_("innerText", function(s) { return this.textContent = s; }); }
插入节点
var achild = element.appendChild(achild); // 在element元素内的末尾追加achild节点
i.e. ul.appendChild("li");
var achild = element.insertBefore(achild, referenceChild); // 在referenceChild元素之前插入achild节点
i.e. users.insertBefore(li, ul.firstChild);
删除节点
var child = element.removeChild(child);
i.e. user.parentNode.removeChild(user);
innerHTML:节点的HTML内容
刚才的例子中,为了添加一个lifeng的<li>节点需要那么长的代码,可以使用innerHTML来提高效率
i.e. 设已经添加了一个li节点在ul的末尾;
li.innerHTML = \'<img src="xxx.jpg">\\
<a href="/user/xxx">lifeng</a>\';
li.innerHTML = ""; // 起到删除所有子节点的作用
那可以使用 li.innerHTML += "<li>.......</li>"; 来实现吗?
可以,但是,相当于重新设置了HTML的内容,之前修改过/添加了的事件/样式/状态就会被清除 (覆盖)
innerHTML的问题:内存泄露、安全问题
i.e. 安全问题:利用innerHTML运行代码
var userName = \'</a><a onclick="alert(\\"我是黑客\\");" href="#">lifeng\'; li.innerHTML = \'<img src="xxx.jpg">\\ <a href="/user/xxx/">\' + userName + \'</a>\';
建议仅用于创建新节点
属性操作
实例:登录框的登陆按钮在点击一次后为了避免重复提交会修改按钮的disabled属性让其不可点击
每个HTML attribute都可以对应一个DOM property,修改DOM property就能实现对HMLT attribute的修改
<div> <label for="userName">用户名:</label> <input id="userName" type="text" class="u-txt"> </div>
i.e. 上例中对应的DOM property为:label.htmlFor="userName"; input.id="userName"; input.type="text"; input.className="u-txt"(因为for和class是关键字)
三种方法进行属性操作:属性访问器、get/setAttribute、自定义属性dataset
property accessor:
i.e. input.className; // "u-txt"
input["id"]; // "userName"
input.value = \'www@163.com\'; // 给input增加一个value属性并赋值
转换的类型:
(Boolean类型的变量除非设置成false,否则不论是空还是0都表示true)
转换过的为实用对象
优缺点:通用性差--名字异常;扩展性差--每增加一个html属性就需要对应一个DOM属性;优点:获得的是一个实用对象
set/getAttribute:
var attribute = element.getAttribute(attributeName); // 读
element.setAttribute(attributeName, value); // 写
i.e. input.getAttribute("class"); // "u-txt"
input.setAttribute("value", "www@163.com");
input.setAttribute("disabled", ""); // 设置disabled属性为true(前面提到了,只要属性出现了而且不是false,那么就是true)
转换的类型都为String,获得的是属性字符串
优缺点:只能处理字符串,推荐如果是纯字符串操作就使用get/setAttribute()
dataset:自定义属性
HTMLElement.dataset
data-*属性集
一般用于元素上保存数据(自定义的数据属性)
<div id="user" data-id="123456" data-account-name="wwq" data-name="smith" data-email="wwq123@163.com" data-mobile="123123">wwq</div>
属性名:将"data-"去掉,如果有连接符,会以大写开头表示
i.e. 鼠标悬停在姓名上时会显示对应详细信息的表格
<body> <ul> <li data-id="123456" data-account-name="wwq" data-name="魏文庆" data-email="wwq123@163.com" data-mobile="13524543878">wwq</li> <li data-id="123457" data-account-name="cjf" data-name="蔡剑飞" data-email="cjf123@163.com" data-mobile="13968789868">cjf</li> </ul> <div id="card" style="display:none"> <!-- 将空卡片隐藏 --> <table> <caption id="accountName"></caption> <tr><th>姓名:</th><td id="name"></td></tr> <tr><th>邮箱:</th><td id="email"></td></tr> <tr><th>手机:</th><td id="mobile"></td></tr> </table> </div> <script> function $(id){ return document.getElementById(id); } var lis = document.getElementsByTagName(\'li\'); for(var i = 0, li;li = lis[i]; i++){ li.onmouseenter = function(event){ event = event || window.event; var user = event.target|| event.srcElement; var data = user.dataset; // 插入相关数据 $(\'accountName\').innerText = data.accountName; $(\'name\').innerText = data.name; $(\'email\').innerText = data.email; $(\'mobile\').innerText = data.mobile; // 显示卡片 $(\'card\').style.display = \'block\'; }; li.onmouseleave = function(event){ $(\'card\').style.display = \'none\'; }; } </script> </body>
dataset在低版本浏览器中不兼容,怎么实现?
function dataset(element) { // my own version(haven\'t checked yet) if (element.dataset) { // check whether the original version od dataset is available return element.dataset; } else { var data = []; for (var i = 0; i < element.attributes.length; i++) { // traverse all the attributes element has if (/^data-/.test(element.attributes[i].nodeName) { // attribute_name starts with "data-" data[element.attributes[i].nodeName.replace("data-","")] = element.attributes[i].nodeValue; } } return data; } }
样式操作
样例:
格式不正确
QQ空间换皮肤等
-- 可通过JS动态修改样式
CSS --> DOM
<head> <link rel="stylesheet" href="base.css"> <style> body {margin: 30;} p {color: #aaa; line-height: 20px; } </style> </head> <body> <p style="color:red;"> paragraph</p> </body>
3中css的引用:
<link> 对应的为element.sheet
<style> 对应的为element.sheet
style="" 对应的为element.style
整张页面的所有样式对应的为document.styleSheets
i.e. element.sheet:
element.sheet.cssRules : 对应body{...} 和 p{...}
element.sheet.cssRules[1] : 对应p{...}
element.sheet.cssRules[1].selectorText : 对应选择器p
element.sheet.cssRules[1].style : 属于类CSSStyleDeclaration的对象。对应p{...}中的css声明color:#aaa; line-height:20px;
element.sheet.cssRules[1].style.lineHeight : 对应属性值20px
element.style:属于CSSStyleDeclaration类的对象,遇上相同,对应css声明color:red;
element.style.color : 对应red
对样式的增删查改:
更新样式:
element.style.borderColor = \'red\';
element.style.color = \'red\';
-- 更新一个属性需要一条语句,而且不是css格式
更好的方式:cssText
i.e. element.style.cssText = \'border-color:red; color:red;\';
-- 一条语句可以设置一个元素,符合css格式
但是:样式与逻辑混合
更好的方式:更新class -- 开发中使用的方法
首先,在css样式中增加样式 .invalid { border-color:red; color:red; }
在JS中输入框对象处:element.className += \'invalid\';
但是如果要一次性修改批量元素的样式呢,比如上述的QQ空间换肤实例:可以使用更换样式表
原版本 <link rel="stylesheet" href="style.css">
可以拆分为两个样式表 base.css 和 skin.spring.css
要换肤时,比如换成夏天皮肤,则将skin.spring.css 换成 skin.summer.css即可
<head> <title>换肤 - 更新样式</title> <link rel="stylesheet" href="base.css"> <link id="skin" rel="stylesheet" href="skin.spring.css"> </head> <body> <div class="m-tw clearfix"> <div class="u-img"> <a href="#"><img src="zhm.jpg" alt=""></a> </div> <div class="txt"> <h3><a href="#">张惠妹</a></h3> <p>亞洲國寶級傳奇天后「 a MEI」我是a MEI,一個你認識很久,卻認識不完的女人。</p> </div> </div> <button id="change">换肤</button> <script src="../util.js"></script> <script> Util.addEventListener($(\'change\'), \'click\', changeSkin); function changeSkin(){ $(\'skin\').href = "skin.summer.css"; } </script> </body>
/* skin.spring.css */ body{background-color: #d6e6c6;} .m-tw .u-img{border-color: #6e9d41;} .m-tw p{color: #367701;} .m-tw h3{background-color: #6e9d41;} .m-tw h3 a, .m-tw h3 a:hover{color: #fff;}
/* skin.summer.css */ body{background-color: #fefaf7;} .m-tw .u-img{border-color: #a84c5b;} .m-tw p{color: #6d3e48;} .m-tw h3{background-color: #a84c5b;} .m-tw h3 a, .m-tw h3 a:hover{color: #fff;}
相似的,也可以进行删除样式表、添加样式表等操作
获取样式:
i.e. 有一<input type="text">
element.style.color; // ""
字体是黑色,为什么获取到的为空呢?因为element.style对应的为内嵌样式表,确实为空
若是<input type="text" style="color:red"> 则可获取到"red"
实际上有三种样式设置方式,而且style获取到的不一定是实际样式,所以不采取上述方法获取样式属性
window.getComputedStyle()
var style = window.getComputedStyle(element [, pseudoElt]);
// 返回值的类型也是CSSStyleDeclaration,但是是只读的,包含了所有的属性名和属性值的键值对
i.e. window.getComputedStyle(input).color; // "rgb(0,0,0)"
电子书 JavaScript DOM编程艺术.pdf