它的出生,并不高貴。
1995年,Web尚處于嬰兒期,絕大數(shù)計算機(jī)用戶還不能告訴你網(wǎng)站到底為何物,而且在當(dāng)時開發(fā)一個網(wǎng)站也是很困難的一件事。微軟當(dāng)時也是剛剛意識到互聯(lián)網(wǎng)的重要性,Google在當(dāng)時也只是在Little Rascals中杜撰的一詞。
1996年早期,JavaScript創(chuàng)建不久,被提交到ECMA進(jìn)行標(biāo)準(zhǔn)化。它涵蓋了JavaScript的核心語法和DOM 0級的一個子集,并且大多數(shù)瀏覽器是以某種形式實現(xiàn)這個規(guī)范。我們很少聽到有人說ECMAScript,主要是因為它的名字太長了,不方便記憶,但是它和JavaScript是一回事。
其實JavaScript成功的原因很簡單,你不需要擔(dān)心數(shù)據(jù)類型,只要用var
就可以聲明一個變量了,往往優(yōu)勢的背后總是隱藏著劣勢,在過去,沒有人把它當(dāng)作面向?qū)ο蟮模悴挥每紤]層級之間的關(guān)系,可以很快的上手,門檻很低,這就導(dǎo)致了問題的出現(xiàn)。
不幸的是,JavaScript的童年是沒有掌聲和鮮花的,活的很委屈。大量的高危安全嚴(yán)重?fù)p害了它早期的名望。更重要的是大多程序員對它的態(tài)度都是消極,認(rèn)為JavaScript缺乏開發(fā)工具,離開了瀏覽器就不能開發(fā)了,在他們眼中就是一個"腳本玩具",JavaScript在編程世界里面就想一只“丑小鴨”。
“對,人們對JavaScript的批評一點也沒有錯,它開始認(rèn)識到自己的缺點,并且它決心要不斷的變好。”
故事結(jié)束,其實JavaScript的遭遇真的比我們有些人還要慘。
一段老式的瀏覽器嗅探程例
function Redirect(){
var WhatBrowser;
var WhatVersion;
//瀏覽器的版本
WhatVersion=navigator.appVersion.toUpperCase();
//瀏覽器的類型
WhatBrowser=navigator.appName.toUpperCase();
if(WhatBrowser.indexOf("MICROSOFT")>=0){
if(WhatVersion.indexOf("3")>=0){
console.log("MICROSOFT3");
}else{
console.log("MICROSOFT");
}
}
if(WhatBrowser.indexOf("NETSCAPE")>=0){
if (WhatVersion.indexOf("2")) {
console.log("NETSCAPE2")
}else{
console.log("NETSCAPE");
}
}
}
這種嗅探瀏覽器版本的代碼流行了很長一段時間。也就意味著,對于不同對的瀏覽器,你需要采取不同的策略,我們需要做兩個版本,一個是為IE設(shè)計的,另一個是為NETSCAPE設(shè)計的,這顯然是兼容的好方法。但在很長的時間它確實是唯一的方法。
JavaScript可以很慢,對,沒錯,直至今日,你也可以很容易寫出性能很差的代碼。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>性能很差的JavaScript舉例</title>
<script type="text/javascript">
function badTest(){
var startTime = new Date().valueOf();
var s ="";
for(var i=0;i<10000000;i++){
s+="This is a test string";
}
return new Date().valueOf() - startTime;
}
function goodTest(){
var startTime = new Date().valueOf();
var stringBuffer = new Array();
for(var i=0;i<10000000;i++){
stringBuffer.push("This is a test string");
}
var s = stringBuffer.join("");
return new Date().valueOf() - startTime;
}
function betterTest(){
var startTime = new Date().valueOf();
var stringBuffer = new Array();
for(var i=0;i<10000000;i++){
stringBuffer[stringBuffer.length]="This is a test string";
}
var s = stringBuffer.join("");
return new Date().valueOf() - startTime;
}
function doTests(){
var htm="";
htm+="Time badTest took:"+badTest()+"<br>";
htm+="Time goodTest took:"+goodTest()+"<br>";
htm+="Time betterTest took:"+betterTest()+"<br>";
document.getElementById("result").innerHTML=htm;
}
</script>
</head>
<body>
<a href="javascript:void(0);" onClick="doTests();">Click here to test</a>
<div id="result"></div>
<body>
</html>
在IE中的結(jié)果是:
從數(shù)字上大家應(yīng)該有個很直觀的感受,也就是使用不同的方法去實現(xiàn)同樣的一種功能,所花的時間上面是有明顯的差別的。這里我使用的瀏覽器已經(jīng)是比較新的版本了,想想以前不同代碼之間所花時間的差距,自己腦補(bǔ)。
萬惡的根源:開發(fā)者!
與語言本身需要進(jìn)化一樣,開發(fā)者同樣需要不斷進(jìn)化。他們需要知道什么好用什么不好用,并且開發(fā)人員需要自己努力去尋找比較好的方法。如果你正在使用Java,那么很有確切地知道使用字符串拼接是件壞事。而字符串緩沖區(qū)是你的好朋友。但是在JavaScript中,沒有字符緩沖區(qū)。
不夠高效的編碼示例
function Person1(firstName,lastName){
this.firstName = firstName;
this.lastName=lastName;
this.toString=function(){
return this.firstName+" "+this.lastName;
}
}
function Person2(attrs){
this.firstName=attrs["firstName"];
this.lastName=attrs["lastName"];
this.toString=function(){
return this.firstName+" "+this.lastName;
}
}
function showPerson(){
var p1 = new Person1("Frank","Zammetti");
var p2 = new Person2({"firstName":"Frank","lastName":"Zammetti"});
document.getElementById("divPerson").innerHTML =p1+"<br>" +p2;
}
這里我們有倆個不同的函數(shù)來表示人:person1和person2,Person1的構(gòu)造函數(shù)接受兩個參數(shù),firstName和lastName。Person2接受一個單獨(dú)的數(shù)組參數(shù)attrs,是屬性的數(shù)組。showPerson函數(shù)創(chuàng)建了兩個同樣的人。一個使用Person1,另一個使用Person2,當(dāng)我們想添加別的屬性來描述一個人會發(fā)生什么呢?對于Person1 ,我們?yōu)闃?gòu)造函數(shù)增加更多的參數(shù)。對于Person2,只需要添加適當(dāng)?shù)淖侄卧O(shè)置代碼,這里的差別就在于程序的可擴(kuò)展性問題。其實person1不僅可擴(kuò)展性差,可讀性也差;通過調(diào)用var p1 = new Person1("Frank","Zammetti");你知道frank,Zammetti這個字符串什么含義嗎?而person2在調(diào)用構(gòu)造函數(shù)你便可以知道frank是這個人的名,zammetti是這個人的型。
JavaScript缺乏塊級作用域的示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript沒有塊級作用域</title>
<script type="text/javascript">
function test(){
var i=1;
if(1){
var i=2;
if(1){
var i=3;
//網(wǎng)頁中彈窗
alert(i);
}
alert(i);
}alert(i);
}
</script>
</head>
<body onLoad="test();">
//正常如果有塊級作用域的編程語言,結(jié)果應(yīng)該321,而對于JavaScript,結(jié)果是333
</body>
</html>
上面所有的一切,確實只是沒有經(jīng)驗的開發(fā)人員會干的事情,因為他們還不知道如何做得更好,這并不是JavaScript的錯。這個鍋,它不背。
不斷的進(jìn)化:接近可用性
在第一批相對沒有經(jīng)驗的開發(fā)人員使用JavaScript之后,新一輪開始隱現(xiàn),人們認(rèn)識到一些普遍存在的錯誤并開始修正。
最重要的是,有經(jīng)驗的開發(fā)人員已經(jīng)看到JavaScript的能力并開始用自己聰明才智來改造它,正是因為這樣的人,才有了今天的JavaScript。
編寫代碼的好習(xí)慣,增加代碼的可讀性
- 寫代碼需要有縮進(jìn)
- 參數(shù)名稱要具有描述性
- 引號要使用一致,不要一會雙引號,一會單引號
- 結(jié)尾最好加上分號
- 統(tǒng)一使用花括號,可能代碼只有一行
- 函數(shù)的名字需要賦予實際意義
- 在函數(shù)前后需要加上注釋
- 注意轉(zhuǎn)義字符和拼寫
另外一個重要的概念是外部的JavaScript文件,也就是說,不要把結(jié)構(gòu),樣式,邏輯分開,有自己單獨(dú)的文件,不要全部嵌套在一起,分開便于后期的修改和擴(kuò)展,也便于代碼的重復(fù)使用。
專業(yè)的JavaScript
在過去的幾年里,主流的瀏覽器已經(jīng)達(dá)到共識,相互之間基本上都能兼容JavaScript,所以現(xiàn)在很少寫分支代碼。你現(xiàn)在可能會發(fā)現(xiàn)的問題其實與JavaScript無關(guān),那都是Dom惹的禍。
假設(shè)我們想在文檔上掛一個KeyDown的鉤子。我們可以這做
document.onkeydown=KeyDown;
但在Firefox中,你必須這么做:document.captureEvent(Event.KEYDOWN);
兩個瀏覽器基本的keydown()函數(shù)的原型都是:function keyDown(e){};
看一個更為復(fù)雜的例子:
document.onkeydown=KeyDown;
if(document.layers){
document.captureEvent(Event.KEYDOWN);
}
function keyDown(e){
var ev = (e)?e:(window.event)?window.event:null;
if(ev){
return (ev.charCode)?ev.charCode:
((ev.keyCode)?ev.keyCode:((ev.which)?ev.which:null));
}
return -1;
}
這段代碼兼容任何瀏覽器。第二行的第一個if只在非IE瀏覽器中為true,那些瀏覽器的document對象會有l(wèi)ayers屬性。這個時候,調(diào)用captureEvent();然后再keyDown()的內(nèi)部,第一行會把ev設(shè)置為傳入的參數(shù)e。如果document.layers屬性不存在,則使用window.event。但是如果尤銘設(shè)置window.event,那么則ev設(shè)置為null。然后如果設(shè)置了ev,則找出鍵值:分別看看里面對象是charCode還是keyCode,如果ev是null,則返回-1;
面向?qū)ο蟮腏avaScript
大多數(shù)程序員的生活從發(fā)現(xiàn)原型(prototype)那天開始就改變了。一旦他們發(fā)現(xiàn)所有JavaScript對象都可以通過原型來擴(kuò)展,并且這個功能允許他們創(chuàng)建自定義的類,那么一切都變了。
var answer = 0;
function addNum(num1,num2){
answer =num2+ num1;
}
function subNum(num1,num2){
answer =num1 - num2;
}
function multiNum(num1,num2){
answer=num1 *num2;
}
function divideNum(num1,num2){
if(num2){
answer=num1/num2;
}else{
answer=0;
}
}
這段代碼,從語法和功能上來說,沒有任何問題,無非就是進(jìn)行一些簡單的算術(shù)運(yùn)算。但是它組織的夠好嗎?我看不怎么樣。在全局作用域里answer變量代碼味太濃,每個函數(shù)都是如此,都是孤立的函數(shù),都是全局作用域里面。我們要避免全局污染。在任何編程中,使用全局變量會被認(rèn)為是一個壞習(xí)慣,因為不在全局變量范圍內(nèi),我們就可以隨意的去修改它。缺乏結(jié)構(gòu)意味著函數(shù)與函數(shù)之間沒有結(jié)構(gòu)關(guān)系,并且沒有邏輯的分組幫助人們從更高級別去理解它。
快來使用面向?qū)ο蟮乃枷氚桑?/p>
面向?qū)ο螅訉I(yè)
function NumberFunctions(){
var answer = 0;
}
NumberFunctions.prototype.addNumbers = function (num1,num2){
this.answer =num2+ num1;
}
NumberFunctions.prototype.subtractNumbers = function (num1,num2){
this.answer =num1 - num2;
}
NumberFunctions.prototype.multiplyNumbers = function (num1,num2){
this.answer=num1 *num2;
}
NumberFunctions.prototype.divideNumbers = function (num1,num2){
if(num2){
this.answer=num1/num2;
}else{
this.answer=0;
}
}
NumberFunctions.prototype.toString =function(){
return this.answer;
}
測試代碼
// 使用構(gòu)造函數(shù)與創(chuàng)建對象
var nf = new NumberFunctions();
//調(diào)用這個對象的方法
nf.addNumbers(2,1);
//通過控制來查看結(jié)果
console.log(nf):
//同理
nf.multiplyNumbers(4,5);
console.log(nf);
結(jié)果:
這樣寫的優(yōu)點
- 沒有對全局變量的污染
- 所有函數(shù)實際上都是NumberFunctions類的成員,因此構(gòu)造了一個清晰的關(guān)系
- 基本的面向?qū)ο螅簲?shù)據(jù)和操作的函數(shù)都封裝的很好
這里再提一個概念“柔性衰減”(graceful degradation):它是針對某個版本的瀏覽器設(shè)計的網(wǎng)頁應(yīng)該對舊的瀏覽器采取柔性衰減的策略:即使是不夠好的,也至少能用。同樣的概念適用在JavaScript中。
--------------------End--------------------
我就是番茄,愛生活,愛學(xué)習(xí),愛寵物的家伙!
如果覺得寫的還行,記得打賞哦!