1,javascript 基礎(chǔ)知識
Array對象
屬性 | 描述 |
---|---|
length | 設(shè)置或返回數(shù)組中的元素的數(shù)目 |
Array對象屬性
屬性 | 描述 |
---|---|
length | 設(shè)置或返回數(shù)組中的元素的數(shù)目 |
方法 | 描述 |
---|---|
concat() | 鏈接兩個或更多的數(shù)組并返回結(jié)果 |
join() | 把數(shù)組的所有的元素放入一個字符串,元素通過指定的分隔符進(jìn)行分割 |
pop() | 刪除并返回數(shù)組的最后一個元素 |
reverse() | 顛倒數(shù)組中的元素的順序 |
shift | 刪除并返回數(shù)組的第一個元素 |
slice() | 從某個已有的數(shù)組返回選定的元素 |
sort() | 對數(shù)組的元素進(jìn)行排序 |
splice() | 刪除元素,冰箱數(shù)組添加新元素 |
unshift() | 向數(shù)組的開頭添加一個或更多元素,并返回新的長度 |
Arrray對象方法
方法 | 描述 |
---|---|
concat() | 鏈接兩個或更多的數(shù)組并返回結(jié)果 |
join() | 把數(shù)組的所有的元素放入一個字符串,元素通過指定的分隔符進(jìn)行分割 |
pop() | 刪除并返回數(shù)組的最后一個元素 |
reverse() | 顛倒數(shù)組中的元素的順序 |
shift | 刪除并返回數(shù)組的第一個元素 |
slice() | 從某個已有的數(shù)組返回選定的元素 |
sort() | 對數(shù)組的元素進(jìn)行排序 |
splice() | 刪除元素,冰箱數(shù)組添加新元素 |
unshift() | 向數(shù)組的開頭添加一個或更多元素,并返回新的長度 |
Date對象
方法 | 描述 |
---|---|
Date() | 當(dāng)日的日期和時間 |
getDate() | 從Date對象返回一個月中的某一天(1-31). |
getDay() | 從Date對象返回一周中的某一天(0-6) |
getMonth() | 從Date對象返回月份(0-11) |
getFullYear() | 從Date對象以四位數(shù)字返回年份 |
getYear() | 請使用getFullYear()方法代替 |
getHours() | 返回Date對象的小時(0-23) |
getMiunute s() | 返回Date對象的分鐘(0-59) |
getSeconds() | 返回Date對象的秒數(shù)(0-59) |
getMiliseconds() | 返回Date的毫秒(0-999) |
getTime() | 返回1970年1月1日至今的毫秒數(shù) |
parse() | 返回1970年1月1日午夜到指定日期(字符串)的毫秒數(shù)) |
setDate() | 設(shè)置Date對象中月的某一天(1-31) |
setMonth() | 設(shè)置Date對象中月份(0-11) |
setFullYear() | 設(shè)置Date對象中的年份(四位數(shù)字) |
setYear() | 請使用setFullYear()方法代替 |
setHours() | 設(shè)置Date對象中的小時(0-23) |
setMinutes() | 設(shè)置Date()對象a的分鐘(0-59) |
setSeconds() | 設(shè)置Date對象中的秒鐘(0-59) |
setMiliseconds() | 設(shè)置Date對象中的毫秒(0-999) |
setTime() | 以毫秒設(shè)置Date對象 |
toString() | 把Date對象轉(zhuǎn)換為字符串 |
toTimeString() | 把Date對象的時間部分轉(zhuǎn)換為字符串 |
toDateString() | 把Date對象的日期部分轉(zhuǎn)換為字符串 |
Date對象方法
方法 | 描述 |
---|---|
Date() | 當(dāng)日的日期和時間 |
getDate() | 從Date對象返回一個月中的某一天(1-31). |
getDay() | 從Date對象返回一周中的某一天(0-6) |
getMonth() | 從Date對象返回月份(0-11) |
getFullYear() | 從Date對象以四位數(shù)字返回年份 |
getYear() | 請使用getFullYear()方法代替 |
getHours() | 返回Date對象的小時(0-23) |
getMiunute s() | 返回Date對象的分鐘(0-59) |
getSeconds() | 返回Date對象的秒數(shù)(0-59) |
getMiliseconds() | 返回Date的毫秒(0-999) |
getTime() | 返回1970年1月1日至今的毫秒數(shù) |
parse() | 返回1970年1月1日午夜到指定日期(字符串)的毫秒數(shù)) |
setDate() | 設(shè)置Date對象中月的某一天(1-31) |
setMonth() | 設(shè)置Date對象中月份(0-11) |
setFullYear() | 設(shè)置Date對象中的年份(四位數(shù)字) |
setYear() | 請使用setFullYear()方法代替 |
setHours() | 設(shè)置Date對象中的小時(0-23) |
setMinutes() | 設(shè)置Date()對象a的分鐘(0-59) |
setSeconds() | 設(shè)置Date對象中的秒鐘(0-59) |
setMiliseconds() | 設(shè)置Date對象中的毫秒(0-999) |
setTime() | 以毫秒設(shè)置Date對象 |
toString() | 把Date對象轉(zhuǎn)換為字符串 |
toTimeString() | 把Date對象的時間部分轉(zhuǎn)換為字符串 |
toDateString() | 把Date對象的日期部分轉(zhuǎn)換為字符串 |
Number對象
屬性 | 描述 |
---|---|
MAX_VALUE | b可表示的最大的數(shù) |
MIN_VALUE | 可表示的最小的數(shù) |
NaN | 非數(shù)字,相當(dāng)于NaN |
NEGATIVE_INFINITY | 負(fù)無窮大,溢出時返回該值,相當(dāng)于-Infinity |
POSITIVE_INFINITY | 正無窮大,溢出時返回該值,相當(dāng)于Infinity |
number對象的屬性
屬性 | 描述 |
---|---|
MAX_VALUE | b可表示的最大的數(shù) |
MIN_VALUE | 可表示的最小的數(shù) |
NaN | 非數(shù)字,相當(dāng)于NaN |
NEGATIVE_INFINITY | 負(fù)無窮大,溢出時返回該值,相當(dāng)于-Infinity |
POSITIVE_INFINITY | 正無窮大,溢出時返回該值,相當(dāng)于Infinity |
方法 | 描述 |
---|---|
toString | 把數(shù)字轉(zhuǎn)換為字符串,使用指定的基數(shù) |
toFixed | 把數(shù)字字符串,結(jié)果的小數(shù)點后有指定位數(shù)的數(shù)字 |
toExponential | 把對象的值轉(zhuǎn)換為指數(shù)計數(shù)法 |
toPrecision | 把數(shù)字格式化為指定的長度 |
valueOf | 返回一個Number對象的基本數(shù)字值 |
Number對象方法
方法 | 描述 |
---|---|
toString | 把數(shù)字轉(zhuǎn)換為字符串,使用指定的基數(shù) |
toFixed | 把數(shù)字字符串,結(jié)果的小數(shù)點后有指定位數(shù)的數(shù)字 |
toExponential | 把對象的值轉(zhuǎn)換為指數(shù)計數(shù)法 |
toPrecision | 把數(shù)字格式化為指定的長度 |
valueOf | 返回一個Number對象的基本數(shù)字值 |
Boolean對象
方法 | 描述 |
---|---|
toString() | 把邏輯值轉(zhuǎn)換為字符串,并返回結(jié)果 |
valueOf() | 返回Boolean對象的原始值 |
Boolean對象方法
方法 | 描述 |
---|---|
toString() | 把邏輯值轉(zhuǎn)換為字符串,并返回結(jié)果 |
valueOf() | 返回Boolean對象的原始值 |
String對象
屬性 | 描述 |
---|---|
length | 字符串的長度 |
String對象屬性
屬性 | 描述 |
---|---|
length | 字符串的長度 |
方法 | 描述 |
---|---|
anchor() | 創(chuàng)建HTML錨 |
big() | 用大號字體顯示字符串 |
blink() | 顯示閃動字符串 |
bold() | 使用粗體顯示字符串 |
fontcolor() | 使用指定的顏色來顯示字符串 |
fontsize() | 使用指定的尺寸來顯示字符串 |
italics() | 使用斜體顯示字符串 |
link() | 將字符串顯示為鏈接 |
small() | 使用小號來顯示字符串 |
strike() | 使用刪除線來顯示字符串 |
sub() | 把字符串顯示為下標(biāo) |
fixed() | 以打字及文本顯示字符串 |
sup() | 把字符串顯示為上標(biāo) |
charAt() | 返回在指定未指定的字符串 |
charCodeAt() | 返回在指定位置的字符的Unicode |
concat() | 鏈接字符串 |
formCharCode() | 從字符編碼創(chuàng)建一個字符串 |
indexOf() | 檢索字符串 |
lastIndexOf() | 從后向前搜索字符串 |
match() | 找到一個或多個正字表達(dá)式的匹配 |
replace() | 替換與正則表達(dá)式匹配的字符串 |
search() | 檢索與正則表達(dá)式相匹配的值 |
slice() | 提取字符串的片段,并在新的字符串中返回被提取的部分 |
split() | 把字符串分隔為字符串?dāng)?shù)組 |
substr() | 從起始索引號提取字符串中指定書目的字符 |
substring() | 提取字符串中兩個指定的索引號之間的字符 |
toLocaleLowerCase() | 把字符串轉(zhuǎn)換為小寫 |
toLocalUpperCase() | 把字符串轉(zhuǎn)換為大寫 |
toLowerCase() | 把字符串轉(zhuǎn)換為小寫 |
toUpperCase() | 把字符串轉(zhuǎn)換為大寫 |
toSource() | 代表對象的源代碼 |
toString() | 返回字符串 |
valueOf() | 返回某個字符串對象的原始值 |
String對象方法
方法 | 描述 |
---|---|
anchor() | 創(chuàng)建HTML錨 |
big() | 用大號字體顯示字符串 |
blink() | 顯示閃動字符串 |
bold() | 使用粗體顯示字符串 |
fontcolor() | 使用指定的顏色來顯示字符串 |
fontsize() | 使用指定的尺寸來顯示字符串 |
italics() | 使用斜體顯示字符串 |
link() | 將字符串顯示為鏈接 |
small() | 使用小號來顯示字符串 |
strike() | 使用刪除線來顯示字符串 |
sub() | 把字符串顯示為下標(biāo) |
fixed() | 以打字及文本顯示字符串 |
sup() | 把字符串顯示為上標(biāo) |
charAt() | 返回在指定未指定的字符串 |
charCodeAt() | 返回在指定位置的字符的Unicode |
concat() | 鏈接字符串 |
formCharCode() | 從字符編碼創(chuàng)建一個字符串 |
indexOf() | 檢索字符串 |
lastIndexOf() | 從后向前搜索字符串 |
match() | 找到一個或多個正字表達(dá)式的匹配 |
replace() | 替換與正則表達(dá)式匹配的字符串 |
search() | 檢索與正則表達(dá)式相匹配的值 |
slice() | 提取字符串的片段,并在新的字符串中返回被提取的部分 |
split() | 把字符串分隔為字符串?dāng)?shù)組 |
substr() | 從起始索引號提取字符串中指定書目的字符 |
substring() | 提取字符串中兩個指定的索引號之間的字符 |
toLocaleLowerCase() | 把字符串轉(zhuǎn)換為小寫 |
toLocalUpperCase() | 把字符串轉(zhuǎn)換為大寫 |
toLowerCase() | 把字符串轉(zhuǎn)換為小寫 |
toUpperCase() | 把字符串轉(zhuǎn)換為大寫 |
toSource() | 代表對象的源代碼 |
toString() | 返回字符串 |
valueOf() | 返回某個字符串對象的原始值 |
Math對象
屬性 | 描述 |
---|---|
E | 安徽算數(shù)常量e,即自然對數(shù)的底數(shù)(約等于2.718) |
LN2 | 返回2的自然對數(shù)(約等于0.693) |
LN10 | 返回10的自然對數(shù)(約等于2.302) |
LOG2E | 返回以2為底的e的對數(shù)(約等于0.434) |
LOG10E | 返回以10為底的e的對數(shù)(約等于0.434) |
PI | 返回圓周率(約等于3014159) |
SQTR1_2 | 返回2的平方根的倒數(shù)(約等于0.707) |
SQRT2 | 返回2的平方個(約等于1.414) |
Math對象的屬性
屬性 | 描述 |
---|---|
E | 安徽算數(shù)常量e,即自然對數(shù)的底數(shù)(約等于2.718) |
LN2 | 返回2的自然對數(shù)(約等于0.693) |
LN10 | 返回10的自然對數(shù)(約等于2.302) |
LOG2E | 返回以2為底的e的對數(shù)(約等于0.434) |
LOG10E | 返回以10為底的e的對數(shù)(約等于0.434) |
PI | 返回圓周率(約等于3014159) |
SQTR1_2 | 返回2的平方根的倒數(shù)(約等于0.707) |
SQRT2 | 返回2的平方個(約等于1.414) |
屬性 | 方法 |
---|---|
abs(x) | 返回數(shù)的絕對值 |
acos(x) | 返回數(shù)的反余弦值 |
asin(x) | 返回數(shù)的反正弦值 |
atan(x) | 以介于-PI/2與PI/2弧度之間的數(shù)值來返回x的反正切值 |
atan2(y,x) | 返回從 x 軸到點 (x,y) 的角度(介于 -PI/2 與 PI/2 弧度之間)壹粟。 |
ceil(x) | 對數(shù)進(jìn)行上舍入。 |
cos(x) | 返回數(shù)的余弦隆箩。 |
exp(x) | 返回 e 的指數(shù)皱坛。 |
floor(x) | 對數(shù)進(jìn)行下舍入涮俄。 |
log(x) | 返回數(shù)的自然對數(shù)(底為e)通砍。 |
max(x,y) | 返回 x 和 y 中的最高值卑硫。 |
min(x,y) | 返回 x 和 y 中的最低值球昨。 |
pow(x,y) | 返回 x 的 y 次冪。 |
random() | 返回 0 ~ 1 之間的隨機數(shù)眨攘。 |
round(x) | 把數(shù)四舍五入為最接近的整數(shù)主慰。 |
sin(x) | 返回數(shù)的正弦。 |
sqrt(x) | 返回數(shù)的平方根鲫售。 |
tan(x) | 返回角的正切共螺。 |
toSource() | 返回該對象的源代碼。 |
valueOf() | 返回 Math 對象的原始值情竹。 |
Math對象的方法
屬性 | 方法 |
---|---|
abs(x) | 返回數(shù)的絕對值 |
acos(x) | 返回數(shù)的反余弦值 |
asin(x) | 返回數(shù)的反正弦值 |
atan(x) | 以介于-PI/2與PI/2弧度之間的數(shù)值來返回x的反正切值 |
atan2(y,x) | 返回從 x 軸到點 (x,y) 的角度(介于 -PI/2 與 PI/2 弧度之間)藐不。 |
ceil(x) | 對數(shù)進(jìn)行上舍入。 |
cos(x) | 返回數(shù)的余弦。 |
exp(x) | 返回 e 的指數(shù)雏蛮。 |
floor(x) | 對數(shù)進(jìn)行下舍入涎嚼。 |
log(x) | 返回數(shù)的自然對數(shù)(底為e)。 |
max(x,y) | 返回 x 和 y 中的最高值挑秉。 |
min(x,y) | 返回 x 和 y 中的最低值法梯。 |
pow(x,y) | 返回 x 的 y 次冪。 |
random() | 返回 0 ~ 1 之間的隨機數(shù)犀概。 |
round(x) | 把數(shù)四舍五入為最接近的整數(shù)立哑。 |
sin(x) | 返回數(shù)的正弦。 |
sqrt(x) | 返回數(shù)的平方根姻灶。 |
tan(x) | 返回角的正切铛绰。 |
toSource() | 返回該對象的源代碼。 |
valueOf() | 返回 Math 對象的原始值产喉。 |
值類型和引用類型
值類型(基本數(shù)據(jù)類型)
- 數(shù)值類型
- 布爾類型
- undefined
- null
- 字符串
值類型是存儲在棧(stack)中的簡單數(shù)據(jù),也就是說,他們的值直接存儲在變量訪問的位置
var num = 10;
var str = 'hello js";
var flag = true;
var un = undefined;
var nu = null;
上面定義的這些值類型的數(shù)據(jù)在內(nèi)村中的存儲如下
引用類型(符合數(shù)據(jù)類型)
- 對象
- 數(shù)組
- 函數(shù)
存儲在堆(heap)中的對象 也就是說存儲在變量出的值是一個指針(point),指向存儲對象的內(nèi)存處
var arr = [1,2,3];
var p1 = {name:"張三",age:18};
var p2 = {
name:"李四",
age:50,
son:{
name:"李曉藝,"
age:18,
}
var p3 = {
name:"王五",
age:50,
children:[
{
name:"王小藝,"
age:20,
},
{
name:"王小二,"
age:15,
},
{
name:"王小三,"
age:12,
}
]
}
上面定義的這些引用類型的數(shù)據(jù)在內(nèi)村中的存儲如下:
值類型和引用類型的特征
值類型和引用類型的賦值
- 值類型賦值,直接將值賦值一份
var num1 = 10;
var num2 = num1;
在內(nèi)存的存儲
-
var num1 = 10;
表示變量num1存儲的是數(shù)字10; - 將數(shù)據(jù)拷貝一份,也就是將
10
拷貝一份,這個時候內(nèi)村中有兩個10
; - 將拷貝的
10
賦值給num2
- 引用類型賦值,是將地址復(fù)制一份
var p ={name:"張三",age:19};
var p1= p;
在內(nèi)存中的體現(xiàn)為:
-
var p = {name:"張三",age:19};p
中存儲的是對象的地址 - 賦值就是將變量
p
中存儲的數(shù)據(jù),也就是地址拷貝一份,然后將該數(shù)據(jù)賦值給p1
- 此時內(nèi)村中只有一個對象,變量
p
和p1
同時指向這個對象
總結(jié)
- 在調(diào)用函數(shù)的時候捂掰,傳參的過程其實就是用實參給形參賦值的過程
- 當(dāng)參數(shù)為值類型的時候,函數(shù)內(nèi)和函數(shù)外的兩個變量完全不同镊叁,僅僅只是存的值一樣而已尘颓,修改時互不影響
- 當(dāng)參數(shù)為引用類型的時候,函數(shù)內(nèi)和函數(shù)外的兩個變量不同晦譬,但是共同指向同一個對象疤苹,在函數(shù)內(nèi)修改對象數(shù)據(jù)時會影響外部
對象的動態(tài)特性
給對象動態(tài)添加屬性
當(dāng)一個對象需要某個屬性的時候,可以用兩種方式添加屬性
- 直接使用
對象名 = 值
這種形式,為對象添加對應(yīng)的屬性 - 使用關(guān)聯(lián)數(shù)組語法
對象名["屬性名"] = 值
這種形式,為對象添加對應(yīng)的屬性
var o = {};//o是一個沒有任何自定義屬性的對象
//現(xiàn)在想讓他擁有name age gender 等屬性
//直接使用 對象名.屬性名 = 值
o.name = "張三";
//使用 對象名["屬性名"] = 值
o["age"] = 18;
注意
當(dāng)要動態(tài)的為一個對象添加屬性的時候,必須使用關(guān)聯(lián)數(shù)組的方式
var str = prompt("請輸入屬性名");
o = {};
//o.str = "這是一個新屬性";這樣寫是不對的,會給對象新增一個str屬性,正確的寫法如下
o[str]=''這是一個新屬性";
對象屬性的訪問形式
- 點語法 :
對象名.屬性名
- 關(guān)聯(lián)數(shù)組:
對象名[屬性名]
var o = {
name:"zhangsan",
sayHello:function(){
console.log('hello,my name is"+this.name);
}
};
//點語法
console.log(o.name);
//關(guān)聯(lián)數(shù)組語法
console.log(o["name"]);
//這兩種用法同樣適用于方法
o.sayHello();
o["sayHello"]();
//可以對這個對象的屬性進(jìn)行遍歷,如果是值,就打印,如果是方法,就調(diào)用
for(var k in o)
{
if(typeof o[k] == 'function'){
o[k]();
}else{
console.log('log:'+o[k]);
}
}
常用的DOM操作
四字總結(jié):增刪改查
- 獲取元素
getElementById getElementsByTagName getElementsByClassName
- 元素節(jié)點操作
appendChild insertBefore removeChid replaceChild cloneNode createElement creatTextNode(創(chuàng)建文本節(jié)點)
- 屬性節(jié)點操作
getAttribute setAttribute removeAttribute
- 常用DOM屬性
className innerHTML innerText.textContent value children
異常處理
常見的異常分類
- 運行環(huán)境的多樣性導(dǎo)致的異常(瀏覽器)
- 語法錯誤 代碼錯誤
異常捕獲
捕獲異常,使用try-catch
語句
try{
//這里寫可能出現(xiàn)異常的代碼
}catch(e){
//這里的e就是捕獲的異常對象
//可以在這里寫,出現(xiàn)異常后的處理代碼
}
異常捕獲語句執(zhí)行的過程為:
1 代碼正常運行, 如果在try中出現(xiàn)了錯誤, try 里面出現(xiàn)錯誤的語句后面的代碼都不再執(zhí)行, 直接跳轉(zhuǎn)到 catch 中
2 catch中處理錯誤信息
3 然后繼續(xù)執(zhí)行后面的代碼
4 如果 try 中沒有出現(xiàn)錯誤, 那么不走 catch 直接執(zhí)行后面的代碼
通過try-catch語句進(jìn)行異常捕獲之后敛腌,代碼將會繼續(xù)執(zhí)行卧土,而不會中斷。
注意:
- 語法錯誤異常用try-catch語句無法捕獲像樊,因為在預(yù)解析階段尤莺,語法錯誤會直接檢測出來,而不會等到運行的時候才報錯生棍。
- try-catch在一般日常開發(fā)中基本用不到颤霎,但是如果要寫框架什么的,用的會非常多涂滴。因為這個會讓框架變得健壯
拋出異常
如何手動拋出異常呢
自己寫一個函數(shù)需要一個參數(shù)如果用戶不傳參數(shù),此時直接給用戶拋出異常,就需要了解如何拋出異常
拋出異常使用throw
關(guān)鍵字,語法如下:
throw 異常對象;
異常對象一般是用new Error("異常消息"), 也可以使用任意對象
function test(para){
if(para == undefined){
throw new Error("請傳遞參數(shù)");
//這里也可以使用自定義的對象
throw {"id":1, msg:"參數(shù)未傳遞"};
}
}
try{
test();
}catch(e){
console.log(e);
}
異常的傳遞機制
function f1 () {
f2(); // f1 稱為調(diào)用者, 或主調(diào)函數(shù), f2 稱為被調(diào)用者, 或被調(diào)函數(shù)
}
function f2 () {
f3();
}
function f3() {
throw new Error( 'error' );
}
f1();
當(dāng)在被調(diào)函數(shù)內(nèi)發(fā)生異常的時候友酱,異常會一級一級往上拋出。
異常捕獲語句的完整模式
異常捕獲語句的完整模式為try-catch-finally
try {
//可能出現(xiàn)錯誤的代碼
} catch ( e ) {
//如果出現(xiàn)錯誤就執(zhí)行
} finally {
//結(jié)束 try 這個代碼塊之前執(zhí)行, 即最后執(zhí)行
}
finally中的代碼柔纵,不管有沒有發(fā)生異常缔杉,都會執(zhí)行。一般用在后端語言中搁料,用來釋放資源或详,JavaScript中很少會用到
調(diào)試工具的使用
調(diào)試窗口介紹
指針: 選擇頁面中的元素
手機: 使用移動端界面調(diào)試
Elements: 查看頁面 DOM 樹
Console: 控制臺(注意, 控制臺與該頁面是一個整體, 在控制臺中的任何操作, 會影響到頁面)
Source: 代碼調(diào)試
調(diào)試工具的使用
逐過程運行, 一次運行一個函數(shù)
單步運行(逐步運行), 一次運行一句, 如果是函數(shù), 進(jìn)入函數(shù)體內(nèi)運行
繼續(xù)運行. 從當(dāng)前狀態(tài)運行下去, 直到出現(xiàn)斷點, 如果沒有斷點則運行結(jié)束
設(shè)置斷點技巧逐步與逐過程混合
斷點加繼續(xù)運行
條件斷點(右鍵添加 add contitional breakpoint)
2,面向?qū)ο?/h3>
構(gòu)造函數(shù)是干什么用的?
在javascript中,構(gòu)造函數(shù)是給對象添加屬性,初始化屬性用的.
對象的創(chuàng)建過程
var p = new Person();
以上面這個p對象創(chuàng)建為例:
- 首先使用new關(guān)鍵字創(chuàng)建對象系羞,類似于使用{},這個時候創(chuàng)建出來的對象是一個"沒有任何成員"的對象。這里需要注意兩點:
- 使用new關(guān)鍵字創(chuàng)建的對象霸琴,對象的類型就是創(chuàng)建這個對象使用的構(gòu)造函數(shù)的函數(shù)名
- 使用{}創(chuàng)建對象椒振,對象的類型一定是Object,相當(dāng)于使用了new Object()
2 使用構(gòu)造函數(shù)為其初始化成員
- 在構(gòu)造函數(shù)調(diào)用開始的時候沈贝,有一個賦值操作杠人,也就是讓this = 剛創(chuàng)建出來的對象
- 在構(gòu)造函數(shù)中,this就代表剛創(chuàng)建出來的對象
- 在構(gòu)造函數(shù)中宋下,利用對象的動態(tài)特性嗡善,為對象添加成員
面向?qū)ο蟮奶匦?/h5>
封裝
對象是將數(shù)據(jù)與功能組合到一起, 即封裝
1.js 對象就是 鍵值對的集合
鍵值如果是數(shù)據(jù)( 基本數(shù)據(jù), 復(fù)合數(shù)據(jù), 空數(shù)據(jù) ), 就稱為屬性
如果鍵值是函數(shù), 那么就稱為方法
2.對象就是將屬性與方法封裝起來
3.方法是將過程封裝起來
繼承
傳統(tǒng)繼承基于模板
子類可以使用從父類繼承的屬性和方法
class Person {
string name;
int age;
}
class Student : Person {
}
var stu = new Student();
stu.name
即:讓某個類型的對象獲得另一個類型的對象的屬性的方法
js繼承基于對象
在JavaScript中,繼承就是當(dāng)前對象可以使用其他對象的方法和屬性学歧。
js繼承實現(xiàn)舉例:混入(mix)
for ( var k in o2 ) {
o1[ k ] = o2[ k ];
}
}
多態(tài)
把不同的子類對象都當(dāng)作父類來看罩引,可以屏蔽不同子類對象之間的差異,寫出通用的代碼枝笨,做出通用的編程袁铐,以適應(yīng)需求的不斷變化。
動物 animal = new 子類(); // 子類:麻雀横浑、狗剔桨、貓、豬徙融、狐貍...
動物 animal = new 狗();
animal.叫();
傳統(tǒng)構(gòu)造函數(shù)存在的問題
發(fā)現(xiàn)問題
現(xiàn)有構(gòu)造函數(shù):
function Person(name, age){
this.name = name;
this.age = age;
this.sayHi = function(){
console.log("你好");
}
}
調(diào)用該構(gòu)造函數(shù)創(chuàng)建對象洒缀,并對比創(chuàng)建出來的對象的sayHi方法:
var p = new Person("張三", 18);
var p1 = new Person("李四", 19);
console.log(p.sayHi == p1.sayHi); //輸出結(jié)果為false
由于每個對象都是由new Person創(chuàng)建出來的,因此每創(chuàng)建一個對象欺冀,函數(shù)sayHi都會被重新創(chuàng)建一次树绩,這個時候,每個對象都擁有一個獨立的隐轩,但是功能完全相同的方法饺饭。
功能相同的函數(shù),完全沒有必要再內(nèi)存中存在這么多份职车。所以就造成了資源浪費瘫俊。
解決問題
這里最好的辦法就是將函數(shù)體放在構(gòu)造函數(shù)之外. 在構(gòu)造函數(shù)中只需要引用該函數(shù)即可。
function sayHello(){
console.log("你好");
}
function Person(name, age){
this.name = name;
this.age = age;
this.sayHi = sayHello;
}
//調(diào)用該構(gòu)造函數(shù)創(chuàng)建對象悴灵,并對比創(chuàng)建出來的對象的sayHi方法
var p = new Person("張三", 18);
var p1 = new Person("李四", 19);
console.log(p.sayHi == p1.sayHi); //輸出結(jié)果為true
這樣寫依然存在問題:
全局變量增多扛芽,會增加引入框架命名沖突的風(fēng)險
代碼結(jié)構(gòu)混亂,會變得難以維護(hù)
使用原型解決構(gòu)造函數(shù)的問題
關(guān)鍵點
- 每一個函數(shù)在定義的時候称勋,都會有跟它關(guān)聯(lián)的一個對象被創(chuàng)建出來
- 每一個由構(gòu)造函數(shù)創(chuàng)建出來的對象,都會默認(rèn)的和構(gòu)造函數(shù)的神秘對象關(guān)聯(lián)
- 當(dāng)使用一個方法進(jìn)行屬性或者方法訪問的時候涯竟,會先在當(dāng)前對象內(nèi)查找該屬性和方法
- 如果當(dāng)前對象內(nèi)未找到赡鲜,就回去跟它關(guān)聯(lián)的神秘對象內(nèi)進(jìn)行查找
function Person(name, age){
this.name = name;
this.age = age;
this.sayHi = function(){
console.log("Hello!");
};
}
var p = new Person("張三", 18);
p.sayHi(); //當(dāng)前對象內(nèi)有這個方法空厌,所以不會去神秘對象內(nèi)進(jìn)行查找
var p1 = new Person("李四", 19);
p1.sayHello(); //當(dāng)前對象沒沒有找到這個方法,所以去神秘對象內(nèi)進(jìn)行查找
如何訪問到這個神秘的對象?
//可以通過構(gòu)造函數(shù).prototype訪問這個神秘對象
console.log(Person.prototype);
當(dāng)嘗試給這個對象新增一個方法之后
Person.prototype.sayHello = function(){
console.log("我是神秘對象中的方法");
};
使用p,p1都可以訪問這個方法:
p.sayHello();
p1.sayHello();
總結(jié)
所有對象共享神秘對象(構(gòu)造函數(shù).prototype)內(nèi)的屬性和方法.
結(jié)局方案
既然所有對象共享神秘對象(構(gòu)造函數(shù).prototype)內(nèi)的屬性和方法银酬。我們只需要將需要共享的東西嘲更,也就是重復(fù)占用內(nèi)存的東西,全部都放到 神秘對象(構(gòu)造函數(shù).prototype)中揩瞪,那么所有對象就都可以使用赋朦,并且內(nèi)存里面也只有一份了。
改造函數(shù)
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function(){
console.log("你好");
};
//測試
var p = new Person("張三", 18);
var p1 = new Person("李四", 19);
console.log(p.sayHi == p1.sayHi); //輸出true
常見的錯誤
將屬性寫在神秘對象(構(gòu)造函數(shù).prototype)
function Car(name){
this.name = name;
}
function Person() {}
Person.prototype.name = '張三'; //基本類型的屬性影響不大
Person.prototype.car = new Car("法拉利"); //引用類型的屬性李破,會被所有的對象共享
var p = new Person();
賦值的錯誤
function Person() {}
Person.prototype.name = '張三';
var p1 = new Person();
var p2 = new Person();
p1.name = '李四';
console.log( p1.name );
console.log( p2.name );
// 如果是訪問數(shù)據(jù), 當(dāng)前對象中如果沒有該數(shù)據(jù)就到構(gòu)造函數(shù)的原型屬性中去找
// 如果是寫數(shù)據(jù), 當(dāng)對象中有該數(shù)據(jù)的時候, 就是修改值; 如果對象沒有該數(shù)據(jù), 那么就添加值
原型的相關(guān)概念
神秘對象與構(gòu)造函數(shù)
- 神秘對象就是構(gòu)造函數(shù)的'原型屬性'
- 簡稱原型(構(gòu)造函數(shù)的原型)
神秘對象與構(gòu)造函數(shù)所創(chuàng)建出來的對象 - 神秘對象針對構(gòu)造函數(shù)創(chuàng)建出來的對象成為'原型對象'
- 簡稱原型(對象的原型)
原型繼承
[1] 構(gòu)造函數(shù)創(chuàng)建的對象 繼承自 構(gòu)造函數(shù)的原型屬性
[2] 構(gòu)造函數(shù)創(chuàng)建的對象 繼承自 該對象的原型對象
- 原型中的成員, 可以直接被實例對象所使用
- 實例對象直接 "含有" 原型中的成員
- 因此實例對象 繼承自 原型
- 這樣的繼承就是 "原型繼承"
原型的使用
使用對象的動態(tài)特性
function Person () { }
Person.prototype.func = function () {
console.log( 'something' );
};
var p = new Person();
p.func();
直接替換原型對象
function Person () { };
Person.prototype = {
func: function () {
console.log( '22222' );
}
};
var p = new Person();
p.func();
直接替換原型會出現(xiàn)的問題
function Person () { }
Person.prototype.func = function () {
console.log( 'something' );
};
var p = new Person();
Person.prototype.func = function () {
console.log( 'something' );
};
var p1 = new Person();
p.func();
p1.func();
替換原型之后宠哄,在替換前創(chuàng)建出來的對象和替換后創(chuàng)建出來的對象的原型對象不一致
對象的proto屬性
標(biāo)識符命名規(guī)則
區(qū)分大小寫,Name和name是兩個不同的變量
標(biāo)識符可以以下劃線_,美元符$或者字母開頭,但是不能是數(shù)字
標(biāo)識符可以由下劃線_嗤攻,美元符$毛嫉,字母,數(shù)字組成
神秘對象的訪問
構(gòu)造函數(shù)的prototype屬性
之前我們訪問神秘對象的時候妇菱,使用的是原型屬性 prototype
function Person(){}
//通過構(gòu)造函數(shù)的原型屬性prototype可以直接訪問原型
Person.prototype;
在之前是無法通過構(gòu)造函數(shù)創(chuàng)建出來的對象訪問原型的
function Person(){}
var p = new Person();
//以前不能直接通過p來訪問神秘對象
**實例對象的proto屬性
__proto__
屬性最早是火狐瀏覽器引入的承粤,用以通過實例對象來訪問原型,這個屬性在早期是非標(biāo)準(zhǔn)的屬性
有了__proto__屬性
闯团,就可以通過構(gòu)造函數(shù)創(chuàng)建出來的對象直接訪問神秘對象
function Person(){}
var p = new Person();
//實例對象的__proto__屬性可以方便的訪問到原型對象
p.__proto__;
//既然使用構(gòu)造函數(shù)的`prototype`和實例對象的`__proto__`屬性
//都可以訪問原型對象
//就有如下結(jié)論
p.__proto__ === Person.prototype;
proto屬性的用途
可以用來訪問原型
在實際開發(fā)中除非有特殊的需求辛臊,不要輕易的使用實例對象的proto屬性去修改原型的成員,
在調(diào)試過程中房交,可以輕易的查看原型的成員
早期是使用實例對象訪問構(gòu)造函數(shù)屬性constuctor
var p = new Person();
p.constructor.prototype;
給實例繼承自原型的屬性賦值時需要注意的問題
function Person(){};
Person.prototype.name = "周華健";
var o1 = new Person();
var o2 = new Person();
o1.name = "李宗盛"; //這里修改的不是原型對象的name屬性彻舰,而是給o1自己新增了一個name屬性,進(jìn)行了賦值
繼承的實現(xiàn)方式
最簡單的繼承實現(xiàn)
直接遍歷父對象的屬性,將所有的屬性加到當(dāng)前對象上
var animal = {
name:"Animal",
sex:"male",
age:5,
bark:function(){
console.log("Animal bark");
}
};
var dog = {};
for (var k in animal){
dog[k]= animal[k];
}
原型繼承
每一個構(gòu)造函數(shù)都有prototype原型屬性涌萤,通過構(gòu)造函數(shù)創(chuàng)建出來的對象都繼承自該原型屬性淹遵。所以可以通過更改構(gòu)造函數(shù)的原型屬性來實現(xiàn)繼承。
function Dog(){
this.type = "yellow Dog";
}
function extend(obj1, obj2){
for (var k in obj2){
obj1[k] = obj2[k];
}
};
//使用混入的方式负溪,將屬性和方法添加到構(gòu)造函數(shù)的原型屬性上透揣,構(gòu)造函數(shù)所創(chuàng)建出來的實例就都有了這些屬性和方法。
extend(Dog.prototype, {
name:"",
age:"",
sex:"",
bark:function(){}
})
//使用面向?qū)ο蟮乃枷氚裡xtend方法重新封裝
//extend是擴(kuò)展的意思川抡,誰要擴(kuò)展就主動調(diào)用extend這個方法
//所以extend應(yīng)該是對象的方法辐真,那現(xiàn)在我們要擴(kuò)展的是構(gòu)造函數(shù)的原型對象
//所以給構(gòu)造函數(shù)的原型對象添加一個extend方法
//如下:
Dog.prototype.extend = function(obj){
for (var k in obj){
this[k]=obj[k];
}
}
//調(diào)用方式就變成了下面這種形式
Dog.prototype.extend({
name:"",
age:"",
sex:"",
bark:function(){}
});
原型三角形的繪制
請嘗試?yán)L制如下三種情況的原型三角形圖:
- 第一種
function Person() {
this.name = '張三';
this.sayHello = function () {
}
}
var p = new Person();
- 第二種
function Person() {
this.name = '張三';
}
Person.prototype.sayHello = function () {
}
var p = new Person();
- 第三種
function Person() {
this.name = '張三';
}
Person.prototype = {
sayHello: function () {
}
};
var p = new Person();
屬性搜索原則
訪問一個對象的成員的時候,首先是在實例中找崖堤,沒有找到, 就去原型中找, 但是原型中沒有怎么辦?
原型鏈
每一個對象都有原型屬性侍咱,那么對象的原型屬性也會有原型屬性,所以這樣就形成了一個鏈?zhǔn)浇Y(jié)構(gòu)密幔,我們稱之為原型鏈楔脯。
屬性搜索原則
所謂的屬性搜索原則,也就是屬性的查找順序胯甩,在訪問對象的成員的時候昧廷,會遵循如下的原則:
1 首先在當(dāng)前對象中查找堪嫂,如果找到,停止查找木柬,直接使用贱呐,如果沒有找到圣蝎,繼續(xù)下一步
2 在該對象的原型中查找,如果找到,停止查找饵隙,直接使用畏鼓,如果沒有找到嘿悬,繼續(xù)下一步
3 在該對象的原型的原型中查找锋爪,如果找到,停止查找梗摇,直接使用拓哟,如果沒有找到,繼續(xù)下一步伶授。
4 繼續(xù)往上查找断序,直到查找到Object.prototype還沒有, 那么是屬性就返回 undefied,是方法糜烹,就報錯xxx is not a function违诗。
原型鏈結(jié)構(gòu)
凡是對象就有原型, 原型又是對象, 因此凡是給定義一個對象, 那么就可以找到他的原型, 原型還有原型. 那么如此下去, 就構(gòu)成一個對象的序列. 稱該結(jié)構(gòu)為原型鏈.
function Person() {
}
var p = new Person();
// p 具有默認(rèn)的原型鏈
默認(rèn)的原型鏈結(jié)構(gòu)就是:
當(dāng)前對象 -> 構(gòu)造函數(shù).prototype -> Object.prototype -> null
在實現(xiàn)繼承的時候, 有時會利用替換原型鏈結(jié)構(gòu)的方式實現(xiàn)原型繼承, 那么原型鏈結(jié)構(gòu)就會發(fā)生改變
使用構(gòu)造函數(shù)創(chuàng)建出對象, 并且沒有利用賦值的方式修改原型, 就說該對象保留默認(rèn)的原型鏈.
默認(rèn)原型鏈結(jié)構(gòu)是什么樣子呢?
function ItcastCollection () {
}
ItcastCollection.prototype = [];
var arr = new ItcastCollection();
// arr -> [] -> Array.prototype -> Object.prototype -> null
// var arr = new Array();
//我們可以對比一下o1和o2的name值
原型式繼承
觀察:DOM對象的原型鏈
原型式繼承就是利用修改原型鏈的結(jié)構(gòu)( 增加一個節(jié)點, 刪除一個節(jié)點, 修改節(jié)點中的成員 ), 來使得實例對象可以使用整條鏈中的所有成員.
繪制原型鏈結(jié)構(gòu)
注意:函數(shù)也有proto屬性,暫時不考慮這個疮蹦!
觀察如下代碼诸迟,繪制相應(yīng)的原型鏈結(jié)構(gòu)圖:
function Person(){};
var p = new Person();
練習(xí):
- {}的原型鏈結(jié)構(gòu)圖
- 繪制[]的原型鏈結(jié)構(gòu)圖
注意:
在 js 中, 所有的對象字面量在解析以后, 就是一個具體的對象了. 那么可以理解為 調(diào)用的 對應(yīng)的構(gòu)造方法.
- 例如在代碼中寫上 {}, 就相當(dāng)于new Object()
- 例如代碼中有 [], 就相當(dāng)于new Array()
- 例如代碼中有 /./, 就相當(dāng)于new RegExp( '.' )
注意: 在底層理論執(zhí)行的過程中, 是否有調(diào)用構(gòu)造函數(shù), 不一定. 和瀏覽器的版本有關(guān).
練習(xí):
繪制如下代碼的原型鏈結(jié)構(gòu):
var o = {
appendTo: function ( dom ) {
}
};
function DivTag() {}
DivTag.prototype = o;
var div = new DivTag();
Object.prototype成員介紹
成員 | 描述 |
---|---|
Object.prototype.proto | 指向當(dāng)對象被實例化的時候,用作原型的對象愕乎。 |
Object.prototype.hasOwnProperty() | 返回一個布爾值 阵苇,表示某個對象是否含有指定的屬性,而且此屬性非原型鏈繼承的感论。 |
Object.prototype.isPrototypeOf() | 返回一個布爾值绅项,表示指定的對象是否在本對象的原型鏈中。 |
Object.prototype.toString() | 返回對象的字符串表示比肄。 |
Object.prototype.valueOf() | 返回指定對象的原始值快耿。 |
Object.prototype常用成員()
成員 | 描述 |
---|---|
Object.prototype.proto | 指向當(dāng)對象被實例化的時候,用作原型的對象芳绩。 |
Object.prototype.hasOwnProperty() | 返回一個布爾值 掀亥,表示某個對象是否含有指定的屬性,而且此屬性非原型鏈繼承的妥色。 |
Object.prototype.isPrototypeOf() | 返回一個布爾值搪花,表示指定的對象是否在本對象的原型鏈中。 |
Object.prototype.toString() | 返回對象的字符串表示。 |
Object.prototype.valueOf() | 返回指定對象的原始值撮竿。 |
函數(shù)的構(gòu)造函數(shù)Function
在 js 中 使用Function可以實例化函數(shù)對象丁稀。也就是說在 js 中函數(shù)與普通對象一樣, 也是一個對象類型. 函數(shù)是 js 中的一等公民.
1 函數(shù)是對象, 就可以使用對象的動態(tài)特性
2 函數(shù)是對象, 就有構(gòu)造函數(shù)創(chuàng)建函數(shù)
3 函數(shù)是函數(shù), 可以創(chuàng)建其他對象
4 函數(shù)是唯一可以限定變量作用域的結(jié)構(gòu)
要解決的問題
1 Function 如何使用
2 Function 與函數(shù)的關(guān)系
3 函數(shù)的原型鏈結(jié)構(gòu)
Function的使用
語法
//Function函數(shù)所有的參數(shù)全都是字符串
//Function函數(shù)的作用就是將所有的參數(shù)組合起來,變成一個函數(shù)
//1倚聚、如果只傳一個參數(shù),那么這個函數(shù)必然是函數(shù)體
//2凿可、如果傳多個參數(shù)惑折,那么最后一個參數(shù)表示函數(shù)體,前面的參數(shù)代表將要創(chuàng)建的函數(shù)的參數(shù)
//3枯跑、如果不傳參數(shù)惨驶,表示創(chuàng)建一個空函數(shù)
new Function(arg1, arg2, arg3, ..., argN, body);
創(chuàng)建一個打印一句話的函數(shù)
//傳統(tǒng)的方式
function foo(){
console.log("你好");
}
//使用Function
var func = new Function("console.log('你好');");
這里兩種方式創(chuàng)建出來的函數(shù)功能是一樣的。
創(chuàng)建一個空函數(shù)
//傳統(tǒng)的方式
function foo(){}
//Function
var func = new Function();
創(chuàng)建一個有參數(shù)的函數(shù)
//傳統(tǒng)的方式
function foo(num){
console.log(num);
}
//Function
var func = new Function(){"num", "console.log(num);"};
練習(xí): 利用 Function 創(chuàng)建一個函數(shù), 要求傳入兩個數(shù)字, 打印其和
var func = new Function(
'num1',
'num2',
'console.log( num1 + num2 );'
);
練習(xí): 利用 Function 創(chuàng)建一個函數(shù), 要求允許函數(shù)調(diào)用時傳入任意個數(shù)參數(shù), 并且函數(shù)返回這些數(shù)字中最大的數(shù)字. 練習(xí): 利用 Function 創(chuàng)建一個求三個數(shù)中最大數(shù)的函數(shù).
// 傳統(tǒng)
function foo ( a, b, c ){
var res = a > b ? a : b;
res = res > c ? res : c;
return res;
}
// Function
var func = new Function( 'a',
'b',
'c',
'var res = a > b ? a : b;res = res > c ? res : c;return res;' )
解決代碼太長的問題
1.利用+連接字符串
var func = new Function( 'a', 'b', 'c',
'var res = a > b ? a : b;' +
'res = res > c ? res : c;' +
'return res;' );
1 . 利用字符串特性
function foo ( a, b, c ) {
var res = a > b ? a : b;
res = res > c ? res : c;
return res;
}
var func = new Function( 'a', 'b', 'c', 'return foo( a, b, c );');
1 . ES6 語法(很少有瀏覽器實現(xiàn)) 使用鍵盤左上角的` 表示可換行字符串的界定符敛助,之前我們用的是單引號或者雙引號來表示一個字符串字面量粗卜,在ES6中可以用反引號來表示該字符串可換行。
2. (最終)利用 DOM 的特性完成該方法
<div id="code" style="display:none">
var res = a > b ? a : b;
res = res > c ? res : c;
return res;
</div>
<script>
var txt = document.getElementbyId("code).innerHtml + ' ';
var func = new Function('a', 'b', 'c', txt);
</script>
靜態(tài)成員與實例成員的概念
靜態(tài)成員和實例成員這兩個概念其實也是從面相對象的編程語言中引入的纳击,對應(yīng)到JavaScript中的理解為:
靜態(tài)成員
靜態(tài)成員是指靜態(tài)屬性和靜態(tài)方法续扔,所謂靜態(tài),就是有構(gòu)造函數(shù)提供的焕数。
實例成員
實例成員是值實例屬性和實例方法纱昧,所謂實例,就是由構(gòu)造函數(shù)創(chuàng)建出來的對象堡赔。
舉例說明:
function Person(){
this.name = "zs",
this.sayHello = function(){
console.log("Hello World");
}
}
//下面這個sayHi方法就是構(gòu)造函數(shù)自己的方法识脆,也就是靜態(tài)方法
Person.sayHi = function(){
console.log("I'm a Person");
}
//原型屬性屬于構(gòu)造函數(shù),所以原型屬性是靜態(tài)屬性
Person.prototype = {};
var p = new Person();
//這里的name是構(gòu)造函數(shù)創(chuàng)建出來的實例對象的屬性善已,所以是實例屬性
p.name = "李四";
//這里的sayHello也是構(gòu)造函數(shù)創(chuàng)建出來的實例對象的方法灼捂,所以是實例方法
p.sayHello();
提示:
一般工具型方法都有靜態(tài)成員提供, 一般與實例對象有關(guān)的方法由實例成員表示.
工具方法:比如jQuery.Ajax()、jQuery.trim()换团、jQuery.Each()
arguments對象
在每一個函數(shù)調(diào)用的過程中, 函數(shù)代碼體內(nèi)有一個默認(rèn)的對象arguments, 它存儲著實際傳入的所有參數(shù)悉稠。
arguments是一個偽數(shù)組對象. 它表示在函數(shù)調(diào)用的過程中傳入的所有參數(shù)的集合。在函數(shù)調(diào)用過程中不規(guī)定參數(shù)的個數(shù)與類型, 可以使得函數(shù)調(diào)用變得非常靈活性啥寇。
JavaScript中的函數(shù)并沒有規(guī)定必須如何傳參:
1 定義函數(shù)的時候不寫參數(shù), 一樣可以調(diào)用時傳遞參數(shù)
2 定義的時候?qū)懥藚?shù), 調(diào)用的時候可以不傳參
3 定義的時候?qū)懥艘粋€參數(shù), 調(diào)用的時候可以隨意的傳遞多個而參數(shù)
在代碼設(shè)計中, 如果需要函數(shù)帶有任意個參數(shù)的時候, 一般就不帶任何參數(shù), 所有的參數(shù)利用arguments對象來獲取. 一般的函數(shù)定義語法, 可以寫成:
function foo ( /* ... */ ) {
}
練習(xí):
利用 Function 創(chuàng)建一個函數(shù), 要求允許函數(shù)調(diào)用時傳入任意個數(shù)參數(shù), 并且函數(shù)返回這些數(shù)字中最大的數(shù)字.
function foo ( ) {
// 所有的參數(shù)都在 arguments 中. 將其當(dāng)做數(shù)組使用
// 問題已轉(zhuǎn)換成在有一個數(shù)組中求最大值
var args = arguments;
var max = args[ 0 ];
for ( var i = 1; i < args.length; i++ ) {
if ( max < args[ i ] ) {
max = args[ i ];
}
}
return max;
}
練習(xí):
利用 Function 寫一個函數(shù), 要求傳入任意個數(shù)字 求和
3,JavaScript的高級知識
遞歸
什么是遞歸
在程序中偎球,所謂的遞歸,就是函數(shù)自己直接或間接的調(diào)用自己辑甜。調(diào)用自己分兩種:
1 .直接調(diào)用自己
2 .間接調(diào)用自己
就遞歸而言最重要的就是跳出結(jié)構(gòu)衰絮,因為跳出了才可以有結(jié)果.
化歸思想
化歸思想:將一個問題由難化易,由繁化簡磷醋,由復(fù)雜化簡單的過程稱為化歸猫牡,它是轉(zhuǎn)化和歸結(jié)的簡稱。
遞歸思想就是將一個問題轉(zhuǎn)換為一個已解決的問題來實現(xiàn)
假如有一個函數(shù)f, 如果它是遞歸函數(shù)的話, 那么也就是說函數(shù)體內(nèi)的問題還是轉(zhuǎn)換為 f的形式.
function f() {
... f( ... ) ...
}
例子
1, 2, 3, 4, 5, ..., 100 求和
1 首先假定遞歸函數(shù)已經(jīng)寫好, 假設(shè)是foo. 即foo(100)就是求1到100的和
2 尋找遞推關(guān)系. 就是n與n-1, 或n-2之間的關(guān)系:foo( n ) == n + foo( n - 1 )
var res = foo(100);
var res = foo(99) + 100;
將遞推結(jié)構(gòu)轉(zhuǎn)化為遞歸體
function foo(n){
return n + foo( n - 1 );
}
上面就是利用了化歸思想:
將 求 100 轉(zhuǎn)換為 求 99
將 求 99 轉(zhuǎn)換為 求 98
...
將求 2 轉(zhuǎn)換為 求 1
求 1 結(jié)果就是 1
即: foo( 1 ) 是 1
**將臨界條件加到遞歸體中(求1的結(jié)果為1)
function foo( n ) {
if ( n == 1 ) return 1;
return n + foo( n - 1 );
}
練習(xí)
求 1, 3, 5, 7, 9, ... 第n項的結(jié)果與前n項和. 序號從0開始
先看求第n項
1 首先假定遞歸函數(shù)已經(jīng)寫好, 假設(shè)是fn. 那么第n項就是fn(n)
2 找遞推關(guān)系:fn(n) == f(n-1) + 2
3 遞歸體
function fn(n) {
return fn(n-1) + 2;
}
1 找臨界條件
求 n -> n-1
求 n-1 -> n-2
...
求 1 -> 0
求 第 0 項, 就是 1
加入臨界條件
function fn( n ) {
if ( n == 0 ) return 1;
return fn( n-1 ) + 2;
}
再看求前n項和
1 假設(shè)已完成, sum( n ) 就是前 n 項和
2 找遞推關(guān)系: 前 n 項和 等于 第 n 項 + 前 n-1 項的和
3 遞歸體
function sum( n ) {
return fn( n ) + sum( n - 1 );
}
1 找臨界條件
- n == 1結(jié)果為 1
2 加入臨界條件
function sum( n ) {
if (n == 0) return 1;
return fn(n) + sum(n - 1);
}
練習(xí)
2, 4, 6, 8, 10 第 n 項與 前 n 項和
解題方法和上一題一樣邓线。
練習(xí)
現(xiàn)有數(shù)列: 1, 1, 2, 4, 7, 11, 16, … 求 第 n 項, 求前 n 項和.
求第n項`
1 假設(shè)已經(jīng)得到結(jié)果 fn, fn( 10 ) 就是第 10 項
2 找遞推關(guān)系
0, 1 => fn( 0 ) + 0 = fn( 1 )
1, 2 => fn( 1 ) + 1 = fn( 2 )
2, 3 => fn( 2 ) + 2 = fn( 3 )
...
n-1, n => fn( n-1 ) + n - 1 = fn( n )
3 遞歸體也就清楚了
4 臨界條件是 n == 0 => 1
function fn( n ) {
if ( n == 0 ) return 1;
return fn( n-1 ) + n - 1;
}
前n項和
function sum( n ) {
if ( n == 0 ) return 1;
return sum( n - 1 ) + fn( n );
}
練習(xí)
Fibonacci 數(shù)列: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, … 求其第 n 項.
遞推關(guān)系:fn(n) == fn(n-1) + fn(n - 2)
function fib( n ) {
if ( n == 0 || n == 1 ) return 1;
return fib( n - 1 ) + fib( n - 2 );
}
練習(xí)
階乘:一個數(shù)字的階乘表示的是從 1 開始 累乘到這個數(shù)字. 例如 3! 表示 1 2 3. 5! 就是 1 2 3 4 5. 規(guī)定 0 沒有階乘, 階乘從1開始淌友。
求n的階乘
function foo ( n ) {
if ( n == 1 ) return 1;
return foo( n - 1 ) * n;
}
練習(xí)
求冪
求冪就是求 某一個數(shù) 幾次方
2*2 2 的 平方, 2 的 2 次方
求 n 的 m 次方
最終要得到一個函數(shù) power( n, m )
n 的 m 次方就是 m 個 n 相乘 即 n 乘以 (m-1) 個 n 相乘
function power ( n, m ) {
if ( m == 1 ) return n;
return power( n, m - 1 ) * n;
}
詞法作用域
域煌恢,表示的是一個范圍,作用域震庭,就是作用范圍瑰抵。
作用域說明的是一個變量可以在什么地方被使用,什么地方不能被使用器联。
塊級作用域
JavaScript中沒有塊級作用域
{
var num = 123;
{
console.log( num );
}
}
console.log( num );
上面這段代碼在JavaScript中是不會報錯的二汛,但是在其他的編程語言中(C#、C拨拓、JAVA)會報錯肴颊。
這是因為,在JavaScript中沒有塊級作用域渣磷,使用{}標(biāo)記出來的代碼塊中聲明的變量num婿着,是可以被{}外面訪問到的。
但是在其他的編程語言中醋界,有塊級作用域竟宋,那么{}中聲明的變量num,是不能在代碼塊外部訪問的形纺,所以報錯袜硫。
詞法作用域
什么是詞法作用域?
詞法( 代碼 )作用域, 就是代碼在編寫過程中體現(xiàn)出來的作用范圍. 代碼一旦寫好, 不用執(zhí)行, 作用范圍就已經(jīng)確定好了. 這個就是所謂詞法作用域.
在 js 中詞法作用域規(guī)則:
函數(shù)允許訪問函數(shù)外的數(shù)據(jù).
整個代碼結(jié)構(gòu)中只有函數(shù)可以限定作用域.
作用域規(guī)則首先使用提升規(guī)則分析
如果當(dāng)前作用規(guī)則中有名字了, 就不考慮外面的名字
例子1:
var num = 123;
function foo() {
console.log( num );
}
foo();
例子2:
if ( false ) {
var num = 123;
}
console.log( num ); // undefiend
例子3:
var num = 123;
function foo() {
var num = 456;
function func() {
console.log( num );
}
func();
}
foo();
練習(xí):
var num1 = 123;
function foo1() {
var num1 = 456;
function foo2() {
num1 = 789;
function foo3 () {
console.log( num1 );
}
foo3();
}
foo2();
}
foo1();
console.log( num1 );
面試題
var num = 123;
function func1(){
console.log(num);
}
function func2(){
var num = 456;
func1();
}
變量名提升
JavaScript是解釋型的語言挡篓,但是他并不是真的在運行的時候逐句的往下解析執(zhí)行婉陷。
我們來看下面這個例子:
func();
function func(){
alert("Funciton has been called");
}
在上面這段代碼中,函數(shù)func的調(diào)用是在其聲明之前官研,如果說JavaScript代碼真的是逐句的解析執(zhí)行秽澳,那么在第一句調(diào)用的時候就會出錯,然而事實并非如此戏羽,上面的代碼可以正常執(zhí)行担神,并且alert出來Function has been called。
所以始花,可以得出結(jié)論妄讯,JavaScript并非僅在運行時簡簡單單的逐句解析執(zhí)行!
JavaScript 預(yù)解析
JavaScript引擎在對JavaScript代碼進(jìn)行解釋執(zhí)行之前酷宵,會對JavaScript代碼進(jìn)行預(yù)解析亥贸,在預(yù)解析階段,會將以關(guān)鍵字var和function開頭的語句塊提前進(jìn)行處理浇垦。
關(guān)鍵問題是怎么處理呢炕置?
當(dāng)變量和函數(shù)的聲明處在作用域比較靠后的位置的時候,變量和函數(shù)的聲明會被提升到作用域的開頭。
重新來看上面的那段代碼
func();
function func(){
alert("Funciton has been called");
}
由于JavaScript的預(yù)解析機制朴摊,上面的代碼就等效于:
function func(){
alert("Funciton has been called");
}
func();
看完函數(shù)聲明的提升默垄,再來看一個變量聲明提升的例子:
alert(a);
var a = 1;
由于JavaScript的預(yù)解析機制,上面這段代碼甚纲,alert出來的值是undefined
口锭,如果沒有預(yù)解析,代碼應(yīng)該會直接報錯a is not defined
介杆,而不是輸出值讹弯。
Wait a minute,
不是說要提前的嗎?那不是應(yīng)該alert出來1这溅,為什么是undefined?
行為 | 說明 |
---|---|
聲明 | 告訴編譯器/解析器有這個變量存在,這個行為是不分配內(nèi)存空間的,在JavaScript中,聲明一個變量的操作為:var a; |
定義 | 為變量分配內(nèi)存空間棒仍,在C語言中悲靴,一般聲明就包含了定義,比如:int a;,但是在JavaScript中莫其,var a;這種形式就只是聲明了癞尚。 |
初始化 | 在定義變量之后,系統(tǒng)為變量分配的空間內(nèi)存儲的值是不確定的乱陡,所以需要對這個空間進(jìn)行初始化浇揩,以確保程序的安全性和確定性 |
賦值 | 賦值就是變量在分配空間之后的某個時間里,對變量的值進(jìn)行的刷新操作(修改存儲空間內(nèi)的數(shù)據(jù)) |
那么在這里有必要說一下聲明憨颠、定義胳徽、初始化的區(qū)別。其實這幾個概念是C系語言的人應(yīng)該都比較了解的爽彤。
行為 | 說明 |
---|---|
聲明 | 告訴編譯器/解析器有這個變量存在,這個行為是不分配內(nèi)存空間的,在JavaScript中养盗,聲明一個變量的操作為:var a; |
定義 | 為變量分配內(nèi)存空間,在C語言中适篙,一般聲明就包含了定義往核,比如:int a;,但是在JavaScript中,var a;這種形式就只是聲明了嚷节。 |
初始化 | 在定義變量之后聂儒,系統(tǒng)為變量分配的空間內(nèi)存儲的值是不確定的,所以需要對這個空間進(jìn)行初始化硫痰,以確保程序的安全性和確定性 |
賦值 | 賦值就是變量在分配空間之后的某個時間里衩婚,對變量的值進(jìn)行的刷新操作(修改存儲空間內(nèi)的數(shù)據(jù)) |
所以我們說的提升,是聲明的提升效斑。
那么再回過頭看谅猾,上面的代碼就等效于:
var a; //這里是聲明
alert(a);//變量聲明之后并未有初始化和賦值操作,所以這里是 undefined
a = 1;
復(fù)雜點的情況分析
通過上一小節(jié)的內(nèi)容,我們對變量税娜、函數(shù)聲明提升已經(jīng)有了一個最基本的理解坐搔。那么接下來,我們就來分析一些略復(fù)雜的情況敬矩。
函數(shù)同名
觀察下面這段代碼:
func1();
function func1(){
console.log('This is func1');
}
func1();
function func1(){
console.log('This is last func1');
}
輸出結(jié)果為:
This is last func1
This is last func1
原因分析:由于預(yù)解析機制概行,func1的聲明會被提升,提升之后的代碼為:
function func1(){
console.log('This is func1');
}
function func1(){
console.log('This is last func1');
}
func1();
func1();
同名的函數(shù)弧岳,后面的會覆蓋前面的凳忙,所以兩次輸出結(jié)果都是This is last func1。
變量和函數(shù)同名
alert(foo);
function foo(){}
var foo = 2;
當(dāng)出現(xiàn)變量聲明和函數(shù)同名的時候禽炬,只會對函數(shù)聲明進(jìn)行提升涧卵,變量會被忽略。所以上面的代碼的輸出結(jié)果為
function foo(){}
我們還是來吧預(yù)解析之后的代碼展現(xiàn)出來:
function foo(){};
alert(foo);
foo = 2;
再來看一種
var num = 1;
function num () {
alert( num );
}
num();
代碼執(zhí)行結(jié)果為:
Uncaught TypeError: num is not a function
直接上預(yù)解析后的代碼:
function num(){
alert(num);
}
num = 1;
num();
預(yù)解析是分作用域的
聲明提升并不是將所有的聲明都提升到window對象下面腹尖,提升原則是提升到變量運行的環(huán)境(作用域)中去柳恐。
function showMsg()
{
var msg = 'This is message';
}
alert(msg); // msg未定義
還是直接把預(yù)解析之后的代碼寫出來:
function showMsg()
{
var msg;
msg = 'This is message';
}
alert(msg); // msg未定義
預(yù)解析是分段的
分段,其實就分script標(biāo)簽的
<script>
func(); // 輸出 AA2;
function func(){
console.log('AA1');
}
function func(){
console.log('AA2');
}
</script>
<script>
function func(){
console.log('AA3');
}
</script>
在上面代碼中热幔,第一個script標(biāo)簽中的兩個func進(jìn)行了提升乐设,第二個func覆蓋了第一個func,但是第二個script標(biāo)簽中的func并沒有覆蓋上面的第二個func绎巨。所以說預(yù)解析是分段的近尚。
tip:但是要注意,分段只是單純的針對函數(shù)场勤,變量并不會分段預(yù)解析戈锻。
函數(shù)表達(dá)式并不會被提升
func();
var func = function(){
alert("我被提升了");
};
這里會直接報錯,func is not a function
和媳,原因就是函數(shù)表達(dá)式舶沛,并不會被提升。只是簡單地當(dāng)做變量聲明進(jìn)行了處理窗价,如下:
var func;
func();
func = function(){
alert("我被提升了");
}
條件式函數(shù)聲明
console.log(typeof func);
if(true){
function(){
return 1;
}
}
console.log(typeof func);
上面這段代碼如庭,就是所謂的條件式函數(shù)聲明,這段代碼在Gecko引擎中打印"undefined"
撼港、"function"
坪它;而在其他瀏覽器中則打印"function"
、"function"
帝牡。
原因在于Gecko加入了ECMAScript以外的一個feature:條件式函數(shù)聲明往毡。
Conditionally created functions Functions can be conditionally declared, that is, a function declaration can be nested within an if statement.
Note: Although this kind of function looks like a function declaration, it is actually an expression (or statement), since it is nested within another statement. See differences between function declarations and function expressions.
Note中的文字說明,條件式函數(shù)聲明的處理和函數(shù)表達(dá)式的處理方式一樣靶溜,所以條件式函數(shù)聲明沒有聲明提升的特性开瞭。
作用域鏈
什么是作用域鏈
只有函數(shù)可以制造作用域結(jié)構(gòu)懒震, 那么只要是代碼,就至少有一個作用域, 即全局作用域嗤详。
凡是代碼中有函數(shù)个扰,那么這個函數(shù)就構(gòu)成另一個作用域。如果函數(shù)中還有函數(shù)葱色,那么在這個作用域中就又可以誕生一個作用域递宅。
將這樣的所有的作用域列出來,可以有一個結(jié)構(gòu): 函數(shù)內(nèi)指向函數(shù)外的鏈?zhǔn)浇Y(jié)構(gòu)苍狰。就稱作作用域鏈办龄。
例如:
function f1() {
function f2() {
}
}
var num = 456;
function f3() {
function f4() {
}
}
繪制作用域鏈的步驟:
1 看整個全局是一條鏈, 即頂級鏈, 記為 0 級鏈
2 看全局作用域中, 有什么成員聲明, 就以方格的形式繪制到 0 級練上
3 再找函數(shù), 只有函數(shù)可以限制作用域, 因此從函數(shù)中引入新鏈, 標(biāo)記為 1 級鏈
4 然后在每一個 1 級鏈中再次往復(fù)剛才的行為
變量的訪問規(guī)則
首先看變量在第幾條鏈上, 在該鏈上看是否有變量的定義與賦值, 如果有直接使用
如果沒有到上一級鏈上找( n - 1 級鏈 ), 如果有直接用, 停止繼續(xù)查找.
如果還沒有再次往上剛找... 直到全局鏈( 0 級 ), 還沒有就是 is not defined
注意,同級的鏈不可混合查找
練習(xí):繪制作用域鏈
function f1() {
var num = 123;
function f2() {
console.log( num );
}
f2();
}
var num = 456;
f1();
如何分析代碼
- 在分析代碼的時候切記從代碼的運行進(jìn)度上來分析, 如果代碼給變量賦值了, 一定要標(biāo)記到圖中
- 如果代碼比較復(fù)雜, 可以在圖中描述代碼的內(nèi)容, 有事甚至需要將原型圖與作用域圖合并分析
練習(xí)
var num = 123;
function f1() {
console.log( num );
}
function f2() {
var num = 456;
f1();
}
f2();
練習(xí)
var num = 123;
function f1() {
console.log( num );
}
function f2() {
num = 456;
f1();
}
f2();
補充
聲明變量使用var
, 如果不使用var
聲明的變量就是全局變量( 禁用 )
因為在任何代碼結(jié)構(gòu)中都可以使用該語法. 那么再代碼維護(hù)的時候會有問題. 所以除非特殊原因不要這么用.
下面的代碼的錯誤
function foo () {
var i1 = 1 // 局部
i2 = 2, // 全局
i3 = 3; // 全局
}
此時注意
var arr = [];
for ( var i = 0; i < 10; i++ ) {
arr.push( i );
}
for ( var i = 0; i < 10; i++ ) {
console.log( arr[ i ] );
}
// 一般都是將變量的聲明全部放到開始的位置, 避免出現(xiàn)因為提升而造成的錯誤
var arr = [],
i = 0;
for ( ; i < 10; i++ ) {
arr.push( i );
}
for ( i = 0; i < 10; i++ ) {
console.log( arr[ i ] );
}
閉包
閉包的概念
閉包從字面意思理解就是閉合, 包起來.
簡單的來說閉包就是,一個具有封閉的對外不公開的, 包裹結(jié)構(gòu), 或空間.
在JavaScript中函數(shù)可以構(gòu)成閉包. 一般函數(shù)是一個代碼結(jié)構(gòu)的封閉結(jié)構(gòu), 即包裹的特性, 同時根據(jù)作用域規(guī)則, 只允許函數(shù)訪問外部的數(shù)據(jù), 外部無法訪問函數(shù)內(nèi)部的數(shù)據(jù), 即封閉的對外不公開的特性. 因此說函數(shù)可以構(gòu)成閉包.
閉包要解決什么問題?
- 閉包內(nèi)的數(shù)據(jù)不允許外界訪問
- 要解決的問題就是間接訪問該數(shù)據(jù)
函數(shù)就可以構(gòu)成閉包, 要解決的問題就是訪問到函數(shù)內(nèi)部的數(shù)據(jù)
我們觀察下面的函數(shù)foo
淋昭,在foo
內(nèi)部有一個變量num
俐填,能否在函數(shù)外部訪問到這個變量num
呢?
function foo () {
var num = 123;
return num;
}
var res = foo();
console.log( res ); // => 123
分析:
在上面的代碼中翔忽,確實可以訪問到num
這個函數(shù)內(nèi)部的變量英融。但是能不能多次訪問呢?
不能呀打,因為每次訪問都得重新調(diào)用一次foo
函數(shù),每次調(diào)用都會重新創(chuàng)建一個num = 123
糯笙,然后返回贬丛。
解決思路
函數(shù)內(nèi)的數(shù)據(jù)不能直接在函數(shù)外被訪問,是因為作用域的關(guān)系给涕,上級作用域不能直接訪問下級作用域中的數(shù)據(jù)豺憔。
但是如果反過來,下級作用域可以直接訪問上級作用域中的數(shù)據(jù)够庙。那么如果在函數(shù)foo內(nèi)定義一個函數(shù)恭应,那么在這個內(nèi)部函數(shù)中是可以直接訪問foo中的num的。
function foo() {
var num = Math.random();
function func() {
return num;
}
return func;
}
var f = foo();
// f可以直接訪問num耘眨,而且多次訪問昼榛,訪問的也是同一個,并不會返回新的num
var res1 = f();
var res2 = f();
如何獲得超過一個數(shù)據(jù)
函數(shù)的返回值只能有一個剔难,那按照上面的方法胆屿,我們只能對函數(shù)內(nèi)部的一個數(shù)據(jù)進(jìn)行操作。怎么操作函數(shù)內(nèi)的多個數(shù)據(jù)呢偶宫?
可以使用對象非迹,代碼如下:
function foo () {
var num1 = Math.random();
var num2 = Math.random();
//可以將多個函數(shù)包含在一個對象內(nèi)進(jìn)行返回,這樣就能在函數(shù)外部操作當(dāng)前函數(shù)內(nèi)的多個變量
return {
num1: function () {
return num1;
},
num2: function () {
return num2;
}
}
}
如何完成讀取一個數(shù)據(jù)和修改這個數(shù)據(jù)
前面講的都是如何去獲取函數(shù)內(nèi)部的數(shù)據(jù)纯趋,接下來我們考慮如何修改函數(shù)內(nèi)部的數(shù)據(jù)憎兽。
同樣冷离,也是使用內(nèi)部的函數(shù)進(jìn)行操作。
function foo() {
var num = Math.random();
//分別定義get和set函數(shù)纯命,使用對象進(jìn)行返回
return {
//get_num負(fù)責(zé)獲取數(shù)據(jù)
get_num: function() {
return num;
},
//set_num負(fù)責(zé)設(shè)置數(shù)據(jù)
set_num: function(value) {
num = value;
}
}
}
閉包的基本結(jié)構(gòu)
一般閉包要解決的的問題就是要想辦法間接的獲得函數(shù)內(nèi)數(shù)據(jù)的使用權(quán). 那么我們的可以總結(jié)出一個基本的使用模型.
- 寫一個函數(shù), 函數(shù)內(nèi)定義一個新函數(shù), 返回新函數(shù), 用新函數(shù)獲得函數(shù)內(nèi)的數(shù)據(jù)
- 寫一個函數(shù), 函數(shù)內(nèi)定義一個對象, 對象中綁定多個函數(shù)( 方法 ), 返回對象, 利用對象的方法訪問函數(shù)內(nèi)的數(shù)據(jù)
函數(shù)的四種調(diào)用模式
函數(shù)模式
特征:就是一個簡單的函數(shù)調(diào)用西剥,函數(shù)名前面沒有任何的引導(dǎo)內(nèi)容
function foo(){}
var func = function(){}
foo();
func();
(function(){})();
this
在函數(shù)模式中的含義: this
在函數(shù)中表示全局對象,在瀏覽器中是window對象
方法模式
特征: 方法一定是依附于一個對象, 將函數(shù)賦值給對象的一個屬性, 那么就成為了方法.
function f() {
this.method = function () {};
}
var o = {
method: function () {}
}
this
在方法模式調(diào)用中的含義:表示函數(shù)所依附的這個對象
構(gòu)造器調(diào)用模式
由于構(gòu)造函數(shù)只是給 this 添加成員. 沒有做其他事情. 而方法也可以完成這個操作, 就 this 而言, 構(gòu)造函數(shù)與方法沒有本質(zhì)區(qū)別.
特征:使用 new 關(guān)鍵字, 來引導(dǎo)構(gòu)造函數(shù).
function Person(){
this.name = "zhangsan";
this.age = 19;
this.sayHello = function(){
};
}
var p = new Person();
構(gòu)造函數(shù)中發(fā)this
與方法中一樣, 表示對象, 但是構(gòu)造函數(shù)中的對象是剛剛創(chuàng)建出來的對象
關(guān)于構(gòu)造函數(shù)中return關(guān)鍵字的補充說明
構(gòu)造函數(shù)中不需要return, 就會默認(rèn)的return this
如果手動的添加return, 就相當(dāng)于 return this
如果手動的添加return 基本類型; 無效, 還是保留原來 返回this
如果手動添加return null; 或return undefiend, 無效
如果手動添加return 對象類型; 那么原來創(chuàng)建的this就會被丟掉, 返回的是 return后面的對象
創(chuàng)建對象的模式
工廠方法
// 工廠就是用來生產(chǎn)的, 因此如果函數(shù)創(chuàng)建對象并返回, 就稱該函數(shù)為工廠函數(shù)
function createPerson( name, age, gender ) {
var o = {};
o.name = name;
o.age = age;
o.gender = gender;
return o;
}
// document.createElement()
構(gòu)造方法
function Person(name, age, gender){
this.name = name;
this.age = age;
this.gender = gender;
}
var p = new Person("zhangsan", 19, "男");
寄生式創(chuàng)建對象
function Person(name, age, gender){
var o = {};
o.name = name;
o.age = age;
o.gender = gender;
return o;
}
var p = new Person("Jack", 18, "male");
混合式創(chuàng)建
混合式繼承就是講所有的屬性放在構(gòu)造方法里面扎附,然后講所有的方法放在原型里面蔫耽,使用構(gòu)造方法和原型配合起來創(chuàng)建對象。
上下文調(diào)用模式
上下文(Context)留夜,就是函數(shù)調(diào)用所處的環(huán)境匙铡。
上下文調(diào)用,也就是自定義設(shè)置this
的含義碍粥。
在其他三種調(diào)用模式中鳖眼,函數(shù)/方法在調(diào)用的時候,this
的值都是指定好了的嚼摩,我們沒辦法自己進(jìn)行設(shè)置钦讳,如果嘗試去給this
賦值,會報錯枕面。
上下文調(diào)用的語法
//第一種愿卒, apply
函數(shù)名.apply(對象, [參數(shù)]);
//第二種, call
函數(shù)名.call(對象, 參數(shù));
//上面兩種方式的功能一模一樣潮秘,只是在傳遞參數(shù)的時候有差異琼开。
功能描述:
語法中的函數(shù)名表示的就是函數(shù)本身,使用函數(shù)調(diào)用模式的時候枕荞,this默認(rèn)是全局對象
語法中的函數(shù)名也可以是方法(如:obj.method)柜候,在使用方法模式調(diào)用的時候,this默認(rèn)是指當(dāng)前對象
在使用apply和call的時候躏精,默認(rèn)的this都會失效渣刷,this的值由apply和call的第一個參數(shù)決定
補充說明
如果函數(shù)或方法中沒有this的操作, 那么無論什么調(diào)用其實都一樣.
如果是函數(shù)調(diào)用foo(), 那么有點像foo.apply( window ).
如果是方法調(diào)用o.method(), 那么有點像o.method.apply( o ).
參數(shù)問題
call
和apply
在沒有后面的參數(shù)的情況下(函數(shù)無參數(shù), 方法無參數(shù)) 是完全一樣的.
如下:
function foo() {
console.log( this );
}
foo.apply( obj );
foo.call( obj );
第一個參數(shù)的使用規(guī)則:
1 如果傳入的是一個對象, 那么就相當(dāng)于設(shè)置該函數(shù)中的 this 為參數(shù)
2 如果不傳入?yún)?shù), 或傳入 null. undefiend 等, 那么相當(dāng)于 this 默認(rèn)為 window
foo();
foo.apply();
foo.apply( null );
foo.call( undefined );
1 如果傳入的是基本類型, 那么 this 就是基本類型對應(yīng)的包裝類型的引用
number -> Number
boolean -> Boolean
string -> String
第二個參數(shù)的使用規(guī)則
在使用上下文調(diào)用的時候, 原函數(shù)(方法)可能會帶有參數(shù), 那么這個參數(shù)在上下文調(diào)用中使用第二個( 第 n 個 )參數(shù)來表示
function foo( num ) {
console.log( num );
}
foo.apply( null, [ 123 ] );
// 等價于
foo( 123 );
上下文調(diào)用模式的應(yīng)用
上下文調(diào)用只是能修改this
, 但是使用的最多的地方上是函數(shù)借用.
1. 將偽數(shù)組轉(zhuǎn)換為數(shù)組
傳統(tǒng)的做法:
var a = {};
a[ 0 ] = 'a';
a[ 1 ] = 'b';
a.length = 2;
// 使用數(shù)組自帶的方法 concat
// 如果參數(shù)中有數(shù)組會把參數(shù)數(shù)組展開
// 語法: arr.concat( 1, 2, 3, [ 4, [ 5 ] ] );
// 特點:不修改原數(shù)組
var arr = [];
var newArr = arr.concat( a );
由于a
是偽數(shù)組, 只是長得像數(shù)組. 所以上面的代碼不能成功,不能使用concat
方法矗烛。
但是apply
方法有一個特性, 可以將數(shù)組或偽數(shù)組作為參數(shù)辅柴。(IE8不支持偽數(shù)組操作)
foo.apply( obj, 偽數(shù)組 ); // IE8 不支持
利用apply
方法,可以寫出以下
//將偽數(shù)組 a 作為 apply 的第二個參數(shù)
var newArr = Array.prototype.concat.apply( [], a )
處理數(shù)組轉(zhuǎn)換, 實際上就是將元素一個一個的取出來構(gòu)成一個新數(shù)組, 凡是涉及到該操作的方法理論上都可以瞭吃。
push方法
//用法:
arr.push( 1 ); //將這個元素加到數(shù)組中, 并返回所加元素的個數(shù)
arr.push( 1, 2, 3 ); //將這三個元素依次加到數(shù)組中, 返回所加個數(shù)
var a = { length: 0 }; // 偽數(shù)組
a[ a.length++ ] = 'abc'; // a[ 0 ] = 'abc'; a.length++;
a[ a.length++ ] = 'def';
// 使用一個空數(shù)組, 將元素一個個放到數(shù)組中即可
var arr = [];
arr.push( a ); // 此時不會將元素展開, 而是將這個偽數(shù)組作為一個元素加到數(shù)組中
// 再次利用 apply 可以展開偽數(shù)組的特征
arr.push.apply( arr, a );
// 利用 apply 可以展開偽數(shù)組的特性, 這里就相當(dāng)于 arr.push( a[0], a[1] )
2. 求數(shù)組中的最大值
傳統(tǒng)的做法
var max = arr[ 0 ];
for ( var i = 1; i < arr.length; i++ ) {
if ( arr[ i ] > max ) {
...
}
}
在 js 中的Math對象中提供了很多數(shù)學(xué)函數(shù)Math.max( 1,2,3 )
還是利用 apply 可以展開數(shù)組的特性
var arr = [ 123456,12345,1234,345345,234,5 ];
Math.max.apply( null, arr );
3.借用構(gòu)造函數(shù)繼承
function Person ( name, age, gender ) {
this.name = name;
this.age = age;
this.gender = gender;
}
// 需要提供一個 Student 的構(gòu)造函數(shù)創(chuàng)建學(xué)生對象
// 學(xué)生也應(yīng)該有 name, age, gender, 同時還需要有 course 課程
function Student ( name, age, gender, course ) {
Person.call( this, name, age, gender );
this.course = course;
}