前端开发工程师 - 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

前端开发工程师 - 06.Mini项目实战 - 项目简介

艺术编程-技术之声第十期

JavaScript DOM编程艺术第二版学习(1/4)

自学前端,有啥好书推荐吗?

JavaScript DOM 编程艺术(第2版)---序中故