-
<script>元素位置
一般慣例是在<head>元素中包含所有的<script>元素,但是這就意味著必須等到全部的javascript代碼都被下載叔收、解析和執(zhí)行完成后齿穗,才開始呈現(xiàn)頁面部分:
<!DOCTYPE html>
<html>
<head>
<script></script>
</head>
<body></body>
</html>
為了避免這個(gè)問題,現(xiàn)代Web應(yīng)用程序一般把全部<scripy>元素放在<body>內(nèi)饺律,且放在頁面的內(nèi)容后面:
<!DOCTYPE html>
<html>
<head></head>
<body>
//
<script></script>
</body>
</html>
但是也會(huì)看到把 <script>元素放在 </body>標(biāo)簽之后 </html> 標(biāo)簽之前的窃页。這么寫的愿景是頁面內(nèi)容加載完再加載js?
按照HTML5標(biāo)準(zhǔn)中的HTML語法規(guī)則复濒,如果在</body>后再出現(xiàn)<script>或任何元素的開始標(biāo)簽脖卖,都是parse error,瀏覽器會(huì)忽略之前的</body>巧颈,即視作仍舊在body內(nèi)畦木。所以實(shí)際效果和寫在</body>之前是沒有區(qū)別的≡曳海總之十籍,這種寫法雖然也能work,但是并沒有帶來任何額外好處唇礁,實(shí)際上出現(xiàn)這樣的寫法很可能是誤解了“將script放在頁面最末端”的教條勾栗。所以還是不要這樣寫為好。雖然將<script>寫在</body>之后盏筐,但最終的DOM樹里围俘,<script>元素還是會(huì)成為body的子節(jié)點(diǎn),這一點(diǎn)很容易在firebug等調(diào)試器里驗(yàn)證。
-
延遲腳本
<script>元素的
defer
屬性界牡。
這個(gè)屬性的用途是標(biāo)明腳本在執(zhí)行時(shí)不會(huì)影響頁面的構(gòu)造簿寂,也就是說,腳本會(huì)被延遲到整個(gè)頁面都解析完畢后再運(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
屬性的目的是不讓頁面等待兩個(gè)腳本下載和執(zhí)行嘹锁,從而異步加載頁面其他內(nèi)容,所以也建議不要在加載期間修改DOM着裹。
異步腳本一定會(huì)在頁面的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è)對應(yīng)的基本包裝類型的對象掀抹,從而能夠讓我們調(diào)用一些方法來對這些基本數(shù)據(jù)進(jìn)行操作虐拓。
var s1 = "some example";
var s2 = s1.substring(2);
上例中s1是一個(gè)字符串基本類型,但是第二行卻調(diào)用了一個(gè)方法傲武,我們知道基本類型值不是對象蓉驹,邏輯上是不該有自己的方法的。其實(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)過此番處理,基本的字符串值就變得跟對象一樣了得院。而且楚午,上面三個(gè)步驟也適用于Boolean和Number。
引用類型與基本包裝類型的主要區(qū)別就是對象的生存期尿招。使用new操作符創(chuàng)建的引用類型的實(shí)例矾柜,在執(zhí)行流離開當(dāng)前作用域之前都一直保存在內(nèi)存中。而自動(dòng)創(chuàng)建的基本包裝類型的對象就谜,則只存在于一行代碼的執(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對象缆瓣。
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è)差異的原因是:代碼中是對falseObject而不是對它的值(false)進(jìn)行求值。布爾表達(dá)式中的所有對象都會(huì)被轉(zhuǎn)換為true虹统,因此falseObject對象在布爾表達(dá)式中代表的是true而不是它的初值false弓坞。
-
Function
- 函數(shù)內(nèi)部屬性
函數(shù)內(nèi)兩個(gè)特殊對象:
arguments
和this
。
arguments
的主要用途是保存函數(shù)參數(shù)车荔,但是它還有一個(gè)重要屬性callee
渡冻,該屬性是一個(gè)指針,指向擁有這個(gè)arguments
對象的函數(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ù)對象的屬性: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蜓谋。所以為了去耦合也可以通過arguments.callee.caller來實(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對象的值退子。
apply()方法接收兩個(gè)參數(shù):一個(gè)是在其中運(yùn)行函數(shù)的作用域岖妄,另一個(gè)是參數(shù)數(shù)組,可以是Array的實(shí)例也可以是arguments對象寂祥。
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()來擴(kuò)充作用域的好處惜犀,就是對象與方法不需要有任何耦合關(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);//方法綁定到對象汽烦,并返回一個(gè)函數(shù)實(shí)例
objectSayColor(); //blue
-
創(chuàng)建對象
- 工廠模式
工廠模式就是可批量生產(chǎn)嘍,就是以函數(shù)的方式創(chuàng)建對象莉御,用函數(shù)來封裝特定接口創(chuàng)建對象的細(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)]有解決對象識別問題(怎樣獲得一個(gè)對象的類型)撇吞。
- 構(gòu)造函數(shù)模式
創(chuàng)建自定義的構(gòu)造函數(shù)意味著可以將它的實(shí)例標(biāo)識為一種特定的類型,而這正是構(gòu)造函數(shù)模式勝過工廠模式的地方礁叔。
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)建的對象person1隱含一個(gè)constructor屬性琅关,該屬性指向Person:
alert(person1.constructor == Person);//true
但是颂砸,檢測對象類型還是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è)對象的作用域中調(diào)用
var o = new Object();
Person.call(o,"Greg",27,"Doctor");
o.sayName();//Greg
- 原型模式
我們創(chuàng)建的每一個(gè)函數(shù)都有一個(gè)prototype(原型)屬性勤篮,這個(gè)屬性是一個(gè)指針,指向一個(gè)對象色罚。
而這個(gè)對象的用途是用來包含所有實(shí)例共享的屬性和方法碰缔。
使用原型對象的好處就是讓所有對象實(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,共享所有屬性和方法
理解原型對象
創(chuàng)建一個(gè)新函數(shù)腌且,就會(huì)創(chuàng)建一個(gè)prototype屬性梗肝,這個(gè)屬性指向函數(shù)的原型對象。
默認(rèn)情況下所有原型對象都會(huì)獲得夜歌constructor屬性铺董,這個(gè)屬性包含一個(gè)指向prototype屬性所在函數(shù)的指針单旁。
以前例來說:
Person.prototype.constructor指向Person雕沿。
當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例,該實(shí)例內(nèi)部將會(huì)包含一個(gè)指針,指向構(gòu)造函數(shù)的原型對象杖剪,ECMA5管這個(gè)指針叫[[Prototype]]涨冀,在瀏覽器控制臺(tái)看到的是proto玄组。
要明確的一點(diǎn)是障贸,這個(gè)連接存在于實(shí)例與構(gòu)造函數(shù)的原型對象之間,而不是存在于實(shí)例與構(gòu)造函數(shù)之間确垫。
圖中弓颈,展示了Person構(gòu)造函數(shù),Person的原型屬性以及Person現(xiàn)有兩個(gè)實(shí)例之間的關(guān)系删掀。
Person.prototype指向原型對象翔冀,
Person.prototype.constructor指回Person,
原型對象中包含constructor及后來添加的其他屬性爬迟,
Person的每一個(gè)實(shí)例都包含一個(gè)內(nèi)部屬性橘蜜,該屬性僅僅指向Person.prototype。
isPrototypeOf
確定對象實(shí)例是否來自某個(gè)對象原型
alert(Person.prototype.isPrototypeOf(person1));//true
Object.getPrototypeOf()
返回對象實(shí)例的[[Prototype]]值付呕。
alert(Object.getPrototypeOf(person1) == Person.prototype);//true
alert(Object.getPrototypeOf(person1).name);//Greg
hasOwnPrototype()
這里涉及到讀取對象屬性時(shí)的搜索順序计福,
搜索先從對象實(shí)例本身開始,如果實(shí)例中存在就返回該屬性的值徽职,
否則繼續(xù)向上搜索指針指向的原型對象象颖。
所以,如果我們在實(shí)例中重寫覆蓋了原型中的值姆钉,那么返回的就是新值说订,而非共享的屬性值抄瓦。
person1.name = "Nicholas";
alert(person1.name);//Nicholas 來自實(shí)例
alert(person2.name);//Greg 來自原型
所以,實(shí)例中重寫的值只是屏蔽對原型中同名屬性的訪問陶冷,而不是修改原型中的那個(gè)屬性钙姊。
如要改回,使用delete操作符就可以完全刪除實(shí)例中的同名屬性埂伦。
使用hasOwnProperty()方法可以檢測一個(gè)屬性是存在于實(shí)例中還是原型中煞额,
這個(gè)方法只在給定屬性存在于對象實(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í)例中不存在
通過該方法就能確定什么時(shí)候訪問的是示例屬性沾谜,什么時(shí)候是原型屬性膊毁。
原型與in操作符
in操作符會(huì)在能夠通過對象訪問給定屬性時(shí)返回true,無論該屬性存在于實(shí)例中還是原型中基跑,
alert("name" in person1);//true
所以婚温,結(jié)合hasOwnProperty()就能判讀屬性存在的位置。
Object.keys()
獲取對象上所有可枚舉的實(shí)力屬性
參數(shù)為對象媳否,返回一個(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í)例屬性逆日,無論是否可枚舉嵌巷。
- 更常用的原型語法
從原型prototype的定義可以知道萄凤,prototype下是一個(gè)對象室抽。
所以:
function Person(){}
Person.prototype = {
name :"Greg",
age : 27,
sayName : function(){alert(this.name);}
};
但是這樣又出現(xiàn)一個(gè)問題,
這樣定義后靡努,constructor不再指向Person了坪圾,
因?yàn)檫@種定義方法本質(zhì)上是對原prototype的完全重寫(所以,對原型完全重寫后的原型就是另一個(gè)原型了)惑朦,而創(chuàng)建新的prototype對象兽泄,這個(gè)對象也會(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è)上面有提到。
- 原型對象的問題
開始有提到吩蔑,原型中所有的屬性是被實(shí)例共享的钮热,
這種共享對函數(shù)很適合,
對于那些包含基本值的屬性也可以烛芬,同名屬性屏蔽隧期,
但是對于引用類型值的屬性飒责,問題就突出了:
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ù)模式來定義示例的非共享屬性宏蛉,
原型模式來定義方法和共享的屬性。
結(jié)果性置,每個(gè)實(shí)例都會(huì)有自己的一份實(shí)例屬性的副本檐晕,
同時(shí)又共享著對方法的引用,最大限度的節(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)芥喇。
- 原型鏈
- 先跳過