一、面向過程與面向對象
1.1 面向過程(POP)
- 面向過程就是分析出解決問題所需要的步驟,然后用函數把這些步驟一步一步實現,使用的時候再一個一個的依次調用就可以了。
例子:1.打開冰箱??2.把大象裝進去??3.把冰箱門關上
1.2 面向對象(OOP)
- 面向對象是把事務分解成為一個個對象,然后由對象之間分工與合作。
以對象功能來劃分問題。
例子:將大象裝進冰箱,面向對象做法。
先找出對象,并寫出這些對象的功能:
1. 大象對象:
- 進去
2. 冰箱對象:
- 打開
- 關閉
3. 使用大象和冰箱的功能
1.3 面向過程與面向對象對比
面向過程 | 面向對象 | |
---|---|---|
優點 | 性能比面向對象高,適合跟硬件聯系很緊密的東西,例如單片機就采用的面向過程編程。適合比較簡單的小型程序。 | 易維護、易復用、易擴展,由于面向對象有封裝性、繼承性、多態性的特性,可以設計出低耦合的系統,使系統 更加靈活、更加易于維護。適合多人合作的大型項目。 |
缺點 | 不易維護、不易復用、不易擴展 | 性能比面向過程低 |
面向過程和面向對象只是對于編程思維的一種描述,面向過程的方法寫出來的程序就像蛋炒飯,如果里面某些食材不愛吃很難挑出來;而用面向對象寫的程序更像蓋澆飯,自己想要什么澆頭自己取用就可。
1.4 面向對象的思維特點
- 抽取(抽象)對象共用的屬性和行為封裝成一個類(模版)
- 對類進行實例化,產生具體的一個個對象
二、對象與類
2.1 對象
對象是由屬性和方法組成的:是一個無序鍵值對(相關屬性與方法)的集合,指的是一個具體的事物。所有的事物都是對象,例如字符串、數值、數組、函數等。
- 屬性:事物的特征,在對象中用屬性來表示(常用名詞)
- 方法:事物的行為,在對象中用方法來表示(常用動詞)
2.1.1 創建對象
//以下代碼是對對象的復習
//字面量創建對象
var ldh = {
name: '劉德華',
age: 18
}
console.log(ldh);
//構造函數創建對象
function Star(name, age) {
this.name = name;
this.age = age;
}
var ldh = new Star('劉德華', 18)//實例化對象
console.log(ldh);
如上兩行代碼運行結果為:2.2 類
- 在 ES6 中新增加了類的概念,可以使用 class 關鍵字聲明一個類,之后以這個類來實例化對象。類抽象了對象的公共部分,它泛指某一大類(class)。對象特指某一個,通過類實例化一個具體的對象
2.2.1創建類
- 語法:
//步驟1 使用class關鍵字
class Name {
// class body
}
//步驟2使用定義的類創建實例 注意new關鍵字
var xx = new Name();
constructor構造函數(構造器)
constructor()方法是類的構造函數(默認方法),用于傳遞參數,返回實例對象,通過new命令生成對象實例時,自動調用該方法。如果我門定義類的時候沒有寫,類內部會自動給我們創建一個constructor()
- 示例
// 1. 創建類 class 創建一個 明星類
class Star {
// 類的共有屬性放到 constructor 里面
constructor(name, age) {
this.name = name;
this.age = age;
}
}
// 2. 利用類創建對象 new
var ldh = new Star('劉德華', 18);
console.log(ldh);
以上代碼運行結果:
通過結果我們可以看出,運行結果和使用構造函數方式一樣
2.2.2 類創建添加屬性和方法
// 1. 創建類 class 創建一個類
class Star {
// 類的共有屬性放到 constructor 里面 constructor是 構造器或者構造函數
constructor(uname, age) {
this.uname = uname;
this.age = age;
}//------------------------------------------->注意,方法與方法之間不需要添加逗號
sing(song) {
console.log(this.uname + '唱' + song);
}
}
// 2. 利用類創建對象 new
var ldh = new Star('劉德華', 18);
console.log(ldh); // Star {uname: "劉德華", age: 18}
ldh.sing('冰雨'); // 劉德華唱冰雨
注意:
- 通過class 關鍵字創建類, 類名我們還是習慣性定義首字母大寫
- 類里面有個constructor 函數,可以接受傳遞過來的參數,同時返回實例對象
- constructor 函數 只要 new 生成實例時,就會自動調用這個函數, 如果我們不寫這個函數,類也會自動生成這個函數
- 多個函數方法之間不需要添加逗號分隔
- 生成實例 new 不能省略
- 語法規范, 創建類 類名后面不要加小括號,生成實例 類名后面加小括號, 構造函數不需要加function
2.2.3 類的繼承
- 語法
// 父類
class Father{
}
// 子類繼承父類
class Son extends Father {
}
- 示例
class Father {
constructor(surname) {
this.surname= surname;
}
say() {
console.log('你的姓是' + this.surname);
}
}
class Son extends Father{ // 這樣子類就繼承了父類的屬性和方法
}
var damao= new Son('劉');
damao.say(); //結果為 你的姓是劉
以上代碼運行結果:
- 子類使用super關鍵字訪問父類的方法
//定義了父類
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
//子元素繼承父類
class Son extends Father {
constructor(x, y) {
super(x, y); //使用super調用了父類中的構造函數
}
}
var son = new Son(1, 2);
son.sum(); //結果為3
注意:
繼承中,如果實例化子類輸出一個方法,先看子類有沒有這個方法,如果有就先執行子類的
繼承中,如果子類里面沒有,就去查找父類有沒有這個方法,如果有,就執行父類的這個方法(就近原則)
如果子類想要繼承父類的方法,同時在自己內部擴展自己的方法,利用super 調用父類的構造函數,super必須在子類this之前調用
// 父類有加法方法
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
// 子類繼承父類加法方法 同時 擴展減法方法
class Son extends Father {
constructor(x, y) {
// 利用super 調用父類的構造函數 super 必須在子類this之前調用,放到this之后會報錯
super(x, y);
this.x = x;
this.y = y;
}
subtract() {
console.log(this.x - this.y);
}
}
var son = new Son(5, 3);
son.subtract(); //2
son.sum();//8
以上代碼運行結果為:
-
時刻注意this的指向問題,類里面的共有的屬性和方法一定要加this使用
- constructor中的this指向的是new出來的實例對象
- 自定義的方法,一般也指向的new出來的實例對象
- 綁定事件之后this指向的就是觸發事件的事件源
在 ES6 中類沒有變量提升,所以必須先定義類,才能通過類實例化對象
三、面向對象版tab 欄切換
3.1 功能需求
- 點擊 tab欄,可以切換效果.
- 點擊 + 號, 可以添加 tab 項和內容項.
- 點擊 x 號, 可以刪除當前的tab項和內容項.
- 雙擊tab項文字或者內容項文字可以修改里面的文字內容
3.2 案例準備
- 獲取到標題元素
- 獲取到內容元素
- 獲取到刪除的小按鈕 x號
- 新建js文件,定義類,添加需要的屬性方法(切換,刪除,增加,修改)
- 時刻注意this的指向問題
3.3 切換
-
為獲取到的標題綁定點擊事件,展示對應的內容區域,存儲對應的索引
this.lis[i].index = i; this.lis[i].onclick = this.toggleTab;
-
使用排他,實現只有一個元素的顯示
toggleTab() { //將所有的標題與內容類樣式全部移除 for (var i = 0; i < this.lis.length; i++) { this.lis[i].className = ''; this.sections[i].className = ''; } //為當前的標題添加激活樣式 this.className = 'liactive'; //為當前的內容添加激活樣式 that.sections[this.index].className = 'conactive'; }
3.4 添加
-
為添加按鈕+ 綁定點擊事件
this.add.onclick = this.addTab;
-
實現標題與內容的添加,做好排他處理
addTab() { that.clearClass(); // (1) 創建li元素和section元素 var random = Math.random(); var li = '<li class="liactive"><span>新選項卡</span><span class="iconfont icon-guanbi"> </span></li>'; var section = '<section class="conactive">測試 ' + random + '</section>'; // (2) 把這兩個元素追加到對應的父元素里面 that.ul.insertAdjacentHTML('beforeend', li); that.fsection.insertAdjacentHTML('beforeend', section); that.init(); }
3.5 刪除
-
為元素的刪除按鈕x綁定點擊事件
this.remove[i].onclick = this.removeTab;
-
獲取到點擊的刪除按鈕的所在的父元素的所有,刪除對應的標題與內容
removeTab(e) { e.stopPropagation(); // 阻止冒泡 防止觸發li 的切換點擊事件 var index = this.parentNode.index; console.log(index); // 根據索引號刪除對應的li 和section remove()方法可以直接刪除指定的元素 that.lis[index].remove(); that.sections[index].remove(); that.init(); // 當我們刪除的不是選中狀態的li 的時候,原來的選中狀態li保持不變 if (document.querySelector('.liactive')) return; // 當我們刪除了選中狀態的這個li 的時候, 讓它的前一個li 處于選定狀態 index--; // 手動調用我們的點擊事件 不需要鼠標觸發 that.lis[index] && that.lis[index].click(); }
3.6 編輯
-
為元素(標題與內容)綁定雙擊事件
this.spans[i].ondblclick = this.editTab; this.sections[i].ondblclick = this.editTab;
-
在雙擊事件處理文本選中狀態,修改內部DOM節點,實現新舊value值的傳遞
editTab() { var str = this.innerHTML; // 雙擊禁止選定文字 window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty(); // alert(11); this.innerHTML = '<input type="text" />'; var input = this.children[0]; input.value = str; input.select(); // 文本框里面的文字處于選定狀態 // 當我們離開文本框就把文本框里面的值給span input.onblur = function() { this.parentNode.innerHTML = this.value; }; // 按下回車也可以把文本框里面的值給span input.onkeyup = function(e) { if (e.keyCode === 13) { // 手動調用表單失去焦點事件 不需要鼠標離開操作 this.blur(); } } }
四、構造函數和原型
4.1 對象的三種創建方式--復習
-
字面量方式
var obj = {};
-
new關鍵字
var obj = new Object();
-
構造函數方式
function Person(name,age){ this.name = name; this.age = age; } var obj = new Person('zs',12);
4.2 靜態成員和實例成員
4.2.1 實例成員
實例成員就是構造函數內部通過this添加的成員 如下列代碼中uname age sing 就是實例成員,實例成員只能通過實例化的對象來訪問
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我會唱歌');
}
}
var ldh = new Star('劉德華', 18);
console.log(ldh.uname);//實例成員只能通過實例化的對象來訪問
4.2.2 靜態成員
靜態成員 在構造函數本身上添加的成員 如下列代碼中 sex 就是靜態成員,靜態成員只能通過構造函數來訪問
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我會唱歌');
}
}
Star.sex = '男';
var ldh = new Star('劉德華', 18);
console.log(Star.sex);//靜態成員只能通過構造函數來訪問
4.3 構造函數的問題
構造函數方法很好用,但是存在浪費內存的問題。
4.4 構造函數原型prototype
構造函數通過原型分配的函數是所有對象所共享的。
JavaScript 規定,每一個構造函數都有一個prototype 屬性,指向另一個對象。注意這個prototype就是一個對象,這個對象的所有屬性和方法,都會被構造函數所擁有。
我們可以把那些不變的方法,直接定義在 prototype 對象上,這樣所有對象的實例就可以共享這些方法。
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我會唱歌');
}
var ldh = new Star('劉德華', 18);
var zxy = new Star('張學友', 19);
ldh.sing();//我會唱歌
zxy.sing();//我會唱歌
4.5 對象原型
對象都會有一個屬性__proto__
指向構造函數的 prototype
原型對象,之所以我們對象可以使用構造函數 prototype
原型對象的屬性和方法,就是因為對象有__proto__
原型的存在。
__proto__
對象原型和原型對象 prototype
是等價的
__proto__
對象原型的意義就在于為對象的查找機制提供一個方向,或者說一條路線,但是它是一個非標準屬性,因此實際開發中,不可以使用這個屬性,它只是內部指向原型對象 prototype
4.6 constructor構造函數
對象原型(__proto__
)和構造函數(prototype
)原型對象里面都有一個屬性 constructor
屬性 ,constructor
我們稱為構造函數,因為它指回構造函數本身。
constructor
主要用于記錄該對象引用于哪個構造函數,它可以讓原型對象重新指向原來的構造函數。
一般情況下,對象的方法都在構造函數的原型對象中設置。如果有多個對象的方法,我們可以給原型對象采取對象形式賦值,但是這樣就會覆蓋構造函數原型對象原來的內容,這樣修改后的原型對象 constructor
就不再指向當前構造函數了。此時,我們可以在修改后的原型對象中,添加一個 constructor
指向原來的構造函數。
如果我們修改了原來的原型對象,給原型對象賦值的是一個對象,則必須手動的利用constructor
指回原來的構造函數如:
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 很多情況下,我們需要手動的利用constructor 這個屬性指回 原來的構造函數
Star.prototype = {
// 如果我們修改了原來的原型對象,給原型對象賦值的是一個對象,則必須手動的利用constructor指回原來的構造函數
constructor: Star, // 手動設置指回原來的構造函數
sing: function() {
console.log('我會唱歌');
},
movie: function() {
console.log('我會演電影');
}
}
var zxy = new Star('張學友', 19);
console.log(zxy)
以上代碼運行結果,設置constructor屬性如圖:
如果未設置constructor屬性,如圖:
4.7 原型鏈
? 每一個實例對象又有一個__proto__
屬性,指向的構造函數的原型對象(prototype
),構造函數的原型對象也是一個對象,也有__proto__
屬性,這樣一層一層往上找就形成了原型鏈。
4.8 構造函數實例和原型對象三角關系
- 構造函數的
prototype
屬性指向了構造函數原型對象 - 實例對象是由構造函數創建的,實例對象的
__proto__
屬性指向了構造函數的原型對象 - 構造函數的原型對象的
constructor
屬性指向了構造函數,實例對象的原型的constructor
屬性也指向了構造函數
4.9 原型鏈和成員的查找機制
任何對象都有原型對象,也就是prototype
屬性,任何原型對象也是一個對象,該對象就有__proto__
屬性,這樣一層一層往上找,就形成了一條鏈,我們稱此為原型鏈;
當訪問一個對象的屬性(包括方法)時,首先查找這個對象自身有沒有該屬性。
如果沒有就查找它的原型(也就是 __proto__
指向的 prototype
原型對象)。
如果還沒有就查找原型對象的原型(Object的原型對象)。
依此類推一直找到 Object 為止(null)。
__proto__
對象原型的意義就在于為對象成員查找機制提供一個方向,或者說一條路線。
4.10 原型對象中this指向
構造函數中的this和原型對象的this,都指向我們new出來的實例對象
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
var that;
Star.prototype.sing = function() {
console.log('我會唱歌');
that = this;
}
var ldh = new Star('劉德華', 18);
// 1. 在構造函數中,里面this指向的是對象實例 ldh
console.log(that === ldh);//true
// 2.原型對象函數里面的this 指向的是 實例對象 ldh
4.11 通過原型為數組擴展內置方法
Array.prototype.sum = function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
//此時數組對象中已經存在sum()方法了 可以始終 數組.sum()進行數據的求
五、組合繼承
5.1 call()
- call()可以調用函數
fn.call()
- call()可以修改this的指向,使用call()的時候 參數一是修改后的this指向,參數2,參數3..使用逗號隔開連接
function fn(x, y) {
console.log(this);
console.log(x + y);
}
var o = {
name: 'andy'
};
fn.call(o, 1, 2);//調用了函數此時的this指向了對象o,
5.2 子構造函數繼承父構造函數中的屬性
- 先定義一個父構造函數
- 再定義一個子構造函數
- 子構造函數繼承父構造函數的屬性(使用call方法)
// 1. 父構造函數
function Father(uname, age) {
// this 指向父構造函數的對象實例
this.uname = uname;
this.age = age;
}
// 2 .子構造函數
function Son(uname, age, score) {
// this 指向子構造函數的對象實例
3.使用call方式實現子繼承父的屬性
Father.call(this, uname, age);
this.score = score;
}
var son = new Son('劉德華', 18, 100);
console.log(son);
5.3 借用原型對象繼承方法
- 先定義一個父構造函數
- 再定義一個子構造函數
- 子構造函數繼承父構造函數的屬性(使用call方法)
// 1. 父構造函數
function Father(uname, age) {
// this 指向父構造函數的對象實例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function() {
console.log(100000);
};
// 2 .子構造函數
function Son(uname, age, score) {
// this 指向子構造函數的對象實例
Father.call(this, uname, age);
this.score = score;
}
// Son.prototype = Father.prototype; 這樣直接賦值會有問題,因為是把父原型對象的地址給了子原型對象;如果修改了子原型對象,父原型對象也會跟著一起變化
Son.prototype = new Father();
// 如果利用對象的形式修改了原型對象,別忘了利用constructor 指回原來的構造函數
Son.prototype.constructor = Son;
// 這個是子構造函數專門的方法
Son.prototype.exam = function() {
console.log('孩子要考試');
}
var son = new Son('劉德華', 18, 100);
console.log(son);
如上代碼結果如圖:
六、ES5新增方法
6.1 數組方法forEach遍歷數組
arr.forEach(function(value, index, array) {
//參數一是:數組元素
//參數二是:數組元素的索引
//參數三是:當前的數組
})
//相當于數組遍歷的 for循環 沒有返回值
6.2 數組方法filter過濾數組
var arr = [12, 66, 4, 88, 3, 7];
var newArr = arr.filter(function(value,index,array) {
//參數一是:數組元素
//參數二是:數組元素的索引
//參數三是:當前的數組
return value >= 20;
});
console.log(newArr);//[66,88] //返回值是一個新數組
6.3 數組方法map處理數組
通過指定函數處理數組的每個元素,并返回處理后的數組
var arr = [1,2,3,4,5,4,3,2,1];
var mapResult = arr.map(function(item, index, array){
return item * 2;
});
console.log(mapResult); // [2,4,6,8,10,8,6,4,2]
6.4 數組方法some和every
按順序查找數組中是否有滿足條件的元素,只要有1個符合條件就返回true,然后立即終止循環,而every()
是執行到最后一項,必須每一項都符合條件才會返回true
var arr = [10, 30, 4];
var flag = arr.some(function(value,index,array) {
//參數一是:數組元素
//參數二是:數組元素的索引
//參數三是:當前的數組
return value < 3;
});
console.log(flag);//false返回值是布爾值,只要查找到滿足條件的一個元素就立馬終止循環
6.5 篩選商品案例
-
定義數組對象數據
var data = [{ id: 1, pname: '小米', price: 3999 }, { id: 2, pname: 'oppo', price: 999 }, { id: 3, pname: '榮耀', price: 1299 }, { id: 4, pname: '華為', price: 1999 }, ];
-
使用forEach遍歷數據并渲染到頁面中
data.forEach(function(value) { var tr = document.createElement('tr'); tr.innerHTML = '<td>' + value.id + '</td><td>' + value.pname + '</td><td>' + value.price + '</td>'; tbody.appendChild(tr); });
-
根據價格篩選數據
-
獲取到搜索按鈕并為其綁定點擊事件
search_price.addEventListener('click', function() { });
-
使用filter將用戶輸入的價格信息篩選出來
search_price.addEventListener('click', function() { var newDate = data.filter(function(value) { //start.value是開始區間 //end.value是結束的區間 return value.price >= start.value && value.price <= end.value; }); console.log(newDate); });
-
將篩選出來的數據重新渲染到表格中
-
將渲染數據的邏輯封裝到一個函數中
function setDate(mydata) { // 先清空原來tbody 里面的數據 tbody.innerHTML = ''; mydata.forEach(function(value) { var tr = document.createElement('tr'); tr.innerHTML = '<td>' + value.id + '</td><td>' + value.pname + '</td><td>' + value.price + '</td>'; tbody.appendChild(tr); }); }
-
將篩選之后的數據重新渲染
search_price.addEventListener('click', function() { var newDate = data.filter(function(value) { return value.price >= start.value && value.price <= end.value; }); console.log(newDate); // 把篩選完之后的對象渲染到頁面中 setDate(newDate); });
-
-
根據商品名稱篩選
獲取用戶輸入的商品名稱
-
為查詢按鈕綁定點擊事件,將輸入的商品名稱與這個數據進行篩選
search_pro.addEventListener('click', function() { var arr = []; data.some(function(value) { if (value.pname === product.value) { // console.log(value); arr.push(value); return true; // return 后面必須寫true } }); // 把拿到的數據渲染到頁面中 setDate(arr); })
-
6.6 some和forEach區別
- 如果查詢數組中唯一的元素, 用some方法更合適,在some里面遇到
return true
就是終止遍歷,迭代效率更高 - 在forEach 里面 return 不會終止迭代
6.7 trim方法去除字符串兩端的空格
不影響原字符串,返回的是新字符串
var str = ' hello '
console.log(str.trim()) //hello 去除兩端空格
var str1 = ' he l l o '
console.log(str1.trim()) //he l l o 去除兩端空格
6.8 獲取對象的屬性名
Object.keys(對象) 獲取到當前對象中的屬性名 ,返回值是一個數組
var obj = {
id: 1,
pname: '小米',
price: 1999,
num: 2000
};
var result = Object.keys(obj)
console.log(result)//[id,pname,price,num]
6.9 Object.defineProperty
Object.defineProperty設置或修改對象中的屬性
Object.defineProperty(對象obj,修改或新增的屬性名prop字符串,{
value:修改或新增的屬性的值,默認為undefined。
writable:true|false,//默認為false。即不允許修改這個屬性的值
enumerable: false,//enumerable 默認值為false,即不允許遍歷(枚舉)。Object.keys(obj)將沒有這個屬性名
configurable: false //configurable 默認值為false,即不允許刪除這個屬性 屬性是否可以被刪除或是否可以再次修改特性(即用Object.defineProperty再次設置這個屬性的第三個參數)
})
var obj = {
id: 1,
pname: '小米',
price: 1999
};
// Object.defineProperty() 定義新屬性或修改原有的屬性
Object.defineProperty(obj, 'num', {
value: 1000,
enumerable: true
});