一. DOM介紹
1. 什么是DOM?
DOM:文檔對象模型。DOM 為文檔提供了結構化表示,並定義了如何通過腳本來訪問文檔結構。
目的其實就是為了能讓js操作html元素而制定的一個規范。
DOM就是由節點組成的。
2. 解析過程
HTML加載完畢,渲染引擎會在內存中把HTML文檔,生成一個DOM樹,getElementById是獲取內中DOM上的元素節點。然后操作的時候修改的是該元素的屬性。
3. DOM樹(一切都是節點)
DOM的數據結構如下:

上圖可知,在HTML當中,一切都是節點:(非常重要)
-
-
元素節點:HMTL標簽。
-
文本節點:標簽中的文字(比如標簽之間的空格、換行)
-
屬性節點::標簽的屬性。
-
整個html文檔就是一個文檔節點。所有的節點都是Object。
4. DOM可以做什么?
-
-
找對象(元素節點)
-
設置元素的屬性值
-
設置元素的樣式
-
動態創建和刪除元素
-
事件的觸發響應:事件源、事件、事件的驅動程序
-
二. DOM節點的獲取
DOM節點的獲取方式其實就是獲取事件源的方式
操作元素節點,必須首先找到該節點。有三種方式可以獲取DOM節點:
var div1 = document.getElementById("box"); //方式一:通過id獲取單個標簽 var arr1 = document.getElementsByTagName("div"); //方式二:通過 標簽名 獲得 標簽數組,所以有s var arr2 = document.getElementsByClassName("box"); //方式三:通過 類名 獲得 標簽數組,所以有s
既然方式二、方式三獲取的是標簽數組,那么習慣性是先遍歷之后再使用。
特殊情況:數組中的值只有1個。即便如此,這一個值也是包在數組里的。這個值的獲取方式如下:
document.getElementsByTagName("div")[0]; //取數組中的第一個元素
document.getElementsByClassName("box")[0]; //取數組中的第一個元素
三. DOM訪問關系的獲取
DOM的節點並不是孤立的,因此可以通過DOM節點之間的相對關系對它們進行訪問。如下:

節點的訪問關系,是以屬性的方式存在的。
JS中的父子兄訪問關系:

這里我們要重點知道parentNode和children這兩個屬性的用法。下面分別介紹。
1. 獲取父節點
調用者就是節點。一個節點只有一個父節點,調用方式就是
節點.parentNode
(1)nextSibling:
指的是下一個節點(包括標簽、空文檔和換行節點)
-
-
火狐、谷歌、IE9+版本:都指的是下一個節點(包括標簽、空文檔和換行節點)。
-
IE678版本:指下一個元素節點(標簽)。
-
(2)nextElementSibling:
-
- 火狐、谷歌、IE9+版本:都指的是下一個元素節點(標簽)。
總結:為了獲取下一個元素節點,我們可以這樣做:在IE678中用nextSibling,在火狐谷歌IE9+以后用nextElementSibling,於是,綜合這兩個屬性,可以這樣寫:
下一個兄弟節點 = 節點.nextElementSibling || 節點.nextSibling
previous的中文是: 前一個
(1)previousSibling:
-
-
火狐、谷歌、IE9+版本:都指的是前一個節點(包括標簽、空文檔和換行節點)。
-
IE678版本:指前一個元素節點(標簽)。
-
(2)previousElementSibling:
-
- 火狐、谷歌、IE9+版本:都指的是前一個元素節點(標簽)。
總結:為了獲取前一個元素節點,我們可以這樣做:在IE678中用previousSibling,在火狐谷歌IE9+以后用previousElementSibling,於是,綜合這兩個屬性,可以這樣寫:
前一個兄弟節點 = 節點.previousElementSibling || 節點.previousSibling
補充:獲得任意一個兄弟節點:
節點自己.parentNode.children[index]; //隨意得到兄弟節點 節點自己.parentNode.children; //獲取所有的兄弟節點(偽數組,包含自己在內)
2. 獲取單個的子元素
2.1. 第一個子節點 | 第一個子元素節點:
(1)firstChild:
-
-
火狐、谷歌、IE9+版本:都指的是第一個子節點(包括標簽、空文檔和換行節點)。
-
IE678版本:指第一個子元素節點(標簽)。
-
(2)firstElementChild:
-
- 火狐、谷歌、IE9+版本:都指的是第一個子元素節點(標簽)。
總結:為了獲取第一個子元素節點,我們可以這樣做:在IE678中用firstChild,在火狐谷歌IE9+以后用firstElementChild,於是,綜合這兩個屬性,可以這樣寫:
第一個子元素節點 = 節點.firstElementChild || 節點.firstChild
2.2. 最后一個子節點 | 最后一個子元素節點:
(1)lastChild:
-
-
火狐、谷歌、IE9+版本:都指的是最后一個子節點(包括標簽、空文檔和換行節點)。
-
IE678版本:指最后一個子元素節點(標簽)。
-
(2)lastElementChild:
-
- 火狐、谷歌、IE9+版本:都指的是最后一個子元素節點(標簽)。
總結:為了獲取最后一個子元素節點,我們可以這樣做:在IE678中用lastChild,在火狐谷歌IE9+以后用lastElementChild,於是,綜合這兩個屬性,可以這樣寫:
最后一個子元素節點 = 節點.lastElementChild || 節點.lastChild
3. 獲取所有的子節點
(1)childNodes:標准屬性。返回的是指定元素的子節點的集合(包括元素節點、所有屬性、文本節點)。是W3C的親兒子。
-
- 火狐 谷歌等高本版會把換行也看做是子節點。(了解)
用法:
子節點數組 = 父節點.childNodes; //獲取所有節點。
(2)children:非標准屬性。返回的是指定元素的子元素節點的集合。【重要】
-
- 它只返回HTML節點,甚至不返回文本節點。
- 在IE6/7/8中包含注釋節點(在IE678中,注釋節點不要寫在里面)。
雖然不是標准的DOM屬性,但它和innerHTML方法一樣,得到了幾乎所有瀏覽器的支持。
用法:(用的最多)
子節點數組 = 父節點.children; //獲取所有節點。用的最多。
四. 關於DOM的事件操作
JS是以事件驅動為核心的一門語言。
1. 事件的三要素
事件的三要素:事件源、事件、事件驅動程序。
比如,我用手去按開關,燈亮了。這件事情里,事件源是:手。事件是:按開關。事件驅動程序是:燈的開和關。
再比如,網頁上彈出一個廣告,我點擊右上角的X,廣告就關閉了。這件事情里,事件源是:X。事件是:onclick。事件驅動程序是:廣告關閉了。
於是我們可以總結出:誰引發的后續事件,誰就是事件源。
總結如下:
-
-
事件源:引發后續事件的html標簽。
-
事件:js已經定義好了(見下圖)。
-
事件驅動程序:對樣式和html的操作。也就是DOM。
-
代碼書寫步驟如下:(重要)
-
獲取事件源:document.getElementById(“box”);
-
綁定事件: 事件源box.事件onclick = function(){ 事件驅動程序 };
-
書寫事件驅動程序:關於DOM的操作
代碼舉例:
點擊div盒子使背景色改為red。 <style> #box{ width:100px; height: 100px; background: green; } </style> <div id="box"></div> <script> var odiv = document.getElementById("box"); odiv.onclick = function(){ odiv.style.background = "red"; } </script>
常見事件如下:

下面針對這事件的三要素,進行分別介紹。
1. 獲取事件源的方式
這個參照上面的介紹。
2. 綁定事件的方式
方式一:直接綁定匿名函數
<script>
var odiv = document.getElementById("box");
odiv.onclick = function(){
odiv.style.background = "red";
}
</script>
方式二:先單獨定義函數,再綁定
<script>
var odiv = document.getElementById("box");
odiv.onclick = fn; //注意,這里是fn,不是fn()。fn()指的是返回值。
//單獨定義函數
function fn(){
odiv.style.background = "red";
}
</script>
方式三:行內綁定
// 行內綁定 <div id="box" onclick="fn()"></div> // 注意第一行代碼,綁定時,是寫的"fn()",不是寫的"fn"。因為綁定的這段代碼不是寫在js代碼里的,而是被識別成了字符串。 <script> function fn(){ odiv.style.background = "red"; } </script>
3. 事件驅動程序
我們上面的例子中改變背景顏色,就是事件驅動程序。
需求:
1. 默認盒子寬度和高度為100px,背景色為綠色;
2. 單擊后的效果為盒子寬度和高度為200px,背景色為紅色;
3. 讓其上面的效果可以來回進行切換。
請看代碼:
<script>
var flag = false;
var odiv = document.getElementById("box");
odiv.onclick = function () {
if (!flag){
odiv.style.width = "200px";
odiv.style.height = "200px";
odiv.style.backgroundColor = "red";
flag = true;
}else{
odiv.style.width = "100px";
odiv.style.height = "100px";
odiv.style.backgroundColor = "green";
flag = false;
}
};
</script>
上方代碼的注意事項:
-
- 在js里寫屬性值時,要用引號
- 在js里寫屬性名時,是backgroundColor,不是CSS里面的background-Color。記得所有的像css屬性的text-*,line-*、backgroun-*等在js中都寫成駝峰
4. onload事件
當頁面加載(文本和圖片)完畢的時候,觸發onload事件。
舉例:
<head>
<script>
window.onload = function () {
console.log(111);
}
</script>
</head>
<body>
<script>
console.log(222);
</script>
</body>
有一點我們要知道:js的加載是和html同步加載的。因此,如果使用元素在定義元素之前,容易報錯。這個時候,onload事件就能派上用場了,我們可以把使用元素的代碼放在onload里,就能保證這段代碼是最后執行。
建議是:整個頁面上所有元素加載完畢在執行js內容。所以,window.onload可以預防使用標簽在定義標簽之前。
2. 事件案例
案例一:
<input type="text" id="input1" value="隱藏">
<button id="btn1">隱藏</button>
<div id="box"></div>
<script>
var oBtn = document.getElementById("btn1");
var oDiv = document.getElementById("box");
var oInput = document.getElementById("input1");
var flag = false;
oBtn.onclick = function () {
if (!flag){
oInput.value = "顯示";
oDiv.style.display = "none";
oBtn.innerText = "顯示";
flag = true;
}else{
oInput.value = "隱藏";
oDiv.style.display = "block";
oBtn.innerHTML = "<span>隱藏</span>";
flag = false;
}
}
</script>
案例二:
要求實現效果:
1. 當鼠標懸停在img上時,更換為另外一張圖片;
2. 鼠標離開時,還原為本來的圖片。
<img src="https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2121206715,2955288754&fm=26&gp=0.jpg" alt="" id="image">
<script>
var oImg = document.getElementById("image");
oImg.onmouseover = function () {
this.src = "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3488187566,3835668069&fm=26&gp=0.jpg";
};
oImg.onmouseout = function () {
this.src = "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2121206715,2955288754&fm=26&gp=0.jpg";
}
</script>
五. DOM節點操作(重要)
前面的內容:節點的訪問關系都是屬性。
節點的操作都是函數(方法)
1. 創建節點
格式如下:
新的標簽(元素節點) = document.createElement("標簽名");
比如,如果我們想創建一個li標簽,或者是創建一個不存在的adbc標簽,可以這樣做:
<script type="text/javascript">
var a1 = document.createElement("li"); //創建一個li標簽
var a2 = document.createElement("adbc"); //創建一個不存在的標簽
console.log(a1);
console.log(a2);
console.log(typeof a1);
console.log(typeof a2);
</script>
結果:

2. 插入節點
插入節點有兩種方式,它們的含義是不同的。
方式1:
父節點.appendChild(新的子節點);
解釋:父節點的最后插入一個新的子節點。
方式2:
父節點.insertBefore(新的子節點,作為參考的子節點);
解釋:
-
-
在參考節點前插入一個新的節點。
-
如果參考節點為null,那么他將在父節點最后插入一個子節點。
-
3. 刪除節點
格式如下:
父節點.removeChild(子節點);
解釋:用父節點刪除子節點。必須要指定是刪除哪個子節點。
如果我想刪除自己這個節點,可以這么做:
node1.parentNode.removeChild(node1);
4. 復制節點(克隆節點)
格式如下:
要復制的節點.cloneNode(); //括號里不帶參數和帶參數false,效果是一樣的。 要復制的節點.cloneNode(true);
括號里帶不帶參數,效果是不同的。解釋如下:
-
-
不帶參數/帶參數false:只復制節點本身,不復制子節點。
-
帶參數true:既復制節點本身,也復制其所有的子節點。
-
六. 設置節點的屬性
我們可以獲取節點的屬性值、設置節點的屬性值、刪除節點的屬性。
我們就統一拿下面這個標簽來舉例:
<img src="https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2121206715,2955288754&fm=26&gp=0.jpg"
class="image_box" title="美女圖片" alt="美女走丟了" id="a1">
下面分別介紹。
1. 獲取節點的屬性值
方式1:
元素節點.屬性;
元素節點[屬性];
舉例:(獲取節點的屬性值)
<script>
var myNode = document.getElementsByTagName("img")[0];
console.log(myNode.src);
console.log(myNode.className); //注意,是className,不是class
console.log(myNode.title);
console.log("------------");
console.log(myNode["src"]);
console.log(myNode["className"]); //注意,是className,不是class
console.log(myNode["title"]);
</script>
方式2:(推薦)
素節點.getAttribute("屬性名稱");
例子:
console.log(myNode.getAttribute("src"));
console.log(myNode.getAttribute("class")); //注意是class,不是className
console.log(myNode.getAttribute("title"));
方式1和方式2的區別在於:前者是直接操作標簽,后者是把標簽作為DOM節點。推薦方式2。
2. 設置節點的屬性值
方式1:
舉例:(設置節點的屬性值)
myNode.src = "images/2.jpg" //修改src的屬性值 myNode.className = "image2-box"; //修改class的name myNode['title'] = "漂亮嗎"; // 修改title的屬性值
方式2:(推薦)
元素節點.setAttribute(屬性名, 新的屬性值);
舉例:(設置節點的屬性值)
myNode.setAttribute("src","images/3.jpg");
myNode.setAttribute("class","image3-box");
myNode.setAttribute("id","aaa");
3. 刪除節點屬性
格式:
元素節點.removeAttribute(屬性名);
舉例:(刪除節點的屬性)
myNode.removeAttribute("class");
myNode.removeAttribute("id");
DOM操作案例請移步:戳我啊!
七. 定時器
在js中的定時器分兩種:1、setTimeout() 2、setInterval()
1. setTimeOut()
只在指定時間后執行一次
<button id="btn1">開始</button>
<button id="btn2">清除</button>
<script>
var oBtn1 = document.getElementById("btn1");
var timer = null;
oBtn1.onclick = function () {
timer = setTimeout(function () {
console.log(1111);
},3000)
};
var oBtn2 = document.getElementById("btn2");
oBtn2.onclick = function () {
clearTimeout(timer);
}
</script>
2.setInterval()
在指定時間為周期循環執行
<button id="start">開啟定時器</button>
<button id="stop">關閉定時器</button>
<div id="box"></div>
<script>
var oDiv = document.getElementById("box");
var oStart = document.getElementById("start");
var oStop = document.getElementById("stop");
var timer = null;
var m_left = 0;
oStart.onclick = function(){
clearInterval(timer); //每次點擊時,先清除原來的定時任務。
timer = setInterval(function () {
m_left += 10;
oDiv.style.marginLeft = m_left + "px";
},1000) // 單位為毫秒
};
// 清除定時任務
oStop.onclick = function () {
clearInterval(timer);
}
</script>
兩種方法根據不同的場景和業務需求擇而取之,
對於這兩個方法,需要注意的是如果要求在每隔一個固定的時間間隔后就精確地執行某動作,那么最好使用setInterval。
八. BOM介紹
1. 什么是BOM
BOM:Browser Object Model,瀏覽器對象模型。
BOM的結構圖:

從上圖也可以看出:
-
-
window對象是BOM的頂層(核心)對象,所有對象都是通過它延伸出來的,也可以稱為window的子對象。
-
DOM是BOM的一部分。
-
window對象:
-
-
window對象是JavaScript中的頂級對象。
-
全局變量、自定義函數也是window對象的屬性和方法。
-
window對象下的屬性和方法調用時,可以省略window。
-
下面講一下 BOM 的常見內置方法和內置對象。
2. 彈出系統對話框
比如說,alert(1)是window.alert(1)的簡寫,因為它是window的子方法。
系統對話框有三種:
alert(); //不同瀏覽器中的外觀是不一樣的 confirm(); //兼容不好 prompt(); //不推薦使用
3. 打開窗口和關閉窗口
(1)打開窗口:
window.open(url,target)
參數解釋:
-
-
url:要打開的地址。
-
target:新窗口的位置。可以是:_blank、_self、_parent 父框架。
-
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<button>打開百度</button>
<button>關閉百度</button>
</body>
<script>
var oBtn = document.getElementsByTagName('button')[0];
var closeBtn = document.getElementsByTagName('button')[1];
var myWindow = null;
oBtn.onclick = function(){
myWindow = open('https://www.baidu.com');
//打開空白頁面
// open('about:blank',"_self")
};
closeBtn.onclick = function(){
if(confirm("是否關閉?")){
myWindow.close();
}
}
</script>
</html>
4. location對象
window.location可以簡寫成location。location相當於瀏覽器地址欄,可以將url解析成獨立的片段。
4.1. location對象的屬性
-
-
href:跳轉
-
hash 返回url中#后面的內容,包含#
-
host 主機名,包括端口
-
hostname 主機名
-
pathname url中的路徑部分
-
protocol 協議 一般是http、https
-
search 查詢字符串
-
location.href屬性舉例:
舉例:點擊盒子時,進行跳轉。
<body>
<div>點我</div>
<script>
var div = document.getElementsByTagName("div")[0];
div.onclick = function () {
location.href = "http://www.baidu.com"; //點擊div時,跳轉到指定鏈接
// window.open("http://www.baidu.com","_blank"); //方式二
}
</script>
</body>
4.2. location對象的方法
location.reload():重新加載
setTimeout(function(){ //3秒之后讓網頁整個刷新 window.location.reload(); },3000)
5. history對象
5.1. 后退:
-
-
history.back()
-
history.go(-1):0是刷新
-
5.2. 前進:
-
- history.forward()
-
-
history.go(1)
-
用的不多。因為瀏覽器中已經自帶了這些功能的按鈕
九. js中的面向對象(了解)
創建對象的幾種常用方式
1.使用Object或對象字面量創建對象
2.工廠模式創建對象
3.構造函數模式創建對象
4.原型模式創建對象
1.使用Object或對象字面量創建對象
JS中最基本創建對象的方式:
var student = new Object(); student.name = "easy"; student.age = "20";
這樣,一個student對象就創建完畢,擁有2個屬性name以及age,分別賦值為"easy"和20。
如果你嫌這種方法有一種封裝性不良的感覺。來一個對象字面量方式創建對象。
var sutdent = { name : "easy", age : 20 };
這樣看起來似乎就完美了。但是馬上我們就會發現一個十分尖銳的問題:當我們要創建同類的student1,student2,…,studentn時,我們不得不將以上的代碼重復n次....
var sutdent1 = { name : "easy1", age : 20 }; var sutdent2 = { name : "easy2", age : 20 }; ... var sutdentn = { name : "easyn", age : 20 };
有個提問?能不能像工廠車間那樣,有一個車床就不斷生產出對象呢?我們看”工廠模式”。
2.工廠模式創建對象
JS中沒有類的概念,那么我們不妨就使用一種函數將以上對象創建過程封裝起來以便於重復調用,同時可以給出特定接口來初始化對象
function createStudent(name, age) { var obj = new Object(); obj.name = name; obj.age = age; return obj; } var student1 = createStudent("easy1", 20); var student2 = createStudent("easy2", 20); ... var studentn = createStudent("easyn", 20);
這樣一來我們就可以通過createStudent函數源源不斷地”生產”對象了。看起來已經高枕無憂了,但貪婪的人類總有不滿足於現狀的天性:我們不僅希望”產品”的生產可以像工廠車間一般源源不斷,我們還想知道生產的產品究竟是哪一種類型的。
比如說,我們同時又定義了”生產”水果對象的createFruit()函數:
function createFruit(name, color) { var obj = new Object(); obj.name = name; obj.color = color; return obj; } var v1 = createStudent("easy1", 20); var v2 = createFruit("apple", "green");
對於以上代碼創建的對象v1、v2,我們用instanceof操作符去檢測,他們統統都是Object類型。我們的當然不滿足於此,我們希望v1是Student類型的,而v2是Fruit類型的。為了實現這個目標,我們可以用自定義構造函數的方法來創建對象
3.構造函數模式創建對象
在上面創建Object這樣的原生對象的時候,我們就使用過其構造函數:
var obj = new Object();
在創建原生數組Array類型對象時也使用過其構造函數:
var arr = new Array(10); //構造一個初始長度為10的數組對象
在進行自定義構造函數創建對象之前,我們首先了解一下構造函數和普通函數有什么區別。
1、實際上並不存在創建構造函數的特殊語法,其與普通函數唯一的區別在於調用方法。對於任意函數,使用new操作符調用,那么它就是構造函數;不使用new操作符調用,那么它就是普通函數。
2、按照慣例,我們約定構造函數名以大寫字母開頭,普通函數以小寫字母開頭,這樣有利於顯性區分二者。例如上面的new Array(),new Object()。
3、使用new操作符調用構造函數時,會經歷(1)創建一個新對象;(2)將構造函數作用域賦給新對象(使this指向該新對象);(3)執行構造函數代碼;(4)返回新對象;4個階段。
ok,了解了構造函數和普通函數的區別之后,我們使用構造函數將工廠模式的函數重寫,並添加一個方法屬性:
function Student(name, age) { this.name = name; this.age = age; this.alertName = function(){ alert(this.name) }; } function Fruit(name, color) { this.name = name; this.color = color; this.alertName = function(){ alert(this.name) }; }
這樣我們再分別創建Student和Fruit的對象:
var v1 = new Student("easy", 20); var v2 = new Fruit("apple", "green");
這時我們再來用instanceof操作符來檢測以上對象類型就可以區分出Student以及Fruit了:
alert(v1 instanceof Student); //true alert(v2 instanceof Student); //false alert(v1 instanceof Fruit); //false alert(v2 instanceof Fruit); //true alert(v1 instanceof Object); //true 任何對象均繼承自Object alert(v2 instanceof Object); //true 任何對象均繼承自Object
這樣我們就解決了工廠模式無法區分對象類型的尷尬。那么使用構造方法來創建對象是否已經完美了呢?使用構造器函數通常在js中我們來創建對象。
我們會發現Student和Fruit對象中共有同樣的方法,當我們進行調用的時候這無疑是內存的消耗。
我們完全可以在執行該函數的時候再這樣做,辦法是將對象方法移到構造函數外部:
function Student(name, age) { this.name = name; this.age = age; this.alertName = alertName; } function alertName() { alert(this.name); } var stu1 = new Student("easy1", 20); var stu2 = new Student("easy2", 20);
在調用stu1.alertName()時,this對象才被綁定到stu1上。
我們通過將alertName()函數定義為全局函數,這樣對象中的alertName屬性則被設置為指向該全局函數的指針。由此stu1和stu2共享了該全局函數,解決了內存浪費的問題
但是,通過全局函數的方式解決對象內部共享的問題,終究不像一個好的解決方法。如果這樣定義的全局函數多了,我們想要將自定義對象封裝的初衷便幾乎無法實現了。更好的方案是通過原型對象模式來解決。
4.原型的模式創建對象
原型鏈甚至原型繼承,是整個JS中最難的一部分也是最不好理解的一部分,在這里由於我們課程定位的原因,如果對js有興趣的同學,可以去查閱一下相關JS原型的一些知識點。更加有助於你以后前端JS的面試。
function Student() { this.name = 'easy'; this.age = 20; } Student.prototype.alertName = function(){ alert(this.name); }; var stu1 = new Student(); var stu2 = new Student(); stu1.alertName(); //easy stu2.alertName(); //easy alert(stu1.alertName == stu2.alertName); //true 二者共享同一函數
