JS中OOP之模擬封裝和繼承和this指向詳解


大家好,今天我帶大家學習一下js的OOP,

大家都知道,面向對象有三個基本特征,繼承,封裝和多態,面向對象的語言有那么幾種,C++,PHP,JAVA等,而功能強大的JS可以模擬實現面向對象的兩大特征,繼承,和封裝,無法實現多態,所以當有人對問你,js是一門面向對象的語言,你應該反駁他,js是一門基於對象的語言,不是面向對象,他就會覺得你很有學問。哈哈!

首先,我們學習一下,什么是對象呢,那就先了解一下類和對象是什么?

1、類:一類具有相特征(屬性)和行為(方法)的集合
eg:人類--->屬性,身高,體重,性別,方法:吃飯,說話,走路
2、對象:從類中,拿出具有確定屬性值和方法的個體。
eg:張三--->屬性,身高180 體重180,方法:說話-->我叫張三,身高180

大家知道什么是類什么是對象了嗎?

兩者有什么關系呢?

類是抽象的,對象是具體的,類是對象的抽象化,對象是類的具體化
類是一個抽象的概念只能說類有屬性和方法,但是不能給屬性附具體的值,
對象是一個具體的個例,是將類中的屬性,進行具體賦值而來的個體
eg:張三是人類的一個個體,可以說張三的姓名叫張三,也就是張三對人類的每一個屬性進行了具體的賦值,那么張三就是由人類產生的一個對象。

是不是通俗易懂呢。

下面我來使用一下類和對象:

首先創建一個類,這里要注意的是,類名必須使用大駝峰法則,即每一個單詞的首字母都要大寫,這是我說知道唯一一個使用大駝峰法則的地方

方法:

function 類名(屬性1){
this.屬性1=屬性1;
this.方法=function(){
方法中要調用自身屬性,必須使用this.屬性。
}
}

function Person(name,age){
    this.name=name;
    this.age=age;            
    this.say=function(content){
        //在類中,要訪問類自身的屬性,必須使用this,屬性調用。
        alert("我叫"+this.name+",今年"+this.age+"了!"+content);
    }
}

這樣我們就創建好了一個類

下面我們實例化出一個對象,

通過 obj=new類名(屬性1的具體值);
obj.屬性;調用屬性
obj.方法();調用方法

var zhangsan = new Person("張三",18);

下面我們要學習一下三個屬性和三個方法

【成員屬性和成員方法】
1、在構造函數中,使用this.屬性聲明。或者在實例化出對象以后,使用“對象.屬性追加的,都屬於成員屬性或成員方法”。也叫實例屬性,和實例方法。
成員屬性和方法是屬於由類new出的對象的。需要使用“對象名.屬性名”調用。

【靜態屬性與靜態方法】
2、 通過“類名.屬性”、“類名.方法”聲明的屬性和方法,稱為靜態屬性,靜態方法。也就類屬性和類方法:

類屬性/類方法,是屬於類的(屬於構造函數的)
通過“類名.屬性名”調用

3、成員屬性時屬於實例化出的對象的,只能使用對象調用。
靜態屬性時屬於構造函數的,只能使用類名調用。

【私有屬性和私有方法】
4、在構造函數中,使用var聲明的變量稱為私有屬性;
在構造函數中,使用function聲明的函數,稱為私有方法;
function Person(){
var num = 1;//私有屬性
function func(){}//私有方法
}
私有屬性和私有方法的作用域,只能在構造函數內容有效,即,只能在構造函數內部使用,在構造函數外部,無論使用對象名還是類名都無法調用。

舉個栗子,

function Person(name){
            this.name=name;//聲明成員屬性
            var sex = "男";
            this.satTime=function(){
                alert("說出當前時間是"+getTime());
            }
            this.writeTime = function(){
                alert("我寫了當前時間是"+getTime());
            }
            function getTime(){
                return new Data();
            }
            
        }
        var zhangsan = new Person("張三");
        zhangsan.age = 14;//追加成員屬性
        
        //alert(zhangsan.age);//調用成員屬性
        Person.count = "60億";//聲明靜態屬性
        console.log(Person.count);//調用靜態屬性
        var lisi = new Person("李四");
        console.log(lisi.count);//undefined 靜態屬性時屬於類的,只能用類名調用
        console.log(lisi.sex);
        console.log(Person.sex);

通俗易懂的,好了下面我們重點來了,就是標題的兩大內容之一,封裝

 

  封裝

1、 什么叫封裝?
① 方法的封裝: 將類內部的函數進行私有化處理,不對外提供調用接口,無法在類外部使用的方法,稱為私有方法,即方法的封裝。
② 屬性的封裝: 將類中的屬性進行私有化處理,對外不能直接使用對象名訪問(私有屬性)。 同時,需要提供專門用於設置和讀取私有屬性的set/get方法,讓外部使用我們提供的方法,對屬性進行操作。 這就叫屬性的封裝。
2、 注意: 封裝不是拒絕訪問,而是限制訪問。 要求調用者,必須使用我們提供的set/get方法進行屬性的操作,而不是直接拒絕操作。
因此,單純的屬性私有化,不能稱為封裝!必須要私有化之后,提供對應的set/get方法。

上面是知識點,大家知道了嗎,簡單的講,就是將屬性保存起來,只讓用戶通過提供的方法去訪問,修改數據,當然,如果你是前端高手,你就可以無視,直接修改源碼。這當然是外話了 --!!

下面我們來一段實例代碼,讓你們感受下封裝的過程

function Person(name, age1) {
          this.name = name;
 //       this.age = age;
          var age = 0;
          this.setAge = function(ages) {
                if(ages > 0 && ages <= 120) {
                    age = ages;
                } else {
                    alert("年齡賦值失敗!");
                }
            }
       // 當實例化類拿到對象時,可以直接通過類名的()傳入年齡,設置私有屬性
            if(age1 != undefined) this.setAge(age1);
            this.getAge = function() {
              return age;
            }
          //私有化的方法,只能在類內部被其他方法調用,而不能對外提供功能。 這就是方法的封裝!
                 
             function getTime() {
               return new Date();
             }
 }
            //    var zhangsan = new Person("張三");
            //    zhangsan.setAge(99);
            //    alert("張三的年齡是:"+zhangsan.getAge());

            var lisi = new Person("李四", 999);
            //lisi.setAge(110);
            alert("李四的年齡是:" + lisi.getAge());
            
            function Person(){
                var age = 0;
                this.getAge=function(){
                    return age;
                }
                this.setAge=function(age1){
                    age = age1;
                }
                function func(){}
            }

封裝其實很簡單,當我們把屬性封裝起來之后,我們就不能直接訪問類的私有屬性,我們只可以通過getAge(),setAge()訪問設置age屬性,這樣就模擬實現了第一個功能,封裝,

繼承

等等,我忽略了一個重要的問題,那就是this指向問題,大家可能問上面我們出現了this,this到底是什么呢,現在我們詳細解釋一下

簡單來說

1、誰最終調用這個函數,this就指向誰!
①this指向誰,不應該考慮函數在哪里聲明,應該考慮函數在哪里調用;
②this指向的永遠只可能是對象,不可能是函數;
③this指向的對象,叫做函數的上下文,也叫函數的context;也叫函數的調用者。

2、this指向的規律!!!(跟函數的調用方式息息相關)
①通過函數名()調用的this永遠指向window;
②通過對象.方法調用的,this指向這個對象;
③函數作為數組中一個元素,通過數組下標調用的,this指向這個數組;
④函數作為window內置函數的回調函數使用;this指向window;
setInterval setTimeout 等
⑤函數作為構造函數時new關鍵字調用,this指向新new出的對象。

以上是我所總結的this指向,this也就這幾中情況了,其他還沒遇到過,如果有其他,請大佬在評論告訴我,先謝了,

在舉個栗子

    var fullname = 'John Doe';
    var obj = {
       fullname: 'Colin Ihrig',
       prop: {
          fullname: 'Aurelio De Rosa',
          getFullname: function() {
             return this.fullname;
          }
       }
    };
    console.log(obj.prop.getFullname()); 
    // 函數的最終調用者 obj.prop 
    
    var test = obj.prop.getFullname;
    console.log(test());  
    // 函數的最終調用者 test()  this-> window
    
    obj.func = obj.prop.getFullname;
    console.log(obj.func()); 
    // 函數最終調用者是obj
    
    var arr = [obj.prop.getFullname,1,2];
    arr.fullname = "JiangHao";
    console.log(arr[0]());
    // 函數最終調用者數組

 

吃了這個栗子,是不是清楚多了,不要被歪的所迷惑,你就找誰最終調用了這個函數就行,

然后是原型和原型鏈,這個是很重要的知識點,大家一定要掌握好,因為繼承需要准備很多知識點,大家耐心學完,

【__proto__與prototype】
1、prototype,函數的原型對象;
①只有函數才有prototype,而且所有函數,必有prototype
②prototype本身也是一個對象!
③prototype指向了當前函數所在的引用地址!

2、__proto__:對象的原型
①只有對象才有__proto__,而且所有對象必有__proto__
②__proto__也是一個對象,所以也有自己的__proto__,順着這條線向上找的順序,就是原型鏈
③函數、數組都是對象,都有自己的__proto__;

3、實例化一個類,拿到對象的原理?
實例化一個類,實際上是將新對象的__proto__,指向構造函數所在的prototype。
也就是說:zhangsan.__proto__==Person.prototype √

4、所有對象的__proto__延原型鏈向上查找,都將指向Object的prototype
Object的prototype的原型,指向null

【原型鏈的指向問題】
研究原型鏈的指向,就是要研究各種特殊對象的__proto__的指向問題。
1、通過構造函數,new出的對象。新對象的__proto__指向構造函數的prototype
2、函數的__proto__,指向了function()的prototype
3、函數的prototype的__proto__指向Object的prototype
(直接使用{}字面量聲明。或使用new Object拿到的對象的__proto__ 直接指向Object的prototype)
4、Object的prototype的__proto__,指向null
Object作為一個特殊函數,他的__proto__指向function()的prototype

這是點先放這里了解釋的很清楚,大家看了之后就清楚原型了,繼承就需要幾個知識點而已,

一、擴展Object實現繼承

     ①聲明父類
         function Parent(){}
     聲明子類
         function Son(){}
    ②通過prototype給object類添加一個擴展方法:
        Object.prototype.extend=function(parent){
            for(var i in parent){
                this[i]=parent[i];
            }
        }
    ③分別拿到父類對象,和子類對象;
        var p = new Person();
        var s = new Student();
    ④用子類對象,調用擴展方法,實現繼承操作;
        s.extend(p);
 3、實現繼承的原理:
     通過循環,將父類對象的所有屬性和方法全部付給子類屬性,關鍵點在for-in循環,即使不擴張object,也能通過簡單的循環實現操作
 4、擴展Object繼承的缺點,
     ①,無法通過一次實例化,直接拿到完整的子類對象,而需要先拿到父類對象和子類對象兩個對象,在手動合並
     ②擴展Object的繼承方法,也會保留只在子類的對象上
/**/
function Person(name,age){
    this.name = name;
    this.age = age;
    this.say = function(){
        alert("我叫"+this.name);
    }
}

function Student(no){
    this.no = no;
    this.Study = function(){
        alert("我在學習!");
    }
}

Object.prototype.extend1 = function(parent){
    for(var i in parent){
        this[i]=parent[i];
    }
}
var p = new Person("張三",12);
var s = new Student("123456");
s.extend1(p);
console.log(s);

擴展繼承我感覺很簡單,以后要用的時候,只需要拿出擴展的extend1方法,就可以繼承了,這個名字大家不要抄啊,可以.a.b都行,高興就好

 使用原型實現繼承

     ①定義父類:
         function Parent(){}
      定義子類:
             function Son(){}
      ②將父類對象,賦值給子類的prototype
             son.prototype = new Person();
      ③拿到子類對象,就會將父類對象的所有屬性和方法,添加到__proto__
             var s = new Son();
     使用原型繼承的原理:
          將父類對象,賦值給子類的prototype,那么父類對象的屬性和方法就會出現在子類的prototype中。那么。實例化子類時,子類的prototype又回到子類對象的__proto__中,最終,父親對象的屬性和方法,會出現在子類的對象的__proto__中;
    這種繼承的特點
          ①子類自身的所有屬性,都是成員屬性,父類繼承過來的屬性,都是原型屬性;
         ②依然無法通過一步實例化拿到完整的子類對象。
        function Person(name,age){
            this.name = name;
            this.age = age;
            this.say = function(){
                alert("我叫"+this.name);
            }
        }
        function Student(no){
            this.no = no;
            this.Study = function(){
                alert("我在學習!");
            }
        }
            
        Student.prototype = new Person("zhangsan",14);
        var s = new Student();
        console.log(s);

 

原型繼承是比較簡單的,子類的Prototype指向父類new的對象,就可以實現繼承啦!

三,

[call/bind/apply]
1、三個函數的作用:通過函數名調用着三個函數,可以強行將函數中的this指定為某個對象。
2、三個函數的寫法(區別):
call寫法:func.call(func的this指向的obj,func參數1,func參數2,...);
apply寫法:func.apply(func的this指向的obj,[func參數1,func參數2,...]);
bind寫法:func.bind(func的this指向的obj)(func參數1,func參數2,...);
3、三個函數的唯一區別,在與接受func的參數列表的方式不同,除此之外,功能上沒有任何差異!!

[使用call/bind/apply實現繼承]
1、實現步驟:
①定義父類
function Perent(name){}
②定義子類時,在子類中使用三個函數,調用父類,將父類函數的this,指向為子類函數的this:
function Son(no,name){
this.no=no;
Person,call(this,name);
}
③實例化子類時,將自動繼承父類屬性,
var s = new Son(12,"zhangsan")

function Person(name,age){
                this.name = name;
                this.age = age;
                this.say = function(){
                    alert("我叫"+this.name);
                }
            }
            function Student(no){
                this.no = no;
                this.study=function(){
                    alert("我在學習");
                }
                Person.call(this.name,age);
            }
            var s =new Student(12,"張三",24);
            console.log(s);

 

以上就是js的繼承和封裝,多態是無法實現的,上面兩個方法在實際運用中能起到很不錯的效果,讓我們少碼很多字,

今天就到這里,謝謝大家


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM