1 JS基本概念
1.1 JS簡介
-
前端三層
- HTML —— 結(jié)構(gòu)層從語義的角度描述頁面的結(jié)構(gòu) - CSS —— 樣式層從裝飾的角度描述頁面的樣式 - JavaScript —— 行為層從交互的角度描述頁面的行為
JS的組成:ECMAScript + BOM + DOM
JavaScript 作用:數(shù)據(jù)驗證格嘁、讀寫HTML元素、與瀏覽器窗口及內(nèi)容交互猿诸、網(wǎng)頁特效、WEB游戲制作、基于Node.js 技術(shù)進行服務(wù)器端編程
-
ECMAScript 版本
ECMAScript 1 (1997年06月:發(fā)布首版) ECMAScript 2 (1998年06月:版本變更) ECMAScript 3 (1999年12月:添加正則表達式、try/catch) ECMAScript 4 (放棄發(fā)布) ECMAScript 5 (2009年12月:添加嚴(yán)格模式固歪、JSON支持) ECMAScript 5.1 (2011年06月:版本變更) ECMAScript 6 (2015年06月:添加類和模塊) ES2015 ECMAScript 7 (2016年06月:增加指數(shù)運算符) ES2016 ECMAScript 8 (2017年06月:增新功能) ES2017 ECMAScript 9 (2018年06月:增新特性) ES2018 ECMAScript 10 (2019年06月:增新特性) ES2019 ECMAScript 11 (草案中) ES2020
1.2 JS引用
<div οnclick="alert('hello world')">點我</div> <!--內(nèi)部引用-->
<body>
...... <!--html代碼-->
<script>alert("hello world");</script> <!--內(nèi)嵌式-->
</body>
<head>
<script src="xxxx.js"></script> <!--外聯(lián)式-->
</head>
[注]
- 老版本的 script 標(biāo)簽中會添加 type="text/javascript" 屬性上沐,HTML5中已不必添加這一屬性。
<noscript>
元素:用于給早期不支持 JavaScript的瀏覽器作替換提示文本东臀;若瀏覽器支持腳本着饥,則會忽略注釋,不會顯示出 noscript 元素中的文本惰赋。
1.2.1 script元素屬性
- src:表示包含要執(zhí)行代碼的外部文件宰掉。
- type:表示編寫代碼使用的腳本語言的內(nèi)容類型(MIME類型),默認(rèn)值為text/javascript赁濒,非必需轨奄。(已替代廢棄的 language 屬性)
- charset:表示通過 src 屬性指定的代碼的字符集。
- async:表示應(yīng)該立即下載腳本拒炎,但不應(yīng)妨礙頁面中的其他操作挪拟,比如下載其他資源或 等待加載其他腳本。只對外部腳本文件有效枝冀。
- defer:表示腳本可以延遲到文檔完全被解析和顯示之后再執(zhí)行舞丛。只對外部腳本文件有效耘子。
延遲腳本與異步腳本
- 使用defer屬性用于表明腳本在執(zhí)行時不會影響頁面的構(gòu)造,腳本會被延遲到整個頁面都解析完畢后再運行球切,即在遇到</html>標(biāo)簽后再執(zhí)行谷誓。
- 使用async屬性是不讓頁面等待兩個腳本下載和執(zhí)行,從而異步加載頁面其他內(nèi)容
1.3 標(biāo)識符
- 第一個字符必須是字母吨凑、下劃線(_)捍歪、美元符號($),不能以數(shù)字開頭
- 其他字符可以是字母鸵钝、下劃線糙臼、美元符號或數(shù)字
- 不能使用JS中的關(guān)鍵字和保留字
- 區(qū)分大小寫
- 遵循駝峰原則
- 建議語義化命名
//正確
var abc;
var _abc;
var $abc;
var _abc1$;
function $$abc_(){}
//錯誤
var 1abc;
var *abc;
function #abc(){}
1.4 注釋
// 單行注釋
/* 多行塊級注釋 */
1.5 關(guān)鍵字和保留字
// 關(guān)鍵字
break do instanceof typeof default
case else new var if
catch finally return void throw
continue for switch while delete
in function this with try
debugger*
// 保留字(ver5,*為非嚴(yán)格模式下)
class* enum* extends* super* const* export* import*
abstract short debugger synchronized let
boolean interface static implements static
byte long protected int implements
char final native volatile protected
float package throws double yield
goto private transient public private
package public interface
1.6 輸出方式
//頁面直接輸出
document.write("sunck is a nice man");
//控制臺輸出
console.log("sunck is a good man");
//提示框輸出
alert("sunck is a handsome man");
//直接在HTML文檔中輸出內(nèi)容
document.write();
2 JS基本語法
2.0 常量
常量也稱字面量(literal)恩商,是一種固定值的表示方法变逃,即字面上的意思,看見什么就能認(rèn)出是什么怠堪,如數(shù)字揽乱、字符串、數(shù)組粟矿、json對象等類型的數(shù)據(jù)都有自己的字面量表示法凰棉。
數(shù)字字面量:常用的數(shù)學(xué)意義上的數(shù)字的表示法,書寫時不需要添加任何特殊符號陌粹,直接寫數(shù)字撒犀。
字符串字面量:字符串是人類說的語言、詞語掏秩,包括各種數(shù)字或舞、符號;由雙引號對或單引號對包圍蒙幻;字符串內(nèi)容被限定在同種引號之間嚷那,在字符串中某些字符需要用轉(zhuǎn)義字符表示,如\n(換行)杆煞、\t(制表符)、"(引號)
console.log("我會說English腐泻,考試拿第1"); //直接顯示引號里的內(nèi)容
console.log(1); //表示字面量1
console.log("1"); //表示字符串1
console.log("這是\n一個引號\"和一個反斜杠\\"); //轉(zhuǎn)義字符
2.1 變量
var message; //定義了一個名為message的變量
var message = "hi"; //定義message變量的同時賦值保存一個字符串值"hi"
var message = "hi", found = false, age = 29;
//使用一條語句决乎,定義多個變量
-
變量(Variables)
- 相當(dāng)于一個容器,可用來保存任何類型的數(shù)據(jù)
- ECMAScript的變量為
松散類型
- 變量名必須按照
標(biāo)識符命名規(guī)范
-
變量的聲明
- 即變量的定義派桩,表示可以往里面存儲數(shù)據(jù)
- 變量必須先聲明(使用關(guān)鍵字
var
)才能使用 - 用 var 聲明一個未賦初值的變量构诚,它的值會提前被設(shè)定為 undefined
- 若試圖訪問一個沒有聲明的變量會顯示RefersnceErmor錯誤
- 對已經(jīng)賦值的變量重新聲明,該變量的值不會丟失
-
變量聲明提升(hoisting)
- JS變量的另一特別之處是變量可以引用稍后聲明铆惑,而不會引發(fā)異常
- 變量會被“舉起”或提升到所有函數(shù)和語句之前范嘱,提升后的變量將返回undefined值送膳,所以即使在使用或引用某個變量之后存在聲明和初始化操作,仍將得到undefined值丑蛤,但變量的提升叠聋,只提升定義,不提升賦值受裹,相當(dāng)于引用的時候只定義碌补,沒賦值,輸出undefined
console.log(a); //先使用
var a = 125; //后定義
//相當(dāng)于
var a; //先定義
console.log(a); //再使用
a = 125; //后賦值
2.2 數(shù)據(jù)類型
JS的數(shù)據(jù)類型是一種動態(tài)的數(shù)據(jù)類型棉饶,體現(xiàn)在變量上厦章,變量的數(shù)據(jù)類型是隨著內(nèi)部存儲數(shù)據(jù)的類型變化而變化的,變量的數(shù)據(jù)類型照藻,就是它內(nèi)部存儲的數(shù)據(jù)的數(shù)據(jù)類型袜啃,且數(shù)據(jù)類型之間也可以進行相互轉(zhuǎn)換
2.2.1 Undefined 類型
var message;
alert(message == undefined); //true
- Undefined 類型只有一個值,即在使用 var 聲明變量但未對其加以初始化時幸缕, 其變量值就是 undefined
- 定義為undefined的變量與尚未定義的變量是不一樣的
- 對未初始化和未聲明的變量執(zhí)行 typeof 操作符都會返回 undefined 值
2.2.2 Null 類型
var car = null;
alert(typeof car); // "object"
- Null 類型只有一個值群发,表示一個==空對象指針==,使用typeof操作符檢測null值時會返回 "object"
- 如果定義的變量準(zhǔn)備在將來用于保存對象冀值,那么最好將該變量初始化為 null 而不是其他值挺狰,這樣一來只要直接檢查 null 值就可以知道相應(yīng)的變量是否已經(jīng)保存了一個對象的引用
- 實際上,undefined 值是派生自 null 值的
- ==可以通過將變量的值設(shè)置為 null 來清空變量==
2.2.3 Boolean 類型
var booleanValue = true;
alert(typeof booleanValue); // "Boolean"
布爾類型只有兩個字面值 —— true 和 false豹爹,但ECMAScript中所有類型的值都有與這兩個 Boolean 值等價的值俺附。要將一個值轉(zhuǎn)換為其對應(yīng)的 Boolean 值,可以調(diào)用 轉(zhuǎn)型函數(shù) Boolean( ) 抵栈,如下例所示:
數(shù)據(jù)類型 | 轉(zhuǎn)換為true的值 | 轉(zhuǎn)換為false的值 |
---|---|---|
Boolean | true | false |
String | 任何非空字符串 | "" (空字符串) |
Number | 任何非零數(shù)字值(包括∞) | 0 和 NaN |
Object | 任何對象 | null |
Undefined | N/A(不適用) | undefined |
2.2.4 Number 類型
/*-------------- 整數(shù)字面量 -----------*/
//十進制:最基本的數(shù)值字面量格式告材,直接在代碼中輸入
console.log(100);
console.log(-12);
//八進制:前綴為0、0O古劲、0o斥赋,若以0為前綴,超出取值范圍會直接轉(zhuǎn)換微十進制顯示产艾;若以0o/0O為前綴疤剑,超出取值范圍直接顯示報錯
console.log(071); //表示為八進制數(shù)(71)
console.log(089); //表示為十進制數(shù)(89)
console.log(0o89); console.log(0O89); //顯示出錯
//十六進制:前綴為0x、0X闷堡,超出取值范圍會直接報錯隘膘,字母可大小寫
console.log(0x5c);
console.log(0x5g); //顯示出錯
/*-------------- 浮點數(shù)字面量 -----------*/
//所有的浮點數(shù)都是以十進制表示
console.log(3.14159);
console.log(10.0); //被解析為整數(shù)10
console.log(0.618);
console.log(.618); //表示0.618
console.log(1.); //小數(shù)點后面沒有數(shù)字會被解析為1
console.log(-.618); //表示-0.618
console.log(42356.89);
console.log(0.4235689e5);//e5表示×10^5,結(jié)果依然為42356.89
/*-------------- 特殊值 -----------*/
//無窮:計算機計算能力有限杠览,如果大于或小于某個臨界點弯菊,計算機沒法算出具體數(shù)字,會直接輸出是一個Infinity或-Infinity踱阿,無窮值無法繼續(xù)參與下一次的計算
console.log(8.6e987654322344677807); //顯示Infinity
console.log(-8.6e98765056760544387); //顯示-Infinity
//NaN(not a number):不是一個數(shù)管钳,說明這個數(shù)字已經(jīng)沒辦法正常數(shù)值形式表示钦铁,不是一個正常意義的數(shù)字,但依然是一個數(shù)字字面量
console.log(0/0); //顯示NaN
console.log(12/0); //顯示NaN
- 進行算術(shù)計算時才漆,所有八進制和十六進制表示的數(shù)值 都將被轉(zhuǎn)換成十進制數(shù)值
- 浮點數(shù)值的最高精度是==17位小數(shù)==牛曹,但有些浮點數(shù)值計算會產(chǎn)生舍入誤差的問題,這是使用基于IEEE754數(shù)值的浮點計算的通病栽烂,也會出現(xiàn)在其他語言上
2.2.5 String 類型
-
字符串:用于表示由零或多個16位Unicode字符組成的字符序列
var firstName = "Nicholas"; var lastName = 'Zakas'; var lastName = 'Nicholas"; // 語法錯誤(左右引號必須匹配)
-
字符字面量:也叫轉(zhuǎn)義序列躏仇,用于表示非打印字符或其他用途
var text = "This is the letter sigma: \u2764."; //該轉(zhuǎn)義字符為? alert(text.length); //任何字符串的長度 可通過訪問其length屬性取得,輸出28
字符串一旦創(chuàng)建腺办,它們的值就不能改變焰手,要改變某個變量保存的字符串,首先要銷毀原來的字符串怀喉,然后再用另一個包含新值的字符串填充該變量书妻,如下
var lang = "Java"; lang = lang + "Script"; /* 把lang的值重新定義為"Java"與"Script"的組合,即"JavaScript" */
2.2.6 Object 類型
2.2.7 數(shù)據(jù)轉(zhuǎn)換
2.2.7.1 轉(zhuǎn)Number
// Number():用于任何數(shù)據(jù)類型
var a = Number(null), //0
b = Number(undefined), //NaN
c = Number(true), //1
d = Number(false), //0
e = Number("-8"), //-8
f = Number("+8"), //8
g = Number("000011"), //11 (忽略0)
h = Number("0xf"), //15 (16轉(zhuǎn)10進制)
i = Number(""), //0 (空白內(nèi)容)
j = Number("12+3"), //NaN(存在符號)
k = Number("12-3"), //NaN(存在符號)
l = Number("a123"), //NaN(存在字母)
m = Number("12.."), //NaN(存在小數(shù)點)
n = Number(" 123"), //123(忽略空格)
o = Number("Hello world!"), //NaN(存在字母躬拢、間空格躲履、符號)
// parseInt():僅能返回整數(shù),丟失小數(shù)部分聊闯,指定基數(shù)工猜,可進行進制轉(zhuǎn)換
var A = parseInt("123blue"), //123
B = parseInt(""), //NaN
C = parseInt("Infinity"), //NaN
D = parseInt("12abc"), //12(忽略字符)
E = parseInt("a12bc"), //NaN(頭部非數(shù)字,識別出錯)
F = parseInt("0xAF", 16), //175
G = parseInt("AF", 16), //175
H = parseInt("AF"), //NaN(指定基數(shù)會出錯)
I = parseInt("10", 2), //2 (按二進制解析)
J = parseInt("10", 8), //8 (按八進制解析)
K = parseInt("10", 10), //10 (按十進制解析)
L = parseInt("10", 16), //16 (按十六進制解析)
M = parseInt(22.5), //22
N = parseInt(5.2e10); //5
// parseFloat():將字符串轉(zhuǎn)為帶小數(shù)的浮點數(shù)
var A1 = parseFloat("12.23"), //12.23
A2 = parseFloat("12.23.34"),//12.23
A3 = parseFloat("12.23AAA"),//12.23(忽略尾部字符)
A4 = parseFloat("12.3+123");//12.3(忽略尾部字符)
2.2.7.2 轉(zhuǎn)String
// console.log():null與undefined類型直接用控制臺輸出變成String類型
console.log(null); //"null"
console.log("Hello"+null); //"Hellonull"
console.log(undefined); //"undefined"
console.log("Hello"+undefined); //"Helloundefined"
// toString()
var a = true;
var aStr = a.toSting(); //"true"
var b = 11;
var bStr = b.toSting(); //"11"
// String():能夠?qū)⑷魏晤愋偷闹缔D(zhuǎn)換為字符串
var W = 10;
var X = true;
var Y = null;
var Z; //未定義值 undefined
alert(String(W)); //"10"
alert(String(X)); //"true"
alert(String(Y)); //"null"
alert(String(Z)); //"undefined"
由于 null 和 undefined 沒有 toString() 方法菱蔬,所以只能使用String()函數(shù)
2.2.7.3 轉(zhuǎn)Boolean
// Boolean():能將大多數(shù)類型的值轉(zhuǎn)換為布爾值
// 絕大多數(shù)轉(zhuǎn)成布爾值篷帅,結(jié)果都為true
// 以下6種情況轉(zhuǎn)為boolean,結(jié)果為false
console.log(Boolean(0)); //false
console.1og(Boolean("")); //false
console.1og(Boolean(false)); //false
console.1og(Boolean(NaN)); //false
console.1og(Boolean(undefined));//false
console.log(Boolean(null)); //false
2.3 操作符
運算符(Operators)拴泌,也稱操作符魏身,是發(fā)起運算的最簡單形式
2.3.1 一元操作符
- 自加(
++
)自減(--
):放于前“先±1再取值”,放于后“先取值再±1” - 一元加(
+
)減(-
):相當(dāng)于正負(fù)號
2.3.2 加性操作符
- 加法(
+
) - 減法(
-
)
+
可以作連接符
進行字符串操作:("OX"+"456" → OX456)
2.3.3 乘性操作符
- 乘法(
*
) - 除法(
/
) - 求模取余(
%
)
2.3.4 賦值操作符
- 賦值運算符(
=
):把右側(cè)的值賦給左側(cè)的變量 - 復(fù)合賦值運算符:
加等+=
減等-=
乘等*=
除等/=
余等%=
左移等<<=
有符號右移等>>=
無符號右移等>>>=
var num = 10;
num = num + 10;
num += 10; // 等價于上一條語句蚪腐,復(fù)合賦值僅作簡化操作
復(fù)合賦值運算時箭昵,等號后面的部分是一個整體,如
y*=x-3
——>y=y*(x-3)
2.3.5 比較操作符
- 關(guān)系操作符(
>
<
>=
<=
) - 相等(
==
)不相等(!=
):只比較數(shù)值回季,不比較類型家制,比較前自動統(tǒng)一類型 - 全等(
===
)不全等(!==
):數(shù)值和類型都要比較
所有的比較操作符都會返回一個布爾值(true 與 false)
關(guān)系運算符不能連用。(例如 1 <= num <= 5 會存在語法錯誤)
2.3.6 位操作符
- 按位與(
~
) - 按位與(
&
) - 按位或(
|
) - 按位異或(
^
) - 左移(
<<
) - 有符號右移(
>>
) - 無符號右移(
>>>
)
2.3.7 布爾操作符
- 邏輯非(
!
):有非則反 - 邏輯與(
&&
):兩真才真 - 邏輯或(
||
):有真就真
對于 A&&B泡一,當(dāng) A 被認(rèn)定為 false慰丛,就不會再進行 B 的操作了
2.3.8 條件操作符
// (判斷條件)? 滿足輸出 : 不滿足輸出
var max = (num1 > num2) ? num1 : num2;
2.3.9 逗號操作符
使用逗號(,
)操作符可以在一條語句中執(zhí)行多個操作。
var num1=1, num2=2, num3=3; // 聲明多個變量
var num = (5,1,4,8,0); // num的值為0(多賦值時會返回表達式中的最后一項)
2.3.X 運算優(yōu)先級
從高到低:算術(shù)操作符 → 比較操作符 → 邏輯操作符 → "="賦值符號
同級運算:按從左到右次序進行瘾杭,多層括號由里向外
2.3.Ⅺ 隱式轉(zhuǎn)換
數(shù)字與純數(shù)字字符串字符串之間進行運算,除了+號之外哪亿,使用其他運算符會自動將字符串轉(zhuǎn)成數(shù)字粥烁,其他數(shù)據(jù)類型參與任何數(shù)學(xué)運算都會被隱式轉(zhuǎn)換贤笆,這個過程是私下進行,不需要使用parseInt等方法
console.log(12+"2"); //結(jié)果為122
console.log(12-"2"); //結(jié)果為10
console.log(12*"2"); //結(jié)果為24
console.log(12/"2"); //結(jié)果為6
console.log(12%"2"); //結(jié)果為0
console.log("12"%"2"); //結(jié)果為122
//其他隱式轉(zhuǎn)換
- 轉(zhuǎn)換為0 : "", null, false
- 轉(zhuǎn)換為1 : true
- 轉(zhuǎn)換為NaN : "非空字符串", undefined, NaN
- 轉(zhuǎn)換為Infinity : 情況由具體決定
2.4 語句
2.4.1 if-else語句
? ==條件判斷語句==讨阻,判斷condition返回的布爾值芥永,若是true執(zhí)行statement1,若是false則執(zhí)行statement2钝吮。
// if (condition) statement1 else statement2
if (i > 25)
alert("Greater than 25."); // 單行語句
else {
alert("Less than or equal to 25."); // 代碼塊{}中的語句
}
// if (condition1) statement1 else if (condition2) statement2 else statement3
if (i > 25) {
alert("Greater than 25.");
} else if (i < 0) { //多層嵌套 條件判斷
alert("Less than 0.");
} else {
alert("Between 0 and 25, inclusive.");
}
2.4.2 do-while 語句
? 一種==后測試循環(huán)語句==埋涧,即先執(zhí)行一次statement后,再判斷expression奇瘦,若返回false棘催,則重新執(zhí)行一次statement,直到返回true耳标,才能往下繼續(xù)執(zhí)行醇坝。
// do {statement} while (expression);
var i = 0;
do {
i += 2;
} while (i < 10);
alert(i);
2.4.3 while 語句
? 一種==前測試循環(huán)語句==,即先判斷expression次坡,若返回true則執(zhí)行一次statement呼猪,執(zhí)行完后重新判斷expression,若為false則跳過statement砸琅,再往下進行宋距。
// while (expression) statement
var count = 10;
var i = 0;
while (i < count) {
alert(i);
i++;
}
2.4.4 for 語句
? 另一種==前測試循環(huán)語句==,先執(zhí)行initialization來設(shè)定一個初始值症脂,再判斷expression谚赎,若返回true則去執(zhí)行statement,執(zhí)行完statement再去執(zhí)行post-loop-expression摊腋,然后重新判斷expression沸版,直到返回false才跳出循環(huán),往下執(zhí)行兴蒸。
// for (initialization; expression; post-loop-expression) statement
var count = 10;
for (var i = 0; i < count; i++){
alert(i);
} // 與前面while代碼塊執(zhí)行的功能相同
- for循環(huán)是while循環(huán)的另一種表達视粮,兩者作用相同;
- initialization可以不使用var關(guān)鍵字而在外部去定義橙凳;
- 由于ECMAScript中不存在塊級作用域蕾殴,因此在循環(huán)內(nèi)部定義的變量也可以在外部被訪問到;
- for 語句中的初始化表達式岛啸、控制表達式和循環(huán)后表達式都是可選(可省略)的钓觉,如 for ( , , )。
2.4.5 switch 語句
? 另一種==條件判斷分支語句==坚踩,設(shè)置一個變量 i荡灾,讓其值與結(jié)構(gòu)中的每個情況 case 值做比較,若存在匹配,則執(zhí)行該 case 關(guān)聯(lián)的代碼塊批幌,執(zhí)行完后使用 break 來跳出此 case同時阻止繼續(xù)運行下一個 case础锐,若無對應(yīng)值時直接執(zhí)行 default 關(guān)聯(lián)的代碼塊。
// switch (expression) {case A: statet1 break; case B: state2 break; default: state3}
switch (i) {
case 25:
alert("25");
break;
case 35:
alert("35");
break;
case 45:
alert("45");
break;
default:
alert("Other");
}
- expression可以使用任何數(shù)據(jù)類型荧缘,無論是字符串或是對象皆警;
- 每個 case 的值不一定是常量,可以是變量截粗,甚至是表達式信姓。
2.4.6 break 和 continue 語句
? 一種==斷點語句==,用于在循環(huán)中精確地控制代碼的執(zhí)行绸罗。
var num = 0;
for (var i=1; i < 10; i++) {
if (i % 5 == 0) {
break; //立即跳出循環(huán)意推,強制繼續(xù)執(zhí)行循環(huán)后面的語句
}
num++;
}
alert(num); // 輸出4
var num = 0;
for (var i=1; i < 10; i++) {
if (i % 5 == 0) {
continue; //結(jié)束本次循環(huán)后會從循環(huán)的頂部重新執(zhí)行
}
num++;
}
alert(num); // 輸出8
2.4.7 label 語句
? 一種==標(biāo)簽語句==,在代碼中添加標(biāo)簽从诲,標(biāo)簽可以在將來由 break 或 continue 語句引用左痢。一般都要與 for 語句等循環(huán)語句配合使用,從而返回代碼中特定的位置系洛。
// label: statement
var num = 0;
outermost:
for (var i=0; i < 10; i++) {
for (var j=0; j < 10; j++) {
if (i == 5 && j == 5) {
break outermost; /* 退出內(nèi)部循環(huán)(使用變量j循環(huán))俊性,也退出外部循環(huán)(使用變量i循環(huán))*/
}
num++;
}
}
alert(num); //55
var num = 0;
outermost:
for (var i=0; i < 10; i++) {
for (var j=0; j < 10; j++) {
if (i == 5 && j == 5) {
continue outermost; /* 強制繼續(xù)執(zhí)行循環(huán) —— 退出內(nèi)部循環(huán),執(zhí)行外部循環(huán) */
}
num++;
}
}
alert(num); //95
2.4.8 for-in 語句
? 一種精準(zhǔn)的==迭代語句==描扯,用來枚舉對象的屬性定页。
// for (property in expression) statement
for (var propName in window) {
document.write(propName);
}
/*
這里顯示了 BOM 中 window 對象的所有屬性(變量 in 對象)。即每次執(zhí)行循環(huán)時绽诚,都會將 window 對象中存在的一個屬性名賦值給變量 propName 典徊。這個過程會一直持續(xù)到對象中的所有屬性都被枚舉一遍為止。
*/
ECMAScript對象的屬性沒有順序恩够,因此不可預(yù)測的卒落。所有屬性都會被返回一次,但返回的先后次序可能會因瀏覽器而異蜂桶。
如果要迭代的對象是null或者是undefined儡毕,那么會拋出異常。
2.4.9 with 語句
? 將代碼的作用域設(shè)置到一個特定的對象中扑媚,目的主要是==為了簡化多次編寫同一個對象的工作==腰湾。
// with (expression) statement;
原程序 //(包含了多個location對象)
var qs = location.search.substring(1);
var hostName = location.hostname;
var url = location.href;
改為
with (location) { // 簡化
var qs = search.substring(1);
var hostName = hostname;
var url = href;
}
大量使用 with 語句會導(dǎo)致性能下降,同時也會給調(diào)試代碼造成困難疆股,因此在開發(fā)大型應(yīng)用程序時费坊,不建議使用 with 語句。
2.5 函數(shù)
函數(shù)可以封裝任意多條語句旬痹,在任何地方在附井、任何時候需要調(diào)用時執(zhí)行讨越。函數(shù)在定義時不必指定是否返回值,但可通過 return 語句來實現(xiàn)值的返回永毅。
function functionName (arg0, arg1, ...) {statements}
function sayHi(name, message) {
alert("Hello " + name + "," + message);
}
sayHi("Nicholas", "how are you today?");
/* 這個函數(shù)的輸出結(jié)果是 "Hello Nicholas,how are you today?" */
function sum (num1, num2) {return num1 + num2;}
var result = sum(5, 10);
function sum(num1, num2) {
return num1 + num2; // 執(zhí)行完return語句之后停止并立即退出
alert("Hello world"); // 永遠不會執(zhí)行
}
return語句也可以不帶有任何返回值(
return;
)谎痢,函數(shù)在停止執(zhí)行后將返回 undefined值,但推薦做法是要么讓函數(shù)始終都返回一個值卷雕,要么永遠都不要返回值
3 JS對象
3.1 Object
- 對象是單個事物的抽象,如一本書票从、一輛車漫雕、一張網(wǎng)頁;當(dāng)實物被抽象成對象峰鄙,實物間的關(guān)系就變成了對象間的關(guān)系浸间,從而可以模擬現(xiàn)實情況,針對對象進行編程吟榴,故“萬物皆對象”
- 對象是一個容器魁蒜,封裝了屬性(property)和方法(method),可簡單理解為數(shù)據(jù)集或功能集吩翻,是狀態(tài)和行為的集合兜看,是變量和函數(shù)的集合,是無序?qū)傩?/strong>的集合
- Object 是一個基礎(chǔ)類型狭瞎,其他所有類型都從 Object 繼承了基本的行為
- 對象在JS中被稱為引用類型的值细移,即每個對象都基于一個引用類型創(chuàng)建,可以是系統(tǒng)內(nèi)置的原生類型熊锭,或是開發(fā)人員自定義類型
- 對象的屬性的操作:增弧轧、刪、改碗殷、查精绎、遍歷
3.1.1 創(chuàng)建與訪問
//使用 new + Object()創(chuàng)建
var person = new Object();
person.name = "Nicholas";
person.age = 29;
//省略new操作符創(chuàng)建 //{}相當(dāng)于new Object() 的縮寫
var person = {};
person.name = "Nicholas";
person.age = 29;
//對象字面量表示法創(chuàng)建 // 再進一步縮寫 可省略對象名
var person = {
name : "Nicholas",
age : 29 //最后一個屬性后不能加逗號(,)
};
// 通過點.或中括號[]來訪問對象
console.log(person.name); //點表示法:更簡潔(推薦)
console.log(person["name"]); //方括號表示法:更方便,可直接通過變量名訪問
3.1.2 屬性與方法
- constructor 返回用于創(chuàng)建當(dāng)前對象的函數(shù)
hasOwnProperty( ) 用于檢查給定的屬性在當(dāng)前對象實例中是否存在
isPrototypeOf( ) 用于檢查傳入的對象是否是傳入對象的原型
propertyIsEnumerable( ) 用于檢查給定的屬性是否能夠使用 for-in 語句來枚舉
toString( ) 返回對象的字符串表示
toLocaleString( ) 返回對象的字符串表示锌妻,該字符串與執(zhí)行環(huán)境的地區(qū)對應(yīng)
valueOf( ) 返回對象的字符串代乃、數(shù)值或布爾值表示
復(fù)制變量值
復(fù)制引用類型的值,副本變量是一個指針(并非自身)从祝,原變量與副本都將引用同一個對象襟己,其一改變都會影響另一變量,因為ECMAScript 中所有函數(shù)的參數(shù)都是按值傳遞
的牍陌,即與復(fù)制變量值一樣
var obj1 = new Object();
var obj2 = obj1;
obj1.name = "Nicholas";
alert(obj2.name); //"Nicholas"
3.2 Array
- Array類型是一組值的有序列表
- 可以存儲多個不同類型的數(shù)據(jù)
- 數(shù)組是一種特殊類型的對象
3.2.1 創(chuàng)建與訪問
// new操作符構(gòu)建法
var arr = new Array(); //創(chuàng)建空數(shù)組
var arr = new Array(20); //創(chuàng)建確定容量的數(shù)組
var arr = new Array("red", "blue", "green");//創(chuàng)建帶有數(shù)據(jù)的數(shù)組
var arr = Array(); //省略new 構(gòu)造法
// 字面量表示法
var arr = []; // 創(chuàng)建空數(shù)組
var arr = ["red","blue","green"]; //創(chuàng)建一個包含3個字符串的數(shù)組
var arr = [1,2,]; //不要這樣擎浴!這樣會創(chuàng)建一個包含2或3項的數(shù)組
var arr = [,,,,,]; //不要這樣!這樣會創(chuàng)建一個包含5或6項的數(shù)組
- 推薦使用字面量表示法來創(chuàng)建數(shù)組
- 若函數(shù)沒有return毒涧,使用new會返回一個對象贮预,不使用new會返回undefined
var colors = ["red", "blue", "green"]; // 序號為0、1、2…
// 通過方括號中的數(shù)字下標(biāo)索引仿吞,來訪問數(shù)組中的值
var color1 = colors[0]; //創(chuàng)建一個變量保存數(shù)組第一項
console.log(color1); //"red"
// 通過賦值來修改數(shù)組
colors[2] = "black"; //修改替換第三項
colors[3] = "brown"; //新增第四項(索引超過項數(shù)時)
console.log(colors); //"red","blue","black","brown"
3.2.2 方法
檢測方法
if (value instanceof Array){} //舊方法滑频,可能分別具有各自不同的構(gòu)造函數(shù)
if (Array.isArray(value)){} //新方法,能最終確定某個值到底是不是數(shù)組
遍歷方法
var array = [1,2,3,4,5];
// forEach():ES5的新方法唤冈,不支持低版本IE
array.forEach(function(item, index, array){
console.log(index + "-" + item);
}); //0-1 1-2 2-3 3-4 4-5
// for-in 快速遍歷
for(var i in array){
console.log(i); //0 1 2 3 4
console.log(array[i]);} //1 2 3 4 5
// for 循環(huán)遍歷
for(var i=0,len=array.length; i<len; i++){
console.log(array[i]);} //1 2 3 4 5
長度方法
var num = [1,2,3,4,5];
//查詢數(shù)組長度(個數(shù))
console.log(num.length); //5
//數(shù)組長度增加與減少
num.length = 7; //增加時峡迷,默認(rèn)賦值undefined
console.log(num); //1,2,3,4,5,undefined,undefined
num.length = 3; //減少時,從末項開始刪除
console.log(num); //1,2,3
//數(shù)組元素值的修改你虹、添加與刪除
num[2] = 10; //若索引號重復(fù)绘搞,直接覆蓋替換
console.log(num); //1,2,10,4,5
num[7] = 10; //若索引號超過數(shù)組長度,則添加指定值并自動補齊undefined
console.log(num); //1,2,3,4,5,undefined,undefined,10
delete num[2]; //delete操作符不會改變數(shù)組長度傅物,只是替換對應(yīng)值為undefined
console.log(num); //1,2,undefined,4,5
操作方法
var num = [1,2,3];
// 棧方法 (類似椇幌剑“后進先出”)
var numPush = num.push(7, 8); //在數(shù)組末尾推入兩項
console.log(numPush); //5 (返回修改后數(shù)組的長度)
console.log(num); //1,2,3,7,8
var numPop = num.pop(); //刪除數(shù)組的末項元素
console.log(numPop); //3 (返回移除的項的長度)
console.log(num); //1,2
//隊列方法 (類似隊列的“先進先出”)
var numShift = num.shift(); //移除第一項
console.log(numShift); //1 (返回該項,這是一個值!)
console.log(num); //2,3
var numUnshift = num.unshift(4,5,6); //在前端添加任意個項
console.log(numUnShift); //6 (返回新數(shù)組的長度)
console.log(num); //4,5,6,1,2,3
//字符串轉(zhuǎn)換方法
var numJoin = num.join(-); //將數(shù)組中的元素用一個字符串拼接起來
console.log(numJoin); //1-2-3 (返回拼接后的結(jié)果)
console.log(num); //1,2,3 (原數(shù)組不會改變)
var num = [1,2,3,4,5,6];
//截取方法
var numASlice = num.slice(1); //單參數(shù)董饰,返回該項至末項的所有項
var numBSlice = num.slice(1,4); //雙參數(shù)蒿褂,返回兩參數(shù)對應(yīng)項間不含末項的所有項
console.log(numASlice); //2,3,4,5
console.log(numBSlice); //2,3,4
console.log(num); //1,2,3,4,5,6 (原數(shù)組不會改變)
//修改方法1:刪除
var numASplice = num.splice(1,2); //兩參數(shù)(起始位置、刪除項數(shù)),
console.log(numASplice); //2,3 (返回刪除項)
console.log(num); //1,4,5,6
//修改方法2:替換
var numBSplice = num.splice(2,2,9,11);//多參數(shù)(起始位置卒暂、刪除項數(shù)啄栓、插入項..),
console.log(numASplice); //3,4 (返回刪除項)
console.log(num); //1,2,9,11,5,6
//拼接方法
var numConcat = num.concat("ok",true,[9,11]);//
console.log(numConcat); //1,2,3,4,5,6,"ok",true,9,11
console.log(num); //1,2,3,4,5,6 (原數(shù)組不會改變)
重排序方法
var num2 = [10,5,40,25,1000,1];
var num3 = ["George","John","Thomas","James","Adrew"];
//倒置數(shù)組項的順序
var num2Reverse = num2.reverse();
console.log(num2Reverse); //1,1000,25,40,5,10
//按字符編碼順序升序排序
var num2Sort = num2.sort();
console.log(num2Sort); //1,10,1000,25,40,5
var num3Sort = num3.sort();
console.log(num3Sort); //Adrew,George,James,John,Thomas
//以上兩種方法都會改變原數(shù)組原來的排序
//冒泡排序
......
位置方法
var num = [2,5,3,4,5,6,1];
//順序查找位置方法
var numX = num.indexOf(3) //單參數(shù) (對應(yīng)值)
var numY = num.indexOf(5,2) //雙參數(shù) (對應(yīng)值,第幾個值)
console.log(numX); //2 (從左到右找到第一個對應(yīng)值3,返回索引值2)
console.log(numY); //4 (從左到右找到第二個對應(yīng)值5介却,返回索引值4)
//逆序查找位置方法
var numM = num.lastIndexOf(6) //單參數(shù) (對應(yīng)值)
var numN = num.lastIndexOf(5,3) //雙參數(shù) (對應(yīng)值,第幾個值)
console.log(numM); //5 (從右到左找到第一個對應(yīng)值6谴供,返回索引值5)
console.log(numN); //-1 (從右到左找到第三個對應(yīng)值5,未找到齿坷,返回索引值-1)
迭代方法
var a = [1, 2];
//使用迭代方法時桂肌,會接收2個參數(shù)(一個是每一項運行的函數(shù),一個是運行該函數(shù)的作用域?qū)ο?永淌,這時會對數(shù)組中的每一項運行一個給定的函數(shù)崎场,這個函數(shù)會接收三個參數(shù)(對應(yīng)值,該項位置,數(shù)組對象本身)
//every():若該函數(shù)對“每”一項都返回true,則最終結(jié)果返回true
a.every(function(item, index, array){ //
return (item >1);}) //false
//some():若該函數(shù)對“任”一項都返回true遂蛀,則最終結(jié)果返回true
a.some(function(item) {
return (item >1);}) //true
//filter():返回該函數(shù)中 會返回true的項 所組成的數(shù)組
a.filter(function(item) {
return (item >1);}) //[2]
//map():返回每次函數(shù)調(diào)用的結(jié)果 組成的數(shù)組
a.map(function(item) {
return item;}) //[1,2]
//forEach():此方法沒有返回值
a.forEach(item) {
console.log(item);} //1 2
歸并方法
var b = [1,2,3,4,5];
//reduce():順序遍歷
var sum1 = b.reduce(function(prev, cur, index, array){
return prev + cur;});
alert(sum1); //15
//reduceRight():逆序遍歷
var sum2 = b.reduceRight(function(prev, cur, index, array){
return prev + cur;});
alert(sum2); //15
3.3 Date
3.3.1 時間格式
- 格里尼治時間(GTM):英國郊區(qū)格林尼治天文臺時間谭跨,用于世界上某些重大時間
- 世界標(biāo)準(zhǔn)時間(UTC):世界時間1970年1月1日0時開始(毫秒數(shù))
3.3.2 創(chuàng)建時間
//使用Date()函數(shù)創(chuàng)建
var nowDate = Date();
console.log(nowDate); //得到運行此語句時刻的當(dāng)前時間
console.log(typeof nowDate); //string (時間實際上是字符串類型)
//使用構(gòu)造函數(shù)創(chuàng)建(對象類型)
var nowDate = new Date();
console.log(typeof nowDate); //object
//可指定參數(shù)來設(shè)置時間格式
var date = new Date("2017/11/3 12:30:12");
console.log(date);//Wed Aug 07 2019 15:43:48 GMT+0800 (中國標(biāo)準(zhǔn)時間)
3.3.3 方法
//Date對象的方法大致分為3類
//get 獲取(年、月李滴、日螃宙、星期、時所坯、分谆扎、秒、毫秒)
//set 設(shè)置(年芹助、月堂湖、日闲先、星期、時无蜂、分伺糠、秒、毫秒)
//to 格式化轉(zhuǎn)換
//[注1]大部分的返回值中,0代表現(xiàn)實意義中的1(如1號斥季、1月份训桶、1秒)
//[注2]時間運算:兩個時間對象相減,得到的是兩個對象間相差的毫秒數(shù)
[圖片上傳失敗...(image-2a712e-1587459554178)]
3.4 RegExp
正則表達式:用于匹配規(guī)律規(guī)則的表達式酣倾,一種過濾邏輯公式渊迁,通常被用來檢索、替換那些符合某個模式(規(guī)則)的文本灶挟。
正則表達式實際上也是一種object類型。
3.4.1 創(chuàng)建
//字面量創(chuàng)建
var 變量名 = /表達式/匹配模式修飾符;
//構(gòu)造函數(shù)創(chuàng)建
var 變量名 = new RegExp("表達式","匹配模式修飾符");
3.4.2 表達式
//常用
[abc] 查找方括號之間的任何字符
[^abc] 查找任何不在方括號之間的字符
[0-9] 查找任何從 0 至 9 的數(shù)字
[a-z] 查找任何從小寫 a 到小寫 z 的字符
[A-Z] 查找任何從大寫 A 到大寫 Z 的字符
[A-z] 查找任何從大寫 A 到小寫 z 的字符
a[bcd]e 字符中間匹配 (abe,ace,ade都行)
X|y|z 或匹配 (匹配x或y或z,存在其中一個即可)
//單字符&數(shù)字
. 匹配除換行符之外的任意字符 (匹配點"."需要轉(zhuǎn)義字符/\./)
\d 匹配數(shù)字
\D 匹配非數(shù)字
\w 匹配字母
\W 匹配非字母
//空白字符
\0 匹配 null 字符
\s 匹配空白字符毒租、空格稚铣、制表符或換行符
\S 匹配非空白字符
\n 匹配換行符
\r 匹配回車符
\f 匹配進紙符
\t 匹配制表符
\v 匹配垂直制表符
//定位符
^ 行首匹配
$ 行尾匹配
\A 只匹配字符串的開始處
\b 匹配單詞邊界,詞在[]內(nèi)無效(\bchild\b ≡ children× child√ )
\B 匹配非單詞邊界
\z 只匹配字符串結(jié)束處
\Z 匹配字符串結(jié)束處或行尾
\G 匹配當(dāng)前搜索的開始位置
//進制數(shù)
\xxx 查找以八進制數(shù) xxx 規(guī)定的字符
\xdd 查找以十六進制數(shù) dd 規(guī)定的字符
\uxxxx 查找以十六進制數(shù) xxxx 規(guī)定的 Unicode 字符
//限定符 量詞
n+ 匹配任何包含至少一個n的字符串 (n+ ≡ a× n√ nnn√)
n* 匹配任何包含零個或多個n的字符串
n? 匹配任何包含零個或一個n的字符串
n{X} 匹配包含X個n的序列的字符串 (n{4} ≡ nnnn)
n{X,Y} 匹配包含X至Y個n的序列的字符串 (an{1,3} ≡ an√ ann√ annn√)
n{X,} 匹配包含至少X個n的序列的字符串 (an{2,} ≡ an× ann√ annnnn√)
^n 匹配任何開頭為n的字符串 (^n ≡ an× na√ nan√)
n$ 匹配任何結(jié)尾為n的字符串
^n$ 匹配開頭為n且結(jié)尾為n的字符串(^n$ ≡ an× na× nan√)
?=n 匹配任何其后緊接指定字符串 n 的字符串
?!n 匹配任何其后沒有緊接指定字符串 n 的字符串
//引用
\1..\9
$1...$9 返回九個在模式匹配期間找到的墅垮、最近保存的部分
匹配模式修飾符
g globle 全局匹配模式(查找所有匹配惕医,而不光在找到第一個匹配后停止)
i ignorCase 忽略大小寫模式
m mutiple 允許多行匹配模式
3.4.3 屬性
實例屬性
global 檢測是否設(shè)置g標(biāo)記
ignoreCase 檢測是否設(shè)置標(biāo)記
multiline 檢測是否設(shè)置了m標(biāo)記
source 返回正則表達式的字符串表示
lastIndex 返回被查找字符串中下一次成功匹配的開始位置
構(gòu)造函數(shù)屬性
$_ input 返回最近一次匹配的字符串
$& lastMatch 返回最近一次的匹配項
$+ lastParen 返回最近一次匹配的捕獲組
$` leftContext 返回被查找的字符串中從字符串開始位置到最后匹配之前的位置之間的字符
$' rightContext 返回被搜索的字符串中從最后一個匹配位置開始到字符串結(jié)尾之間的字符
s* multiline 檢測表達式是否采用多行模式匹配m
3.4.4 方法
實例方法
//exec() 檢索字符串中指定的值。返回找到的值算色,并確定其位置抬伺。
特性
- 有全局標(biāo)記g 持續(xù)查找所有匹配項并返回
- 無全局標(biāo)記g 始終返回第一個匹配項信息
執(zhí)行過程
1.檢索字符串參數(shù),獲取正則表達式匹配文本
2.找到匹配文本則返回一個數(shù)組
- 第0個元素 與整個模式匹配的字符串
- 其他元素 與捕獲組匹配的字符串
3.否則返回null
//test() 在字符串中測試模式匹配灾梦,返回true或false
var reg = /aaa/;
console.1og(reg.test("aabc")); //false
字符串方法
//match() 將符合條件的正則表達式的內(nèi)容提取出來
- 默認(rèn)情況下,只會找到第一個符合要求的內(nèi)容峡钓,找到后停止檢索
- 可以設(shè)置全局匹配模式,這樣就能匹配到所有符合的內(nèi)容
- 匹配到的內(nèi)容會封裝到一個數(shù)組中返回若河,即使只查詢到一個結(jié)果
var str = "1a2b3c4d5e6f7A8B9C";
var result1 = str.match(/[a-z]/);
var result2 = str.match(/[a-z]/ig); //可設(shè)置多個匹配模式 順序無所謂
console.log(result1); //a
console.log(result2); //a,b,c,d,e,f,A,B,C
console.log(typeof result1); //object
//replace("表達式","替換內(nèi)容") 將與正則表達式匹配的值替換為新的內(nèi)容
- 默認(rèn)只會替換第一個
var str = "1a2b3c4";
var result = str.match(/[a-z]/,"@_@");
var result2 = str.match(/[a-z]/ig,"@_@");
console.log(result1); //1@_@2b3c4
console.log(result2); //1@_@2@_@3@_@4
//search() 檢索與指定的正則表達式相匹配的值
- 如果搜索到指定內(nèi)容能岩,則會返回第一次出現(xiàn)的索引
- 如果沒有搜索到返回-1
var str = "hello abc hello aec afc";//搜索字符串中是否含有abc或aec或afc
var result = str.search(/a[bef]c/);
console.1og(result); //6 (在索引6處首次被搜到)
//split() 把字符串分割為字符串?dāng)?shù)組
var str="1a2b3c4d5e6f7";
var result=str.split(/[A-z]/); //1,2,3,4,5,6,7
3.4.5 實例
//手機號的規(guī)則 13567890123 (11位)
1.以1開頭 ^1
2.第二位3-9任意數(shù)字 [3-9]
3.三位以后任意數(shù)字9個 [0-9]{9}$
var phoneStr = "13067890123"
var phoneReg = /^1[3-9][0-9]{9}$/;
console.log(phoneStr.test(phoneReg)); //ture
//去除誤輸入的字符前后空格 - 使用""來替換空格
var str = " he llo ";
str = str.replace(/\s/,""); //去除一個空格
str = str.replace(/\s/g,""); //去除所有的空格,包括中間
str = str.replace(/^\s*/,""); //去除開頭的空格
str = str.replace(/\s*$/,""); //去除結(jié)尾的空格
str = str.replace(/^\s*$/g,""); //無效
str = str.replace(/^\s*|\s*$/g,"");//匹配開頭和結(jié)尾的空格
console.log(str); //"hello"
//匹配中文字符
[\u4e00-\u9fa5]
//匹配雙字節(jié)字符(包括漢字)
[^\x00-\xff]
//匹配空白行的正則表達式(用來刪除空白行)
\n\s*\r
//匹配HTML標(biāo)記(對復(fù)雜的嵌套標(biāo)記無用)
<(\S*?)[^>]*>.*?</\1>|<.*? />
//匹配首尾空白字符(刪除行首行尾的空白字符)
^\s*|\s*$
//匹配Email地址(表單驗證)
\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
//匹配網(wǎng)址URL的正則表達式
[a-zA-z]+://[^\s]*
//匹配帳號是否合法(表單驗證:字母開頭萧福,允許5-16字節(jié)拉鹃,允許字母數(shù)字下劃線)
^[a-zA-Z][a-zA-Z0-9_]{4,15}$
//匹配國內(nèi)電話號碼(形式如 0511-4405222 或 021-87888822)
\d{3}-\d{8}|\d{4}-\d{7}
//匹配騰訊QQ號(QQ號從10000開始)
[1-9][0-9]{4,}
//匹配中國郵政編碼(6位數(shù)字)
[1-9]\d{5}(?!\d)
//匹配身份證(一代15位或二代18位)
\d{15}|\d{18}
//匹配ip地址(用于提取ip地址)
\d+\.\d+\.\d+\.\d+
//匹配特定數(shù)字(處理大量數(shù)據(jù))
^[1-9]\d*$ //正整數(shù)
^-[1-9]\d*$ //負(fù)整數(shù)
^-?[1-9]\d*$ //整數(shù)
^[1-9]\d*|0$ //非負(fù)整數(shù)(正整數(shù) 與 0)
^-[1-9]\d*|0$ //非正整數(shù)(負(fù)整數(shù) 與 0)
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ //正浮點數(shù)
^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ //負(fù)浮點數(shù)
^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$ //浮點數(shù)
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$ //非負(fù)浮點數(shù)
^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$ //非正浮點數(shù)
//匹配特定字符串
^[A-Za-z]+$ //由26個英文字母組成的字符串
^[A-Z]+$ //由26個英文字母的大寫組成的字符串
^[a-z]+$ //由26個英文字母的小寫組成的字符串
^[A-Za-z0-9]+$ //由數(shù)字和26個英文字母組成的字符串
^\w+$ //由數(shù)字稿辙、26個英文字母或者下劃線組成的字符串
3.5 Function
函數(shù)實際上是 Function 類型的實例款慨,因此函數(shù)也是對象高氮;而這一點正是 JavaScript有特色的地 方卑惜。由于函數(shù)是對象陪毡,所以函數(shù)也擁有方法丸卷,可以用來增強其行為蔚出。
屬性與方法
-
arguments
- arguments和this類似汹粤,都是函數(shù)中的隱含的參數(shù)
- arguments是一個類數(shù)組元素逾雄,它用來封裝函數(shù)執(zhí)行過程中的實參
所以即使不定義形參阀溶,也可以通過arguments來使用實參 - arguments中有一個屬性callee表示當(dāng)前執(zhí)行的函數(shù)對象
-
this 函數(shù)的上下文對象腻脏,根據(jù)函數(shù)的調(diào)用方式不同會執(zhí)向不同的對象
- 以函數(shù)的形式調(diào)用時,this是window
- 以方法的形式調(diào)用時银锻,this是調(diào)用方法的對象
- 以構(gòu)造函數(shù)的形式調(diào)用時永品,this是新建的那個對象
- 使用call和apply調(diào)用時,this是指定的那個對象
- 在全局作用域中this代表window
- call( ) 直接調(diào)用函數(shù)1击纬,直接傳遞函數(shù)的實參
- apply( ) 直接調(diào)用函數(shù)2鼎姐,將實參封裝到一個數(shù)組中傳遞
3.6 Boolean
Boolean 對象在 ECMAScript 中的用處不大,因為它經(jīng)常會造成人們的誤解
var falseValue = false; //創(chuàng)建布爾值
result = falseValue && true;
alert(result); //false
var falseObject = new Boolean(false); //創(chuàng)建布爾對象
var result = falseObject && true;
alert(result); //true
//所以進行邏輯運算時更振,有這樣一個規(guī)律
Boolean() && boolean //結(jié)果是boolean
boolean && Boolean() //結(jié)果是Boolean( )
//基本類型與引用類型的布爾值在進行typeof()instanceof()測試時炕桨,返回的結(jié)果都不同
alert(typeof falseObject); //object
alert(typeof falseValue); //boolean
alert(falseObject instanceof Boolean); //true
alert(falseValue instanceof Boolean); //false
//所以,建議是永遠不要使用 Boolean 對象!!!
3.7 Number
Number對象提供了
valueOf() toLocaleString() toString() toFixed() toExponential() toPrecision()
等方法肯腕,但是對不同的瀏覽器兼容性不同献宫,并且在進行typeof()instanceof()測試時出現(xiàn)的情況與 Boolean 對象相似。因此实撒,仍然不建議直接使用實例化 Number 對象f⑼尽!知态!
3.8 String
字符串有兩種形式:基本類型捷兰、對象類型
- 字符串在底層中是以字符數(shù)組的形式保存的
- 對象類型字符串封裝了一些屬性及方法,而基本類型則沒有
可直接用基本類型字符串調(diào)用字符串對象封裝的屬性及方法负敏,無須手動轉(zhuǎn)換
//基本類型字符串 String
var str1 = "Hello World";
console.log(typeof str1); //string
//對象類型字符串 Object
var str2 = new String("Hello World");
console.log(typeof str2); //object
3.8.1 方法
字符方法
var str ="what color do you like";
//charAt(index) 獲取對應(yīng)下標(biāo)處的字符
console.log(str.charAt(9)); //r
console.log(str.charAt(30)); //""(未找到返回空字符串)
console.log(typeof str.charAt(30)); //string
//charCodeAt(index) 獲取對應(yīng)下標(biāo)處的字符編碼(0~65535)
console.log(str.charCodeAt(3)); //116 (ASCII碼)
console.log(str.charCodeAt(30)); //NaN
console.log(typeof str.charCodeAt(30)); //number
//下標(biāo)索引直接獲取法 (IE7即以下不支持)
console.log(str[11]); //d
//String.fromCharCode(ASCII) 獲取ASCII碼對應(yīng)的字符
var str1ASCII = String.fromCharCode(117);
console.log(str1ASCII); //u
var str2ASCII = String.fromCharCode(104, 101, 108, 108, 111);
console.log(str2ASCII); //"hello"
操作方法
var str = "hello world";
//concat() 將字符串拼接起來贡茅,返回拼接后的新字符串
var strConcat1 = str.concat(" wow");
var strConcat2 = str.concat(" wow", "!"); //多拼接
console.log(strConcat1); //"hello world wow"
console.log(strConcat2); //"hello world wow!"
console.log(str); //"hello world"
//slice() 截取介于兩個指定下標(biāo)之間的字符(開始索引,結(jié)束索引)
//substring() 截取介于兩個指定下標(biāo)之間的字符(開始索引,結(jié)束索引)
//substr() 截取從頭索引開始的指定個數(shù)的字符(開始索引,截取位數(shù))
//單參數(shù)時,默認(rèn)截取到尾部
console.log(str.slice(3)); //"lo world"
console.log(str.substring(3)); //"lo world"
console.log(str.substr(3)); //"lo world"
console.log(str.slice(3, 7)); //"lo w"
console.log(str.substring(3,7)); //"lo w"
console.log(str.substr(3, 7)); //"lo worl"
//參數(shù)為負(fù)值時,則從后往前截取 (substring所有負(fù)值參數(shù)都轉(zhuǎn)換為0)
console.log(stringValue.slice(-3)); //"rld"
console.log(stringValue.substring(-3)); //"hello world"
console.log(stringValue.substr(-3)); //"rld"
console.log(stringValue.slice(3, -4)); //"lo w"
console.log(stringValue.substring(3, -4)); //"hel"
console.log(stringValue.substr(3, -4)); //""(空字符串)
console.log(str); //"hello world"(原字符串不改變)
分割方法
//split() 把一個字符串分割成字符串?dāng)?shù)組
var str= "Tom is a good man!";
//未指定長度
console.log(str.split(" ")); //["Tom","is",a","good","man!"]
//指定返回數(shù)組的最大長度3
console.log(str.split(" ",3)); //["Tom","is","a"]
console.log(str.split("",6)); //["T","o","m","","i","s"]
位置方法
var str = "hello world";
//indexOf() 順序查找指定字符其做,并返回索引值
//lastIndexOf() 逆序序查找指定字符顶考,并返回索引值
console.log(str.indexOf("o")); //4
console.log(str.lastIndexOf("o")); //7
//雙參數(shù)時,第二個參數(shù)表示從哪個索引值位置開始搜索
console.log(str.indexOf("o", 6)); //7
console.log(str.lastIndexOf("o", 6)); //4
//若找不到指定字符妖泄,會返回-1
console.log(str.indexOf("y")); //-1
修整方法
//trim() 刪除前置及后綴的所有空格創(chuàng)建其副本村怪,返回結(jié)果
var str = " hello world ";
var strTrim = str.trim();
console.log(strTrim); //"hello world"
console.log(str); //" hello world "(原字符串不改變)
//trimLeft() 刪除頭部所有空格創(chuàng)建其副本,返回結(jié)果
//trimRight() 刪除尾部所有空格創(chuàng)建其副本浮庐,返回結(jié)果
大小寫轉(zhuǎn)換方法
var str = "hello world";
console.log(str.toUpperCase()); //"HELLO WORLD" (轉(zhuǎn)大寫)
console.log(str.toLowerCase()); //"hello world" (轉(zhuǎn)小寫)
//針對特定地區(qū)的少數(shù)語言,使用以下方法
console.log(str.toLocaleUpperCase()); //"HELLO WORLD"
console.log(str.toLocaleLowerCase()); //"hello world"
console.log(str); //"hello world"(原字符串不改變)
模式匹配方法
//match()
//search()
//replace() 字符串替換
var str = "A good man!A nice man";//只能替換第一次出現(xiàn)的字符串
var strRep = str.replace("man","woman");
console.log(strRep); //"A good woman!A nice man"
strRep = str.replace(/man/g,"woman");//通過正則表達式實現(xiàn)全部替換
console.log(strRep); //"A good woman!A nice woman"
console.log(str); //"A good man!A nice man" (原字符串不變)
比較大小方法
//localeCompare() 從左往右單字符比較其ASCII碼值,值大者大
var str = "yellow";
console.log(str.localeCompare("brick")); //1 (yellow更大)
console.log(str.localeCompare("yellow")); //0 (相等)
console.log(str.localeCompare("zoo")); //-1 (zoo更大)
HTML方法
早期時為了方便而擴展這種動態(tài)格式化HTML標(biāo)準(zhǔn)甚负,但無語義化,==請避免使用审残!==
[圖片上傳失敗...(image-a69306-1587459554178)]
3.9 Global
3.9.1 方法
URI 編碼方法
//encodeURI() 對本身不屬于URI的特殊字符進行編碼
//encodeURIComponent() 對任何非標(biāo)準(zhǔn)字符進行編碼 (開發(fā)中更常用)
var uri = "http://www.wrox.com/illegal value.htm#start";
alert(encodeURI(uri));
//"http://www.wrox.com/illegal%20value.htm#start"
alert(encodeURIComponent(uri));
//"http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start"
URI 解碼方法
//decodeURI() 只能對使用encodeURI()替換的字符進行解碼
//decodeURIComponent() 可解碼任何特殊字符的編碼
var uri="http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start";
alert(decodeURI(uri));
//http%3A%2F%2Fwww.wrox.com%2Fillegal value.htm%23start
alert(decodeURIComponent(uri));
//http://www.wrox.com/illegal value.htm#start
在以前會使用escape()和unescape()方法梭域,但在ECMA-262第3版已經(jīng)被廢棄,請不要使用=两巍2≌恰!
ECMAScript 解析器
//eval() 獲取返回值璧坟,可將字符串轉(zhuǎn)換為代碼執(zhí)行既穆,并返回一或多個值赎懦,在執(zhí)行時遇錯,則拋出異常給調(diào)用者
eval("alert('hi')"); //等價于 alert("hi");
var msg = "hello world";
eval("alert(msg)"); //"hello world"
eval()方法解釋代碼字符串的能力非常強大幻工,但也非常危險励两,所以謹(jǐn)慎使用!D衣当悔!
3.9.2 屬性
[圖片上傳失敗...(image-ec8db-1587459554178)]
瀏覽器都會將全局對象作為window對象的一部分來實現(xiàn)。因此在全局作用域中聲明的所有變量和函數(shù)踢代,都是window 對象的屬性
3.X Math
3.X.1 屬性
Math.E //自然對數(shù)的底數(shù)盲憎,即常量 e 的值
Math.LN10 //10的自然對數(shù)
Math.LN2 //2的自然對數(shù)
Math.LOG2E //以2為底 e 的對數(shù)
Math.LOG10E //以10為底 e 的對數(shù)
Math.PI //π的值
Math.SQRT1_2 //1/2的平方根(即2的平方根的倒數(shù))
Math.SQRT2 //2的平方根
3.X.2 方法
Math.round(4.5) //5 四舍五入
Math.ceil(3.1) //4 向上取整
Math.floor(3.9) //3 向下取整
Math.max(3,6,76,8,89,32) //89 取最大值
Math.min(3,6,76,8,89,32) //3 取最小值
Math.abs(-10) //10 取絕對值
Math.pow(2,3) //8 x的y次方
Math.sqrt(25) //5 開平方
Math.random() //0.6334986525422 生成一個0-1之間的隨機數(shù)
4 DOM/BOM
定時器
function fn1(){
console.log('test1');
}
function fn2(){
console.log('test2');
}
function fn3(){
console.log('test3');
}
function fn4(a){
console.log(a);
}
setTimeout(fn1, 2000); // 無括號 定時執(zhí)行
setTimeout(fn2(), 2000); // 空參數(shù) 立即執(zhí)行
setTimeout('fn3()', 2000); // 空參數(shù)作為字符串 定時執(zhí)行
setTimeout(fn4('test4'), 2000); // 帶參數(shù) 立即執(zhí)行
setTimeout("fn4('test5')", 2000); // 帶參數(shù)作為字符串 定時執(zhí)行
/* --------最后執(zhí)行結(jié)果------
(立即)
test2
test4
(2s后)
test1
test3
test5
--------------------------*/
5 函數(shù)
- 返回值:函數(shù)執(zhí)行的結(jié)果
- 使用
return
來設(shè)置函數(shù)的返回值,它可以被一個變量所接收 - return 后的代碼不會執(zhí)行胳挎,一旦執(zhí)行到return語句時饼疙,函數(shù)將會立刻退出
- return 后可跟任意類型的值,基本數(shù)據(jù)類型或?qū)ο?/li>
- 如果return后不跟值慕爬,或者是不寫return宏多,則函數(shù)默認(rèn)返回undefined
- 使用
- 參數(shù):
- 形參:function(1, 2)
- 實參:function(a, b)
- 作用域:一個變量的作用范圍
- 全局作用域
- 全局作用域在打開頁面時創(chuàng)建,在頁面關(guān)閉時銷毀
- 全局作用域中有一個全局對象window澡罚,window對象由瀏覽器提供
- 在全局作用域中創(chuàng)建的變量和函數(shù)可以在頁面的任意位置訪問
- 盡量不要在全局中創(chuàng)建變量
- 函數(shù)作用域
- 每次調(diào)用函數(shù)都會創(chuàng)建一個新的函數(shù)作用域
- 函數(shù)作用域在函數(shù)執(zhí)行時創(chuàng)建,在函數(shù)執(zhí)行結(jié)束時銷毀
- 在函數(shù)作用域中創(chuàng)建的變量肾请,不能在全局中訪問
- 當(dāng)在函數(shù)作用域中使用一個變量時留搔,它會先在自身作用域中尋找,如果找到了則直接使用铛铁,如果沒有找到則到上一級作用域中尋找
- 全局作用域
- 函數(shù)的聲明提前:和變量的聲明提前一樣隔显,在全局作用域中,使用函數(shù)聲明
function
創(chuàng)建的函數(shù)饵逐,會被瀏覽器預(yù)先把聲明調(diào)到前面去括眠,即預(yù)解析。
6 JS深入
6.1 重新認(rèn)識JS
-
JS是一種JIT編譯語言倍权,也叫“即時編譯”掷豺,是”動態(tài)編譯“的一種,當(dāng)某段代碼即將第一次被執(zhí)行時進行編譯薄声。解釋一行執(zhí)行一行当船,相較于其他語言較慢。
java默辨、c#等語言是“編譯執(zhí)行”德频,一次性把代碼編譯成可執(zhí)行代碼,再一行一行執(zhí)行缩幸,速度較快壹置。
具有靈活性竞思,動態(tài)特性,可以隨時隨意給對象增加屬性和方法
函數(shù)是JS中的一等公民
一般在宿主環(huán)境下運行钞护,如瀏覽器(除node.js)
JS運行前會進行“預(yù)解析”盖喷,先全局再函數(shù)內(nèi)部
6.2 面對對象編程
6.2.1 編程思想
- 面向過程:所有的事情都親力親為,注重
過程
- 面向?qū)ο螅禾岢鲂枨?- 找對象 - 對象解決患亿,注重
結(jié)果
- JS不是一門面向?qū)ο蟮恼Z言传蹈,而是基于對象,將執(zhí)行者轉(zhuǎn)變成指揮者步藕,用JS來模擬面向?qū)ο蟮恼Z言惦界,它不是面向過程的替代,而是面向過程的封裝
- 面向?qū)ο螭翁匦?
- 封裝:即包裝咙冗,把一些重用的內(nèi)容進行打包沾歪,在需要時直接使用
- 繼承:類與類之間的關(guān)系,JS中沒有類的概念雾消,但有構(gòu)造函數(shù)的概念灾搏,基于原型,是可以有繼承的
- 多態(tài)(抽象性):同一個行為立润,針對不同的對象狂窑,產(chǎn)生不同的效果
- 面向?qū)ο缶幊蹋∣OP),是一種編程開發(fā)思想桑腮,是過程式代碼的一種高度封裝泉哈,目的在于提高代碼的開發(fā)效率和可維護性,比起傳統(tǒng)過程式編程破讨,更適合多人合作大型軟件項目
- 面向?qū)ο蟮某橄蟪潭缺群瘮?shù)要高丛晦,因為一個類/模板(Class)既包含數(shù)據(jù),又包含操作數(shù)據(jù)的方法
6.2.2 體會面向?qū)ο?/h3>
//面向過程的方式
// 1 記錄學(xué)生的成績
var stu1 = {name: 'zs', subject: '語文', score: 90};
var stu2 = {name: 'ls', subject: '語文', score: 80};
// 2 打印學(xué)生的成績
console.log(stu1.name, stu1.subject, stu1.score);
console.log(stu2.name, stu2.subject, stu2.score);
// 面向?qū)ο蟮姆绞?
/*-----------------------------------------
首選思考的不是程序執(zhí)行流程提陶,而是把`Student`視為對象烫沙,并擁有`name`和`score`兩個屬性;
而打印成績,首先必須創(chuàng)建出這個`Student`對應(yīng)的對象隙笆,再給對象發(fā)一個`printScore`消息锌蓄,讓對象把自己的數(shù)據(jù)打印出來
-----------------------------------------*/
// 1 抽象數(shù)據(jù)行為變成模板 (Class)
function Student(name, subject, score) {
this.name = name;
this.subject = subject;
this.score = score;
this.printScore = function () {
console.log(this.name, this.subject, this.score);
}
}
// 2 根據(jù)模板創(chuàng)建具體實例對象 (Instance)
var stuA = new Student('zs', '語文', 90);
var stuB = new Student('ls', '語文', 80);
// 3 實例對象具有自己的具體行為 (指揮Instance得到結(jié)果)
stuA.printScore();
stuB.printScore();
/*-----------------------------------------
在此例中,我們處理的多個對象都具有一個共同點撑柔,即他們都是學(xué)生煤率,所以把他們抽象歸成 類/模板(Class),再根據(jù)每一個具體的學(xué)生去創(chuàng)建具體的實例對象(Instance)乏冀,最后再單獨賦予給他們各自的具體行為
-----------------------------------------*/
- 在JS中創(chuàng)建對象的模板是構(gòu)造函數(shù)蝶糯,而在其他語言中創(chuàng)建對象的模板是類
- 創(chuàng)建實例對象(Instance)時,需要使用new操作符
6.2.3 創(chuàng)建對象的方法
// 1 new Object()創(chuàng)建
// var person = {}; 也可以省略new Object() 縮寫成{}
var person = new Object();
person.name = "Nico";
person.age = 21;
person.sayHi = function(){
console.log("hello, I am" + this.name);
}
// 2 對象字面量創(chuàng)建
var person = {
name: "Nico",
age: 21;
sayHi: function(){
console.log("hello, I am" + this.name);
}
}
// 3 工廠函數(shù)
/*---------------------------------------
當(dāng)要創(chuàng)建多個實例對象時 就要重復(fù)寫很多份對象 代碼太冗余 不方便 使用我們使用工廠函數(shù) 它用于批量創(chuàng)建多個對象 解決代碼重復(fù)問題
----------------------------------------*/
function createPerson (name, age) {
return {
name: name,
age: age,
sayHi: function () {
console.log("hello, I am" + this.name);
}}}
// 再生成實例對象
var p1 = createPerson("Nico", 21);
var p2 = createPerson("Mike", 18);
/*-------------------------------------
工廠函數(shù)雖解決了批量創(chuàng)建對象的問題 但存在缺點:它無法確定真實的對象類型
-------------------------------------*/
console.log(typeof p1); // Object
console.log(p1 instanceof createPerson); // false
構(gòu)造函數(shù)
構(gòu)造函數(shù)是專門用來創(chuàng)建對象的函數(shù)
一個構(gòu)造函數(shù)我們也可以稱為一個類
通過一個構(gòu)造函數(shù)創(chuàng)建的對象辆沦,我們稱該對象時這個構(gòu)造函數(shù)的實例
通過同一個構(gòu)造函數(shù)創(chuàng)建的對象昼捍,我們稱為一類對象
-
構(gòu)造函數(shù)就是一個普通的函數(shù)识虚,只是他的調(diào)用方式不同
- 如果直接調(diào)用,它就是一個普通函數(shù)
- 如果使用new來調(diào)用妒茬,則它就是一個構(gòu)造函數(shù)
// 4 構(gòu)造函數(shù)
/*------------------------------------
1 這里引入更優(yōu)解 即更優(yōu)雅的工廠函數(shù) 即構(gòu)造對象的函數(shù)
2 構(gòu)造函數(shù)比工廠函數(shù)創(chuàng)建更方便
2.1 會在內(nèi)存中創(chuàng)建一個空對象 (無需內(nèi)部創(chuàng)建新對象)
2.2 直接將屬性和方法賦給了this 讓this指向剛剛創(chuàng)建好的對象
2.3 執(zhí)行構(gòu)造函數(shù)中的代碼
2.4 沒有return語句 無需返回對象(會自動返回)
3 構(gòu)造函數(shù)一般用首字母大寫 普通函數(shù)用小寫 便于區(qū)分
4 同時解決了工廠函數(shù)不能識別對象具體類型的缺陷
------------------------------------*/
function Person (name, age) {
this.name = name,
this.age = age,
this.sayHi = function () {
console.log("hello, I am" + this.name);
}}
// 生成實例對象必須使用new操作符
var p1 = new Person("Nico", 21);
var p2 = new Person("Mike", 18);
console.log(p1 instanceof Person); // true
/*---------------------------------------
每一個實例對象中的 __proto__ 中有一個constructor屬性担锤,該屬性指向創(chuàng)建該實例的構(gòu)造函數(shù),它最初是用來標(biāo)識對象類型的乍钻,它雖然可以用于檢測對象的類型肛循,但是生產(chǎn)中還是使用instanceof操作符更可靠。
console.log(p1.constructo); // 返回它的對象構(gòu)造器
console.log(p1.constructor === Person); // true
--------------------------------------*/
/*-----------------------------------------
用上面的方法去構(gòu)造函數(shù)雖然方便银择,但當(dāng)多個對象去調(diào)用這個構(gòu)造函數(shù)時多糠,會產(chǎn)生存儲多個方法的內(nèi)存區(qū)域,會造成極大的內(nèi)存浪費浩考。
p1.sayHi();
p2.sayhi();
console.log(p1.sayHi === p2.sayHi); // false
// 說明兩者不是指向同一方法 而是創(chuàng)建了兩塊內(nèi)存區(qū)來存儲
所以我們把需要共享的函數(shù)定義到構(gòu)造函數(shù)的外部夹孔,但若有多個需要共享的函數(shù),可能會造成全局命名空間沖突的問題析孽,因此有可以把多個函數(shù)放到一個對象中來保存來避免問題搭伤。
----------------------------------------*/
function sayHi() {
console.log("hello, I am" + this.name);
}
//或
var fns = {
sayHi: function () {
console.log("hello, I am" + this.name);
},
sayAge: function () {
console.log("I am" + this.age);
}
}
// 這樣基本上解決了構(gòu)造函數(shù)的內(nèi)存浪費問題 但代碼看起來會有些怪 所以引入原型
原型
? 通過構(gòu)造函數(shù)創(chuàng)建的對象,解析器都會默認(rèn)在函數(shù)中添加一個prototype屬性對象袜瞬,即每一個構(gòu)造函數(shù)都有一個prototype屬性怜俐,稱為原型/原型對象。原型中的成員邓尤,都會被構(gòu)造函數(shù)的實例間接繼承拍鲤,故可以把所有對象實例需要共享的屬性和方法直接定義在prototype對象上。
// 5 原型
/*-----------------------------------------
-------------------------------------------*/
function Person (name, age) {
this.name = name;
this.age = age;
}
Person.prototype.type = "student";
Person.prototype.sayHi = function () {
console.log("hello, I am" + this.name);
}
var p1 = new Person("Nico", 21);
var p2 = new Person("Mike", 18);
console.log(p1.sayHi === p2.sayHi); //true
構(gòu)造函數(shù)裁赠、實例、原型三者之間的關(guān)系
(圖略)
console.log(p1.__proto__ === Person.prototype); //true
console.log(p1.constructor === Person); //true
-
prototype
屬性是一個對象赴精,所有實例屬性和方法都指向它佩捞,它們共用同一個內(nèi)存地址,標(biāo)準(zhǔn)屬性蕾哟,給程序員使用
-
__proto__
屬性是非標(biāo)準(zhǔn)的屬性一忱,指向構(gòu)造函數(shù)的原型,供瀏覽器使用
-
constructor
是構(gòu)建器谭确,指向構(gòu)建函數(shù)
實例對象訪問原型對象中的成員的搜索原則:原型鏈
- 搜索首先從對象實例本身開始
- 若在實例中找到了指定屬性帘营,則返回該屬性的值
- 若未找到,則繼續(xù)搜索指針指向的原型對象逐哈,在原型對象中查找指定屬性
- 若在原型對象中找到了指定屬性芬迄,則返回該屬性的值
- 若一直到原型鏈末端還未找到,則返回
undefined
- Object的原型的原型為
null
// 6 最優(yōu)的原型
/*-----------------------------------
為減少不必要的每次都輸入一遍Person.prototype昂秃,常見做法是用一個包含所有屬性和方法的對象字面量來重寫整個原型對象
----------------------------------*/
function Person (name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
constructor: Person, //避免原型對象丟失了constructor成員
type: "student",
sayHi: function () {
console.log("hello, I am" + this.name);
}
}
什么數(shù)據(jù)需要寫在原型中
- 數(shù)據(jù)共享是原型的作用之一禀梳,故需要共享的數(shù)據(jù)就可以寫在原型中
- 不需要共享的數(shù)據(jù)寫在構(gòu)造函數(shù)中
說明
- 任何函數(shù)都有prototype屬性對象杜窄,包括原生對象的原型,如Date.prototype算途,因此可以利用此特性來擴展原生對象
- 最好不要讓實例之間互相共享數(shù)組或?qū)ο蟪蓡T塞耕,因為一旦修改會導(dǎo)致數(shù)據(jù)的走向很不明確,而且難以維護
- 原型對象的使用建議
- 私有成員(如非函數(shù)成員)放到構(gòu)造函數(shù)中
- 共享成員(一般為函數(shù))放到原型對象中
- 如果重置了
prototype
記得修正 constructor
的指向
原型的作用
- 數(shù)據(jù)共享(為了節(jié)省內(nèi)存空間)
- 繼承
6.3 繼承
- 繼承是指一個對象直接使用另一對象的屬性和方法
- 一般會創(chuàng)建子類和父類嘴瓤,子類可使用父類的所有功能扫外,且對這些功能進行擴展
- 繼承有兩種方式:接口繼承和實現(xiàn)繼承,ECMAScript只支持實現(xiàn)繼承廓脆,主要是依靠原型鏈來實現(xiàn)
6.3.1 對象拷貝
對象拷貝:將對象的成員復(fù)制一份給需要繼承的對象筛谚,但并非真正的繼承,真正的繼承是類與類的關(guān)系
// 案例1 一般的對象拷貝
var obj1 = {
name: 'zs',
age: 18,
sex: '男'
}
var obj2 = {};
// 封裝函數(shù) - 把o1的成員復(fù)制給o2
function copy(o1, o2) {
for (var key in o1) {
o2[key] = o1[key];
}
}
copy(obj1, obj2);
console.dir(obj2); // obj2 擁有了obj1的name age sex屬性
obj1.name = 'xx'; // 修改obj1的成員
console.dir(obj2); // 不會影響obj2
那么要如何實現(xiàn) 完全的拷貝 呢狞贱?
這里涉及到遞歸的知識 去查看 ----> 遞歸 (按住Ctrl點擊)
// 案例2
// 創(chuàng)建父對象
var father = {
name: '王健林',
money: 10000000,
cars: ['BMW', 'Tesla'],
hobby: function () {
console.log('Play golf');
}
}
// 創(chuàng)建需要繼承的子對象
var son = {
name: '王思聰'
}
// 對象拷貝函數(shù) (使用for...in...循環(huán) 并封裝到函數(shù)中)
function extend(parent, child) {
for (var key in parent) {
if (child[key]) { // 若有相同屬性則跳過
continue;
}
child[key] = parent[key];
}
}
extend(father, son);
// 此時son就也有了father的cars money hobby 但name依然自己保留
/*--------------------------------------
對象拷貝存在問題:
如果繼承過來的成員是引用類型的話(即對象屬性 對象里包含另一個對象)刻获,那么這個引用類型的成員在父對象和子對象之間是共享的,也就是說修改了之后瞎嬉,父子對象都會受到影響蝎毡。
這個問題可以通過函數(shù)"遞歸"來解決。
---------------------------------------*/
6.3.2 原型式繼承
// 借用構(gòu)造函數(shù)的原型對象實現(xiàn)繼承
// 創(chuàng)建父構(gòu)造函數(shù)
function Parent(name){
this.name = name;
this.showName = function(){
console.log(this.name);
}
}
// 設(shè)置父構(gòu)造器的原型對象
Parent.prototype.showAge = function(){
console.log(this.age);
}
// 創(chuàng)建子構(gòu)造函數(shù)
function Child(){
}
// 設(shè)置子構(gòu)造函數(shù)的原型對象實現(xiàn)繼承
Child.prototype = Parent.prototype;
var child = new Child();
console.dir(child); //child只在原型上繼承了showAge方法
/*--------------------------------------
問題:
父構(gòu)造函數(shù)的原型對象和子構(gòu)造函數(shù)的原型對象上的成員有共享問題氧枣,包含引用類型的屬性值會共享沐兵,且只能繼承父構(gòu)造函數(shù)的原型對象上的成員, 不能繼承父構(gòu)造函數(shù)的實例對象的成員
--------------------------------------*/
6.3.3 原型鏈繼承
原型鏈:每個對象都有原型對象,原型對象也有原型對象便监。由此扎谎,我們的對象,和對象的原型烧董,以及原型的原型毁靶,就構(gòu)成了一個原型鏈。
// 核心:子構(gòu)造函數(shù).prototype = new 父構(gòu)造函數(shù)()
// 創(chuàng)建父構(gòu)造函數(shù)
function Parent(){
this.name = 'baba';
this.age = 35;
this.showName = function(){
console.log(this.name);
}
}
// 設(shè)置父構(gòu)造函數(shù)的原型
Parent.prototype.friends = ['小名', '小強'];
Parent.prototype.showAge = function(){
console.log(this.age);
}
// 創(chuàng)建子構(gòu)造函數(shù)
function Child(){
}
// 實現(xiàn)繼承
// 同時修改子構(gòu)造函數(shù)的原型的構(gòu)造器屬性 防止執(zhí)行錯誤
Child.prototype = new Parent();
Child.prototype.constructor = Child;
// 實例對象
var child = new Child();
console.log(child.name); // baba
console.log(child.age); // 35
child.showName(); // baba
child.showAge(); // 35
console.log(child.friends); // ['小名','小強']
// 當(dāng)我們改變friends時, 父構(gòu)造函數(shù)的原型對象的也會跟著變化
child.friends.push('小王');
console.log(child.friends); // ["小名", "小強", "小王"]
var father = new Parent();
console.log(father.friends); // ["小名", "小強", "小王"]
/*--------------------------------------------
特點:
- 非常純粹的繼承關(guān)系逊移,實例是子類的實例预吆,也是父類的實例
- 父類新增原型方法/原型屬性,子類都能訪問到
問題:
- 創(chuàng)建子類實例時胳泉,不能給父構(gòu)造函數(shù)傳遞參數(shù)
- 要想為子類新增屬性和方法拐叉,必須創(chuàng)建實例對象后才行,不能放到構(gòu)造器中
- 無法實現(xiàn)多繼承
- 來自原型對象的引用屬性是所有實例共享的扇商,子對象修改時父對象也會被動修改
---------------------------------------------*/
6.3.4 借用構(gòu)造函數(shù)
// 使用call借用其他構(gòu)造函數(shù)的成員, 用父類的構(gòu)造函數(shù)來增強子類實例凤瘦,等于是復(fù)制父類的實例屬性給子類
// 創(chuàng)建父構(gòu)造函數(shù)
function Parent(name, age) {
this.name = name;
this.age = age;
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
// 創(chuàng)建子構(gòu)造函數(shù)
function Child(name, age, sex) {
Parent.call(this, name, age, sex);
this.sex = sex;
}
// 創(chuàng)建實例對象
var son = new Child('zs', 18, '男');
console.dir(son);
// son訪問到了Child的sex 還訪問到了Parent的name和age 但未訪問到Parent原型中的sayName方法
/*-----------------------------------------------
特點:
- 解決了傳遞參數(shù)、子類實例共享父類引用屬性的問題
- 可以實現(xiàn)多繼承(call多個父類對象)但不完美案铺,沒有父類方法
缺點:
- 實例并不是父類的實例蔬芥,只是子類的實例
- 只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法
- 無法實現(xiàn)函數(shù)復(fù)用,每個子類都有父類實例函數(shù)的副本坝茎,影響性能
----------------------------------------------*/
6.3.5 組合繼承
// 借用構(gòu)造函數(shù) + 原型式繼承
// 創(chuàng)建父構(gòu)造函數(shù)
function Parent(name, age) {
this.name = name;
this.age = age;
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
// 創(chuàng)建子構(gòu)造函數(shù)
function Child(name, age, sex) {
Parent.call(this, name, age, sex);
this.sex = sex;
}
// 通過原型涤姊,讓子類型,繼承父類型中的方法
Child.prototype = new Parent();
Child.prototype.constructor = Child;
// 可以賦予子類型特有的方法
Child.prototype.sayHi = function () {
console.log('你好');
}
/*-------------------------------------------------
特點:
- 彌補了借用構(gòu)造函數(shù)的缺陷嗤放,可繼承實例屬性/方法 和 原型屬性/方法
- 既是子類的實例思喊,也是父類的實例
- 不存在引用屬性共享問題
- 可傳參
- 函數(shù)可復(fù)用
- 可以實現(xiàn)多繼承
缺點:
- 調(diào)用了兩次父類構(gòu)造函數(shù),生成了兩份實例(子類實例將子類原型上的那份屏蔽了次酌,僅僅多消耗了一點內(nèi)存)
---------------------------------------------------*/
6.3.6 寄生式繼承
//面向過程的方式
// 1 記錄學(xué)生的成績
var stu1 = {name: 'zs', subject: '語文', score: 90};
var stu2 = {name: 'ls', subject: '語文', score: 80};
// 2 打印學(xué)生的成績
console.log(stu1.name, stu1.subject, stu1.score);
console.log(stu2.name, stu2.subject, stu2.score);
// 面向?qū)ο蟮姆绞?
/*-----------------------------------------
首選思考的不是程序執(zhí)行流程提陶,而是把`Student`視為對象烫沙,并擁有`name`和`score`兩個屬性;
而打印成績,首先必須創(chuàng)建出這個`Student`對應(yīng)的對象隙笆,再給對象發(fā)一個`printScore`消息锌蓄,讓對象把自己的數(shù)據(jù)打印出來
-----------------------------------------*/
// 1 抽象數(shù)據(jù)行為變成模板 (Class)
function Student(name, subject, score) {
this.name = name;
this.subject = subject;
this.score = score;
this.printScore = function () {
console.log(this.name, this.subject, this.score);
}
}
// 2 根據(jù)模板創(chuàng)建具體實例對象 (Instance)
var stuA = new Student('zs', '語文', 90);
var stuB = new Student('ls', '語文', 80);
// 3 實例對象具有自己的具體行為 (指揮Instance得到結(jié)果)
stuA.printScore();
stuB.printScore();
/*-----------------------------------------
在此例中,我們處理的多個對象都具有一個共同點撑柔,即他們都是學(xué)生煤率,所以把他們抽象歸成 類/模板(Class),再根據(jù)每一個具體的學(xué)生去創(chuàng)建具體的實例對象(Instance)乏冀,最后再單獨賦予給他們各自的具體行為
-----------------------------------------*/
- 在JS中創(chuàng)建對象的模板是構(gòu)造函數(shù)蝶糯,而在其他語言中創(chuàng)建對象的模板是類
- 創(chuàng)建實例對象(Instance)時,需要使用new操作符
// 1 new Object()創(chuàng)建
// var person = {}; 也可以省略new Object() 縮寫成{}
var person = new Object();
person.name = "Nico";
person.age = 21;
person.sayHi = function(){
console.log("hello, I am" + this.name);
}
// 2 對象字面量創(chuàng)建
var person = {
name: "Nico",
age: 21;
sayHi: function(){
console.log("hello, I am" + this.name);
}
}
// 3 工廠函數(shù)
/*---------------------------------------
當(dāng)要創(chuàng)建多個實例對象時 就要重復(fù)寫很多份對象 代碼太冗余 不方便 使用我們使用工廠函數(shù) 它用于批量創(chuàng)建多個對象 解決代碼重復(fù)問題
----------------------------------------*/
function createPerson (name, age) {
return {
name: name,
age: age,
sayHi: function () {
console.log("hello, I am" + this.name);
}}}
// 再生成實例對象
var p1 = createPerson("Nico", 21);
var p2 = createPerson("Mike", 18);
/*-------------------------------------
工廠函數(shù)雖解決了批量創(chuàng)建對象的問題 但存在缺點:它無法確定真實的對象類型
-------------------------------------*/
console.log(typeof p1); // Object
console.log(p1 instanceof createPerson); // false
構(gòu)造函數(shù)是專門用來創(chuàng)建對象的函數(shù)
一個構(gòu)造函數(shù)我們也可以稱為一個類
通過一個構(gòu)造函數(shù)創(chuàng)建的對象辆沦,我們稱該對象時這個構(gòu)造函數(shù)的實例
通過同一個構(gòu)造函數(shù)創(chuàng)建的對象昼捍,我們稱為一類對象
-
構(gòu)造函數(shù)就是一個普通的函數(shù)识虚,只是他的調(diào)用方式不同
- 如果直接調(diào)用,它就是一個普通函數(shù)
- 如果使用new來調(diào)用妒茬,則它就是一個構(gòu)造函數(shù)
// 4 構(gòu)造函數(shù)
/*------------------------------------
1 這里引入更優(yōu)解 即更優(yōu)雅的工廠函數(shù) 即構(gòu)造對象的函數(shù)
2 構(gòu)造函數(shù)比工廠函數(shù)創(chuàng)建更方便
2.1 會在內(nèi)存中創(chuàng)建一個空對象 (無需內(nèi)部創(chuàng)建新對象)
2.2 直接將屬性和方法賦給了this 讓this指向剛剛創(chuàng)建好的對象
2.3 執(zhí)行構(gòu)造函數(shù)中的代碼
2.4 沒有return語句 無需返回對象(會自動返回)
3 構(gòu)造函數(shù)一般用首字母大寫 普通函數(shù)用小寫 便于區(qū)分
4 同時解決了工廠函數(shù)不能識別對象具體類型的缺陷
------------------------------------*/
function Person (name, age) {
this.name = name,
this.age = age,
this.sayHi = function () {
console.log("hello, I am" + this.name);
}}
// 生成實例對象必須使用new操作符
var p1 = new Person("Nico", 21);
var p2 = new Person("Mike", 18);
console.log(p1 instanceof Person); // true
/*---------------------------------------
每一個實例對象中的 __proto__ 中有一個constructor屬性担锤,該屬性指向創(chuàng)建該實例的構(gòu)造函數(shù),它最初是用來標(biāo)識對象類型的乍钻,它雖然可以用于檢測對象的類型肛循,但是生產(chǎn)中還是使用instanceof操作符更可靠。
console.log(p1.constructo); // 返回它的對象構(gòu)造器
console.log(p1.constructor === Person); // true
--------------------------------------*/
/*-----------------------------------------
用上面的方法去構(gòu)造函數(shù)雖然方便银择,但當(dāng)多個對象去調(diào)用這個構(gòu)造函數(shù)時多糠,會產(chǎn)生存儲多個方法的內(nèi)存區(qū)域,會造成極大的內(nèi)存浪費浩考。
p1.sayHi();
p2.sayhi();
console.log(p1.sayHi === p2.sayHi); // false
// 說明兩者不是指向同一方法 而是創(chuàng)建了兩塊內(nèi)存區(qū)來存儲
所以我們把需要共享的函數(shù)定義到構(gòu)造函數(shù)的外部夹孔,但若有多個需要共享的函數(shù),可能會造成全局命名空間沖突的問題析孽,因此有可以把多個函數(shù)放到一個對象中來保存來避免問題搭伤。
----------------------------------------*/
function sayHi() {
console.log("hello, I am" + this.name);
}
//或
var fns = {
sayHi: function () {
console.log("hello, I am" + this.name);
},
sayAge: function () {
console.log("I am" + this.age);
}
}
// 這樣基本上解決了構(gòu)造函數(shù)的內(nèi)存浪費問題 但代碼看起來會有些怪 所以引入原型
? 通過構(gòu)造函數(shù)創(chuàng)建的對象,解析器都會默認(rèn)在函數(shù)中添加一個prototype屬性對象袜瞬,即每一個構(gòu)造函數(shù)都有一個prototype屬性怜俐,稱為原型/原型對象。原型中的成員邓尤,都會被構(gòu)造函數(shù)的實例間接繼承拍鲤,故可以把所有對象實例需要共享的屬性和方法直接定義在prototype對象上。
// 5 原型
/*-----------------------------------------
-------------------------------------------*/
function Person (name, age) {
this.name = name;
this.age = age;
}
Person.prototype.type = "student";
Person.prototype.sayHi = function () {
console.log("hello, I am" + this.name);
}
var p1 = new Person("Nico", 21);
var p2 = new Person("Mike", 18);
console.log(p1.sayHi === p2.sayHi); //true
構(gòu)造函數(shù)裁赠、實例、原型三者之間的關(guān)系
(圖略)
console.log(p1.__proto__ === Person.prototype); //true
console.log(p1.constructor === Person); //true
-
prototype
屬性是一個對象赴精,所有實例屬性和方法都指向它佩捞,它們共用同一個內(nèi)存地址,標(biāo)準(zhǔn)屬性蕾哟,給程序員使用 -
__proto__
屬性是非標(biāo)準(zhǔn)的屬性一忱,指向構(gòu)造函數(shù)的原型,供瀏覽器使用 -
constructor
是構(gòu)建器谭确,指向構(gòu)建函數(shù)
實例對象訪問原型對象中的成員的搜索原則:原型鏈
- 搜索首先從對象實例本身開始
- 若在實例中找到了指定屬性帘营,則返回該屬性的值
- 若未找到,則繼續(xù)搜索指針指向的原型對象逐哈,在原型對象中查找指定屬性
- 若在原型對象中找到了指定屬性芬迄,則返回該屬性的值
- 若一直到原型鏈末端還未找到,則返回
undefined
- Object的原型的原型為
null
// 6 最優(yōu)的原型
/*-----------------------------------
為減少不必要的每次都輸入一遍Person.prototype昂秃,常見做法是用一個包含所有屬性和方法的對象字面量來重寫整個原型對象
----------------------------------*/
function Person (name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
constructor: Person, //避免原型對象丟失了constructor成員
type: "student",
sayHi: function () {
console.log("hello, I am" + this.name);
}
}
什么數(shù)據(jù)需要寫在原型中
- 數(shù)據(jù)共享是原型的作用之一禀梳,故需要共享的數(shù)據(jù)就可以寫在原型中
- 不需要共享的數(shù)據(jù)寫在構(gòu)造函數(shù)中
說明
- 任何函數(shù)都有prototype屬性對象杜窄,包括原生對象的原型,如Date.prototype算途,因此可以利用此特性來擴展原生對象
- 最好不要讓實例之間互相共享數(shù)組或?qū)ο蟪蓡T塞耕,因為一旦修改會導(dǎo)致數(shù)據(jù)的走向很不明確,而且難以維護
- 原型對象的使用建議
- 私有成員(如非函數(shù)成員)放到構(gòu)造函數(shù)中
- 共享成員(一般為函數(shù))放到原型對象中
- 如果重置了
prototype
記得修正constructor
的指向
原型的作用
- 數(shù)據(jù)共享(為了節(jié)省內(nèi)存空間)
- 繼承
- 繼承是指一個對象直接使用另一對象的屬性和方法
- 一般會創(chuàng)建子類和父類嘴瓤,子類可使用父類的所有功能扫外,且對這些功能進行擴展
- 繼承有兩種方式:接口繼承和實現(xiàn)繼承,ECMAScript只支持實現(xiàn)繼承廓脆,主要是依靠原型鏈來實現(xiàn)
對象拷貝:將對象的成員復(fù)制一份給需要繼承的對象筛谚,但并非真正的繼承,真正的繼承是類與類的關(guān)系
// 案例1 一般的對象拷貝
var obj1 = {
name: 'zs',
age: 18,
sex: '男'
}
var obj2 = {};
// 封裝函數(shù) - 把o1的成員復(fù)制給o2
function copy(o1, o2) {
for (var key in o1) {
o2[key] = o1[key];
}
}
copy(obj1, obj2);
console.dir(obj2); // obj2 擁有了obj1的name age sex屬性
obj1.name = 'xx'; // 修改obj1的成員
console.dir(obj2); // 不會影響obj2
那么要如何實現(xiàn) 完全的拷貝 呢狞贱?
這里涉及到遞歸的知識 去查看 ----> 遞歸 (按住Ctrl點擊)
// 案例2
// 創(chuàng)建父對象
var father = {
name: '王健林',
money: 10000000,
cars: ['BMW', 'Tesla'],
hobby: function () {
console.log('Play golf');
}
}
// 創(chuàng)建需要繼承的子對象
var son = {
name: '王思聰'
}
// 對象拷貝函數(shù) (使用for...in...循環(huán) 并封裝到函數(shù)中)
function extend(parent, child) {
for (var key in parent) {
if (child[key]) { // 若有相同屬性則跳過
continue;
}
child[key] = parent[key];
}
}
extend(father, son);
// 此時son就也有了father的cars money hobby 但name依然自己保留
/*--------------------------------------
對象拷貝存在問題:
如果繼承過來的成員是引用類型的話(即對象屬性 對象里包含另一個對象)刻获,那么這個引用類型的成員在父對象和子對象之間是共享的,也就是說修改了之后瞎嬉,父子對象都會受到影響蝎毡。
這個問題可以通過函數(shù)"遞歸"來解決。
---------------------------------------*/
// 借用構(gòu)造函數(shù)的原型對象實現(xiàn)繼承
// 創(chuàng)建父構(gòu)造函數(shù)
function Parent(name){
this.name = name;
this.showName = function(){
console.log(this.name);
}
}
// 設(shè)置父構(gòu)造器的原型對象
Parent.prototype.showAge = function(){
console.log(this.age);
}
// 創(chuàng)建子構(gòu)造函數(shù)
function Child(){
}
// 設(shè)置子構(gòu)造函數(shù)的原型對象實現(xiàn)繼承
Child.prototype = Parent.prototype;
var child = new Child();
console.dir(child); //child只在原型上繼承了showAge方法
/*--------------------------------------
問題:
父構(gòu)造函數(shù)的原型對象和子構(gòu)造函數(shù)的原型對象上的成員有共享問題氧枣,包含引用類型的屬性值會共享沐兵,且只能繼承父構(gòu)造函數(shù)的原型對象上的成員, 不能繼承父構(gòu)造函數(shù)的實例對象的成員
--------------------------------------*/
原型鏈:每個對象都有原型對象,原型對象也有原型對象便监。由此扎谎,我們的對象,和對象的原型烧董,以及原型的原型毁靶,就構(gòu)成了一個原型鏈。
// 核心:子構(gòu)造函數(shù).prototype = new 父構(gòu)造函數(shù)()
// 創(chuàng)建父構(gòu)造函數(shù)
function Parent(){
this.name = 'baba';
this.age = 35;
this.showName = function(){
console.log(this.name);
}
}
// 設(shè)置父構(gòu)造函數(shù)的原型
Parent.prototype.friends = ['小名', '小強'];
Parent.prototype.showAge = function(){
console.log(this.age);
}
// 創(chuàng)建子構(gòu)造函數(shù)
function Child(){
}
// 實現(xiàn)繼承
// 同時修改子構(gòu)造函數(shù)的原型的構(gòu)造器屬性 防止執(zhí)行錯誤
Child.prototype = new Parent();
Child.prototype.constructor = Child;
// 實例對象
var child = new Child();
console.log(child.name); // baba
console.log(child.age); // 35
child.showName(); // baba
child.showAge(); // 35
console.log(child.friends); // ['小名','小強']
// 當(dāng)我們改變friends時, 父構(gòu)造函數(shù)的原型對象的也會跟著變化
child.friends.push('小王');
console.log(child.friends); // ["小名", "小強", "小王"]
var father = new Parent();
console.log(father.friends); // ["小名", "小強", "小王"]
/*--------------------------------------------
特點:
- 非常純粹的繼承關(guān)系逊移,實例是子類的實例预吆,也是父類的實例
- 父類新增原型方法/原型屬性,子類都能訪問到
問題:
- 創(chuàng)建子類實例時胳泉,不能給父構(gòu)造函數(shù)傳遞參數(shù)
- 要想為子類新增屬性和方法拐叉,必須創(chuàng)建實例對象后才行,不能放到構(gòu)造器中
- 無法實現(xiàn)多繼承
- 來自原型對象的引用屬性是所有實例共享的扇商,子對象修改時父對象也會被動修改
---------------------------------------------*/
// 使用call借用其他構(gòu)造函數(shù)的成員, 用父類的構(gòu)造函數(shù)來增強子類實例凤瘦,等于是復(fù)制父類的實例屬性給子類
// 創(chuàng)建父構(gòu)造函數(shù)
function Parent(name, age) {
this.name = name;
this.age = age;
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
// 創(chuàng)建子構(gòu)造函數(shù)
function Child(name, age, sex) {
Parent.call(this, name, age, sex);
this.sex = sex;
}
// 創(chuàng)建實例對象
var son = new Child('zs', 18, '男');
console.dir(son);
// son訪問到了Child的sex 還訪問到了Parent的name和age 但未訪問到Parent原型中的sayName方法
/*-----------------------------------------------
特點:
- 解決了傳遞參數(shù)、子類實例共享父類引用屬性的問題
- 可以實現(xiàn)多繼承(call多個父類對象)但不完美案铺,沒有父類方法
缺點:
- 實例并不是父類的實例蔬芥,只是子類的實例
- 只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法
- 無法實現(xiàn)函數(shù)復(fù)用,每個子類都有父類實例函數(shù)的副本坝茎,影響性能
----------------------------------------------*/
// 借用構(gòu)造函數(shù) + 原型式繼承
// 創(chuàng)建父構(gòu)造函數(shù)
function Parent(name, age) {
this.name = name;
this.age = age;
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
// 創(chuàng)建子構(gòu)造函數(shù)
function Child(name, age, sex) {
Parent.call(this, name, age, sex);
this.sex = sex;
}
// 通過原型涤姊,讓子類型,繼承父類型中的方法
Child.prototype = new Parent();
Child.prototype.constructor = Child;
// 可以賦予子類型特有的方法
Child.prototype.sayHi = function () {
console.log('你好');
}
/*-------------------------------------------------
特點:
- 彌補了借用構(gòu)造函數(shù)的缺陷嗤放,可繼承實例屬性/方法 和 原型屬性/方法
- 既是子類的實例思喊,也是父類的實例
- 不存在引用屬性共享問題
- 可傳參
- 函數(shù)可復(fù)用
- 可以實現(xiàn)多繼承
缺點:
- 調(diào)用了兩次父類構(gòu)造函數(shù),生成了兩份實例(子類實例將子類原型上的那份屏蔽了次酌,僅僅多消耗了一點內(nèi)存)
---------------------------------------------------*/
寄生式繼承是與原型式繼承緊密相關(guān)的一種思路恨课,寄生式繼承的思路與寄生構(gòu)造函數(shù)和工廠模式類似,即創(chuàng)建一個僅用于封裝繼承過程的函數(shù)岳服,該函數(shù)在內(nèi)部以某種方式來增強對象剂公,最后再像真地是它做了所有工作一樣返回對象。
6.3.7 寄生組合式繼承
寄生組合式繼承吊宋,集寄生式繼承和組合繼承的優(yōu)點與一身纲辽,是實現(xiàn)基于類型繼承的最有效方式。
6.4 函數(shù)進階
定義函數(shù)的方式有兩種:函數(shù)聲明璃搜、函數(shù)表達式
// 1 函數(shù)聲明
function fn() {
console.log('這是函數(shù)聲明拖吼!');
}
// 2 函數(shù)表達式
var fn = function () {
console.log('這是函數(shù)表達式');
}
fn();
區(qū)別:在執(zhí)行函數(shù)時,
fn( );
的代碼順序?qū)?code>1函數(shù)聲明沒有任何影響这吻,但若把fn( );
寫于2函數(shù)表達式
之前吊档,程序會報錯。原因:JS代碼是按從上往下順序鏈執(zhí)行的唾糯,但在執(zhí)行前會進行預(yù)解析怠硼,預(yù)先把代碼中的函數(shù)聲明(
function
)和變量聲明(var
)提前,內(nèi)部運行原理如下:fn1(); function fn1(){}; fn2(); var fn2 = function (){}; ===> function fn1(){}; // 被提前 var fn2; // 被提前 fn1(); // 運行無問題 fn2(); // 運行 發(fā)現(xiàn)首個空變量 運行出錯移怯! fn2 = function (){}; // fn2最后才被賦值 已無效
注意:不要在if else while for等語句中去定義一個函數(shù)聲明香璃,因為在現(xiàn)代瀏覽器中,這些語句中函數(shù)聲明不會被提前舟误,若這樣做葡秒,運行時容易出錯!
6.4.1 函數(shù)中this
this 指上下文對象脐帝,每次調(diào)用函數(shù)時同云,解析器都會將一個this作為隱含參數(shù)傳遞進函數(shù)糖权。根據(jù)函數(shù)調(diào)用形式的不同堵腹,this的值也不同,函數(shù)的調(diào)用方式?jīng)Q定了this的指向
調(diào)用方式 | this指向 | 備注 |
---|---|---|
普通函數(shù)調(diào)用 | window | 嚴(yán)格模式下是 undefined |
構(gòu)造函數(shù)調(diào)用 | 實例對象 | 原型方法中 this 也是實例對象 |
對象方法調(diào)用 | 該方法所屬對象 | 緊挨著的對象 |
事件綁定方法 | 綁定/觸發(fā)事件對象 | |
定時器函數(shù) | window |
總結(jié):函數(shù)內(nèi)部的this星澳,是由函數(shù)被調(diào)用時來確定其指向的疚顷,即 誰調(diào)用指向誰
6.4.2 函數(shù)對象
函數(shù)其實也是一種特殊的對象,即
Function()
,它擁有原型腿堤,擁有一些屬性和方法阀坏,且所有函數(shù)都是Function
構(gòu)造函數(shù)的實例[圖片上傳失敗...(image-4d38b9-1587459554178)]
方法
call()
bind()
apply()
是函數(shù)常用的方法,用于改變函數(shù)中的this指向
function fn(x, y) {
console.log(this); // this原本指向 window
console.log(x + y); // 求兩數(shù)之和
}
fn(5, 6); // 11
var another = {
value: "這是另一個對象的值"
}
fn.call(anotherFn, 3, 4); //返回 "anotherFn對象" 和 "7"
fn.apply(anotherFn, [3, 4]);//返回 "anotherFn對象" 和 "7"
fn.bind(anotherFn, 3, 4); //運行無反應(yīng) 無效語句
var other = fn.bind(anotherFn, 3, 4); //只能先綁定函數(shù)
other(); // 再去運行變量 //返回 "anotherFn對象" 和 "7"
/*--------------------------------------------
call()
:調(diào)用一個函數(shù)笆檀,其具有一個指定的this
值和傳入提供的參數(shù)
apply()
:調(diào)用一個函數(shù)忌堂,其具有一個指定的this
值和作為數(shù)組傳入提供的參數(shù)
bind()
:創(chuàng)建一個新函數(shù)(稱為綁定函數(shù)),新函數(shù)與被調(diào)函數(shù)(綁定函數(shù)的目標(biāo)函數(shù))具有相同的函數(shù)體酗洒,目標(biāo)函數(shù)會重新指定this指向士修,并傳入?yún)?shù)
- 參數(shù):
- 第一個參數(shù) 設(shè)置函數(shù)內(nèi)部this的指向
- 其它參數(shù),對應(yīng)函數(shù)的參數(shù)
- call函數(shù)的返回值:
- 即指定函數(shù)傳參后的返回值
call()和apply()的區(qū)別:call()可接受若干個參數(shù)的列表樱衷,而apply()接受的是一個包含多個參數(shù)的數(shù)組
call()和bind()的區(qū)別:call()改過this的指向后棋嘲,會再執(zhí)行函數(shù),bind()改過this后矩桂,不會執(zhí)行調(diào)用函數(shù)沸移,而是把函數(shù)復(fù)制一份,返回給一個綁定新this的函數(shù)
共同點:一般都用于改變this的指向
// call()的應(yīng)用
// 對于真正的數(shù)組侄榴,我們可以用push()和splice()方法對數(shù)組元素進行增加或刪除雹锣,但對于偽數(shù)組,這些方法是不可行的牲蜀,但可以調(diào)用call()方法來實現(xiàn)
var fakeArr = { // 這是一個偽數(shù)組
0: 100,
1: 10,
2: 11,
3: 20,
length: 4
};
//一般解決方法
fakeArr['4'] = 30;
fakeArr.length ++;
//調(diào)用call()方法 (length會自動增減 無需操作)
Array.prototype.push.call(fakeArr, 30); //增加元素
Array.prototype.splice.call(fakeArr, 0, 3); //刪減元素
// apply()的應(yīng)用
//Math.max()方法可以求傳入數(shù)值參數(shù)的最大值或者打印出來笆制,但若傳入的是數(shù)組,它是不可行的涣达,但可以調(diào)用apply()方法來實現(xiàn)
console.log(Math.max(3, 5, 6)); // 6 (可以求最大值)
var arr = [5, 10, 1, 3];
console.log(Math.max(arr));// NaN (說明不能求數(shù)組中的最大值)
// 調(diào)用apply()方法
console.log(Math.max.apply(null, arr)); // 10
console.log(Math.max.apply(Math, arr)); // 10 (指向Math)
console.log(1, 2, 3); // 1 2 3
console.log(arr); //(5) [5, 10, 1, 3] (不是我們想要的形式)
// 調(diào)用apply()方法 打印
console.log.apply(console, arr);//5 10 1 3 (指向console)
// bind()的應(yīng)用 常用于改變this的指向
//實現(xiàn)1秒打印對象中的指定屬性的值
var obj = {
value: '1234',
fn: function() {
setInterval(function() {
console.log(this.value);
}.bind(this), 1000);
}
}
obj.fn(); // (輸出的是空字符串 并未實現(xiàn)打印)
btn.onclick = function () {
// 事件處理函數(shù)中的this 是觸發(fā)該事件的對象
// 通過bind 改變事件處理函數(shù)中this的指向
}.bind(obj); // 1234...1234... (調(diào)用bind()實現(xiàn))
屬性
- arguments 偽數(shù)組 獲取函數(shù)的實參 返回變成數(shù)組形式
- caller 返回函數(shù)的調(diào)用者在辆,但在全局范圍調(diào)用時返回 null
- length 返回形參的個數(shù)
- name 返回函數(shù)的名稱
6.4.3 高階函數(shù)
- 函數(shù)可以作為參數(shù)
- 函數(shù)可以作為返回值
// 作為參數(shù)
function eat(fn) {
setTimeout(function () {
console.log('吃完了');
fn();
}, 1000)
}
eat(function () {console.log('去唱歌');});//吃完了 去唱歌
// 作為返回值 (實際上利用到了"閉包"現(xiàn)象)
// 案例一 返回隨機數(shù)
function getRandom() {
var random = parseInt(Math.random() * 10) + 1;
return function () {
return random;
}
}
var fn = getRandom();
console.log(fn()); // 7
console.log(fn()); // 7 (兩次返回的隨機數(shù)都不會改變)
// 案例二 求兩個數(shù)的和
function getFun(n) {
return function (m) {
return n + m;
}
}
var fn100 = getFun(100); // 求 100 + m
var fn1000 = getFun(1000); // 求 1000 + m
console.log(fn100(1)); // 101
console.log(fn1000(1)); // 1001
6.5 閉包
閉包:即在一個作用域中可以訪問另一個作用域的變量,它延展了函數(shù)的作用域范圍度苔,可簡單理解成 “定義在一個函數(shù)內(nèi)部的函數(shù)”匆篓,本質(zhì)上就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁
閉包的用途:
- 解決變量私有化問題
- 可以在函數(shù)外部讀取函數(shù)內(nèi)部成員
- 讓函數(shù)內(nèi)成員始終存活在內(nèi)存中
- 緩存數(shù)據(jù)
- 延長作用域鏈
// 未發(fā)生閉包
function fn() {
var n = 10;
return n;
}
fn();
// 發(fā)生閉包 (函數(shù)嵌套在return中 作為返回值返回)
function fn() {
var n = 10;
return function () {
return n;
}
}
var f = fn();
console.log(f()); // 10
/*----------------------------------------
函數(shù)在Global作用域上訪問到了Local作用域中的n值,并出現(xiàn)Closure(fn())寇窑,這就是閉包鸦概,它會作為一個函數(shù)來返回
----------------------------------------*/
閉包案例1
<ul id="phone">
<li>華為</li>
<li>小米</li>
<li>vivo</li>
<li>oppo</li>
</ul>
<script>
// 實現(xiàn)點擊li的時候輸出當(dāng)前l(fā)i對應(yīng)的索引
var phone = document.getElementById('heroes');
var list = heroes.children;
// 方式1 一般實現(xiàn) (利用索引)
for (var i = 0; i < list.length; i++) {
var li = list[i];
li.index = i;
li.onclick = function () {
console.log(this.index);
}
}
// 方式2 用閉包實現(xiàn) (利用自調(diào)用函數(shù))
for (var i = 0; i < list.length; i++) {
var li = list[i];
(function (i) {
li.onclick = function () {
console.log(i);
}
})(i);
}
</script>
閉包案例2
console.log('start');
setTimeout(function () {
console.log('timeout');
}, 0);
console.log('over');
// 輸出順序 start over timeout
/*—————————————————————————————————————————————————————
為什么timeout會最后輸出?
原因是setTimeout()本身就是一個函數(shù)甩骏,它被放到"執(zhí)行棧"中窗市,運行到它時,它會把內(nèi)部存在的函數(shù)先暫存到"任務(wù)列表"中饮笛,待"執(zhí)行棧"中的命令執(zhí)行完咨察,再去執(zhí)行"任務(wù)列表"中的命令。
————————————————————————————————————————————————————*/
[圖略]
console.log('start');
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}
console.log('end');
// 輸出順序 start over 3 3 3
console.log('start');
for (var i = 0; i < 3; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
}, 0);
})(i);
}
console.log('end');
// 輸出順序 start over 0 1 2 (因為這里調(diào)用了自調(diào)用函數(shù) 產(chǎn)生了閉包 訪問到了其他作用域)
閉包案例3
<body>
*** 改變這行文字的字體大小 ***
<div id="box">
<button id="btn1" size="12">按鈕1</button>
<button id="btn2" size="14">按鈕2</button>
<button id="btn3" size="16">按鈕3</button>
</div>
<script>
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
var btn3 = document.getElementById('btn3');
// 方法1 一般實現(xiàn)
btn1.onclick = function () {
document.body.style.fontSize = '12px';
}
btn2.onclick = function () {
document.body.style.fontSize = '14px';
}
btn3.onclick = function () {
document.body.style.fontSize = '16px';
}
// 方法2 利用函數(shù)閉包實現(xiàn)
function makeFun(size) {
return function () {
document.body.style.fontSize = size + 'px';
}
}
btn1.onclick = makeFun(12);
btn2.onclick = makeFun(14);
btn3.onclick = makeFun(16);
// 方法3 利用閉包 避免重復(fù)代碼
var box = document.getElementById('box');
var buttons = box.children;
for (var i = 0; i < buttons.length; i++) {
var btn = buttons[i]; // 為標(biāo)簽添加自定義屬性
var size = btn.getAttribute('size');
btn.onclick = makeFun(size);
}
</script>
閉包思考
// 案例1
var name = "The Window";
var object = {
name: "My Object",
getNameFn: function () {
return function () {
return this.name; //指向全局
}
}
}
console.log(object.getNameFn()()); // The Window
// 案例2
var name = "The Window";
var object = {
name: "My Object",
getNameFn: function () {
var that = this; // 改變this指向
return function () {
return that.name; //指向object內(nèi)部
}
}
}
console.log(object.getNameFn()()); // My Object
// getNameFn()() 首個括號僅返回函數(shù) 第二個括號返回內(nèi)部的return值
6.6 遞歸
遞歸: 函數(shù)自己調(diào)用自己
注意:遞歸一般都要寫一個結(jié)束的條件福青,防止過程中出錯摄狱,造成內(nèi)存溢出(超過了最大的堆棧大小脓诡,類似出現(xiàn)死循環(huán))
遞歸案例1
// 用遞歸來實現(xiàn) 1 + 2 + 3 + 4 + .... + n
function getSum(n) {
if (n === 1) { // 設(shè)定遞歸結(jié)束條件
return 1;
}
return n + getSum(n - 1);
}
console.log(getSum(3)); // 6
/*--------------------------------------------
***遞歸內(nèi)部過程***
getSum(3)
n = 3, 3 + getSum(3 - 1)
getSum(3 - 1)
n = 2, 2 + getSum(2 - 1)
getSum(2 - 1)
n = 1, 1
getSum(3) + getSum(3 - 1) + getSum(2 - 1) // 3+2+1
return 6
----------------------------------------------*/
遞歸案例2
// 用遞歸實現(xiàn) n的階乘 1 * 2 * 3....* n
function fn(n) {
if (n === 1) {
return 1;
}
return n * fn(n - 1);
}
console.log(fn(3));
// n = 3, 3 * fn(3 - 1)
// n = 2, 2 * fn(2 - 1)
// n = 1, 1
遞歸案例3
// 用遞歸實現(xiàn) 斐波那契數(shù)列 1、1媒役、2祝谚、3、5酣衷、8交惯、13、21穿仪、34兽泣、.....
function fn(n) {
if (n === 1 || n === 2) {
return 1;
}
return fn(n - 1) + fn(n - 2);
}
console.log(fn(3)); // 2
console.log(fn(5)); // 5
遞歸案例4
/*----------- 淺拷貝 --------------*/
// 若obj1中擁有 引用類型屬性
var obj1 = {
name: 'zs',
age: 18,
sex: '男',
dog: { // 為obj1對象里又添加一層對象
name: '金毛',
age: 2,
yellow: '黃色'
}
}
var obj2 = {};
copy(obj1, obj2); //調(diào)用拷貝函數(shù)
// 再去修改obj1的成員
obj1.name = 'xx'; // 修改obj1的成員
obj1.dog.name = '大黃';
console.dir(obj2); // obj2的name屬性沒被改變 但是dog.name變了
/*------------------------------------------------------
原因:因為obj2拷貝過去的dog對象 與 obj1自己的dog對象 都是指向同一個對象负蚊,拷貝后诬辈,再做修改操作雨饺,等于是對深層中的同一個對象進行修改,由于兩者共用共享這一對象钠龙,所以出現(xiàn)屬性改變
而這里的拷貝炬藤,只復(fù)制了一層,即只是把第一層(淺層)的屬性拷貝過去去生成一個副本碴里,而下一層(深層)的對象沈矿,只做指向,并未被拷貝到另一個對象的副本中
**** 這被稱為"淺拷貝" ****
-----------------------------------------------*/
/*----------- 深拷貝 --------------*/
var obj1 = {
name: 'zs',
age: 18,
sex: '男',
dog: {
name: '金毛',
age: 2
},
friends: ['ls', 'ww']
}
var obj2 = {};
// 封裝"深拷貝"函數(shù) 把o1的成員拷貝給o2
function deepCopy(o1, o2) {
for (var key in o1) {
var item = o1[key];
if (item instanceof Object) { // 若item是對象{}
o2[key] = {};
deepCopy(item, o2[key]);
} else if (item instanceof Array) { // 若item是數(shù)組[]
o2[key] = [];
deepCopy(item, o2[key]);
} else { // (否則)若是簡單類型
o2[key] = o1[key];
}
}
}
deepCopy(obj1, obj2); // 執(zhí)行深拷貝函數(shù)
console.dir(obj2);
// 此時修改obj1中的成員 是否會影響obj2咬腋?
obj1.dog.name = 'xxx';
obj1.friends[0] = 'xxx';
console.dir(obj2); // 發(fā)現(xiàn) obj2的全部屬性都沒有被改變
/*------------------------------------------------
這就是"深拷貝"羹膳,復(fù)制對象成員時,把普通屬性和對象屬性(底層與深層)一起都復(fù)制過來根竿,實現(xiàn)完全的拷貝陵像。
--------------------------------------------------*/
(返回 [對象拷貝](#6.3.1 對象拷貝))
遞歸案例5
// 利用遞歸 實現(xiàn)遍歷DOM樹 即遍歷某元素下的所有(包括更深層的)子元素
function loadTree(parent) {
for (var i = 0; i < parent.children.length; i++) {
var child = parent.children[i];
// console.log(child); 調(diào)試檢查
loadTree(child); // 遞歸 調(diào)用自身
}
}
loadTree(document.body); // 遍歷body標(biāo)簽下的所有子元素
// 實際上對于上例 僅實現(xiàn)遍歷的意義并不大 這里賦予它更多的功能
// 傳入一個新的功能 去操作遍歷完后獲取的元素對象 實現(xiàn)定制目的
function loadTree(parent, callback) {
for (var i = 0; i < parent.children.length; i++) {
var child = parent.children[i];
if (callback) {
callback(child);
}
loadTree(child); // 遞歸調(diào)用
}
}
// 遍歷完ul列表 并實現(xiàn)點擊某項列表項 顯示它的內(nèi)容
var ul = document.getElementById('list');
loadTree(ul, function (element) {
element.onclick = function () {
console.log(this.innerText);
}
});