前言
面向?qū)ο缶幊叹褪菍⒛愕男枨蟪橄蟪梢粋€對象,針對這個對象分析其特征(屬性)和動作(方法)犀被,這個對象稱為“類”椅您。JavaScript 的核心是支持面向?qū)ο蟮模瑫r它也提供了強大靈活的 OOP 語言能力寡键,遺憾的是對于JavaScript這種解釋性的弱類型語言掀泳,沒有強類型語言中那種通過class等關(guān)鍵字實現(xiàn)類的方式,但JavaScript可以通過一些特性模仿實現(xiàn)面向?qū)ο缶幊獭?/p>
面向?qū)ο笥腥齻€基本特征:封裝
西轩、繼承
员舵、多態(tài)
。
封裝
封裝藕畔,就是把客觀事物封裝成抽象的類固灵,類中包含了事物的屬性和方法,并且類可以把自己的數(shù)據(jù)和方法只讓可信的類或者對象操作劫流,對不可信的進行信息隱藏。
JavaScript創(chuàng)建一個類很容易,通過聲明一個函數(shù)保存在一個變量里來實現(xiàn)祠汇,這個類的類名通常會采用首字母大寫的形式來表示仍秤,然后在這個函數(shù)(類)的內(nèi)部使用this
關(guān)鍵字來定義類的屬性和方法。例如:
var Person = function(name,sex,age){
this.name = name;
this.sex = sex;
this.age = age;
}
也可以在類的原型上添加屬性和方法可很。例如:
Person.prototype.say = function(){
// Say something
}
或者
Person.prototype = {
say: function(){ … }
}
這樣就完成了Person類的封裝诗力,當我們要使用這個類時,需要通過new
關(guān)鍵字來實例化(創(chuàng)建)一個新的對象我抠,通過.
操作符訪問對象的屬性和方法苇本。例如:
var person = new Person('Scott','male',20);
console.log(person.name); // Scott
通過this
添加的屬性和方法是在當前對象上添加的,而JavaScript是一種基于原型prototype的語言菜拓,每創(chuàng)建一種對象時瓣窄,都有一個原型prototype用于指向其繼承的屬性和方法,通過prototype繼承的屬性和方法不是屬于對象本身的纳鼎,在使用這些方法時俺夕,會通過原型鏈進行查找。當創(chuàng)建一個對象時贱鄙,會創(chuàng)建this指向的屬性和方法劝贸,而通過prototype繼承的屬性或方法是該類的每個對象所共有的,不會再次創(chuàng)建逗宁。
當創(chuàng)建一個函數(shù)或者對象時都會為其創(chuàng)建一個prototype對象映九,原對象中的__proto__
屬性指向該原型對象,prototype對象中會有一個constructor
屬性指向擁有整個原型對象的函數(shù)或者類瞎颗。
通過new
關(guān)鍵字創(chuàng)建對象時實際上是對新對象中this
的不斷賦值件甥,并將prototype指向類的原型對象,而在類外通過.
操作符定義的屬性和方法是不會添加到新建對象上的言缤,通過對象進行訪問的結(jié)果是undefined嚼蚀。例如:
Person.isChinese = true;
Person.eat = function(){ … }
var person = new Person("Alex","female",19);
console.log(person.name); // Alex
console.log(person.isChinese); // undefined
console.log(person.eat()); // undefined
如果你忽略了new
關(guān)鍵字直接調(diào)用類,如:var person = Person("Alex","female",19);
管挟,此時會直接調(diào)用Person
這個函數(shù)轿曙,如果這個函數(shù)是在全局作用域里執(zhí)行的,則此時類中的this
指向的當前對象就是全局變量僻孝,在頁面中全局變量就是window
导帝,所以通過this
添加的屬性或方法會被添加到window
中,并且最終的person
對象會是undefined
穿铆。
要解決這個問題可以采用“安全模式”您单,例如:
var Person = function(name,sex,age){
if(this instanceof Person){
this.name = name;
this.sex = sex;
this.age = age;
}else{ // 未使用 new
return new Person(name,sex,age);
}
}
var person = Person("Scott","male",20);
這樣就不用當心創(chuàng)建對象時忘記使用new
關(guān)鍵字了。
繼承
繼承可以使用現(xiàn)有類的所有功能荞雏,并在無需重新編寫原來的類的情況下對這些功能進行擴展虐秦。繼承所涉及的對象不止一個平酿,JavaScript并沒有提供繼承這一現(xiàn)有的機制,也正因為JavaScript少了這些顯性的限制悦陋,使其更具有靈活性蜈彼。在JavaScript中可以使用類式繼承、構(gòu)造函數(shù)繼承俺驶、組合繼承來達到繼承的效果幸逆。
類式繼承
// 聲明父類
function Parent(){
this.parentValue = true;
}
// 為父類添加共有方法
Parent.prototype.getParentValue = function(){
return this.parentValue;
}
// 聲明子類
function Child(){
this.childValue = false;
}
// 繼承父類
Child.prototype = new Parent();
// 為子類添加共有方法
Child.prototype.getChildValue = function(){
return this.childValue;
}
類的原型對象的作用是為類的原型添加共有屬性和方法,但類必須通過原型prototype來訪問這些屬性和方法暮现。當實例化一個父類時还绘,新建對象復(fù)制了父類構(gòu)造函數(shù)內(nèi)的屬性和方法,并且將原型__proto__
指向了父類的原型對象栖袋,這樣就擁有了父類原型對象上的屬性和方法拍顷,新建對象可以直接訪問父類原型對象的屬性和方法,接著將這個新建的對象賦值給子類的原型栋荸,那么子類的原型就可以訪問父類的原型屬性和方法菇怀。將這個對象賦值給子類的原型,那么這個子類就可以訪問父類原型上的屬性和方法晌块,并且可以訪問從父類構(gòu)造函數(shù)中復(fù)制的屬性和方法爱沟。我們可以來測試一下:
var child = new Child();
console.log(child.getParentValue()); // true
console.log(child.getChildValue()); // false
console.log(child instanceof Parent); // true
console.log(child instanceof Child); // true
console.log(Child instanceof Parent); // false
但這種繼承方式有2個缺點:
- 由于子類是通過其原型prototype對父類實例化,如果父類中的共有屬性是引用類型匆背,會被所有實例所共享呼伸,一個子類的實例修改了該屬性會直接影響到所有實例。例如:
function Parent(){
this.values = ['A','B','C'];
}
function Child(){}
Child.prototype = new Parent();
var child1 = new Child();
var child2 = new Child();
console.log(child2.values); // ["A","B","C"]
child1.values.push('D');
console.log(child2.values); // ["A","B","C","D"]
- 創(chuàng)建父類實例時钝尸,是無法向父類傳遞參數(shù)的括享,也就是無法對父類構(gòu)造函數(shù)內(nèi)的屬性進行初始化。例如這種錯誤的繼承方式:
function Parent(name){
this.name = name;
}
function Child(){}
Child.prototype = new Parent('name'); // 錯誤
構(gòu)造函數(shù)繼承
// 聲明父類
function Parent(name){
this.name = name;
this.values = ['A','B','C'];
}
Parent.prototype.showName = function(){
console.log(this.name);
}
// 聲明子類
function Child(name){
Parent.call(this,name);
}
var child1 = new Child('one');
var child2 = new Child('two');
child1.values.push('D');
console.log(child1.name); // one
console.log(child1.values); // ["A","B","C","D"]
console.log(child2.name); // two
console.log(child2.values); // ["A","B","C"]
child1.showName(); // TypeError
語句Parent.call(this,name);
是構(gòu)造函數(shù)繼承的精華珍促,call
方法可以更改函數(shù)的作用環(huán)境铃辖,在子類中執(zhí)行該方法相當于將子類的變量在父類中執(zhí)行一遍,此時父類方法中的this
屬性指的是子類中的this
猪叙,由于父類中是給this
綁定屬性的娇斩,所以子類也就繼承了父類的屬性和方法。構(gòu)造函數(shù)繼承并沒有涉及原型prototype穴翩,所以父類的原型方法不會被子類繼承犬第,子類的每個實例會單獨擁有一份父類的屬性方法而不能共用,如果想被子類繼承就必須放在構(gòu)造函數(shù)中芒帕,要實現(xiàn)這樣的效果可以采用組合繼承的方式歉嗓。
組合繼承
類式繼承是通過子類的原型prototype對父類實例化來實現(xiàn)的,構(gòu)造函數(shù)繼承是通過在子類的構(gòu)造函數(shù)作用環(huán)境中執(zhí)行一次父類的構(gòu)造函數(shù)來實現(xiàn)的背蟆,而組合繼承則同時做到這兩點鉴分。
// 聲明父類
function Parent(name){
this.name = name;
this.values = ['A','B','C'];
}
Parent.prototype.getName = function(){
console.log(this.name);
}
// 聲明子類
function Child(name,id){
Parent.call(this, name);
this.id = id;
}
Child.prototype = new Parent();
Child.prototype.getId = function(){
console.log(this.id);
}
var child1 = new Child('child1', 1);
child1.values.push('D');
console.log(child1.values); // ["A", "B", "C", "D"]
child1.getName(); // child1
child1.getId(); // 1
var child2 = new Child('child2', 2);
console.log(child2.values); // ["A", "B", "C"]
child2.getName(); // child2
child2.getId(); // 2
子類的實例中更改父類繼承下來的引用類型屬性哮幢,不會影響到其它實例,并且子類實例化過程中又能將參數(shù)傳遞到父類的構(gòu)造函數(shù)中冠场。
多態(tài)
多態(tài)就是同一個方法多種調(diào)用方式家浇,JavaScript可以通過對傳入的參數(shù)列表arguments
進行判斷來實現(xiàn)多種調(diào)用方式。例如:
function Add(){
// 無參數(shù)
function zero(){
return 0;
}
// 一個參數(shù)
function one(num){
return num;
}
// 兩個參數(shù)
function two(num1, num2){
return num1 + num2;
}
this.add = function(){
// 獲取參數(shù)列表及參數(shù)個數(shù)
var arg = arguments,
len = arg.length;
switch(len){
case 0:
return zero();
case 1:
return one(arg[0]);
case 2:
return two(arg[0], arg[1]);
}
}
}
var A = new Add();
console.log(A.add()); // 0
console.log(A.add(1)); // 1
console.log(A.add(1,2)); // 3
當調(diào)用add進行運算時碴裙,會根據(jù)參數(shù)列表的不同做相應(yīng)的運算,這就是JavaScript的多態(tài)實現(xiàn)方式点额。
總結(jié)
面向?qū)ο笤O(shè)計方法的應(yīng)用解決了傳統(tǒng)結(jié)構(gòu)化開發(fā)方法中客觀世界描述工具與軟件結(jié)構(gòu)的不一致性問題舔株,縮短了開發(fā)周期,解決了從分析和設(shè)計到軟件模塊結(jié)構(gòu)之間多次轉(zhuǎn)換映射的繁雜過程还棱,是一種高效率的軟件開發(fā)方式载慈,特別是在多人協(xié)作開發(fā)的情況下,可以提高代碼的可復(fù)用性和維護性珍手,使開發(fā)更有效率办铡。
JavaScript系列文章:
本文為作者kMacro原創(chuàng),轉(zhuǎn)載請注明來源:http://www.reibang.com/p/c2083cf275ec琳要。