大家好,今天我帶大家學習一下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的繼承和封裝,多態是無法實現的,上面兩個方法在實際運用中能起到很不錯的效果,讓我們少碼很多字,
今天就到這里,謝謝大家