面向?qū)ο蟮腏avaScript基本知識指南大全
譯文由于jQuery和MooTools等精心開發(fā)的庫,JavaScript已成為前端開發(fā)的基礎(chǔ)。不過,我們要留意這些優(yōu)秀庫中所運(yùn)用的較高級概念,這點(diǎn)極其重要。原因何在?因?yàn)樽鳛閃eb開發(fā)人員,對待學(xué)習(xí)最新的編程趨勢和試圖把那些趨勢推向極致,我們必須予以一視同仁。要不然,Web開發(fā)領(lǐng)域就不會出現(xiàn)創(chuàng)新。所以,我們不妨花點(diǎn)時(shí)間來了解JavaScript面向?qū)ο缶幊痰幕局R,包括類、繼承和范圍。
類
在我們學(xué)習(xí)如何把類實(shí)施到代碼中之前,不妨討論一下類是什么、為什么有必要學(xué)習(xí)/使用類。
正如Java文檔聲明的那樣:“類是用來創(chuàng)建一個(gè)個(gè)對象的藍(lán)圖。”這藍(lán)圖就像造房子過程中所用的實(shí)際藍(lán)圖。建造人員使用藍(lán)圖來評估房子有什么樣的屬性,房子會有什么樣的功能。類是表示對象屬性的一種很方便的方式,無論這對象是房子、汽車還是人。當(dāng)存在的某個(gè)對象不止一個(gè)時(shí),類就變得特別有用。
比如說,我們不使用類來比較一下兩個(gè)實(shí)際的對象。這體現(xiàn)了程序思考過程,而不是面向?qū)ο蟮乃伎歼^程。我們將描述一個(gè)名叫Rob的男子和一個(gè)名為Emillee的小女孩。我們必須假定我們對人體一無所知,因?yàn)槲覀儧]有藍(lán)圖(類)可供使用。
Rob:
1. Rob在身體的上部有兩個(gè)橢圓形的結(jié)構(gòu),相隔幾英寸。這些橢圓形結(jié)構(gòu)有一個(gè)黑色背景,中間是棕色。
2. Rob有兩個(gè)與地面相對平行的結(jié)構(gòu),似乎表明了人體中最垂直的部分,這仍是身體基部的一部分。
3. Rob有兩個(gè)附屬物,從另外兩個(gè)附屬物延伸過來。這些似乎可用來抓取物件。它們似乎比較大。
4. Rob高度約6英尺。
5. Rob無意識地吸入氧,把氧轉(zhuǎn)換成二氧化碳。
Emilee:
1. Emillee在身體的上部有兩個(gè)橢圓形的結(jié)構(gòu),相隔幾英寸。這些橢圓形結(jié)構(gòu)有一個(gè)黑色背景,中間是藍(lán)色。
2. Emillee有兩個(gè)與地面相對平行的結(jié)構(gòu),似乎表明了人體中最垂直的部分,這仍是身體基部的一部分。
3. Emillee有兩個(gè)附屬物,從另外兩個(gè)附屬物延伸過來。這些似乎可用來抓取物件。它們似乎比較小。
4. Emillee高度約1.5英尺。
5. Emillee無意識地吸入氧,把氧轉(zhuǎn)換成二氧化碳。
單單描述一個(gè)人的1)眼睛、2)肩膀、3)雙手、4)身高和5)呼吸行為就有大量的工作要做。要注意:我們不得不兩次給出幾乎一模一樣的看法,因?yàn)槲覀儧]有藍(lán)圖可供使用。雖然描述兩個(gè)人不是太費(fèi)勁,但是如果我們想要描述100個(gè)人、1000個(gè)人或者100萬個(gè)人,怎么辦?肯定有一種更高效的方法來描述有著類似屬性的對象:這正是類的亮點(diǎn)。
我們不妨使用面向?qū)ο蟮睦砟睿匦驴紤]前一個(gè)例子。由于我們描述的是男子和小女孩,我們知道他們都是人類。所以不妨先為人類創(chuàng)建一個(gè)簡單的藍(lán)圖。
人類:
1. 身體的上部有兩個(gè)橢圓形的結(jié)構(gòu)。這些橢圓形結(jié)構(gòu)有一個(gè)黑色背景,中間顏色不一樣。我們稱之為眼睛。
2. 有兩個(gè)與地面相對平行的結(jié)構(gòu),似乎表明了人體中最垂直的部分,這仍是身體基部的一部分。我們稱之為肩膀。
3. 有兩個(gè)附屬物,從另外兩個(gè)附屬物延伸過來。這些似乎可用來抓取物件。它們的大小不一樣。我們稱之為雙手。
4. 視年齡及其他因素而定,高度不一樣。我們稱之為身高。
5. 無意識地吸入氧,并把氧轉(zhuǎn)換成二氧化碳。我們稱之為呼吸。
于是我們已聲明,人類的屬性是,他們有眼睛,有肩膀,有雙手,有身高。我們還已聲明,這些屬性可能不一樣。定義了人類的藍(lán)圖后,并且聲明了Rob和Emillee是人類后,我們可以將已經(jīng)知道的關(guān)于人類的屬性運(yùn)用到Rob和Emillee。
Rob是人類。
1. Rob有棕色的眼睛
2. Rob有肩膀
3. Rob有大大的雙手
4. Rob身高6英寸
5. Rob會呼吸
Emillee是人類。
1. Emillee有藍(lán)色的眼睛
2. Emillee有肩膀
3. Emillee有小小的雙手
4. Emillee身高1.5英尺
5. Emillee會呼吸
我們只要明確聲明Rob和Emillee是人類,就可以把與人類有關(guān)的屬性和功能直接運(yùn)用到Rob和Emillee。這讓我們可以避免重新定義身體的所有部位,同時(shí)讓我們可以高效地描述這兩個(gè)對象之間的重要區(qū)別。
下面是關(guān)于類及對象(名為類的實(shí)例)的幾個(gè)例子,以便你明白兩者之間的關(guān)系。
類Student(學(xué)生)
◆ 屬性:年級、年齡、出生日期和學(xué)生身份標(biāo)志(SSID)
◆ 功能:計(jì)算年級平均成績、查看缺課情況、更新操行評語
類Employee(員工)
◆ 屬性:雇主身份識別號(EIN)、小時(shí)工資、聯(lián)系號碼、保險(xiǎn)
◆ 功能:設(shè)定薪水、查看工作效率和獲取簡歷
類Computer(電腦)
◆ 屬性:處理器、主機(jī)、顯示器
◆ 功能:開機(jī)、關(guān)機(jī)和重啟
好了,我們已明白了類背后的概念,不妨把所知道的東西運(yùn)用到JavaScript。與包括PHP和C++在內(nèi)的語言不一樣,JavaScript沒有類數(shù)據(jù)類型。不過,如果我們借助JavaScript的靈活性,就很容易使用函數(shù)來模擬類。
我們以前面一個(gè)例子為例,使用類來表示學(xué)生。
創(chuàng)建一個(gè)類時(shí),你必須做兩件事:必須知道這個(gè)類有什么屬性/函數(shù)(又叫方法);你需要用一個(gè)值來初始化屬性。
- function Student(name, gender, age, grade, teacher)
- {
- this.name = name;
- this.gender = gender;
- this.age = age;
- this.grade = grade;
- this.teacher = teacher;
- }
- var bob = new Student("bob", "male", 15, 10, "Marlow");
- alert(bob.age); //輸出15
- var susan = new Student("susan", "female", 10, 5, "Gresham");
- alert(susan.gender); //輸出 'female'
我們可以從這個(gè)例子中看出,類的實(shí)例使用新的運(yùn)算符來進(jìn)行初始化。類的屬性和方法使用. (dot)運(yùn)算符來訪問。所以為了獲得名為bob的Student類實(shí)例的屬性年齡,我們只要使用bob.age。同樣,我們創(chuàng)建了Student類的一個(gè)實(shí)例,把它分配給susan。為了獲得susan的性別,我們只要使用susan.gender。類在代碼可讀性方面帶來了巨大的好處:你不需要有任何編程經(jīng)驗(yàn),就能推斷出bob.age是bob的年齡。
不過,前一個(gè)例子有兩個(gè)不好(但很容易修復(fù))的缺點(diǎn)。
1)任何語句都可以訪問類屬性
2)參數(shù)必須以一定的次序來傳遞
#p#
確保屬性值的私有性
請注意:在前一個(gè)例子中,我們只要調(diào)用bob.age,就能獲得bob.age的值。此外,我們可以在程序中任何地方將bob.age設(shè)成自己喜歡的任何值。
- var bob = new Student("bob", "male", 15, 10, "Marlow");
- alert(bob.age); //輸出15
- bob.age = 9;
- alert(bob.age); //輸出9;
看起來沒有害處,是不是?那么請考慮這個(gè)例子。
- var bob = new Student("bob", "male", 15, 10, "Marlow");
- alert(bob.age); //輸出15
- bob.age = -50;
- alert(bob.age); //輸出-50;
我們看到了年齡是負(fù)值:這在邏輯上不一致。我們只要使用私有變量(private variable)這個(gè)概念,就可以防止諸如此類的問題、確保數(shù)據(jù)的完整性。私有變量是只能在類本身里面訪問的變量。雖然JavaScript再次沒有用于確保變量私有性的保留字,但是JavaScript為我們提供了創(chuàng)造同樣效果的工具。
- function Student(name, gender, age, grade, teacher)
- {
- var studentName = name;
- var studentGender = gender;
- var studentGrade = grade;
- var studentTeacher = teacher;
- var studentAge = age;
- this.getAge = function()
- {
- return studentAge;
- };
- this.setAge = function(val)
- {
- studentAge = Math.abs(val); //使用絕對值,確保年齡是正值
- };
- }
- var bob = new Student("bob", "male", 15, 10, "Marlow");
- alert(bob.studentAge); //未定義,因?yàn)槟挲g在類定義中受私有保護(hù)
- alert(bob.getAge()); //輸出15
- bob.setAge(-20);
- alert(bob.getAge()); //輸出20
通過使用變量聲明,而不是直接為類賦予屬性,我們保護(hù)了年齡數(shù)據(jù)的完整性。由于JavaScript使用了函數(shù)范圍,我們的類里面聲明的變量在該類外面是無法訪問的,除非由該類里面的函數(shù)明確返回。方法this.getAge將學(xué)生年齡返回到調(diào)用環(huán)境,它名為訪問器方法(Accessor method)。訪問器方法返回屬性的值,那樣該值就可以在類的外面使用,而不影響類里面的值。按照約定,訪問器方法的前綴通常是“get”這個(gè)字。方法this.setAge名為更改器方法(Mutator method)。其目的是,改變屬性的值,保護(hù)完整性。
我們已看到了在類里面使用訪問器方法和更改器方法來保護(hù)數(shù)據(jù)完整性的好處。不過,為每個(gè)屬性創(chuàng)建訪問器方法帶來了極其冗長的代碼。
- function Student(name, gender, age, grade, teacher)
- {
- var studentName = name;
- var studentGender = gender;
- var studentGrade = grade;
- var studentTeacher = teacher;
- var studentAge = age;
- this.getName = function()
- {
- return studentName;
- };
- this.getGender = function()
- {
- return studentGender;
- };
- this.getGrade = function()
- {
- return studentGrade;
- };
- this.getTeacher = function()
- {
- return studentTeacher;
- };
- this.getAge = function()
- {
- return studentAge;
- };
- this.setAge = function(val)
- {
- studentAge = Math.abs(val); //使用絕對值,確保年齡是正值
- };
- }
- var bob = new Student("bob", "male", 15, 10, "Marlow");
- alert(bob.studentGender); //未定義,因?yàn)樾詣e在類定義中受私有保護(hù)
- alert(bob.getGender()); //輸出 'male'
教我C++的教授總是說:“如果你發(fā)現(xiàn)自己反復(fù)輸入相同的代碼,這表明你的做法不對。”的確,有更高效的方法可以為每個(gè)屬性創(chuàng)建訪問器方法。此外,這種機(jī)制還不需要以特定次序來調(diào)用函數(shù)參數(shù)。
動(dòng)態(tài)創(chuàng)建的訪問器方法
這個(gè)演示來自John Resig所寫的《專業(yè)JavaScript技巧》一書(我強(qiáng)烈建議各位讀一讀。前三章就非常值得一讀)。
- function Student( properties )
- {
- var $this = this; //將類范圍存儲到名為$this的變量中
- //迭代處理對象的屬性
- for ( var i in properties )
- {
- (function(i)
- {
- // 動(dòng)態(tài)創(chuàng)建訪問器方法
- $this[ "get" + i ] = function()
- {
- return properties[i];
- };
- })(i);
- }
- }
- // 創(chuàng)建一個(gè)新的用戶對象實(shí)例,并傳遞屬性的對象
- var student = new Student(
- {
- Name: "Bob",
- Age: 15,
- Gender: "male"
- });
- alert(student.name); //因?qū)傩允撬接械亩炊x
- alert(student.getName()); //輸出 "Bob"
- alert(student.getAge()); //輸出15
- alert(student.getGender()); //輸出 "male"
通過實(shí)施這個(gè)技巧,我們不但確保自己的屬性是私有的,而且不需要按次序來指定參數(shù)。下面的類實(shí)例化都相同:
- var student = new Student(
- {
- Name: "Bob",
- Age: 15,
- Gender: "male"
- });
- var student = new Student(
- {
- Age: 15,
- Name: "Bob",
- Gender: "male"
- });
- var student = new Student(
- {
- Gender: "male",
- Age: 15,
- Name: "Bob"
- });
#p#
繼承
在這篇文章中,我使用“類”這個(gè)術(shù)語極其寬松。如前所述,JavaScript沒有類實(shí)體,但是后面仍可以跟類的模式。JavaScript與其他面向?qū)ο笳Z言的區(qū)別主要在于繼承模型。C++和Java體現(xiàn)了基于類的繼承或傳統(tǒng)繼承。另一方面,JavaScript體現(xiàn)了原型繼承(Prototypal Inheritance)。在其他面向?qū)ο笳Z言中,類是一個(gè)實(shí)際的數(shù)據(jù)類型,表示創(chuàng)建對象的藍(lán)圖。在JavaScript中,雖然我們可以使用函數(shù)來模擬對象藍(lán)圖,但是它們實(shí)際上本身就是對象。然后,這些對象用作其他對象的模型(又叫原型),可以參閱文章《JavaScript原型繼承》(http://www.webreference.com/programming/javascript/prototypal_inheritance/index.html)。
運(yùn)用原型繼承這個(gè)概念讓我們得以創(chuàng)建“子類”,即繼承另一個(gè)對象的屬性的對象。如果我們想使用另一個(gè)對象的稍有一些變動(dòng)的方法,這就變得極其有用。
以類Employee(員工)為例。假設(shè)我們有兩種類型的員工:一種基于薪水,一種基于傭金。這些員工類型會有許多相似的屬性。比如說,不管某員工通過傭金獲得收入還是通過薪水獲得收入,該員工都有名稱。不過,對基于傭金的員工和基于薪水的員工來說,收入方式完全不一樣。下面這個(gè)例子體現(xiàn)了這個(gè)概念:
- function Worker()
- {
- this.getMethods = function(properties, scope)
- {
- var $this = scope; //將類范圍存儲到名為$this的變量中
- //迭代處理對象的屬性
- for ( var i in properties )
- {
- (function(i)
- {
- // 動(dòng)態(tài)創(chuàng)建訪問器方法
- $this[ "get" + i ] = function()
- {
- return properties[i];
- };
- //動(dòng)態(tài)地創(chuàng)建一個(gè)分析整數(shù),并確保是正值的更改器方法。
- $this[ "set" + i ] = function(val)
- {
- if(isNaN(val))
- {
- properties[i] = val;
- }
- else
- {
- properties[i] = Math.abs(val);
- }
- };
- })(i);
- }
- };
- }
- // CommissionWorker "子類"和WageWorker "子類"
- //繼承Worker的屬性和方法。
- CommissionWorker.prototype = new Worker();
- WageWorker.prototype = new Worker();
- function CommissionWorker(properties)
- {
- this.getMethods(properties, this);
- //計(jì)算收入
- this.getIncome = function()
- {
- return properties.Sales * properties.Commission;
- }
- }
- //要求有下列屬性:薪水、每周小時(shí)數(shù)、每年周數(shù)
- function WageWorker(properties)
- {
- this.getMethods(properties, this);
- //計(jì)算收入
- this.getIncome = function()
- {
- return properties.Wage * properties.HoursPerWeek * properties.WeeksPerYear;
- }
- }
- var worker = new WageWorker(
- {
- Name: "Bob",
- Wage: 10,
- HoursPerWeek: 40,
- WeeksPerYear: 48
- });
- alert(worker.wage); //未定義。薪水是私有屬性。
- worker.setWage(20);
- alert(worker.getName()); //輸出 "Bob"
- alert(worker.getIncome()); //輸出 38,400 (20*40*48)
- var worker2 = new CommissionWorker(
- {
- Name: "Sue",
- Commission: .2,
- Sales: 40000
- });
- alert(worker2.getName()); //輸出 "Sue"
- alert(worker2.getIncome()); //輸出8000(2% 乘40,000)
前一個(gè)例子中最重要的兩個(gè)語句是:
- CommissionWorker.prototype = new Worker();
- WageWorker.prototype = new Worker();
這聲明,對新的CommissionWorker或新的WageWorker對象的每個(gè)實(shí)例而言,Worker的屬性和方法將傳遞到那些新對象。如果需要的話,這些方法和屬性可以在“子類”定義里面被覆蓋寫入。
范圍
JavaScript體現(xiàn)了所謂的函數(shù)范圍。這意味著,函數(shù)中聲明的變量在函數(shù)(變量來自該函數(shù))外面最初是無法訪問的。不過,在語句塊中(如條件語句),可以對調(diào)用環(huán)境進(jìn)行變量聲明或改動(dòng)。
- var car = "Toyota";
- if(car == "Toyota")
- {
- car = "Toyota - We never stop...and you won't either.";
- }
- alert(car); //輸出Toyota——我們從未停上,你也如此。
- car = "Toyota"; //將汽車設(shè)回成原始值。
- function makeFord(car)
- {
- car = "Ford";
- }
- makeFord(car);
- alert(car); //輸出"Toyota",因?yàn)槠囋诤瘮?shù)范圍中已改動(dòng)。
不過,如果你想要改動(dòng)值的函數(shù),可以將對象作為參數(shù)來傳遞,并改動(dòng)對象的屬性。
- var car = new Object();
- car.brand = "Toyota"
- function makeFord(car)
- {
- car.brand = "Ford";
- }
- makeFord(car);
- alert(car.brand); //輸出“Ford”
這名為“通過調(diào)用”傳遞,將值傳遞給函數(shù)。只有你在類里面創(chuàng)建方法,又知道對象含有什么屬性,我一般才會建議采用通過調(diào)用傳遞。
現(xiàn)在你已經(jīng)掌握了運(yùn)用到JavaScript的面向?qū)ο蟮幕局R。運(yùn)用這些原則,就可以為你在將來的開發(fā)項(xiàng)目簡化代碼。
原文:http://www.1stwebdesigner.com/design/object-oriented-basics-javascript/
【編輯推薦】