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.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(){}; // 私有方法
}
私有屬性和私有方法的做用域,只在構造函數內部有效。即,只能在構造函數內部使用,不管使用對象名仍是類名都沒法調用。編程
一、 什麼叫封裝?
①
方法的封裝:將類內部的函數進行私有化處理,不對外提供調用接口,沒法在類外部使用的方法稱爲私有方法,即方法的封裝。
②
屬性的封裝:將類中的屬性進行私有化處理,對外不能直接使用對象名訪問(私有屬性)。同時,須要提供專門用於設置和讀取私有
屬性的set/get方法,讓外部使用咱們 提供的方法,對屬性進行操做。這就叫屬性的封裝。
二、
注意:封裝不是拒絕訪問,而是限制訪問。要求調用者,必須使用咱們提供的set/get方法進行屬性的操做,而不是直接拒絕操做。
所以,單純的屬性私有化,不能成爲封裝!必須私有化以後,提供對應的set/get方法
function Person(){
var age=0;
this.setAge=function(ages){
age=ages;
}
this.getAge=function(){
return age;
}
}
4.1誰最終調用函數,this就指向誰
4.1.1 this指向誰,不該該考慮函數在哪聲明,而應該考慮函數在哪調用!!!
4.1.2 this指向的,永遠只多是對象,而不多是函數!!!
4.1.3 this指向的對象,叫作函數的上下文/context,也叫函數的調用者
4.2this指向的規律!!!(與函數的調用方式息息相關)
4.2.1 經過函數名()調用的,this永遠指向window
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 等
4.2.5 函數做爲構造函數,使用new關鍵字調用,this指向新new出的對象。
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.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();
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.1擴展Object實現繼承
8.1.1 擴展Object實現繼承
① 聲明父類
聲明子類
② 經過prototype給Object類添加一個擴展方法:
Object.prototype.extend = function(parent){
for(var i in parent){
this[i]=parent[i];
}
}
③ 分別拿到父類對象和子類對象:
var p = new Parent();
var s = new Son();
④ 用子類對象,調用擴展方法,實現繼承操做:
8.1.2 實現繼承的原理:
經過循環將父類對象的全部屬性和方法,所有賦給子類對象。關鍵點在於for-in循環,
即便不擴展Object,也能經過簡單的循環實現操做。
8.1.3 擴展Object繼承的缺點:
① 沒法經過一次實例化,直接拿到完整的子類對象,而須要先拿到父類對象和子類對象兩個對象,再手動合併。
② 擴展Object的繼承方法,也會保留在子類的對象上。
8.2使用原型實現繼承
8.2.1 使用原型實現繼承
① 定義父類
定義子類
② 將父類對象,賦值給子類的prototype
Son.prototype = new Parent();
③ 拿到子類對象,就會將父類對象的全部屬性和方法,添加到__proto__
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實現繼承]
一、 實現步驟:
① 定義父類
② 定義子類時,在子類中使用三個函數,調用父類,將父類函數中的this,指向位子類函數中的this:
function Son(no,name){
this.no = no;
Parent.call(this,name);
}
③ 實例化子類時,將自動繼承父類屬性
var s = new Son(12,"張三");
[JS中的做用域]
一、 全局變量:函數外聲明的變量,成爲全局變量;
局部變量:函數內部使用var聲明的變量,稱爲局部變量;
在JS中,只有函數做用域,沒有塊級做用域!!也就是說,if/for等有{}結構體,
並不能具有本身的做用域
因此,函數外部不能訪問函數內部的局部變量(私有屬性)。由於,函數內部的變量,在
函數執行完畢之後,就會被釋放掉。
二、 使用閉包,能夠訪問函數的私有變量!
JS中,提供了一種"閉包"的概念:在函數內部,定義一個子函數,子函數能夠訪問父
函數的私有變量。能夠在子函數中進行操做,最後將子函數經過return返回
function func1(){
var num = 1;
function func2(){
return num;
}
return func2;
}
var num = func1()();
三、 閉包的做用:
① 能夠在函數外部訪問函數的私有變量;
② 讓函數內部的變量,能夠始終存在於內存中,不會在函數完成後釋放掉