前端學習筆記十三-面向對象

一、面向過程與面向對象

1.1 面向過程(POP)

  • 面向過程就是分析出解決問題所需要的步驟,然后用函數把這些步驟一步一步實現,使用的時候再一個一個的依次調用就可以了。

例子:1.打開冰箱??2.把大象裝進去??3.把冰箱門關上

1.2 面向對象(OOP)

  • 面向對象是把事務分解成為一個個對象,然后由對象之間分工與合作。

以對象功能來劃分問題。

例子:將大象裝進冰箱,面向對象做法。
先找出對象,并寫出這些對象的功能:
1. 大象對象:

  • 進去

2. 冰箱對象:

  • 打開
  • 關閉

3. 使用大象和冰箱的功能

1.3 面向過程與面向對象對比

面向過程 面向對象
優點 性能比面向對象高,適合跟硬件聯系很緊密的東西,例如單片機就采用的面向過程編程。適合比較簡單的小型程序。 易維護、易復用、易擴展,由于面向對象有封裝性、繼承性、多態性的特性,可以設計出低耦合的系統,使系統 更加靈活、更加易于維護。適合多人合作的大型項目。
缺點 不易維護、不易復用、不易擴展 性能比面向過程低

面向過程和面向對象只是對于編程思維的一種描述,面向過程的方法寫出來的程序就像蛋炒飯,如果里面某些食材不愛吃很難挑出來;而用面向對象寫的程序更像蓋澆飯,自己想要什么澆頭自己取用就可。

1.4 面向對象的思維特點

  1. 抽取(抽象)對象共用的屬性和行為封裝成一個類(模版)
  2. 對類進行實例化,產生具體的一個個對象

二、對象與類

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. 語法:
//步驟1 使用class關鍵字
class Name {
  // class body
}     
//步驟2使用定義的類創建實例  注意new關鍵字
var xx = new Name();     

constructor構造函數(構造器)
constructor()方法是類的構造函數(默認方法),用于傳遞參數,返回實例對象,通過new命令生成對象實例時,自動調用該方法。如果我門定義類的時候沒有寫,類內部會自動給我們創建一個constructor()

  1. 示例
 // 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('冰雨'); // 劉德華唱冰雨

注意:

  1. 通過class 關鍵字創建類, 類名我們還是習慣性定義首字母大寫
  2. 類里面有個constructor 函數,可以接受傳遞過來的參數,同時返回實例對象
  3. constructor 函數 只要 new 生成實例時,就會自動調用這個函數, 如果我們不寫這個函數,類也會自動生成這個函數
  4. 多個函數方法之間不需要添加逗號分隔
  5. 生成實例 new 不能省略
  6. 語法規范, 創建類 類名后面不要加小括號,生成實例 類名后面加小括號, 構造函數不需要加function
2.2.3 類的繼承
  1. 語法
// 父類
class Father{   
} 

// 子類繼承父類
class  Son  extends Father {  
}       
  1. 示例
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

注意:

  1. 繼承中,如果實例化子類輸出一個方法,先看子類有沒有這個方法,如果有就先執行子類的

  2. 繼承中,如果子類里面沒有,就去查找父類有沒有這個方法,如果有,就執行父類的這個方法(就近原則)

  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

以上代碼運行結果為:

  1. 時刻注意this的指向問題,類里面的共有的屬性和方法一定要加this使用

    • constructor中的this指向的是new出來的實例對象
    • 自定義的方法,一般也指向的new出來的實例對象
    • 綁定事件之后this指向的就是觸發事件的事件源
  2. 在 ES6 中類沒有變量提升,所以必須先定義類,才能通過類實例化對象

三、面向對象版tab 欄切換

3.1 功能需求

  1. 點擊 tab欄,可以切換效果.
  2. 點擊 + 號, 可以添加 tab 項和內容項.
  3. 點擊 x 號, 可以刪除當前的tab項和內容項.
  4. 雙擊tab項文字或者內容項文字可以修改里面的文字內容

3.2 案例準備

  1. 獲取到標題元素
  2. 獲取到內容元素
  3. 獲取到刪除的小按鈕 x號
  4. 新建js文件,定義類,添加需要的屬性方法(切換,刪除,增加,修改)
  5. 時刻注意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 對象的三種創建方式--復習

  1. 字面量方式

    var obj = {};
    
  2. new關鍵字

    var obj = new Object();
    
  3. 構造函數方式

    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 構造函數實例和原型對象三角關系

  1. 構造函數的prototype屬性指向了構造函數原型對象
  2. 實例對象是由構造函數創建的,實例對象的__proto__屬性指向了構造函數的原型對象
  3. 構造函數的原型對象的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 子構造函數繼承父構造函數中的屬性

  1. 先定義一個父構造函數
  2. 再定義一個子構造函數
  3. 子構造函數繼承父構造函數的屬性(使用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 借用原型對象繼承方法

  1. 先定義一個父構造函數
  2. 再定義一個子構造函數
  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 篩選商品案例

  1. 定義數組對象數據

    var data = [{
                id: 1,
                pname: '小米',
                price: 3999
            }, {
                id: 2,
                pname: 'oppo',
                price: 999
            }, {
                id: 3,
                pname: '榮耀',
                price: 1299
            }, {
                id: 4,
                pname: '華為',
                price: 1999
            }, ];
    
  2. 使用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);
     });
    
  3. 根據價格篩選數據

    1. 獲取到搜索按鈕并為其綁定點擊事件

      search_price.addEventListener('click', function() {
      });
      
    2. 使用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);
       });
      
    3. 將篩選出來的數據重新渲染到表格中

      1. 將渲染數據的邏輯封裝到一個函數中

        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);
          });
         }
        
      2. 將篩選之后的數據重新渲染

         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);
        });
        
    4. 根據商品名稱篩選

      1. 獲取用戶輸入的商品名稱

      2. 為查詢按鈕綁定點擊事件,將輸入的商品名稱與這個數據進行篩選

         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
});
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容