-
<script>元素位置
一般慣例是在<head>元素中包含所有的<script>元素,但是這就意味著必須等到全部的javascript代碼都被下載、解析和執(zhí)行完成后,才開始呈現(xiàn)頁(yè)面部分:
<!DOCTYPE html>
<html>
<head>
<script></script>
</head>
<body></body>
</html>
為了避免這個(gè)問題,現(xiàn)代Web應(yīng)用程序一般把全部<scripy>元素放在<body>內(nèi),且放在頁(yè)面的內(nèi)容后面:
<!DOCTYPE html>
<html>
<head></head>
<body>
//
<script></script>
</body>
</html>
但是也會(huì)看到把 <script>元素放在 </body>標(biāo)簽之后 </html> 標(biāo)簽之前的。這么寫的愿景是頁(yè)面內(nèi)容加載完再加載js?
按照HTML5標(biāo)準(zhǔn)中的HTML語(yǔ)法規(guī)則,如果在</body>后再出現(xiàn)<script>或任何元素的開始標(biāo)簽,都是parse error,瀏覽器會(huì)忽略之前的</body>,即視作仍舊在body內(nèi)。所以實(shí)際效果和寫在</body>之前是沒有區(qū)別的。總之,這種寫法雖然也能work,但是并沒有帶來(lái)任何額外好處,實(shí)際上出現(xiàn)這樣的寫法很可能是誤解了“將script放在頁(yè)面最末端”的教條。所以還是不要這樣寫為好。雖然將<script>寫在</body>之后,但最終的DOM樹里,<script>元素還是會(huì)成為body的子節(jié)點(diǎn),這一點(diǎn)很容易在firebug等調(diào)試器里驗(yàn)證。
-
延遲腳本
<script>元素的
defer屬性。
這個(gè)屬性的用途是標(biāo)明腳本在執(zhí)行時(shí)不會(huì)影響頁(yè)面的構(gòu)造,也就是說(shuō),腳本會(huì)被延遲到整個(gè)頁(yè)面都解析完畢后再運(yùn)行。因此,在<script>元素中設(shè)置defer屬性,相當(dāng)于告訴瀏覽器立即下載,但延遲執(zhí)行。
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" defer="defer" scr="path/jsname.js"></script>
</head>
<body></body>
</html>
defer屬性只適用于外部腳本文件,會(huì)忽略嵌入腳本設(shè)置的defer屬性。
-
異步腳本
html5為<script>元素定義了
async屬性。與defer類似的是,都用于改變處理腳本的行為,且也只適用于外部腳本。
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" async="async" scr="path/jsname1.js"></script>
<script type="text/javascript" async="async" scr="path/jsname2.js"></script>
</head>
<body></body>
</html>
上面代碼中,第二個(gè)腳本可能會(huì)先于第一個(gè)腳本執(zhí)行,因此,使用了async就要確保腳本間互不依賴。
指定async屬性的目的是不讓頁(yè)面等待兩個(gè)腳本下載和執(zhí)行,從而異步加載頁(yè)面其他內(nèi)容,所以也建議不要在加載期間修改DOM。
異步腳本一定會(huì)在頁(yè)面的load事件前執(zhí)行,但可能會(huì)在DOMContentLoad事件觸發(fā)之前或之后執(zhí)行。
-
重排序方法
兩個(gè)可以直接使用的重排序方法:
- reverse():反轉(zhuǎn)數(shù)組項(xiàng)的順序;
- sort():默認(rèn)情況下按升序排列數(shù)組項(xiàng)。但是為了實(shí)現(xiàn)排序,sort()方法會(huì)調(diào)用每個(gè)數(shù)組項(xiàng)的toString()方法,然后比較得到的字符串。所以,即使數(shù)組中的每一項(xiàng)都是數(shù)值,該方法比較的也是字符串。
var values=[0, 1, 5, 10, 15];
values.sort();
console.log(values); //0, 1, 10, 15, 5
所以sort()方法可以接收一個(gè)比較函數(shù)作為參數(shù),一邊自定義比較方法。該標(biāo)膠函數(shù)要接收兩個(gè)參數(shù),如果要求第一個(gè)參數(shù)應(yīng)該位于第二個(gè)參數(shù)之前則返回一個(gè)負(fù)數(shù),如果兩個(gè)參數(shù)相等則返回0,如果第一個(gè)參數(shù)應(yīng)該位于第二個(gè)參數(shù)之后則返回一個(gè)正數(shù)。
function compare(value1, value2){
if(value1 < value2) return -1;
else if(value1 > value2) return 1;
else return 0;
}
var values=[0, 1, 5, 10, 15];
values.sort(compare);
console.log(values); //0, 1 , 5, 10, 15
//所以compare也可以這樣實(shí)現(xiàn):
function compare(value1, value2){
return value1-value2;
}
-
基本包裝
為了便于操作基本數(shù)據(jù)類型,ECMAScript提供了3個(gè)特殊的引用類型:Boolean,Number,String。每當(dāng)讀取一個(gè)基本類型值的時(shí)候,后臺(tái)就會(huì)創(chuàng)建一個(gè)對(duì)應(yīng)的基本包裝類型的對(duì)象,從而能夠讓我們調(diào)用一些方法來(lái)對(duì)這些基本數(shù)據(jù)進(jìn)行操作。
var s1 = "some example";
var s2 = s1.substring(2);
上例中s1是一個(gè)字符串基本類型,但是第二行卻調(diào)用了一個(gè)方法,我們知道基本類型值不是對(duì)象,邏輯上是不該有自己的方法的。其實(shí),系統(tǒng)為了實(shí)現(xiàn)該操作,在后臺(tái)自動(dòng)完成了一系列的處理。在讀取模式中訪問字符串時(shí),后臺(tái)都會(huì)自動(dòng)完成下列處理:
- 創(chuàng)建String類型的一個(gè)實(shí)例;
- 在實(shí)例上調(diào)用指定的方法;
- 銷毀這個(gè)實(shí)例。
所以,上面的兩行實(shí)際是這樣執(zhí)行的:
var s1="some example";
var s3=new String(s1);
var s2=s3.substring(2);
s3=null;
console.log(s2); //me example
經(jīng)過(guò)此番處理,基本的字符串值就變得跟對(duì)象一樣了。而且,上面三個(gè)步驟也適用于Boolean和Number。
引用類型與基本包裝類型的主要區(qū)別就是對(duì)象的生存期。使用new操作符創(chuàng)建的引用類型的實(shí)例,在執(zhí)行流離開當(dāng)前作用域之前都一直保存在內(nèi)存中。而自動(dòng)創(chuàng)建的基本包裝類型的對(duì)象,則只存在于一行代碼的執(zhí)行瞬間,而后立即被銷毀,這也意味著我們不能在運(yùn)行時(shí)為基本類型值添加屬性和方法。
var s1 = "some example";
s1.color = "red";
console.log(s1.color); //undefined
console.log(s1); some example
var ss=new String("some example");
ss.color="red";
console.log(ss); //String {0: "s", 1: "o", 2: "m", 3: "e", 4: " ", 5: "e", 6: "x", 7: "a", 8: "m", 9: "p", 10: "l", 11: "e", color: "red", length: 12, [[PrimitiveValue]]: "some example"}
-
Boolean
他們的建議是永遠(yuǎn)不要使用Boolean對(duì)象。
var falseObject = new Boolean(false);
var result = falseObject && true;
console.log(result); // true
//---
var falseValue = false;
var result = falseValue && true;
console.log(result); //false
出現(xiàn)上面這個(gè)差異的原因是:代碼中是對(duì)falseObject而不是對(duì)它的值(false)進(jìn)行求值。布爾表達(dá)式中的所有對(duì)象都會(huì)被轉(zhuǎn)換為true,因此falseObject對(duì)象在布爾表達(dá)式中代表的是true而不是它的初值false。
-
Function
- 函數(shù)內(nèi)部屬性
函數(shù)內(nèi)兩個(gè)特殊對(duì)象:
arguments和this。
arguments的主要用途是保存函數(shù)參數(shù),但是它還有一個(gè)重要屬性callee,該屬性是一個(gè)指針,指向擁有這個(gè)arguments對(duì)象的函數(shù)。
//例如階乘函數(shù)
function factorial(num){
if(num<=1) return 1;
else{return num*factorial(num-1);}
}
//使用arguments.callee
function factorial(num){
if(num<=1) return 1;
else{return num*arguments.callee(num-1);}
}
上面的函數(shù)在函數(shù)有名字,且函數(shù)名字不變的情況下是沒問題的。但是這個(gè)函數(shù)的執(zhí)行與函數(shù)名factorial緊緊耦合在一起,為了消除這種耦合,使用arguments.callee。
////////////////////////////////////////////////////////
另一個(gè)函數(shù)對(duì)象的屬性:caller。這個(gè)屬性中保存著調(diào)用當(dāng)前函數(shù)的函數(shù)的引用。如果是在全局作用域中調(diào)用當(dāng)前函數(shù),它的值為null。
function outer(){
inner();
}
function inner(){
console.log(inner.caller);
}
outer(); //輸出outer的源代碼,因?yàn)槭莖uter調(diào)用的inner。所以為了去耦合也可以通過(guò)arguments.callee.caller來(lái)實(shí)現(xiàn)。
- 函數(shù)屬性和方法
每個(gè)函數(shù)都包含兩個(gè)屬性:length,prototype。
length表示函數(shù)希望接收的命名參數(shù)的個(gè)數(shù)。
prototype保存它們所有實(shí)例方法的真正所在。
- apply() 與 call()
這兩個(gè)方法的用途都是在特定的作用域中調(diào)用函數(shù),實(shí)際上等于設(shè)置函數(shù)體內(nèi)的this對(duì)象的值。
apply()方法接收兩個(gè)參數(shù):一個(gè)是在其中運(yùn)行函數(shù)的作用域,另一個(gè)是參數(shù)數(shù)組,可以是Array的實(shí)例也可以是arguments對(duì)象。
call()與apply()的區(qū)別在參數(shù)不同:第一個(gè)也是this,而后面的參數(shù)是逐個(gè)列舉傳遞。
事實(shí)上,apply()和call()最大的用武之地,真正強(qiáng)大的地方是能擴(kuò)充函數(shù)賴以運(yùn)行的作用域(類似C++的多態(tài)?):
window.color = "red";
var object = {"color":"blue"};
function sayColor(){
alert(this.color);
}
sayColor(); //red
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(object); //blue
使用call()或apply()來(lái)擴(kuò)充作用域的好處,就是對(duì)象與方法不需要有任何耦合關(guān)系。
- bind
這個(gè)方法會(huì)創(chuàng)建一個(gè)函數(shù)的實(shí)例,其this值會(huì)被綁定到傳給bind()函數(shù)的值(類似C++多態(tài)預(yù)編譯時(shí)多態(tài)?)
window.color = "red";
var object = {"color": "blue"};
function sayColor(){
alert(this.color);
}
var objectSayColor=sayColor.bind(object);//方法綁定到對(duì)象,并返回一個(gè)函數(shù)實(shí)例
objectSayColor(); //blue
-
創(chuàng)建對(duì)象
- 工廠模式
工廠模式就是可批量生產(chǎn)嘍,就是以函數(shù)的方式創(chuàng)建對(duì)象,用函數(shù)來(lái)封裝特定接口創(chuàng)建對(duì)象的細(xì)節(jié):
function createPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var persion1 = createPerson("Greg",27,"Doctor");
但是該方式?jīng)]有解決對(duì)象識(shí)別問題(怎樣獲得一個(gè)對(duì)象的類型)。
- 構(gòu)造函數(shù)模式
創(chuàng)建自定義的構(gòu)造函數(shù)意味著可以將它的實(shí)例標(biāo)識(shí)為一種特定的類型,而這正是構(gòu)造函數(shù)模式勝過(guò)工廠模式的地方。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName= function(){
alert(this.name);
};
}
var person1 = new Person("Greg",27,"Doctor");
與工廠模式相似,但是明顯不同。
創(chuàng)建的對(duì)象person1隱含一個(gè)constructor屬性,該屬性指向Person:
alert(person1.constructor == Person);//true
但是,檢測(cè)對(duì)象類型還是instanceof操作符更可靠一些。
alert(person1 instanceof Object);//true
alert(person1 instanceof Person);//true
- 構(gòu)造函數(shù)與普通函數(shù)
兩者的區(qū)別就是在調(diào)用是產(chǎn)生的,否則構(gòu)造函數(shù)就是普通函數(shù),普通函數(shù)也可作為構(gòu)造函數(shù)調(diào)用:
//構(gòu)造
var person = new Person("Greg",27,"Doctor");
person.sayName();//Greg
//普通函數(shù)調(diào)用
Person("Greg",27,"Doctor");
window.sayName();//Greg
//在另一個(gè)對(duì)象的作用域中調(diào)用
var o = new Object();
Person.call(o,"Greg",27,"Doctor");
o.sayName();//Greg
- 原型模式
我們創(chuàng)建的每一個(gè)函數(shù)都有一個(gè)prototype(原型)屬性,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象。
而這個(gè)對(duì)象的用途是用來(lái)包含所有實(shí)例共享的屬性和方法。
使用原型對(duì)象的好處就是讓所有對(duì)象實(shí)例共享它所包含的屬性和方法。
同時(shí),這也是它最大的缺點(diǎn)。
function Persion(){}
Person.prototype.name = "Greg";
Person.prototype.age = 27;
Person.prototype.sayName = function(){alert(this.name);};
var person1 = new Person();
person1.sayName();//Greg
var person2 = new Person();
person2.sayName();//Greg
alert(person1.sayName == person2.sayName);//true,共享所有屬性和方法
理解原型對(duì)象
創(chuàng)建一個(gè)新函數(shù),就會(huì)創(chuàng)建一個(gè)prototype屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。
默認(rèn)情況下所有原型對(duì)象都會(huì)獲得夜歌constructor屬性,這個(gè)屬性包含一個(gè)指向prototype屬性所在函數(shù)的指針。
以前例來(lái)說(shuō):
Person.prototype.constructor指向Person。
當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例,該實(shí)例內(nèi)部將會(huì)包含一個(gè)指針,指向構(gòu)造函數(shù)的原型對(duì)象,ECMA5管這個(gè)指針叫[[Prototype]],在瀏覽器控制臺(tái)看到的是proto。
要明確的一點(diǎn)是,這個(gè)連接存在于實(shí)例與構(gòu)造函數(shù)的原型對(duì)象之間,而不是存在于實(shí)例與構(gòu)造函數(shù)之間。
圖中,展示了Person構(gòu)造函數(shù),Person的原型屬性以及Person現(xiàn)有兩個(gè)實(shí)例之間的關(guān)系。
Person.prototype指向原型對(duì)象,
Person.prototype.constructor指回Person,
原型對(duì)象中包含constructor及后來(lái)添加的其他屬性,
Person的每一個(gè)實(shí)例都包含一個(gè)內(nèi)部屬性,該屬性僅僅指向Person.prototype。
isPrototypeOf
確定對(duì)象實(shí)例是否來(lái)自某個(gè)對(duì)象原型
alert(Person.prototype.isPrototypeOf(person1));//true
Object.getPrototypeOf()
返回對(duì)象實(shí)例的[[Prototype]]值。
alert(Object.getPrototypeOf(person1) == Person.prototype);//true
alert(Object.getPrototypeOf(person1).name);//Greg
hasOwnPrototype()
這里涉及到讀取對(duì)象屬性時(shí)的搜索順序,
搜索先從對(duì)象實(shí)例本身開始,如果實(shí)例中存在就返回該屬性的值,
否則繼續(xù)向上搜索指針指向的原型對(duì)象。
所以,如果我們?cè)趯?shí)例中重寫覆蓋了原型中的值,那么返回的就是新值,而非共享的屬性值。
person1.name = "Nicholas";
alert(person1.name);//Nicholas 來(lái)自實(shí)例
alert(person2.name);//Greg 來(lái)自原型
所以,實(shí)例中重寫的值只是屏蔽對(duì)原型中同名屬性的訪問,而不是修改原型中的那個(gè)屬性。
如要改回,使用delete操作符就可以完全刪除實(shí)例中的同名屬性。
使用hasOwnProperty()方法可以檢測(cè)一個(gè)屬性是存在于實(shí)例中還是原型中,
這個(gè)方法只在給定屬性存在于對(duì)象實(shí)例中時(shí)才會(huì)返回true。
alert(person2.hasOwnProperty("name"));//false 實(shí)例中不存在
alert(person1.hasOwnProperty("name"));//true 實(shí)例中存在
delete person1.name;
alert(person1.hasOwnProperty("name"));//false 實(shí)例中不存在
通過(guò)該方法就能確定什么時(shí)候訪問的是示例屬性,什么時(shí)候是原型屬性。
原型與in操作符
in操作符會(huì)在能夠通過(guò)對(duì)象訪問給定屬性時(shí)返回true,無(wú)論該屬性存在于實(shí)例中還是原型中,
alert("name" in person1);//true
所以,結(jié)合hasOwnProperty()就能判讀屬性存在的位置。
Object.keys()
獲取對(duì)象上所有可枚舉的實(shí)力屬性
參數(shù)為對(duì)象,返回一個(gè)包含所有可枚舉屬性的字符串?dāng)?shù)組。
var keys = Object.keys(Person.prototype);//["name","age","sayName"],因?yàn)閏onstructor的Enumrable默認(rèn)為false
person1.name = "Bob";
var keys1 = Object.keys(person1);//["name"] 只輸出實(shí)例的
getOwnPrototypeNames()
返回所有實(shí)例屬性,無(wú)論是否可枚舉。
- 更常用的原型語(yǔ)法
從原型prototype的定義可以知道,prototype下是一個(gè)對(duì)象。
所以:
function Person(){}
Person.prototype = {
name :"Greg",
age : 27,
sayName : function(){alert(this.name);}
};
但是這樣又出現(xiàn)一個(gè)問題,
這樣定義后,constructor不再指向Person了,
因?yàn)檫@種定義方法本質(zhì)上是對(duì)原prototype的完全重寫(所以,對(duì)原型完全重寫后的原型就是另一個(gè)原型了),而創(chuàng)建新的prototype對(duì)象,這個(gè)對(duì)象也會(huì)自動(dòng)重新獲得constructor,所以新的consytructor就不指回原Person了,
var friend = new Person();
alert(friend instanceof Object);//true
alert(friend instanceof Person);//true
alert(friend.constructor == Person );//false
解決方法,
如果constructor的值很重要,就重新指定回原值
Person.prototype = {
constructor:Person,
........
........
}
- 原型的動(dòng)態(tài)性
重寫整個(gè)原型,就會(huì)切斷構(gòu)造函數(shù)與最初原型之間的聯(lián)系,這個(gè)上面有提到。
- 原型對(duì)象的問題
開始有提到,原型中所有的屬性是被實(shí)例共享的,
這種共享對(duì)函數(shù)很適合,
對(duì)于那些包含基本值的屬性也可以,同名屬性屏蔽,
但是對(duì)于引用類型值的屬性,問題就突出了:
function Person(){}
Person.prototype = {
name = "Bob",
age = 27,
friends: ["Sheldy", "Court"]
};
var person1 = new Person();
var person2 = new Person();
person1.friend.push("Vans");
alert(person1.friends);//Sheldy,Court,Vans
alert(person2.friends);//Sheldy,Court,Vans
alert(person1.friends == person2.friends);//true
- 組合使用構(gòu)造函數(shù)模式和原型模式
解決上面遇到的問題
同時(shí),也是創(chuàng)建自定義類型的最常用方式。
構(gòu)造函數(shù)模式來(lái)定義示例的非共享屬性,
原型模式來(lái)定義方法和共享的屬性。
結(jié)果,每個(gè)實(shí)例都會(huì)有自己的一份實(shí)例屬性的副本,
同時(shí)又共享著對(duì)方法的引用,最大限度的節(jié)省了內(nèi)存。
而且,這種混合模式還支持向構(gòu)造函數(shù)傳遞參數(shù)。
function Person(name,age,friends){
this.name = name;
this.age = age;
this,friends = friends;
}
Person.prototype = {
constructor: Person,
sayName:function(){alert(this.name);}
}
-
繼承
- js只支持實(shí)現(xiàn)繼承,而且主要是依靠原型鏈實(shí)現(xiàn)。
- 原型鏈
- 先跳過(guò)