前端开发工程师

第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)"

IE9以下不兼容,可以使用element.currentStyle(不规范)

http://web.zhydaxq.com/2017/04/14/%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9%E7%89%88%E7%9A%84window-getcomputedstyle/

  

事件

什么是DOM事件?当我们点击一个DOM元素时,或键盘按下一个键,或在输入框中输入内容,或页面加载完成时,都是DOM事件

事件流:

DOM事件的处理过程 http://www.w3.org/TR/uievents/#dom-event-architecture 

i.e. 

当点击了a标签时,就会产生一个DOM的click事件,事件的处理过程:

capture phase:从DOM树的根节点开始捕获直到事件节点的父元素:window->document->html->body->div->p

target phase:事件的触发过程 从父节点p到事件发生所在节点a

bubble phase:冒泡过程:从事件所在节点的父节点开始,冒泡到最顶层的window对象

(IE低版本没有捕获过程;而且也不是所有事件都有这三个过程,比如页面load事件并没有冒泡过程)

事件注册与触发

注册事件

eventTarget.addEventListener(type, listener [,useCapture])

type: 事件类型;listener:事件处理函数;useCapture:是否为捕获过程(默认处理的是冒泡过程)

var element = document.getElementById('div1');
var clickHandler = function(event) {
    // TO-DO
}
element.addEventListener('click', clickHandler, false); // 默认为false

还有一种方法进行注册:

element.onclick = clickHandler;

缺陷:这种注册方式下的事件处理函数只能有一个,但很多情况下需要注册多个事件处理函数

取消事件注册

eventTarget.removeEventListener(type, listener [,useCapture])

i.e. element.removeEventListener('click', clickHandler, false);

或者通过 element.onclick = null; 来取消注册

事件触发:使用代码来触发事件(不包括点击之类的触发)

eventTarget.dispatchEvent(type);  // type:事件类型;就能触发相应类型的DOM事件了

i.e. elem.dispatchEvent('click');  // 使用代码触发elem元素的单击事件

低版本浏览器的兼容:IE6/7/8没有采用这些W3C标准

没有capture捕获阶段,只能处理冒泡阶段

事件注册与取消:attchEvent/detachEvent

事件触发:fireEvent(e)

var addEvent = document.addEventListener ?
    function(elem, type, listener, useCapture) {
        elem.addEventListener(type, listener, useCapture);
    } :
    function(elem, type, listener, useCapture) {
        elem.attachEvent('on' + type, listener);
    };    // 区别:1. 事件类型前缀了'on',2. 没有useCapture参数

var delEvent = document.removeEventListener ?
    function(elem, type, listener, useCapture) {
        elem.removeEventListener(type, listener, useCapture);
    } :
    function(elem, type, listener, useCapture) {
        elem.detachEvent('on' + type, listener);
    };

事件对象

当事件触发时,会调用事件处理函数,此时需要事件状态的信息,事件对象就包含了这些信息。引擎在调用处理函数时会传入事件对象

i.e. 上例elem.addEventListener('click', clickHandler, false);中

触发click事件时会调用clickHandler函数,执行时会传入event对象,即事件对象(var clickHandler = function(event) {...})

可能该事件对象包含了鼠标位置等信息

在IE的低版本里有一些不同,事件的event对象并不是直接通过函数传入的,而是放在window对象中

兼容版本:

var elem = document.getElementById('div1');
var clickHandler = function(event) {
    event = event || window.event;
    // TO-DO
}

事件对象的属性和方法:

不同分类的事件在不同场景下的事件对象都有可能不同

通用的属性和方法:

属性:

type: 事件类型,如click

target(IE低版本为srcElement): 事件触发的节点,如a元素

currentTarget: 当前处理事件的节点(处理一个事件时,不一定要把事件注册在target上,可以注册在父节点(由于冒泡))

只有事件处于目标阶段时,currentTarget和target的值才是肯定相同的

方法:

stopPropagation: 阻止传播,使事件的冒泡停止

preventDefault: 阻止默认行为

默认行为:比如双击文字时文字会被选中,比如单击连接时连接会被打开

stopImmediatePropagation: 阻止冒泡

i.e. 

event.stopPropagation() (W3C): 阻止冒泡到父节点

event.cancelBubble=true (IE低版本)

event.stopImmediatePropagation() (W3C): 两个结果:1. stopPropagation(); 2. 所有该节点的后续事件也不会触发

event.preventDefault() (W3C)

event.returnValue=false (IE低版本)

 

事件分类 http://www.w3.org/TR/uievents/ 

Abstract: 事件分类及继承关系;鼠标事件类型、鼠标事件对象、鼠标事件举例;键盘、输入、焦点事件类型、事件对象、事件举例

MouseEvent:

事件类型:

click:单击

dbclick:双击

mousedown:按下鼠标

mousemove:鼠标移动

mouseout:鼠标从某元素上移开

mouseover:鼠标在某元素上(若鼠标在元素子元素上,也是可行的--可冒泡)

mouseup:释放鼠标

mouseenter:鼠标进入元素(只有鼠标在该元素上时,不可冒泡)

mouseleave:鼠标离开元素(不可冒泡,与mouseout(可冒泡)的区别和mouseenter和mouseover的区别一样)

属性:

clientX, clientY:到页面的左上的距离

screenX, screenY:到屏幕的左上的距离

ctrlKey, shiftKey, altKey, metaKey:事件触发时若键按下则为true

button:值为0/1/2,表示按下的是鼠标的左键/中间键/右键

顺序:

i.e. 鼠标从元素A外面移动到元素A上面,再划出去的过程中:

mousemove -> mouseover(A) -> mouseenter(A) -> mousemove -> mouseout(A) -> mouseleave(A)

(mousemove一直在触发,触发间隔由浏览器决定)

i.e. 点击元素:

mousedown -> [mousemove] -> mouseup -> click

(click事件是在鼠标松开之后才触发的)

实例. 拖拽div

<div id="div1"></div> 
<style type="text/css">
    #div1{ position:absolute; top:0; left:0;
        border:1px solid #000;
        width:100px; height:100px;
    }
</style> 
var elem = document.getElementById("div1");
var clientX, clientY, moving;
var mouseDownHandler = function (event) {
    // 鼠标按下
    event = event || window.event;
    clientX = event.clientX;
    clientY = event.clientY;
    moving = !0;  // 点下去后设置moving为true
}
var mouseMoveHandler = function(event) {
    // 鼠标移动(drag的移动过程)
    if (!moving) return;  // 鼠标还未down
    event = event || window.event;
    var newClientX = event.clientX,
          newClientY = event.clientY;
    var left = parseInt(elem.style.left) || 0,
          top = parseInt(elem.style.top) || 0;
    elem.style.left = left + (newClientX - clientX) +'px';  // 用offset量来确定div的移动
    elem.style.top = top + (newClientY - clientY) +'px';
    clientX = newClientX;  // 更新clientX和clientY,用于下次move触发
    clientY = newClientY;
}
var mouseUpHandler = function (event) {
    moving = !1;
}
addEvent(elem, 'mousedown', mouseDownHandler);
addEvent(elem, 'mousemove', mouseMoveHandler);
addEvent(elem, 'mouseup', mouseUpHandler); 

WheelEvent:从MouseEvent继承

事件类型只有一种 wheel

属性: 

deltaMode:指定delta值的单位

deltaX、deltaY、deltaZ:在X/Y/Z方向上的偏移量

FocusEvent:元素获取或失去焦点的时候触发(比如点击输入框出现光标/点击页面其他地方取消输入框可输入状态)

事件类型:

blur:元素失去焦点时

focus:元素获得焦点时

focusin:元素即将获得焦点时(获得焦点之前)

focusout:元素即将失去焦点时(失去焦点之前)

属性:

relatedTarget:当一个元素失去焦点时,另一个元素就会获得焦点,blur/focusout中获得焦点的元素就是relatedTarget

 

当一个元素获得焦点时,另一个失去焦点的元素就是focus/focusin里面的relatedTarget

InputEvent:处理输入事件

事件类型:

beforeinput:输入在页面上还不能看到时

input:当输入框里的内容已经有输入内容时(继续输入时也会不断触发input事件)

在IE低版本中没有input事件,使用onpropertychange

KeyboardEvent:处理键盘事件

事件类型:

keydown:按下键

keyup:松开键

属性:

key:字符串,按下的按键

code:字符串,按键对应码

ctrlKey/shiftKey/altKey/metaKey:标识是否被按下

repeat:一个键持续按着

以上为W3C,以下为常用非标准

keyCode、charCode、which:用于获取按键对应的ASCII码,实际编程用这些ASCII码来判断

Event 最基本的事件:

事件类型:

load:代表元素 (如window, image, iframe等依赖网络加载的元素) 加载完成

unload:与load对应,代表元素退出时(被关闭)

error:加载错误,比如讲img的src路径写错了

select:比如input/textarea输入框被选择时

abort:window/image等元素正在加载时,按下了esc

按对象来解释这些事件:

window:

load:页面的所有请求都完成了,所有需要加载的元素都加载了的时候

unload:当关闭当前页面时

error:浏览器加载当前页面异常

abort:退出时

 

image:

load:图片按照src地址通过网络连接加载完成时

error:图片加载异常

abort:图片加载时,按下了esc等中断图片的加载

<!-- 通常会这么写 -->
<image alt="photo" src=".../photo.jpg" onerror="this.src='...default.jpg'"/>

UIEvent:

事件类型:

resize:修改浏览器或iframe窗体大小时,

scroll:页面发生滚动时触发。若滚动是在一个元素上触发的,则会冒泡;如果是系统(document)滚动条,则不会冒泡

 

事件代理

场景:一个ul中有很多li,要对这些li都注册一个click事件,需要一个一个注册吗?

-- 直接将click事件注册到ul上即可,因为click事件支持冒泡:li上的事件最终肯定会冒泡到ul上,只需在ul上处理所有li的事件即可

-- 事件代理:将事件注册到元素的父节点上

优点:1. 不用注册那么多的事件;2. 内存分配少

缺点:如果把事件全放入父元素,事件管理起来会很复杂(比如将所有事件都注册到window对象上,事件处理函数会很复杂)

(数据通信、数据存储、动画、音频与视频、canvas、BOM、表单操作、列表操作见基础篇(下))

 

原文地址:https://www.cnblogs.com/FudgeBear/p/7464839.html