JavaScriptOOP

1. OOP基礎


1.1面向對象編程OOP

一、語言的分類:
     ① 面向機器:彙編語言
     ② 面向過程:C語言
     ③ 面向對象:C++ Java PHP 等
 
二、面向過程與面向對象
     ① 面向過程:專一於如何去解決一個問題的過程步驟。編程特色是由一個個的函數去實
                            現每一步的過程步驟,沒有類和對象的概念。
     ② 面向對象:專一於由哪個對象來解決這個問題。編程特色是出現了一個個的類,從
                           類中拿到對象,由這個對象去解決具體問題。
 
對於調用者來講,面向過程須要調用者本身去實現各類函數;而面向對象,只須要告訴調用者對象中具體方法的功能,而不須要調用者瞭解方法中的實現細節。
 
[面向對象的三大特徵]
繼承、封裝、多態
JS能夠模擬實現繼承和封裝,可是沒法模擬實現多態,因此說JS是一門基於對象的語言,而並不是是面向對象的語言!
 

1.2類和對象

一、類:一類具備相同特徵(屬性)和行爲(方法)的集合。
  eg:人類-->屬性:身高、體重、性別 方法:吃飯、睡覺、說話
 
二、對象:從類中,拿出具備肯定屬性值和方法的個體。
  eg:張三-->屬性:身高180 體重160 方法:說話-->我叫張三
 
三、類和對象的關係:
  類是抽象的,對象是具體的(類是對象的抽象化,對象是類的具體化)
 
類是一個抽象的概念,只能說類有屬性和方法,可是不能給屬性賦具體的值。
eg:人類有姓名,可是不能說人類的姓名是什麼
 
對象是一個具體的個例,是將類中的屬性進行具體賦值而來的個體
eg:張三是人類的一個個體,能夠說張三的姓名是張三,也就是張三對人類的每個屬性進行了賦值,那麼張三就是由人類產生的一個對象。
 
四、使用類和對象的步驟:
    ① 建立一個類(構造函數):類名必須使用大駝峯法則,即每一個單詞首字母都要大寫。
        function Person(name,age){
            this.name=name;
            this.age=age;
            this.say=function(content){
                // 在類中,訪問類自身的屬性,必須使用this.屬性 調用
                alert("我叫"+this.name+",今年"+this.age+"歲。我說了一句話:"+content);
            }
        }        
 
 ② 經過類,實例化(new)出一個對象。
var zhangsan = new Person("張三",18);
    var obj = new 類名(屬性1的具體值);
    obj.屬性; 調用屬性
    obj.方法; 調用方法
 
  ③ 注意事項:
      >>> 經過類名,new出一個對象的過程,叫作"類的實例化"
      >>> 類中的this,會在實例化的時候,指向新new出的對象;
    因此,this.屬性 this.方法 其實是將屬性和方法綁定在即將new出的對象上面
    >>> 在類中,要調用自身屬性,必須使用this.屬性名。若是直接使用變量名,則沒法訪問對應屬性。
      >>> 類名必須使用大駝峯法則,注意與普通函數區分。
 
五、兩個重要屬性:
    ① constructor:返回當前對象的構造函數。
        >>> lisi.constructor==Person; √
    ② instanceof:檢測一個對象,是否是一個類的實例
        >>> lisi instanceof Person √ lisi是經過Person類new出的
        >>> lisi instanceof Object √ 全部對象都是Object的實例
        >>> Person instanceof Object √ 函數自己也是對象
 
六、廣義對象與狹義對象:
    ① 狹義對象:只有屬性和方法,除此以外沒有任何其餘內容。
                           var obj = {} var obj = new Object();
    ② 廣義對象:除了用字面量聲明的基本數據類型以外,JS中萬物皆對象。換句話說,只要可以添加屬性和方法的變量,均可以成爲對象。
var s="123"; // 不是對象
s.name="aaa";
console.log(typeof s); // String
console.log(s.name); // Undefined 字面量聲明的字符串不是對象,不能添加屬性。

 

2. 成員屬性、靜態屬性和私有屬性

 

2.1成員屬性與成員方法

在構造函數中,使用this.屬性 聲明,或者在實例化出對象之後,使用"對象.屬性"追加的,都屬於成員屬性或成員方法,
也叫實例屬性和實例方法。
 
成員屬性/方法,是屬於由類new出的對象的。
須要使用"對象名.屬性名"調用
        var zhangsan = new Person("張三");
        zhangsan.age = 14; // 追加成員屬性
        console.log(zhangsan.age); // 調用成員屬性

 

2.2靜態屬性與靜態方法

一、經過"類名.屬性"、"類名.方法"聲明的屬性和方法,稱爲靜態屬性、靜態方法,也叫類屬性和類方法。
 
   類屬性/類方法,是屬於類的(屬於構造函數的)
   經過"類名.屬性名"調用
 
二、成員屬性是屬於實例化出的對象的,只能使用對象調用。
   靜態屬性是屬於構造函數的,只能使用類名調用。
        Person.count = "60億"; // 聲明靜態屬性
        console.log(Person.count); // 調用靜態屬性
        var lisi = new Person("李四");
        console.log(lisi.count); // Undefined 靜態屬性是屬於類的,只能用類名調用

 

2.3私有屬性與私有方法

在構造函數中,使用var聲明的變量,稱爲私有屬性;
在構造函數中,使用function聲明的函數,稱爲私有方法;
             function Person(){
                  var num = 1; // 私有屬性
                  function func(){}; // 私有方法
             }

私有屬性和私有方法的做用域,只在構造函數內部有效。即,只能在構造函數內部使用,不管使用對象名仍是類名都沒法調用。編程

 

3. JS模擬實現封裝
 
一、 什麼叫封裝?
    ① 方法的封裝:將類內部的函數進行私有化處理,不對外提供調用接口,沒法在類外部使用的方法稱爲私有方法,即方法的封裝。
 
    ② 屬性的封裝:將類中的屬性進行私有化處理,對外不能直接使用對象名訪問(私有屬性)。同時,須要提供專門用於設置和讀取私有
                              屬性的set/get方法,讓外部使用咱們 提供的方法,對屬性進行操做。這就叫屬性的封裝。
 
二、 注意:封裝不是拒絕訪問,而是限制訪問。要求調用者,必須使用咱們提供的set/get方法進行屬性的操做,而不是直接拒絕操做。
                  所以,單純的屬性私有化,不能成爲封裝!必須私有化以後,提供對應的set/get方法
function Person(){
   var age=0;
   this.setAge=function(ages){
      age=ages;
   }
   this.getAge=function(){
      return age;
   }
}

 

4. JS中的this指向詳解


4.1誰最終調用函數,this就指向誰

4.1.1  this指向誰,不該該考慮函數在哪聲明,而應該考慮函數在哪調用!!!
4.1.2  this指向的,永遠只多是對象,而不多是函數!!!
4.1.3  this指向的對象,叫作函數的上下文/context,也叫函數的調用者
 

4.2this指向的規律!!!(與函數的調用方式息息相關)

4.2.1  經過函數名()調用的,this永遠指向window  
 func();
 
4.2.2  經過對象.方法調用的,this指向這個對象
obj.func(); // 狹義對象
document.getElementById("div1").onclick = func; // 廣義對象
 
4.2.3  函數做爲數組中的一個元素,用數組下標調用的,this指向這個數組
var arr = [1,2,3,func,4,5,6];
arr[3]();
 
4.2.4  函數做爲window內置函數的回調函數使用,this指向window。setInterval setTimeout 等
setTimeout(func,1000);
 
4.2.5  函數做爲構造函數,使用new關鍵字調用,this指向新new出的對象。
var obj1 = new func();

 

5. 原型與原型鏈

 

5.1__proto__與prototype

5.1.1   prototype:函數的原型對象
     ① 只有函數纔有prototype,並且全部函數必有prototype。
     ② prototype自己也是一個對象!
     ③ prototype指向了當前函數所在的引用地址!
 
5.1.2   __proto__:對象的原型!
     ① 只有對象纔有__proto__,並且全部對象必有__proto__。
     ② __proto__也是一個對象,因此也有本身的__proto__,順着這條線向上找的順序,就是原型鏈。
     ③ 函數、數組都是對象,都有本身的__proto__
 
5.1.3   實例化一個類,拿到對象的原理?
      實例化一個類,其實是將新對象的__proto__,指向構造函數所在的prototype。
      也就是說:zhangsan.__proto__==Person.prototype √
 
5.1.4   全部對象的__proto__沿原型鏈向上查找,都將指向Object的prototype。
      Object的prototype的__proto__,指向null
 

5.2原型鏈的指向問題

研究原型鏈的指向,就是要研究各類特殊對象的__proto__指向問題。
5.2.1   經過構造函數,new出的對象,新對象的__proto__指向構造函數的prototype
5.2.2   函數的__proto__,指向Function()的prototype
5.3.3   函數的prototype的__proto__指向Object的prototype
           (直接使用{}字面聲明,或使用new Object拿到的對象的__proto__直接指向Object的prototype)
5.2.4   Object的prototype的__proto__指向null
           (Object做爲一個函數,它的__proto__指向Function()的prototype)
 

 

 

6. 原型屬性與原型方法
6.1   成員屬性與成員方法:
   this.name = ""; this.func = function(){}
   >>> 屬於實例化出的新對象,使用 對象.屬性 調用
 
6.2   靜態屬性與靜態方法:
   Person.name = ""; Person.func = function(){}
   >>> 屬於類的,用 類名.屬性 調用
 
6.3   私有屬性與私有方法:
   構造函數中,使用var聲明屬性,使用function聲明方法;
   >>> 只在類內部可以使用,外部沒法使用任何方式調用。
 
6.4   原型屬性與原型方法:
   Person.prototype.name = "";
   Person.prototype.func= function(){};
   >>> 將屬性或方法寫到類的prototype上,在實例化的時候,這些屬性和方法就會進入
      到新對象的 __proto__上,就可使用對象名調用
 
也就是說,1/4適用對象名訪問,2使用類名訪問,3只能在函數內部使用。
 
6.5   當訪問對象的屬性或方法時,會優先使用對象自身上的成員屬性和成員方法。若是沒
   有找到,則使用 __proto__上面的原型屬性和原型方法。若是依然沒有繼續沿原型鏈
   查找,最後返回Undefined。
 
6.6   習慣上,咱們會將屬性寫爲成員屬性,講方法定義爲原型方法:
    例如:
function Person(){
    this.name = "張三"; // 成員屬性
}
Person.prototype.say = function(){};
   緣由:
   ① 原型屬性在定義後不能改變,沒法在實例化時進行賦值。因此屬性不能使用原型
        屬性。可是方法,寫完之後基本不用改變,因此,方法可使用原型方法。
   ② 實例化出對象後,屬性全在對象上,方法全在原型上,結構清晰。
   ③ 使用for-in遍歷對象時,會將屬性和方法所有打印出來。而方法每每並不須要展
        示,那麼將方法寫在原型上,就可使用hasOwnProperty將原型方法過濾掉。
   ④ 將方法寫道prototype上,將更節省內存
   ⑤ 官方都這麼寫
 
6.7   經過prototype擴展系統內置函數的原型方法
/**
* 給數組添加一個遍歷打印全部值的方法
*/
Array.prototype.eachAll = function(){
    for (var i=0;i<this.length;i++) {
        console.log(this[i]);
    }
}
var arr = [1,2,3,4,5,6,7];
arr.eachAll();    

 

7. for-in循環
for-in 循環主要用於遍歷對象
for()中的格式: for(keys in zhangsan){}
 
keys表示obj對象的每個鍵值對的鍵!因此循環中,須要使用 obj[keys]來取到每個的值
 
for-in循環,遍歷時不僅能讀取對象自身上面的成員屬性,也能沿原型鏈遍歷出對象的原型屬性。
因此,可使用 hasOwnProperty判斷一個屬性是否是對象自身上的屬性。
zhangsan.hasOwnProperty(keys)==true 表示這個屬性是對象的成員屬性,而不是原型屬性。
        function Person(){
            this.name = "張三";
            this.age = 14;
            this.func1 = function(){
} } Person.prototype.name1
= "zhangsan"; Person.prototype.age1 = 15; Person.prototype.func2 = function(){}; var zhangsan = new Person(); for(keys in zhangsan) { if (zhangsan.hasOwnProperty(keys)) { console.log(zhangsan[keys]); } }

 

8. 繼承

 

8.1擴展Object實現繼承

8.1.1   擴展Object實現繼承
      ① 聲明父類
   function Parent(){}
          聲明子類
   function Son(){}
 
   ② 經過prototype給Object類添加一個擴展方法:
    Object.prototype.extend = function(parent){
        for(var i in parent){
            this[i]=parent[i];
        }
    }
 
   ③ 分別拿到父類對象和子類對象:
   var p = new Parent();
   var s = new Son();

 

   ④ 用子類對象,調用擴展方法,實現繼承操做:
   s.extend(p);

 

8.1.2   實現繼承的原理:
   經過循環將父類對象的全部屬性和方法,所有賦給子類對象。關鍵點在於for-in循環,
   即便不擴展Object,也能經過簡單的循環實現操做。
 
8.1.3   擴展Object繼承的缺點:
   ① 沒法經過一次實例化,直接拿到完整的子類對象,而須要先拿到父類對象和子類對象兩個對象,再手動合併。
   ② 擴展Object的繼承方法,也會保留在子類的對象上。
 

8.2使用原型實現繼承

8.2.1   使用原型實現繼承
   ① 定義父類
   function Parent(){}
    定義子類
   function Son(){}
 
   ② 將父類對象,賦值給子類的prototype
   Son.prototype = new Parent();

 

   ③ 拿到子類對象,就會將父類對象的全部屬性和方法,添加到__proto__
   var s = new Son();

 

8.2.2   使用原型繼承的原理:
   將父類對象,賦值給子類的prototype,那麼父類對象的屬性和方法就會出如今子類的
   prototype中,那麼,實例化子類時,子類的prototype又會到子類對象的__proto__
   中,最 終,父類對象的屬性和方法,會出如今子類對象的__proto__中。
 
8.2.3   這種繼承的特色:
   ① 子類自身的全部屬性,都是成員屬性,父類繼承過來的屬性,都是原型屬性
   ② 依然沒法經過一步實例化拿到完整的子類對象。
 

8.3call/apply/bind實現繼承

[call/bind/apply]
一、 三個函數的做用:經過函數名調用這三個函數,能夠強行將函數中的this指定爲某個對象
 
二、 三個函數的寫法(區別):
   call寫法:func.call(func的this指定的obj,func參數1,func參數2,...);
   apply寫法:func.call(func的this指定的obj,[func參數1,func參數2,...]);
   bind寫法:func.call(func的this指定的obj)(func參數1,func參數2,...);
 
三、 三個函數的惟一區別,在於接收func的參數列表的方式不一樣,除此以外,功能上沒有任何差別!!!
 
[使用call/bind/apply實現繼承]
一、 實現步驟:
  ① 定義父類
  function Parent(name){}

 

  ② 定義子類時,在子類中使用三個函數,調用父類,將父類函數中的this,指向位子類函數中的this:
    function Son(no,name){
        this.no = no;
        Parent.call(this,name);
    }

 

  ③ 實例化子類時,將自動繼承父類屬性
  var s = new Son(12,"張三");

 

9. 閉包
 
[JS中的做用域]
一、 全局變量:函數外聲明的變量,成爲全局變量;
  局部變量:函數內部使用var聲明的變量,稱爲局部變量;
 
  在JS中,只有函數做用域,沒有塊級做用域!!也就是說,if/for等有{}結構體,
  並不能具有本身的做用域
 
  因此,函數外部不能訪問函數內部的局部變量(私有屬性)。由於,函數內部的變量,在
  函數執行完畢之後,就會被釋放掉。
 
二、 使用閉包,能夠訪問函數的私有變量!
  JS中,提供了一種"閉包"的概念:在函數內部,定義一個子函數,子函數能夠訪問父
  函數的私有變量。能夠在子函數中進行操做,最後將子函數經過return返回
       function func1(){
         var num = 1;
         function func2(){
            return num;
         }
         return func2;
      }
      var num = func1()();

 

三、 閉包的做用:
  ① 能夠在函數外部訪問函數的私有變量;
  ② 讓函數內部的變量,能夠始終存在於內存中,不會在函數完成後釋放掉
相關文章
相關標籤/搜索