探討JavaScript:優(yōu)雅的封裝還是執(zhí)行效率?
優(yōu)雅封裝的程序看起來是那么的美妙:每個(gè)屬性被隱藏在對(duì)象之后,你所能看到的就是這個(gè)對(duì)象讓你看到的,至于它到底是怎么操作的,這個(gè)不需要你操心。
執(zhí)行的效率就是另外一回事。就像是C語言和面向?qū)ο蟮?a target="_blank" >C++之間的差別:C++很優(yōu)雅,但是執(zhí)行效率,無論是編譯后的二進(jìn)制代碼還是運(yùn)行期的內(nèi)存的占用,都要比簡(jiǎn)單的C語言多出一截來。
這個(gè)問題在腳本語言中顯得更加重要,因?yàn)镴avaScript根本就是一種解釋語言,解釋語言的執(zhí)行效率要比編譯語言低很多。
1. 優(yōu)雅的封裝
我們先來看看變量封裝。這里的變量不僅僅是屬性,也包括函數(shù)。前面已經(jīng)說過,JavaScript中并沒有類這個(gè)概念,是我們利用變量作用域和閉包“巧妙的模擬”出來的,這是一種優(yōu)雅的實(shí)現(xiàn)。還是溫故一下以前的代碼:
- function Person() {
- var id;
- var showId = function() {
- alert("My id is " + id);
- }
- this.getId = function() {
- return id;
- }
- this.setId = function(newId) {
- id = newId;
- }
- }
- var p = new Person();
- p.setId(1000);
- alert(p.id); // undefined
- // p.showId(); error: function not defined
- var p2 = new Person();
- alert(p.getId == p2.getId); // false
- function Person() {
- var id;
- var showId = function() {
- alert("My id is " + id);
- }
- this.getId = function() {
- return id;
- }
- this.setId = function(newId) {
- id = newId;
- }
- }
- var p = new Person();
- p.setId(1000);
- alert(p.id); // undefined
- // p.showId(); error: function not defined
- var p2 = new Person();
- alert(p.getId == p2.getId); // false
我們很優(yōu)雅的實(shí)現(xiàn)了私有變量——盡管是投機(jī)取巧的實(shí)現(xiàn)的。但是,這段代碼又有什么問題呢?為什么兩個(gè)對(duì)象的函數(shù)是不同的呢?
想一下,我們使用變量的作用域模擬出私有變量,用閉包模擬出公有變量,那么,也就是說,實(shí)際上每個(gè)創(chuàng)建的對(duì)象都會(huì)有一個(gè)相同的代碼的拷貝!不僅僅是那個(gè)id,就連那些showId、getId 等函數(shù)也會(huì)創(chuàng)建多次。注意,考慮到JavaScript函數(shù)就是對(duì)象,就不會(huì)感到那么奇怪了。
但是毫無疑問,這是一種浪費(fèi):每個(gè)變量所不同的只是自己的數(shù)據(jù)域,函數(shù)代碼都是相同的,因?yàn)槲覀冞M(jìn)行的是同一種操作。其他語言一般不會(huì)遇到這種問題,因?yàn)槟切┱Z言的函數(shù)和對(duì)象的概念是不同的,像Java,每個(gè)對(duì)象的方法其實(shí)指向了同一份代碼的拷貝,而不是每個(gè)對(duì)象都會(huì)有自己的代碼拷貝。
#p#
2. 去看效率
那種封裝雖然優(yōu)雅,但是很浪費(fèi)。好在JavaScript是一種靈活的語言,于是,我們馬上想到,把這些函數(shù)的指針指向另外的一個(gè)函數(shù)不就可以了嗎?
- function show() {
- alert("I'm a person.");
- }
- function Person() {
- this.show = show;
- }
- var p1 = new Person();
- var p2 = new Person();
- alert(p1.show == p2.show); // true
- function show() {
- alert("I'm a person.");
- }
- function Person() {
- this.show = show;
- }
- var p1 = new Person();
- var p2 = new Person();
- alert(p1.show == p2.show); // true
這個(gè)辦法不錯(cuò),解決了我們以前的那個(gè)問題:不同的對(duì)象共享了一份代碼。但是這種實(shí)現(xiàn)雖然有了效率,可是卻太不優(yōu)雅了——如果有很多類,那么豈不是有很多全局函數(shù)?
好在JavaScript中還有一個(gè)機(jī)制:prototype。還記得這個(gè)prototype嗎?每個(gè)對(duì)象都維護(hù)著一個(gè)prototype屬性,這些對(duì)象的prototype屬性是共享的。那么,我們就可以把函數(shù)的定義放到prototype里面,于是,不同的對(duì)象不就共享了一份代碼拷貝嗎?事實(shí)確實(shí)如此:
- function Person() {
- }
- Person.prototype.show = function() {
- alert("I'm a person.");
- }
- var p1 = new Person();
- var p2 = new Person();
- alert(p1.show == p2.show); // true
- function Person() {
- }
- Person.prototype.show = function() {
- alert("I'm a person.");
- }
- var p1 = new Person();
- var p2 = new Person();
- alert(p1.show == p2.show); // true
不過,這種分開定義看上去很別扭,那么好,為什么不把函數(shù)定義也寫到類定義里面呢?
- function Person() {
- Person.prototype.show = function() {
- alert("I'm a person.");
- }
- }
- var p1 = new Person();
- var p2 = new Person();
- alert(p1.show == p2.show); // true
- function Person() {
- Person.prototype.show = function() {
- alert("I'm a person.");
- }
- }
- var p1 = new Person();
- var p2 = new Person();
- alert(p1.show == p2.show); // true
實(shí)際上這種寫法和上面一種沒有什么不同:唯一的區(qū)別就是代碼位置不同。這只是一個(gè)“看上去很甜”的語法糖,并沒有實(shí)質(zhì)性差別。
最初,微軟的.Net AJAX框架使用前面的機(jī)制模擬了私有變量和函數(shù),這種寫法和C#很相像,十分的優(yōu)雅。但是,處于效率的緣故,微軟后來把它改成了這種原型的定義方式。雖然這種方式不那么優(yōu)雅,但是很有效率。
在JavaScript中,這種封裝的優(yōu)雅和執(zhí)行的效率之間的矛盾一直存在?,F(xiàn)在我們***的解決方案就是把數(shù)據(jù)定義在類里面,函數(shù)定義在類的prototype屬性里面。
原文鏈接:http://devbean.javaeye.com/blog/412296
【編輯推薦】