# 【尚硅谷】JavaScript基礎&實戰 筆記 ###### tags: `coding` `javascript` ## 一、JavaScript簡介 ### JavaScript的起源 在JS出現前是將資料傳送至服務器,再將結果回傳。但當時網速慢,導致使用者體驗不佳,為改進此問題而產生的。JavaScript出現主要是用於處理前端網頁驗證。例如:用戶名的長度、密碼的長度、郵箱的格式。 演變至今, 實際上,如今網速已經改進許多,JS誕生的初衷已不再重要。JS的運用也變得廣泛,包括:遊戲、後端…。 為了確保不同瀏覽器上運行的JavaScript標準一致,所以幾個公司共同訂了JS的標準命名為ECMAScript。 ### 實現 ECMAScript是一個標準,而這個標準需要由各廠商去實現。不同瀏覽器廠商對該標準會有不同的實現。 | 瀏覽器 | JavaScript實現方式 | | ------- | ------------------ | | Firefox | SpiderMonkey | | Safari | JavaScriptCore | | Chrome | v8 | ![](https://i.imgur.com/7aWgfbs.png) ### JavaScript的特點 1. 解釋型語言 2. 類似於C和Java的語法結構 3. 動態語言 4. 基於原型的物件導向 ## 二、JS基礎 ### 三種基本輸出方式 程式執行順序是由上至下依序執行 ``` =javascript //1.向控制台輸出 console.log('msg'); //2.向頁面輸出 document.write('this is my first page'); //3.彈跳提示窗 alert('warning'); ``` ### JS編寫位置 ``` =javascript 注意:引號外雙內單 html標籤(不建議耦合性過高的寫法) 1. onclick屬性 <button onclick="alert('JS可寫在onclick屬性上');">click</button> 2. href屬性 <a href="javascript:alert('JS可寫在onclick屬性上');">click</a> [補充]以下為避免點擊作用 <a href="javascript:;">click</a> JS代碼鑲嵌 3. script內部 <script type="text/javascript"></script> 4. 外部引用(內部不可再引用) <script src="path"></script> ``` ### 基本語法 1.JS嚴格區分大小寫 2.每一條語句結尾應加上分號。 如果不加,瀏覽器會自動加上。但有時會導致一些無預期結果 (ASI自動插入分號) 3.JS終會自動忽略多個空格和換行,所以可以用來格式化 (最常見的問題:究竟要空四格還是空二格好?) ### 字面量(literal)和變量(variable) ![](https://i.imgur.com/EjURrgn.png) > [字面量表示如何表達這個值,一般除去表達式,給變量賦值時,等號右邊都可以認為是字面量。 字面量分為字符串字面量(string literal )、數組字面量(array literal)和對象字面量(object literal),另外還有函數字面量(function literal)] 一般不會直接使用字面量,而是將字面量保存在變量中,使用變量。 字面量都是一些不可改變的量。比如:1、2、3、4、5 ``` =javascript //1.為變量賦值 a = 123; a = 456; a = 123546576587; //2.聲明和賦值同時進行 var b = 789; var c = 0; ``` ### 標識符(identifier) 在JS中基本上可以由我們命名的就可以稱為標識符 比如:變量名、函式名、屬性名…。 以下為基本命名規範: 1. 標識符可以包含數字、英文字母、_、$ 2. 標識符不可以數字開頭 3. 不可使用關鍵字(keyword)及保留字(reserve word) 4. 建議採用駝峰命名法 JS或底層是以**unicode**編碼的方式保存標識符 ### 基本數據類型 字面量可以為以下幾種數據類型,當中又區分為**基本數據類型**和**引用數據類型**: ``` =javascript 數據類型檢查工具: 1. typeof運算符 : 返回結果值 (不能判斷null,無法仔細區分object中的哪種類型) 2. instanceof - 判斷object的具體類型(object, array, function) 3. === / == :返回布林值 (用來判斷是否為null, undefined) 1.基本 var a; console.log(a, typeof a); //undefined 'undefined' console.log(typeof typeof a); //string console.log(undefined === 'undefined'); //false console.log(a === undefined, typeof a==='undefined'); var b = null; console.log(b, typeof b); //null, 'object' //判斷是否為null要用全等來判斷 console.log(b === null); //true 2.對象 var b1 = { b2:[1,'abc',console.log], b3: function(){ console.log("b3"); } } console.log(b1 instanceof Object); //true console.log(b1.b2 instanceof Array, b1.b2 instanceof Object); //true true console.log(b1.b3 instanceof Function, b1.b3 instanceof Object);//true true console.log(typeof b1.b3 === 'function'); //true ``` 基本數據類型: #### 1.字串 ``` =javascript 1)一般字符 //需注意引號使用 var hello = '789'; //使用單引號 var world = "123"; //使用雙引號 var str = '我說:"今天天氣真不錯"'; //外單內雙 var str1 = "我還是說:'今天天氣真不錯'"; //外雙內單 var str2 = "我說:\"我堅持要用雙引號\""; //堅持嵌套則使用轉譯字符 2)轉譯字符 \" 表示 " \' 表示 ' \n 表示換行(newline) \t 制表符 (tab) \\ 表示 \ str = "我說:\"今天\t天氣不錯!\""; //我說:"今天 天氣不錯!" str = "\\\\\\"; // \\\ 3)樣板字串 Template Literial 4)強制轉換為String (個別型別還有自己的包裹物件) 方式一:toString() 1.該方法不會影響到原變量,他會將轉換結果返回 2.注意null和undefined沒有toString()方法,調用會報錯 方式二:String() 1.要被轉會的變數作為參數傳進String()函式 2.Number和Boolean實際上是調用toString()方法, String()方法會直接將null和undefined轉換為"null"和"undefined" ``` #### 2.數字 ``` =javascript 數字包括整數和浮點數(小數) 1)JS中可以表示的數字最大值 Number.MAX_VALUE //如果使用Number表示的數字超過最大值,則會返回Infinity(表示正無窮) a = Number.MAX_VALUE * Number.MAX_VALUE; console.log(a); 2)JS中可以表示的數字最小正值 Number.MIN_VALUE a = Number.MIN_VALUE * Number.MIN_VALUE; console.log(a); //0 3)NaN //Not a number的意思 4)浮點運算結果可能不精確 var c = 0.1 + 0.2; console.log(c); //0.3000000004 反正就是結果不精確 //因為所有運算最終都要轉換成二進制,但二進制沒辦法準確表達十進位制 //所以涉及錢的計算(精確度比較高的運算),都建議在服務器端做計算 5)強制轉換為Number 方法一:Number(),也是透過參數形式傳遞 1.字串轉數字 -純數字字串 --> 數字 -非數字字串或數字與非數字混合之字串 --> NaN -空串或內容皆為空格的字串 --> 0 2.布林轉數字 -true --> 1 -false --> 0 3.null轉數字 -null --> 0 4.undefined轉數字 -undefined --> NaN (超級重要,不要忘記) 方法二:parseInt()和parseFloat() //這種方式專門用來對付字串(數字與非數字混合字串特好用) 1.字串 //由傳入參數第一位開始比對,直至非數字字串為止 var a = "123456px"; a = parseInt(a); console.log(a); //123456 a = "a123456px"; a = parseInt(a); console.log(a); //NaN a = "123.456px"; a = parseInt(a); console.log(a); //123 a = "123.456px"; a = parseFloat(a); console.log(a); //123.456 2.非字串 a = true; a = parseInt(a); console.log(a); //NaN 6)其他進制的數字 1.十六進制 --> 0x開頭 2.八進制 --> 0開頭 //小心不要和十進位混淆 3.二進制 --> 0b開頭 a = "070"; a = parseInt(a,10); //第二參數是進位制,這樣就可讓所有瀏覽器顯示一致的結果 ``` #### 3.布林 ``` =javascript 1)布林值只有兩個,用來做邏輯判斷 1.true 2.false 2)轉換為Boolean() // 13 Truthy 與 Falsy 1.數字轉布林 --> 除了0和NaN,其餘都是true var a = 123; a = Boolean(a); console.log(a); //正值為true a = Infinity; a = Boolean(a); console.log(a); //true a = -123; a = Boolean(a); console.log(a); //負值也是true 2.字串轉布林 -->除了空串,其餘都是true var a = "hello"; a = Boolean(a); console.log(a); //true a = "false"; a = Boolean(a); console.log(a); //true a = ""; //沒空格 a = Boolean(a); console.log(a); //false a = " "; //有空格 a = Boolean(a); console.log(a); //true 3.null&undefined轉布林 -->都是false 4.物件轉布林 --> true ``` #### 4.null(空值) ``` =javascript null類型值只有一個,就是null null這個值專門用來表示一個為空的物件(這句很重要!) var a = null; console.log(typeof a); //'object' ``` #### 5.undefined(未定義) ``` =javascript undefined類型值只有一個,就是undefined 聲明變量卻未賦值時的預設值 ``` #### 6.symbol(es6) #### 7.bigInt(es6) 引用數據類型: 物件(MDN將陣列包括在物件中) ### 算術運算符 運算符也叫操作符 通過運算符可以對一個或多個值進行運算,並“返回運算結果” (這概念很重要) 比如:typeof就是運算符,他會將該值的類型以字串形式返回 ``` =javascript var a = 123; var result = typeof a; console.log(typeof result); //string 算術運算符不會改變原值!!!!!(很重要) + 1)運算符左右沒字串 -->轉數字再行運算(任何值跟NaN相加都是NaN) 2)運算符左右有字串 -->轉字串再行拼接 3)c = c+ "";是種隱式的類型轉換,由瀏覽器自動完成,實際上它也是調用String()函數 - 、*、/ 都轉數字再運算 ``` #### 加法運算符 ``` =javascript a + 1; console.log(a); 123 result = a + 1; //將返回之運算結果賦值給變數 console.log(a); //123 a = a + 1; //替a重新賦值 console.log(a); //124 result = true + 1; console.log(result); //2 把true轉為數字計算 result = true + false; console.log(result); //1 result = 2 + null; console.log(result); //2 result = 2 + NaN; console.log(result); //NaN //任何值跟NaN相加都是NaN result = "123" + "456"; console.log(result); //"123456" //字串相加結果為拼接字串 result = "123"+ 1; console.log(result); //"1231" //任何值和字串相加,都會先轉為字串再拼接 result = true + "hello"; console.log(result); // "truehello" var c = 123; c = c+ ""; //轉字串 console.log(c); //"123" //運算是由左至右 result = 1 + 2 + "3"; console.log("result = "+result); //"33" result = "1" + 2 + 3; console.log("result = "+result); //"12 ``` #### 減法運算符 ``` =javascript result = 100 - "1"; console.log(result); //99 //都轉數字再運算 ``` #### 乘法運算 ``` =javascript result = 2 * undefined; //NaN result = 3 * null; ``` ### 一元運算符 ``` =javascript 一元運算符,只需要一個操作數 + 正號 - 負號 對數字取反 //對非Number類型的值,會先轉換成Number,然後再運算 進行隱式數字轉型,與調用Number函式相同 var a = 123; console.log(a); //123 a = true; a = +a; console.log(a); //1 a = "18"; a = +a; console.log(a); //18 var result = 1 + "2" + 3; console.log(result); //"123" result = 1 + +"2" + 3; //將字串2轉型為數字2 console.log(result); //6 ``` ### 自增和自減 1.自增 ++ 通過自增可以使變量在自身基礎上增加1 注意:算術運算符不會改變原值!!!!!(很重要) 但是對一個變量自增以後,原變量的值會立即改變 自增分兩種:後++(a++)和前++(++a) 無論是哪種自增,都會使原變量立即自增 不同的是a++和++a的**值**不同 a++(表達式)的值等於原變量的值(自增前的值) ++a(表達式)的值等於變量的新值(自增後的值) 2.自減 - - 道理相同 ``` =javascript var a = 1; a++; //自增 原變量的值會立即改變(所以這句話很重要!!!!) console.log(a); //2 //仔細區分變量與表達式的不同 a 變數 a++ 表達式 var a = 1; console.log(a++); //1 值等於原變量的值(自增前的值) console.log(a); //2 var a = 1; console.log(++a); //2 值等於變量的值(自增後的值) console.log(a); //2 var c = 10; c++; //c++值為10,c的值為11 console.log(c++); //c++的值為11,c的值為12 var d =20; console.log(++d); //++d的值為21,d的值為21 console.log(++d); //++d的值為22,d的值為22 var d = 20; var result = d++ + ++d + d; console.log(result); //20+22+22 var d = 20; d = d++; //d++值為20,然後賦值給d變量,所以變量d是20 console.log(d); ``` ### 自增自減練習 ``` =javascript var n1 = 10,n2 = 20; var n = n1++; //10 console.log(n); //10 console.log(n1);//11 n = ++n1; //12 console.log(n); //12 console.log(n1);//12 n = n2--; //20 console.log(n); //20 console.log(n2);//19 n = --n2; //18 console.log(n); //18 console.log(n2);//18 ``` ### 邏輯運算符 **!非** !!a -->如果對非布林值進行隱式的類型轉換,等同調用Boolean() //對非布林值進行與或運算時,會先將其轉為布林值,再做運算,並且返回"""原值"""" **&&與** 找假,若前者為真,則返回後者 **||或** 找真,若前者為假,則返回後者 ``` =javascript var a = true; console.log(a); a = !a; console.log(a); //false var b = 10; b = !b; //先把10轉為布林就是true,然後取反,就是false console.log(b); //false var result = 1 && 2; console.log(result); //2 result = 0 && 2; console.log(result); //0 result = 2 && 0; console.log(result); //0 result = NaN && 0; console.log(result); //NaN result = 1 || 2; console.log(result); //1 result = 2 || NaN; console.log(result); //2 result = 0 || 2; console.log(result); //2 result = " "|| 0; console.log(result); // (一片空白) result = ""|| {}; console.log(result); //{} ``` ### 賦值運算符 = += -= *= /= %= ```=javascript 可以將運算符右側值賦予給左側變量 var a = 10; a += 5; //a = a + 5 console.log(a); //15 a -= 5; //a = a - 5 console.log(a); //10 ``` ### 關係運算符 通過關係運算符比較兩個值之間的大小,並**返回布林值** >、=、<、>=、<= **運算符左右都是數字 -->直接運算 運算符左右至少一側是數字 --> 對於非數字,也會先轉換成數字,再行運算 運算符左右都是字串 --> 依unicode一位一位做比較 如果字串內為數字,則可能會得到不如預期的結果,要先對字串進行轉換** ``` =javascript 5 > 10 //false 5 > 4 //true console.log(1 >= "0") //true console.log(1 > true) //將true先轉為數字1,但是1>1不成立,所以返回結果為false console.log(10 > null) //true console.log(true > false) //true 因為1>0 //任何值和NaN做比較都是false console.log(10 <= "hello"); //false //首先,把"hello"轉為數字結果為NaN,然後任何值跟NaN比較都是false console.log("a" < "b") //true console.log("abc" < "bcd") //true console.log("11" < "5"); //true //如果字串內為數字,則可能會得到不如預期的結果,要先對字串進行轉換 console.log("11234" > +"5") //先將其中一邊轉為數字 ``` ### unicode編碼 1.在字串中使用unicode **\u四位編碼** ``` =javascript console.log("\u2620"); ``` 2.在網頁中使用unicode編碼 **&#編碼;** 這裡的編碼需要的是10進制(需要進行轉換) ``` =html <h1>&#1;</h1> ``` ### 相等運算符 ``` =javascript 用來比較兩個值是否相等,並返回布林值 1.寬鬆相等運算 == 當使用==比較時,如果類型不同,會先轉換成同類型再比較 2.寬鬆不相等 != 3.嚴格相等運算(全等) === 不會轉換型態 4.嚴格不相等運算 !== var a = 10; console.log(a == 4); //false console.log("1" == 1); //true console.log(true == "1"); //true //將字串和布林值轉為數字做比較 console.log(null == 0); //false //這裡沒有把null轉成數字0做比較 //undefined衍生自null,所以做判斷時,會返回true console.log(undefined == null); //true //NaN不和任何值相等,包括他自己 console.log(NaN == NaN); //false //如果想判斷是否為NaN,該如何處置? var b = NaN; //此時可通過isNaN函式判斷,也是返回布林值 isNaN(b); //true console.log(10 != 5); //true console.log("1" != 1); //false console.log(null === undefined); //null跟undefined相等但不全等 ``` ### 條件運算符 條件運算符也叫三元運算符 語法: 條件表達式?語句1 : 語句2; 執行的流程: 條件運算符在執行時,首先對條件表達式進行求值 如果該值為true,則執行語句1,並返回執行結果 如果該值為false,則執行語句2,並返回執行結果 如果條件表達式非布林值,則會自動轉換為布林值 ``` =javascript true?alert("語句1"):alert("語句2"); //alert("語句1") var a = 10; var b = 20; a > b? alert("a大"):alert("b大"); //alert("b大") //獲取a和b中的最大值 var max = a > b ? a : b; //先對條件運算符進行運算,然後將結果賦值給變數max console.log(max); //20 //獲取a b c 中的大值 max = max > c? max : c; //不建議使用,可讀性不高 var max = (a > b) ? (a > c? a:c):(b > c?b:c); ``` ### 運算符的優先級 ``` =javascript var result = 1 || 2 && 3; console.log(result); //1 //與的優先級高於或 ``` ### 包裝類 JS中提供了**三個包裝類**,通過這三個包裝類可以將基本數據類型的數據轉換為物件(引用數據類型) 1.String() 2.Number() 3.Boolean() ->首字母大寫,都是構造函數 但是注意,我們在**實際運用**中**不會使用**基本數據類型的對象 ``` =javascript //創建一個Number類型的"對象" var num = new Number(3); var num2 = new Number(3); console.log(num); //3 console.log(num2); //3 console.log(num == num2); //false 比較兩個地址 console.log(typeof num); //object //向num中添加一個屬性 num.hello = "abcdefg"; c.f:向基本數據類型的a變量(number類型)添加屬性會返回undefined var a = 3; console.log(a.hello); //undefined var str = new String("hello"); console.log(str); //"hello" console.log(typeof str); //object var bool = new Boolean(true); var bool2 = true; console.log(bool); //true console.log(typeof bool); //object console.log(bool == bool2); //false var b = new Boolean(false); if(b){ alert("我運行了"); } //仍然可以運行,因為b是一個物件 ``` <用途>如果上述的包裝類不建議使用,其用途何在? 方法和屬性只能添加給對象,不能添加給基本數據類型 當我們對一些基本數據類型的值去調用屬性和方法時, 瀏覽器會臨時使用包裝類將其轉換為對象,再調用對象的屬性和方法 調用完以後,再將其轉會為基本數據類型 ``` =javascript var s = 123; s = s.toString(); s.hello = "你好"; console.log(s.hello); //這裡的s和上面的s不同 console.log(s); //"123" console.log(typeof s); //string ``` ### 字串的方法 以下方法都不會影響到原字串 ``` =javascript 在底層字串是以字符陣列的方式保存 //創建一個字串 var str = "hello atguigu"; //在底層字串是以字符陣列的形式保存 ["h","e","l"]; console.log(str[2]); //"l" console.log(str.length); 1. charAt(index) - 可以返回字串中指定位置的字符 - 根據索引獲取指定的字符 str = "hello atguigu"; var result = str.charAt(0); console.log(result); //"h" 2. charCodeAt(index) - 獲取指定位置字符的字符編碼(unicode編碼) result = str.charCodeAt(1); console.log(result); //101 e的unicode是101 3. String.formCharCode(unicode) - 可以根據字符編碼去獲取字符 result = String.fromCharCode(74); console.log(result); //"J" 4. concat() - 可以用來連接兩個或多個字串 - 跟+法效果一樣 result = str.concat("你好","再見"); console.log(result); //"hello atguigu你好再見" 5. indexOf() - 該方法可以檢索一個字串中是否含有指定內容 - 如果字串中含有該內容,則會返回其""第一次""出現的""索引"" - 可以指定一個第二個參數,指定開始查找的位置 str = "hello atguigu"; result = str.indexOf("h"); console.log(result); //"0" result = str.indexOf("g",6); 6.lastIndexOf() - 該方法的用法和indexOf()一樣, 不同的是indexOf是從前往後找,而lastIndexOf是從後往前找 str = "hello atguigu"; result = str.lastIndexOf("g"); console.log(result); //"11" 7. slice() - 可以從字串中擷取指定的內容 -不會影響原字串 str = "abcdefghijk"; str.slice(0,2); console.log(str); //"ab" 8. substring() - 可以用來擷取一個字串,與slice()類似 不同的是: 1.這個方法不能接受負值作為參數,如果傳遞了一個負值,則默認使用0 2.而且它還自動調整參數位置,如果第二個參數小於第一個,則自動交換 str = "abcdefghijk"; result = str.substring(0,2); console.log(result); //"ab" result = str.substring(1,-10000); //"b" 9. substr() - 用來擷取字串 - 參數: 1.擷取開始位置的索引 2.擷取的長度 str = "abcdefg"; result = str.substr(1,2); //"bc" 從索引1開始長度為2的字串 10.split() - 可以將一個字串拆分為一個"""陣列""" - 參數:需要一個字串作為參數,將會根據該字串去拆分陣列 str = "abc, bcd, efg, hij"; result = str.split(","); console.log(result); //"abc, bcd, efg, hij" console.log(result[0]); //"abc" console.log(typeof result); //object console.log(Array.isArray(result)); //true str = "abc, bcd, efg, hij"; result = str.split("d"); console.log(result); //"abc, bc,,efg, hij" console.log(result[0]); //"abc, bc" //如果傳遞一個空串作為參數,則會將每個字符都拆分為陣列中的一個元素 str = "abcdefghij"; result = str.split("") console.log(result); //"a,b,c,d,e,f,g,h,i,j" 11.toUpperCase() str = "abcdefg"; result = str.toUpperCase(); //"ABCDEFG" 12.toLowerCase() str = "ABCDEFG"; result = str.toLowerCase(); //"abcdefg" ``` ## 三、JS循環流程 ### 代碼塊 1. 在JS中替語句分組,沒有其他作用 2. {}內的語句稱為一個代碼塊 3. 在代碼塊後面不加分號 ### 流程控制概念 通過流程控制語句可以控制程序執行流程,使程序可以根據一定的條件來選擇執行 語句分類: 1. 條件判斷語句 2. 條件分支語句 3. 循環語句 #### 1.條件判斷語句 ``` =javascript if語句 語句一: if(條件表達式){語句} //if語句在執行時,會先對條件表達式進行求值判斷 var a = 10; if(a > 10) console.log("a比10大"); console.log("誰也管不了我"); //發現他會自動跳出,換言之,需要用{}來分組 語句二:if...else...語句 var age = 50; if(age >= 60){ alert("你已經退休了"); } 語句三:if...else if...else語句 若值為true,則執行當前語句; 若值為false,則繼續向下執行; 如果所有條件都不滿足,則執行最後一個else後的語句 //注意:應該思考條件表達式的順序,否則會產生死代碼 //如果堅持要用升冪,那就要把條件表達式的範圍標示清楚 age = 18; if(age > 17 && age <= 30){ alert("你已經成年了"); }else if(age > 30){ alert("你已經中年了"); }else if(age > 60){ alert("你已經退休了"); }else{ alert("你歲數大了") } ``` #### 條件判斷語句練習 從鍵盤輸入小明的期末成績: 當成績為100時,"獎勵一輛BMW" 當成績為[80-99]時,"獎勵一台iphone15s" 當成績為[60-80]時,"獎勵一本參考書" 其他時,什麼獎勵也沒有 ```=javascript //升冪寫法 var score = prompt("請輸入小明的期末成績:"); if(score >= 60 && score <80){ alert("獎勵一本參考書"); }else if(score >= 80 && score <= 99){ alert("獎勵一台iphone15s"); }else if(score == 100){ alert("獎勵一輛BMW"); } //降冪寫法(比較常用) var score = prompt("請輸入小明的期末成績:"); if(score > 100 || score <0 || isNaN(score)){ alert("請重新輸入0-100之間的數字"); }else{ if(score == 100){ alert("獎勵一輛BMW"); }else if(score >= 80){ alert("獎勵一台iphone15s"); }else if(score >=60){ alert("獎勵一本參考書"); } } ``` 編寫程序,由鍵盤輸入三個整數分別存入變量num1、num2、num3 對他們進行排序,並且**從小到大輸出** ``` =javascript var num1 = prompt("請輸入第一個數字:"); var num2 = prompt("請輸入第二個數字:"); var num3 = prompt("請輸入第三個數字:"); if(num1 > num2){ if(num3 > num1){ console.log(`${num2} , ${num1} , ${num3}`); }else{ if(num3 > num2){ console.log(`${num2} , ${num3} , ${num1}`); }else{ console.log(`${num3} , ${num2} , ${num1}`); } } }else{ if(num3 > num2){ console.log(`${num1} , ${num2} , ${num3}`); }else{ if(num3 > num1){ console.log(`${num1} , ${num3} , ${num2}`); }else{ console.log(`${num3} , ${num1} , ${num2}`); } } } ``` 大家都知道,男大當婚,女大當嫁,那麼女方家長要嫁女兒,當然要提出一定的條件: 高:180cm以上;富:1000萬以上;帥:500以上 如果三個條件同時滿足,則:'我一定要嫁給他' 如果三個條件有為真的滿足,則:'嫁吧,比上不足,比下有餘' 如果三個條件都不滿足,則:'不嫁!' ``` =javascript var height = prompt("請輸入你的身高(CM):"); var money = prompt("請輸入你的資產總額(萬):"); var face = prompt("請輸入你的顏值(PX):"); if(height>180 && money>1000 && face>500){ alert("我一定要嫁給他"); }else if(height>180 || money>1000 || face>500){ alert("嫁吧,比上不足,比下有餘"); }else{ alert("不嫁!"); } ``` #### 2.條件分支語句 ``` =javascript 條件分支語句也叫switch語句 //根據num的值,輸出對應的中文 var num = 1; //switch與if(條件判斷語句)等價,兩者功能重複 if(num == 1){ console.log("壹"); }else if(num ==2){ console.log("貳"); }else if(num ==3){ console.log("參"); } 語法: switch(條件表達式){ case 表達式: 語句... break; case 表達式: 語句... break; case default: 語句... break; } //在執行一次將case後的表達式值和switch後的條件表達式值進行全等比較 如果比較結果為true,則從當前case處開始執行代碼 當前case後的所有代碼都會執行,我們可以在case後面跟著break 這樣可以確保只會執行當前case後的語句,而不會執行其他case 如果比較結果為false,則繼續向下比較 如果所有的比較結果都為false,則只執行default後的語句 switch(num){ case 1: console.log("壹"); break; case 2: console.log("貳"); break; case 3: console.log("參"); break; default: console.log("非法數字"); break; } ``` #### 條件分支語句練習 對於成績大於60分的,輸出"合格",低於60分的,輸出"不合格" ``` =javascript //我的解法 var score = prompt("請輸入您的成績:"); switch(true){ case score>=60: console.log("合格"); break; case score<60: console.log("不合格"); break; default: console.log("此為非法輸入!"); break; } //老師的解法 var score = 55; //比較十位數取整結果 switch(parseInt(score/10)){ case 10: case 9: case 8: case 7: case 6: console.log("合格"); break; default: console.log("不合格"); break; } ``` 從鍵盤接收整數參數,如果該數為1-7,打印對應的星期,否則打印非法整數。 ``` =javascript var day = prompt("請輸入1-7之間的數字:"); day = parseInt(day); switch(day){ case 1: console.log("星期一"); break; case 2: console.log("星期二"); break; case 3: console.log("星期三"); break; case 4: console.log("星期四"); break; case 5: console.log("星期五"); break; case 6: console.log("星期六"); break; case 7: console.log("星期日"); break; default: console.log("非法整數"); break; } ``` #### 3.循環語句 ``` =javascript 向頁面中輸出連續的數字 第一階段: document.write(1); document.write(2); //會發現數字連在一起,向頁面輸出結果為12 第二階段: //如果希望能輸出完一個數字後換行。對於換行會聯想到字串內容中的轉譯字符\n(new line) 但是因為現在是針對頁面輸出,所以要使用網頁標籤<br />,變成以下: document.write(1+"<br />"); document.write(2+"<br />"); document.write(3+"<br />"); document.write(4+"<br />"); 第三階段: //觀察到數字遞增,所以聯想到自增用法,改寫為以下: var n = 1; document.write(n++ +"<br />"); document.write(n++ +"<br />"); document.write(n++ +"<br />"); document.write(n++ +"<br />"); 第四階段: //使用循環語句,達到反覆執行一段代碼多次的效果 while循環 語法: while(條件表達式){ 語句... } while語句在執行時, 先對條件表達式進行求值判斷, 如果值為true,則執行循環體, 循環體執行完畢以後,繼續對表達式進行判斷 如果為true,則繼續執行循環體,以此類推 如果值為false,則終止循環 var n = 1; //將這種將條件表達式寫死為true的循環,稱為死循環 //可以使用break,來終止循環 while(true){ alert(n++); //判斷n是否是10 if(n ==10){ //退出循環 break; } } //創建一個循環,往往需要三個步驟 // 1. 創初始化一個變量 var i = 1; // 2. 在循環中設置一個條件表達式 while(i <= 10){ alert(i); // 3. 定義一個更新表達式,每次更新初始化變量 d ``` #### do...while循環 語法: do{ 語句... }while(條件表達式) ``` =javascript 可以將while中的範例改寫如下: var i = 1; do{ document.write(i++ +"<br />"); }while(i<=10); ``` do...while語句在執行時,會先執行循環體, 循環體執行完畢後,再對while後的條件表達式進行判斷 如果結果為true,則繼續執行循環體, 循環體執行完畢以後,繼續對表達式進行判斷,以此類推 如果值為false,則終止循環 do...while無論成立不成立,都至少會執行一次 #### for語句 for循環中提供了專門的位置用來方三個表達式: 1.初始化表達式 2.條件表達式 3.更新表達式 for循環的語法: for(初始化表達式;條件表達式;更新表達式){ 語句... } #### while vs for ``` =javascript while vs for // 1. 創初始化一個變量 var i = 1; // 2. 在循環中設置一個條件表達式 while(i <= 10){ alert(i); // 3. 定義一個更新表達式,每次更新初始化變量 document.write(i++ +"<br />"); } //把上面改寫成以下: for(var i = 1;i<=10;i++){ document.write(i +"<br />"); } for循環的執行流程: 1.執行初始化表達式,初始化變量(只會執行一次) 2.執行條件表達式,判斷是否執行循環 如果為true,則執行語句(3.) 如果為false,終止循環 4.執行更新表達式,更新表達式執行完畢繼續重複(2.) 1. --> 2. --> 3. --> 4. --> 2. --> 3. --> 4....... //注意:for循環中的三個部分都可以省略,也可以寫在外部(像寫while一樣) var i = 0; for(;i<10;){ alert(i++); } //注意二:如果在for循環中不寫任何的表達式,只寫兩個; 此時循環是一個死循環會一直執行下去 for(;;){ alert("hello"); } ``` #### 循環語句練習 假如投資的年利率為5%,試求從1000塊漲到5000塊,需要花費多少年? ``` =javascript var money = 1000, year = 0; while(money < 5000){ money *= 1.05; year++; } console.log(year); console.log(money); ``` **條件判斷語句練習(修改:加上prompt的判斷,直到合法有效輸入才進行下一個程序)** ``` =javascript while(true){ //無論如何都一定要執行 var score = prompt("請輸入小明的期末成績:"); if(score < 100 && score > 0){ break; } alert("請輸入0-100之間的成績!"); } if(score == 100){ alert("獎勵一輛BMW"); }else if(score >= 80){ alert("獎勵一台iphone15s"); }else if(score >=60){ alert("獎勵一本參考書"); } ``` 打印1-100之間所有奇數之和 ``` =javascript //我的解法 let sum = 0; for(var n =0;2*n+1<=100;n++){ let odd = 2*n+1; sum += odd; } console.log(sum); //老師的解法(不能被二整除的) let sum = 0; for(let i=1;i<=100;i++){ if(i %2){ sum+= i; } } console.log(sum); ``` 打印1-100之間的數 ``` =javascript for(let i=1;i<=100;i++){ console.log(i); } ``` 打印1-100之間所有7的倍數的個數及總和 ``` =javascript let sum = 0, count = 0; for(let i=1;i<=100;i++){ if(i %7 == 0){ sum+= i; count++; } } console.log(sum); console.log(count); ``` 水仙花數是指一個3位數,他的每個位上的數字的3次冪之和等於他本身。 (例如:1^3+5^3+3^3 = 153),請打印所有的水仙花數。 ``` =javascript for(let i=100;i<=999;i++){ a=parseInt(i/100); b=parseInt((i-a*100)/10); c=i/10; if((Math.pow(a,3)+Math.pow(b,3)+Math.pow(c,3)) == i){ console.log(i); } } ``` 在頁面中接收一個用戶輸入的數字,並判斷該數是否為質數。 質數:只能被1和他自身整除的數,1不是質數也不是合數,質數必須是大於1的自然數。 ``` =javascript var num = prompt("請輸入一個自然數:"); if(num <= 1){ alert("該值不合法!"); }else{ var flag = true; //設置flag!!!!! for(let i=2;i<num; i++){ if(num%i == 0){ // 被2到(自身-1)整除一定不是質數 flag =false; //不可以用break,不然跳出的是整個迴圈 } } if(flag){ alert(num+"是質數"); }else{ alert("這不是質數"); } } ``` 打印2-100之間的所有質數 for嵌套寫法 **<補充>如何測試性能? 在程序執行前,開啟計時器 console.time("計時器名稱") 它需要一個字串作為參數,這個字串將會作為計時器的標示 他需要在外瀏覽器上使用** ``` =javascript console.time("test"); for(let i=2;i<=100;i++){ var flag = true; //設置flag!!!!! for(let j=2;j<i; j++){ if(i%j == 0){ // 被2到(自身-1)整除一定不是質數 flag =false; break; //透過計時器可知break和continue可以大大提升性能 } } if(flag){ console.log(i); } } console.timeEnd("test"); //終止計時器 ``` 打印2-100之間的所有質數 for嵌套寫法(提升性能) **從因數角度分析** 試以36為例 1 36 2 18 3 12 4 9 6 6 僅需嘗試1,2,3,4,6,不需測試6,9,12,18,36 換言之,以開根號為基準,後面的數字都可以不用測試了。 ``` =javascript for(let i=2;i<=100;i++){ var flag = true; //設置flag!!!!! for(let j=2;j<=Math.sqrt(i); j++){ if(i%j == 0){ // 被2到(自身-1)整除一定不是質數 flag =false; break; //透過計時器可知break和continue可以大大提升性能 } } if(flag){ console.log(i); } } ``` 打印*三角形 ``` =javascript 行數 *個數 * 1 j<1 i=0 ** 2 j<2 i=1 *** 3 j<3 i=2 **** 4 j<4 i=3 ***** 5 j<5 i=4 for(let i=0;i<5;i++){ //圖高(行數) for(let j=0;j<i+1;j++){ //圖寬(*個數) document.write("*&nbsp;&nbsp;&nbsp;"); //空格還是用&nbsp;控制 } document.write("<br />"); } ``` 打印99乘法表 ``` =javascript 行數(i) j是寬度,相當於該行上變動的數 1*1=1 1 1*2=2 2*2=4 2 1*3=3 2*3=6 3*3=9 3 ... 1*9=9 ... 9*9=81 9 for(let i=1;i<10;i++){ for(let j=1;j<=i ;j++){ document.write(`${j}*${i}=${i*j}&nbsp;&nbsp;`); } document.write("<br />"); } ``` #### Break和Continue ``` =javascript break關鍵字可用來退出循環語句或switch(但不包括if) //if語句中不能使用break和continue if(true){ break; console.log("hello"); } //在for循環中可以使用break for(let i=0;i<5;i++){ console.log(i); break; } //在for循環中的if裡面可以使用break; //因為break是終止外層的循環語句 for(let i=0;i<5;i++){ if(i ==2){ break; } } //break只會對離自己最近的循環產生影響 for(let i=0;i<5;i++){ console.log("@外層循環"+i); for(let j=0;j<5;j++){ break; //僅終止內層,不會影響到外層 console.log("內層循環"+j); } } //想終結特定的循環,則 可以為循環語句創建一個label,來標示當前循環 label:循環語句 使用break語句時,可以在break後跟著一個label, 這樣break將會結束指定的循環,而不是最近的 outer: for(let i=0;i<5;i++){ console.log("@外層循環"+i); for(let j=0;j<5;j++){ break outer; //在內層指定終止外層循環 console.log("內層循環:"+j); } } //continue關鍵字可以用來跳過當次循環,後續程序不執行,直接接續更新表達式 //continue也是默認只對最近循環起作用 //可以在continue後跟著一個label, 這樣continue將會結束指定的循環,而不是最近的 outer: for(let i=0;i<5;i++){ console.log("@外層循環"+i); for(let j=0;j<5;j++){ //continue outer; //效果就和break一樣 if(j == 2){ continue; //內層循環在等於2的時候跳過當次循環 } console.log("內層循環:"+j); } ``` ## 四、JS引用數據類型:物件 ### 基本介紹 基本數據類型是單一的值,值和值之間沒有任何關聯。 在JS中如果以基本數據類型表達一個人的信息,呈現如下: ``` =javascript var name = "孫悟空"; var gender = "男"; var age = 18; //如果使用基本數據類型,我們創建的變量都是獨立,不能成為一體。 ``` 相對的,物件中屬於複合的數據類型,在物件中可以保存多個不同數據類型的屬性。 物件的分類: - 內建物件   - 由ES標準定義的對象,在任何ES(一般瀏覽器、NodeJS)的實現中都可以使用   - 比如:Math, String, Number, Boolean,Function, Object… - 宿主物件 - 由JS瀏覽器提供的對象,目前來講主要指由瀏覽器提供的物件 - 比如:DOM, BOM, console, document - 自定義對象 - 由開發人員自己創建的物件 ### 創建物件 使用new關鍵字調用的函數,是構建函數constructor 構造函數是專門用來創建物件的函數 使用typeof檢查一個物件時,會返回object ``` =javascript var obj = new Object(); //調用一個Object方法 console.log(obj); //{} console.log(typeof obj); //object ``` #### 添加屬性 語法:物件.屬性名 = 屬性值 ```=javascript obj.name = "孫悟空"; obj.gender = "男"; obj.age = 18; console.log(obj.gender); ``` #### 讀取對象 語法:物件.屬性名 ``` =javascript console.log(obj.gender); console.log(obj.hello); //沒有的屬性,返回undefined ``` #### 修改物件的屬性值 語法:物件.屬性名 = 新值 ``` =javascript obj.name = "tom"; ``` #### 刪除物件的屬性 語法:delete 物件.屬性名 ``` =javascript delete obj.name; console.log(obj.age); ``` #### 向物件中添加屬性 屬性名: - 物件的屬性名不強制要求遵守標識符的規範(可以使用關鍵字和保留字,但還是建議遵守規範) ``` =javascript obj.var = "hello"; console.log(obj.var); ``` - 如果要使用特殊的屬性名,不能採用.的方式來操作 需要使用另一種方式: 語法: 物件["屬性名"] = 屬性值 ``` =javascript obj["123"] = 789; obj["#&)UPWOEURP@#"] = 555; console.log(obj["123"]); ``` 使用[]這種形式去操作形式,更加的靈活, 在[]中可以直接傳遞一個變量,這樣變量值是多少就會讀取哪個屬性 ``` =javascript var n = "nihao"; console.log(obj[n]); ``` #### in 運算符 通過該運算符可以檢查一個是否由指定的屬性 如果有則返回true,沒有則返回false 語法: "屬性名" in 物件 ``` =javascript //檢查obj中是否含有test2屬性 console.log("test2" in obj); ``` ### 傳值跟傳址 #### 傳值 JS的變量都是保存到棧內存中,基本數據類型的值直接在棧內存中存儲。 值與值之間是獨立存在的,修改一個變量不會影響其他變量的。 ``` =javascript var a = 123; var b = a; a ++; //a是123,a++是124 console.log("a = "+a); //124 console.log("b = "+b); //123 ``` #### 傳址 物件是保存到堆內存中,每創建一個新的物件,就會在堆內存中開闢出一個新的空間。 而變量保存的是物件的新地址(對象的引用),如果兩個變量保存的是同一個物件引用。 當一個通過一個變量修改屬性時,另一個也會受到影響。 ``` =javascript var obj = new Object(); obj.name = "孫悟空"; var obj2 = obj; console.log(obj.name); console.log(obj2.name); //孫悟空 ``` 當比較兩個基本數據類型的值時,就是比較值。 而比較兩個引用數據類型時,它是比較的對象的內存地址。 如果兩個物件是一模一樣的,但是地址不同,他也會返回false ``` =javascript var obj3 = new Object(); var obj4 = new Object(); obj3.name = 'shs'; obj4.name = 'shs'; console.log(obj3 == obj4); //false ``` ### 物件字面量 屬性名和屬性值是一組一組的名值對結構, 名和值之間使用:連接,多個名值對之間用,隔開 如果一個屬性之後沒有其他的屬性,就不要寫, ``` =javascript var obj2 = { name:"豬八戒", age:28, gender:"男", test:{name:"沙和尚"} } console.log(obj2.test); ``` ### 函式 1. 函數也是一個物件 2. 函數中可以封裝一些功能(代碼),在需要時可以執行這些功能(代碼) 3. 函數中可以保存一些代碼在需要時候調用 4. 使用typeof檢查一個函數物件時,會返回function #### 創建一個物件 可以將要封裝的代碼以字串形式傳遞給構造函數 ``` =javascript var fun = new Function("console.log('hello 這就是一個構造函數')"); ``` 封裝到函數中的代碼不會立即執行 函數中的代碼會在函數調用的時候執行 調用函數 語法: 函數對象() 當調用函數時,函數中封裝的代碼會按照順序執行 ``` =javascript fun(); ``` 使用函數聲明來創建一個函數 語法: function 函數名([形參1,形參2,形參3]){ 語句... } ``` =javascript //函數表達式 function fun2(){ console.log("這是我的第二個函數"); } console.log(fun2); //函數陳述式 var fun3 = function(){ console.log("我是匿名函數中封裝的代碼"); } fun3(); ``` ### 定義一個用來求兩個數的函數 可以在函數的()中指定一個或多個形參(形式參數) 多個形參之間使用,隔開,聲明形參就相當於**在函數內部聲明了對應的變量,但是並不賦值**。 ``` =javascript function sum(a, b){ console.log(a+b); } ``` 在調用函數時,可以在()中指定時(實際參數) 實參將會賦值給函數中對應的形參 ``` =javascript sum(1,2); ``` 算術運算符不會改變原值!!!!!(很重要) - 運算符左右沒字串 -->轉數字再行運算(任何值跟NaN相加都是NaN) - 運算符左右有字串 -->轉字串再行拼接 調用函數時解析器不會檢查實參的類型,所以要注意,是否有可能會接收到非法的參數 ``` =javascript sum(123,"hello"); //"123hello" sum(true, false); //1 ``` 調用函數時,解析器也不會檢查實參的數量 多餘實參不會被賦值 如果實參數量少於形參,則沒有對應實參的形參將是undefined ``` =javascript sum(123,456,"hello", true, null); //579 sum(123); //NaN ``` ### Arguments 在調用函數時,瀏覽器每次都會傳遞這兩個隱含的參數: - 函數的上下文對象this - 封裝實參的對象arguments - arguments是一個**類陣列**對象,它也可以通過索引來操作數據,也可以獲取長度 - 在調用函數時,我們所傳遞的實參都會在arguments中保存 - arguments的長度就是我們的實參數量 - 它裡邊有一個屬性叫做callee, 這個屬性對應一個函數對象,就是當前正在指向的函數對象 ``` =javascript function fun(){ console.log(arguments); } fun(); //[object Arguments] ``` #### 檢查是否為陣列的方法 ``` =javascript 1.方法一:instanceof function fun(){ console.log(arguments instanceof Array); } fun(); //false 2.方法二:isArray() function fun(){ console.log(Array.isArray(arguments)); } fun(); //false ``` #### arguments的長度就是我們的實參數量 ``` =javascript function fun(){ console.log(arguments.length); } fun(); //0 function fun(){ console.log(arguments.length); } fun("hello",true); //2 ``` #### 如何取出arguments的值? ``` =javascript function fun(){ console.log(arguments[0]); } fun("hello",true); //"hello" function fun(a, b){ console.log(arguments.callee == fun); //就是當前正在指向的函數 } fun(); //true ``` ### 函數的返回值 ``` =javascript 可以使用return來設置函數的返回值 語法: return 值 return後的值將會作為函數的執行結果返回 function sum(a, b, c){ var d = a + b + c; return d; //在函數中return後不跟任何值就相當於回undefined return ; } 調用函數 創建變量接收函數執行結果 函數返回結果及變量接收值 sum(4,7,8); var result = alert("hello"); console.log("result = "+result); //"result = undefined" //實參可以是任何值 1.定義一個函數,判斷一個數字是否是偶數,如果是返回true,否則返回false function isOdd(num){ return num%2 ==0; 寬鬆相等運算 == 當使用==比較時,如果類型不同,會先轉換成同類型再比較 } var result = isOdd(3); console.log("result ="+result); //"result = false" 2.定義一個函數,可以根據半徑計算一個圓的面積(pi*r^2),並返回計算結果 function area(r){ return 3.14*r*r; } result = area(10); console.log("result = "+result); //"result = 314" //實參可以是物件 3.創建一個函數,可以在控制台中輸出一個人的信息 可以輸出人的name age gender address function sayHello(name, age, gender, address){ console.log("我是${name},今年我${age}了,我是一個${gender}") } sayHello("豬八戒",28,"男","高老莊"); <問題>形參多了,就容易在輸入實參時順序出問題,該如何改善這個問題? 當我們的參數過多時,可以將參數封裝到一個物件中,然後通過參數傳達 var obj = { name:"孫悟空", age:18, gender:"男", address:"花果山" }; function sayHello(o){ console.log("我是${o.name},今年我${o.age}了,我是一個${o.gender}"); } sayHello(obj); 4.函數也可以作為一個實參 function fun(a){ console.log("a = "+a); } fun(function(){alert("hello")}); //將匿名函式作為參數傳遞給另一個函式 5.函式名 vs 函式名() 函式名() - 調用函數 - 相當於使用的函數的返回值 函式名 - 函數對象 - 相當於直接使用函數對象 ``` ### 立即執行函數 函數定義完,立即被調用,這種函數叫做立即執行函數 立即執行函數往往只會執行一次 ``` =javascript (function(){ alert("我是一個匿名函數"); })(); (function(a, b){ //注意:匿名函數不可單獨存在於()外,會報錯 console.log("a = "+a); console.log("b = "+b); })(123,456); //立即執行函數可以傳參 ``` ### 方法 ``` =javascript //創建一個物件 var obj = new Object(); //向物件添加屬性 obj.name = "孫悟空"; obj.age = 18; //物件的屬性值可以是任何數據類型,包括函數 obj.sayName = function(){ console.log(obj.name); } function fun(){ console.log(obj.name); } console.log(obj.sayName); //方法和函數只是名稱上的差別,本質上沒有差異 obj.sayName(); //調用方法 fun(); //調用函數 //物件中的函數屬性稱為方法 //同理, console.log(); //調用console的方法 document.write(); //調用document的方法 ``` ### 枚舉物件中的屬性 在開發過稱中,常常會有拿到一個物件,但不知道內部有哪些屬性的時候? 一般會透過枚舉物件中屬性,得知物件長相 ``` =javascript var obj = { name:"孫悟空", age:18, gender:"男", address:"花果山" } 如何枚舉? 使用for...in語句 語法: for(var 變量 in 物件){} for(var n in obj){ console.log("hello"); //打印四次,因為obj中有四個屬性 } //for...in語句 物件中有幾個屬性,循環體就會執行幾次 (很重要) 每次執行時,會將物件中的一個屬性名字賦值給變量 換言之,以此為例,第一個n的值為name,第二個n的值為age,以此類推。 for(var n in obj){ console.log(n); //打印所有屬性名 console.log(obj[n]); //打印所有屬性值 } ``` ### 全局作用域 作用域指一個變量的作用範圍 在JS中一共有兩種作用域: - 全局作用域 - 在script標籤之間的代碼都是全局變數 - 全局作用域在頁面打開時創建,在頁面關閉時消滅 - 全局作用域創建時,自動生成一個全局物件window - 基本上全局變量是window的屬性,函式則為window的方法 - 抬升(hoisting)分兩部分討論,變數和函式: - 變數 - 變數使用var宣告時,會在所有程式被執行之前被聲明(但不會賦值) 但是如果聲明變量不適用var關鍵字,則變量不會被提前聲明 ``` =javascript console.log(a); //undefined var a = 10; console.log(a); //10 console.log(b); //b is not defined b = 15; //b沒有宣吿,這裡b形同對window添加屬性(window.b) console.log(b); //15 ``` - 函式 - 使用函數聲明形式創建的函數function函數(){} 它會在所有代碼執行之前被創建 ```=javascript fun1(); //I'm fun1! fun2(); //undefined function fun1(){ //會被提前創建 console.log("I'm fun1!"); } var fun2 = function(){ //不會被提前創建 console.log("I'm fun2!"); } ``` - 函數作用域 - 調用函數時創建函數作用域,函數執行完畢後,函數作用域銷毀 - 每調用一次函數就會創建一次新的函數作用域,彼此之間獨立 - 在函數作用域中可以訪問到全局作用域的變數(全局作用域中的變量都能被訪問到) 但是全局作用域中無法訪問到函數作用域的變量 - 當在函數作用域操作一個變量時,他會先在自身作用域尋找,如果有就使用,若無則向外查找。 簡單來說,作用域的方向是由內向外可以不斷查找,但是無法由外向內 - 函數作用域的抬生(hoisting)特性與全局作用域一樣 ``` =javascript var a = "我是全局作用域的變數"; function fun(){ console.log(a); //我是全局作用域的變數 } fun(); var a = "我是全局作用域的變數"; function fun(){ var a = "我是函數作用域的變數"; console.log(a); //我是函數作用域的變數 } fun(); var a = "我是全局作用域的變數"; function fun(){ var a = "我是函數作用域的變數"; console.log(a); //我是函數作用域的變數 function fun2(){ console.log(a); //我是函數作用域的變數 } fun2(); } fun(); <重要> var c = 33; //在函數中,不使用var聲明的變數都會變成全局變數 function fun5(){ console.log(c); //33 //c=10因為沒有使用var所以不會抬生,這時會向外查找 c = 10; //因為沒有使用var,所以這是個全局變數,所以它把var c = 33的值代換成10 } fun5(); console.log(c); //10 var e = 10; function fun6(e){ console.log(e); //undefined } fun6(); console.log(e); //10 ``` ### 作用域練習 說出以下代碼的執行結果 ``` =javascript var a = 123; function fun(){ alert(a); //123 } fun(); var a = 123; function fun(){ alert(a); //undefined var a = 456; } fun(); alert(a); //123 var a = 123; function fun(){ alert(a); //123 a = 456; } fun(); alert(a); //456 var a = 123; function fun(a){ alert(a); //undefined a = 456; //賦值給形參 } fun(); alert(a); //123 var a = 123; function fun(a){ alert(a); //123 a = 456; } fun(123); alert(a); //123 ``` ### 執行上下文 - 全局執行上下文 - 在執行全局代碼前將window確定為全局執行上下文 - 對全局數據進行預處理 - var定義的全局變量==>undefined,添加為window的屬性 - function聲明的全局變數==>賦值(fun),添加為window的方法 - this ==>賦值(window) - 開始執行全局代碼 - 函數執行上下文 - 在調用函數,準備執行函數體之前,創建對應的函數執行上下文對象 - 對局部數據進行預處理 - 形參變量==>賦值(實參)==>添加為執行上下文的屬性 - arguments==>賦值(實參列表),添加為執行上下文的屬性 - var定義的局部變量==>undefined,添加為執行上下文的屬性 - function聲明的函數==>賦值(fun),添加為執行上下文的方法 - this ==>賦值(調用函數的對象) - 開始執行函數體代碼 ``` =javascript function fn(a1){ console.log(a1); //2 console.log(a2); //undefined a3(); //a3() console.log(this); //window console.log(arguments); //[2,3] var a2 =3; function a3(){ console.log('a3()'); } } fn(2,3); var a = 10; var bar = function(x){ var b = 5; foo(x + b); } var foo = function(y){ var c = 5; console.log(a + c + y); } bar(10); // x=10 ==> foo(15) ==> y=15 ==> 10+5+15 =30 1.依次輸出什麼? 2.整個過程中產生了幾個執行上下文? 5 // f1,f2,f3,f4,window console.log("global begin:"+i); var i = 1; foo(1); function foo(i){ if(i == 4){ return; } console.log("foo() begin:"+i); foo(i+1); console.log("foo() end:"+i); } console.log("global end:"+i); // global begin:undefined // foo() begin:1 // foo() begin:2 // foo() begin:3 // foo() end:3 // foo() end:2 // foo() end:1 // global end:1 ``` ### this 每次調用函數時,都會向函數內部傳進一個隱含參數this this指向一個物件,這個物件叫做函數執行的上下文物件 根據調用方式的不同,this所指向的對象也不同 ``` =javascript var name = "全局"; function fun(){ console.log(this); } fun(); //[object Window] obj.callName(); //[object Object] ``` ``` =javascript var name = "全局"; function fun(){ console.log(this.name); //this使用時機:當許多地方出現相同的變數名稱時 } var obj = { name:"孫悟空", age:18, callName:fun } var obj2 = { name:"豬八戒", age:28, callName:fun } console.log(fun == obj.callName); //true fun(); //"全局" obj.callName(); //"孫悟空" obj2.callName(); //"豬八戒" ``` ### 使用工廠函數創建物件 創造大量屬性相同的物件,但是工廠函數無法區分類別 ``` =javascript //創建物件 var obj = { name:"孫悟空", gender:"男", age:18, sayName:function(){ alert(this.name); } } ``` <觀念>看見重複的代碼時,可以將其提取出來放入函式中 <問題>當要創建大量的物件時,該如何做? 可以使用工廠方法創建物件(且更具擴充性) ``` =javascript function createPerson(name, gender, age){ var obj = new Object(); obj.name = name; obj.gender = gender; obj.age = age; obj.sayName = function(){ alert(this.name); }; return obj; } function createDog(name, age){ var obj = new Object(); obj.name = name; obj.age = age; obj.bark = function(){ console.log("旺旺"); } return obj; } 使用工廠方法創建的物件,使用構造函數都是Object 所以創建的物件都是Object這個類型, 就導致我們無法區分出多種不同類型的物件-->使用構造函數則可區分 console.log(createPerson("孫悟空","男",18)); console.log(createPerson("豬八戒","男",28)); console.log(createPerson("蜘蛛精","女",40)); var dog = createDog("旺財",3); console.log(dog); ``` ### 構造函數 創造大量屬性相同的物件,但是可區分類別 構造函數和普通函數創建方式相同,只不過函數名首字母大寫 構造函數和普通函數最大的不同在於調用方式的不同: 普通函數是直接調用,而構造函數需要使用new關鍵字來調用(important!!!) 構造函數的函數名作為創建物件的類別名(這就是構造函數和工廠函數的差異) 構造函數的執行流程: 1. 立刻創建一個物件 2. 將新建的物件設置為函數中的this,在構造函數中可以使用this來引用新建的對象 3. 逐行執行函數中的代碼 4. 將新建的物件作為返回值返回 ``` =javascript function Person(){ } var per = new Person(); console.log(per); //[object Object] 以函式創建一個物件 ``` ``` =javascript function Person(name, gender, age){ this.name = name; this.gender = gender; this.age = age; this.sayName = function(){ alert(this.name); }; } function Dog(name, age){ } var dog = new Dog(); var per = new Person("孫悟空","男",18); console.log(per); //Person{name:"孫悟空",gender:"男",age:18,...} console.log(per instanceof Person); //true 檢測該實例是否屬於該類 console.log(dog instanceof Person); //false ``` 在上面例子的構造函數中,我們為每一個物件添加了一個方法,這是在構造函數內部創建的。 換句話說,每調用一次就會為實例創建一個新的方法,如果執行10000次就會創建10000個不同的方法 ``` =javascript function Person(name, gender, age){ this.name = name; this.gender = gender; this.age = age; this.sayName = function(){ alert(this.name); }; } var per = new Person("孫悟空","男",18); var per1 = new Person("豬八戒","男",28); console.log(per.sayName == per1.sayName); //false ``` 但其實所有實例調用的都是同一個方法,沒必要為每個實例分別創建。另一方面,性能也會因此下降,所以為了提升性能,較好的作法就是將方法提到構造函數外當作函數。 ``` =javascript function Person(name, gender, age){ this.name = name; this.gender = gender; this.age = age; this.sayName = fun; } ``` 但是將函數提出來又產生了另一個問題:在全作用域下創建函數,污染了全局作用域的命名空間 另外在全局作用域中命名也沒有資安保障 因此接下來談原型 ``` =javascript function fun(){ alert(this.name); } var per = new Person("孫悟空","男",18); var per1 = new Person("豬八戒","男",28); console.log(per.sayName == per1.sayName); //true ``` ### 原型物件 構造函數中共有的內容就會設置到原型中,既可提升性能,又不會污染公共區域。 我們所創建的每一個函數,解析器都會向函數中添加一個屬性prototype 這個屬性對應一個物件,這個物件就是原型物件 **-->我的理解:prototype是函數內部特有的(顯式原型)** function Fn(){//內部語句:this.prototype = {}} 如果函數作為普通函數,prototype就沒有用 函數式作為構造函數,則其所創建實例都有一個隱含屬性,指向該構造函數的原型對象(很重要) **-->我的理解:實例可以通過proto__去訪問構造函數的原型對象(隱式原型)** var fn = new Fn(); //內部語句:this.__proto__ = Fn.prototype; 原型就相當於一個公共的區域,所有同類的實例都可以訪問這個原型對象, 我們可以將物件中共有的內容,統一設置到原型物件中。 當我們訪問物件的一個屬性或方法時,它會先在物件自身中尋找,如果有則直接使用, 如果沒有則會去原型物件中尋找,如果找到則直接使用。 **-->我的理解:作用域方向是由內向外可以查找,這裡則是向上查找** ``` =javascript function MyClass(){ } MyClass.prototype.a = 123; MyClass.prototype.sayHello = function(){ alert("hello"); }; var mc = new MyClass(); //實例 console.log(mc.a); //123 ``` ``` =javascript function Person(name, gender, age){ this.name = name; this.gender = gender; this.age = age; } //改良成原型寫法 Person.prototype.sayName = function(){ alert(this.name); } var per = new Person("孫悟空","男",18); var per1 = new Person("豬八戒","男",28); <補充>函數的prototype屬性: 1.每個函數都有一個prototype屬性,他默認指向一個Object空對象(原型對象) 2.原型對象中都有一個contructor屬性,他指向函數對象 3.所有函數都是Function的實例(包含function) 每個創建的函式都是一個Function(函式)構造函式的實例, 所以每個我們定義的函式都有__proto__以及一個身為函式會有的prototype console.log(Date.prototype, typeof Date.prototype); function fun(){ } fun.prototype.test = function(){ console.log("test()"); } console.log(fun.prototype); ``` ### hasOwnProperty()介紹 只希望查看該實例中存有的屬性(不包含其原型屬性),使用hasOwnProperty() ``` =javascript function MyClass(){ } MyClass.prototype.name = "原型中的name"; var mc = new MyClass(); console.log(mc.name); //"原型中的name" 因為本身沒有,會向上查找 //使用in檢查物件中是否有該屬性,注意:如果該物件沒有但原型有,依然返回true console.log("name" in mc); //true 因為源興中有這屬性而不是因為實例本身有 如果只希望查看該實例中存有的屬性(不包含其原型屬性),就要使用hasOwnProperty() console.log(mc.hasOwnProperty("name")); //false ``` ### console.log()的本質 ``` =javascript function Person(name, age, gender){ this.name = name; this.age = age; this.gender = gender; } var per = new Person("孫悟空",18,"男") console.log(per); //[object Object] <問題>為什麼返回的是[object Object]? 因為當我們直接在頁面中打印一個對象時,實際上是輸出的對象的toString()方法的返回值 var result = per.toString(); console.log(result); //[object Object] //查找toString方法在哪一層? console.log(per.hasOwnProperty("toString")); //false console.log(per.__proto__.hasOwnProperty("toString")); //false console.log(per.__proto__.__proto__.hasOwnProperty("toString")); //true //在Object中有toString方法 //如果不想打印出[object Object],則為per實例添加屬性 per.toString = function(){ return `Person[name=${this.name},age=${this.age},gender=${this.gender}]`; } //但是這樣的做法其他實例無法共有,若想大家共有則需要放在構建函數中。 Person.toString = function(){ return `Person[name=${this.name},age=${this.age},gender=${this.gender}]`; } ``` ### call和apply call()和apply() - 這兩個方法都是函數的方法,需要通過函數對象來調用 - 當對函數調用call()和apply()都會調用函數執行 - 在調用call()和apply()可以將一個對象指定為第一個參數 此時這個對象將會成為函數執行時的this - call()方法可以將實參在對象之後依次傳遞 - apply()方法需要將實參封裝到一個陣列中統一傳遞 - this的情況: 1.以函數形式調用時,this永遠都是window 2.以方法形式調用時,this是調用方法的對象 3.以構造函數的形式調用時,this是新創建的實例 4.使用call和apply調用時,this就是指定的那個對象 ``` =javascript function fun(){ alert(this); } var obj = {}; fun(); //[object Window] fun.call(obj); //[object Object] fun.apply(obj); //[object Object] ``` ``` =javascript function fun(){ alert(this.name); } var obj = { name:"obj" }; var obj2 = { name:"obj2" }; fun.call(obj); //"obj" this變成obj fun.call(obj2); //"obj2" this變成obj2 fun.apply(obj); ``` ``` =javascript function fun(){ alert(this.name); } var obj = { name:"obj", sayName:function(){ alert(this.name); } }; var obj2 = { name:"obj2" }; obj.sayName(); //"obj" obj.sayName.apply(obj2); //"obj2" 指定this為obj2 ``` #### call和apply的差別 ``` =javascript function fun(a, b){ console.log("a = "+a); console.log("b = "+b); } fun.call(obj, 2, 3); // a = 2 b = 3 fun.apply(obj,[2, 3]); //apply的參數要用陣列封裝(apply with array) ``` ### Date對象 在JS中使用Date對象來表示一個時間 ``` =javascript //創建一個Date對象 //如果直接使用構造函數創建一個Date對象,則會封裝為"""當前"""代碼執行的時間 var d = new Date(); //構造函數 console.log(d); //創建一個指定的時間對象 //需要在構建函數中傳遞一個表示時間的"""字串"""作為參數 //日期的格式: 月份/日/年 時:分:秒 var d2 = new Date("12/03/2020 11:10:30"); //2020年12月3日 11點10分30秒 console.log(d2); //利用時間戳來測試代碼的執行性能 //獲取當前的時間戳(單位是毫秒) var time = Date.now(); var start = Date.now(); for(let i=0;i<100;i++){ console.log(i); } var end = Date.now(); console.log(`執行了,${end - start}毫秒`); //計算for循環執行的時間 <補充>時間函式的運用時機 1. json中時間判斷篩選資料 2.新增一筆資料時間紀錄 3.時鐘 不同做法的時鐘(參考codepen、) 電子鐘、復古鐘 涉及setInterval、setTimeout…概念 →概念介紹 運用:c.f 計時器、碼表 ``` #### Date( )中常用的原型方法 ![](https://i.imgur.com/xC7U8A1.png) #### 在Date( )中自定義方法 ``` =javascript Date.prototype.getFullDate = function(){ let yyyy = this.getFullYear(); let mm = String(this.getMonth() + 1); let dd = String(this.getDate()) let nowString = yyyy + ' / '+ mm + ' / '+ dd; return nowString; } let now = new Date(); now.getFullDate(); '2020 / 11 / 10' ``` ### Math Math和其他的物件不同,它不是一個構造函數, 它屬於一個工具類不用創建對象,它裡邊封裝了數學運算相關的屬性和方法 ``` =javascript var m = new Math(); //[object Math] //圓周率 console.log(Math.PI); //絕對值abs = absolute console.log(Math.abs(-1)); //1 //無條件進位 ceil console.log(Math.ceil(1.5)); //2 //無條件捨棄 floor console.log(Math.floor(1.1)); //1 //四捨五入 round console.log(Math.round(1.4)); //1 console.log(Math.round(1.8)); //2 //亂數 random //可以用來生成一個0-1之間(不含0和1)的隨機數 console.log(Math.random()); //生成一個0-10的隨機數 for(let i=0;i<100;i++){ console.log(Math.random()*10); } ``` ``` =javascript //如果想生成包含0和10的數,就透過四捨五入 for(let i=0;i<100;i++){ console.log(Math.round(Math.random()*10)); } //生成1-10 就是先生成0-9再加1,就變1-10 for(let i=0;i<100;i++){ console.log(Math.round((Math.random()*9))+1); } //最大值 max var max = Math.max(10,20,30); console.log(max); //30 //最小值 min var min = Math.min(10,45,30,100); console.log(min); //10 //次方 pow console.log(Math.pow(2,3)); //8 //開平方根 sqrt console.log(Math.sqrt(9)); //3 ``` ### 垃圾回收 沒有參照的物件就稱為垃圾,所以JS的自動回收機制會將其銷毀 ``` =javascript var obj = new Object(); //各種使用後 obj = null; //JS回收機制自動回收,提升內存空間 ``` ## 五、JS陣列 ### 陣列簡介 回顧先前所提,物件分三種:1.內建對象2.宿主對象3.自定義對象。 只是自定義對象麻煩又困難,所以一般而言使用上還是以內建對象及宿主對象為主。 陣列就是內建對象中常見的對象。他也是用來存儲值,且存儲性能佳(最常用)。不同的是普通對象使用字串作為屬性名,陣列是使用數字作為索引值。陣列中的成員稱作元素 #### 創建陣列 ``` =javascript 方法一:使用構造函數創建時也可同時添加元素 var arr = new Array(); console.log(typeof arr); //object var arr3 = new Array(10,20,30); //這裡是元素 方法二:可以在創建同時添加元素 var arr = []; var arr1 = [1,2,3,4,5,6]; <比較> var arr3 = new Array(10,20,30); //這裡是創建並添加元素,返回[10,20,30] var arr4 = new Array(10); //這裡是創建一個長度為10的陣列 var arr5 = [10]; //這裡是創建並添加元素,返回[10] ``` #### 添加元素 語法:陣列[索引] = 值 ``` =javascript arr[0] = 10; arr[1] = 33; arr[2] = 22; arr[10] = 8; ``` #### 讀取元素 語法:陣列[索引] ``` =javascript console.log(arr[0]); //10 console.log(arr[7]); //undefined 沒有該元素不會報錯,而是返回undefined ``` #### 獲取陣列長度 注意:對於非連續陣列,使用length會獲取到陣列最大索引+1 #### 修改length 如果修改的length大於原長度,則多出部分會空出來 如果修改的length小於原長度,則多餘部分會刪除 ``` =javascript arr.length = 10 ``` #### 向陣列最後添加一元素 ``` =javascript arr[arr.length] = 70; //因為長度是最大索引值+1 ``` ### 陣列的四個方法 #### 1.push 對陣列最後添加一個或多個元素,並返回新的陣列**長度**。 比起arr[arr.length]的方式更具彈性 ``` =javascript var arr = ["a","b","c"]; arr.push("d"); //4 arr.push("e","f","g"); //可以同時添加多個元素 var newLen = arr.push("h"); //設置變數接收新長度 console.log(newLen); //8 console.log(arr); //["a","b","c","d","e","f","g","h"] ``` #### 2.pop 刪除陣列最後一個元素,並返回刪除元素 ``` =javascript arr.pop(); ``` #### 3.unshift 對陣列最前添加一個或多個元素,並返回新的陣列**長度**。 ``` =javascript arr.unshift("before a-1","before a-2"); ``` #### 4.shift 刪除陣列第一個元素,並返回刪除元素 ``` =javascript arr.shift(); ``` ![](https://i.imgur.com/2Kl4mNj.png) ### 陣列的遍歷 #### 1.使用for循環 ``` =javascript function Person(name, age){ this.name = name; this.age = age; } var per = new Person("Al",8); var per1 = new Person("Bill",17); var per2 = new Person("Cody",18); var per3 = new Person("Daniel",15); var per4 = new Person("Eddie",58); //創建一個陣列,將18歲以上的人提取出來放入新陣列返回 var perArr = [per, per1, per2, per3, per4]; function get18(array){ var newArr = []; for(let i=0; i<array.length; i ++){ if(array[i].age>=18){ newArr.push(array[i]); } } return newArr; } get18(perArr); ``` #### 2.使用forEach forEach()方法需要一個函數作為參數 - 像這種函數,由我們創建但是不由我們調用,而是由瀏覽器調用,我們稱為回調函數 - 陣列中有幾個元素就執行幾次,每次執行時,瀏覽器將會遍歷所有的元素以實參的形式傳遞進來, 我們可以來定義形參,來讀取這些內容 - 瀏覽器會在回調函數中傳遞三個參數, 第一個參數,就是當前正在遍歷的元素 第二個參數,就是當前正在遍歷的元素的索引 第三個參數,就是當前正在遍歷的陣列 ``` =javascript perArr.forEach(function(element, index, array){ console.log("hello"); //對每一個元素都執行一次 }) ``` ### slice和splice #### 1.slice(start,end):從某個已有的陣列返回選定的元素(MDN定義) - 可以用來從陣列提取指定元素 - 該方法不會改變元素陣列,而是將擷取到的元素封裝到一個新陣列中返回 - 參數: 1. 擷取開始的位置的索引,包含開始索引 2. 擷取結束的位置的索引,""不""包含結束索引(很重要!!!) (optional) 不設置結束索引時,預設選定到最後一個元素 - 參數可以傳遞一個負值 -1代表最後一個元素的索引,-2到表倒數第二個元素的索引 以此類推 ``` =javascript var perArr = ["Alex", "Bill", "Cody", "Daniel", "Eddie","Flynn"]; var result = perArr.slice(1,4); // "Bill", "Cody", "Daniel", "Eddie" perArr.slice(4); //"Eddie","Flynn" perArr.slice(1,-1); // "Bill", "Cody", "Daniel", "Eddie","Flynn" ``` #### 2.splice() - 刪除元素,並向陣列添加新元素 - 會改變原陣列長相,會將指定元素從原陣列中刪除,並將被刪除的元素作為返回值返回 - 參數: 第一個,表示開始位置的索引 第二個,表示刪除的數量 第三個及以後,可以傳遞一些新的元素, 這些元素將會自動插入到開始位置索引前 ``` =javascript var perArr = ["Alex", "Bill", "Cody", "Daniel", "Eddie","Flynn"]; //刪除該指定位置元素 arr.splice(index,1); [一個元素] perArr.splice(1,2); //"Bill", "Cody" console.log(perArr); //["Alex", "Daniel", "Eddie","Flynn"] //替換該指定位置元素 arr.splice(index,1,"new element"); perArr.splice(1,1,"Bill","Bruce"); //在該指定位置新增元素 arr.splice(index,0,"new element2"); //對整個陣列而言的對應索引位置新增該元素(很重要!!!!!) //在當前該索引位置前新增該元素 perArr.splice(1,0,"Brandson"); //[] 因為沒有刪除任何元素 console.log(perArr); //["Alex","Brandson", "Bill","Bruce", "Eddie","Flynn"] perArr.splice(3,0,"before Bruce") console.log(perArr); ``` ### 陣列去重練習 創建一個有九個元素的陣列,當中有重複的數字,現在要把內部重複數字去除(使用for嵌套) ``` =javascript var arr = [1,2,3,2,1,3,4,2,5]; //遍歷就陣列中每個元素 for(let i=0; i<arr.length; i++){ let base = arr[i]; for(let j=i+1; j<arr.length;j++){ if(base == arr[j]){ arr.splice(j,1); } } } console.log(arr); ``` 但是這麼做會產生一個問題,就是當陣列內重複兩次時,無法使splice過後的陣列保持不重複。 ``` =javascript var arr = [1,2,3,2,2,1,3,4,2,5]; for(let i=0; i<arr.length; i++){ let base = arr[i]; for(let j=i+1; j<arr.length;j++){ if(base == arr[j]){ arr.splice(j,1); j--; } } } ``` ### 陣列的剩餘方法 目前會改變原陣列的有:四個方法、splice、reverse、sort ``` =javascript var arr = ["孫悟空","豬八戒","沙和尚"]; var arr2 = ["白骨精","玉兔精","蜘蛛精"]; var arr3 = ["二郎神","太上老君","玉皇大帝"]; ``` #### 1.concat() - 連接兩個或多個的陣列,並返回結果(concat不會改變原陣列) 很重要!! - 該方法不會對原陣列產生影響 ``` =javascript var result = arr.concat(arr2, arr3,"牛魔王","鐵扇公主"); //可連接多個陣列或元素 console.log(result); ``` #### 2.join() - 把陣列轉換成字串 - 在join()中可以指定一個字串作為參數,這個字串將會成為陣列中元素的運算符(,是預設) ``` =javascript result = arr.join(); console.log(result); //"孫悟空,豬八戒,沙和尚" console.log(typeof result); //string result = arr.join("hello"); console.log(result); //"孫悟空hello豬八戒hello沙和尚" ``` #### 3.reverse() - 陣列中元素的順序前後對換 ``` =javascript arr.reverse(); //["沙和尚","豬八戒","孫悟空"] ``` #### 4.sort() - 對陣列的元素進行排序 - 也會影響原陣列,默認會按照unicode編碼進行排序(所以對數字進行排序時可能會得到錯誤順序) - 我們可以自己來指定排序的規則:(在sort裡面放一個回調) 回調函數中需要定義兩個形參, 瀏覽器將會分別使用陣列中的元素作為實參去調用回調函數 使用哪個元素調用不確定,但是肯定的是在陣列中a一定在b前邊 - 瀏覽器會根據回調函數的返回值來決定元素的順序, 如果返回一個大於0的值,則元素會交換位置 如果返回一個小於0的值,則元素位置不變 如果返回一個0,則認為兩個元素相等,也不交換位置 ``` =javascript arr = ["b","d","e","c","a"]; arr.sort(); //["a","b","c","d","e"] arr = [3,4,11,2,5]; arr.sort(); //[11, 2, 3, 4, 5] 因為是用unicode排序 arr = [4,5]; arr.sort(function(a, b){ console.log("a = "+a); console.log("b = "+b); }); console.log(arr); //返回 a = 4 b = 5 arr = [4,5,3]; arr.sort(function(a, b){ console.log("a = "+a); console.log("b = "+b); }); console.log(arr); //返回 a = 4 b = 3 //返回一個大於0的值(元素會交換位置) arr = [5,4]; arr.sort(function(a, b){ return 1; }) //返回一個小於0的值(元素位置不變) arr = [5,4]; arr.sort(function(a, b){ return -1; }) //返回一個等於0的值(元素位置不變) arr = [5,4]; arr.sort(function(a, b){ return 0; }) arr = [5,4]; //希望升冪 arr.sort(function(a, b){ if(a>b){ return 1; }else if(a<b){ return -1; }else{ return 0; } }) //改寫:升冪 arr = [5,4]; arr.sort(function(a, b){ return a - b; }) //改寫:降冪 arr = [5,4]; arr.sort(function(a, b){ return b - a; }) ``` ## 六、正則表達式 ### 介紹 對**字串**的規則 計算機可以根據正則表達式正則表達式,來檢查一個字串是否符合規則 獲取將字串中符合規則的內容提取出來 創建正則表達式的物件 語法: **var 變數 = new RegExp("正則表達式","匹配模式");** 前後兩個參數都要是**字串** 在構造函數中可以傳遞一個匹配模式作為第二個參數, 可以是 i 忽略大小寫 g 全局匹配模式 遇到只返回第一個匹配內容的方法時,可透過全局匹配**返回所有符合的值** ``` =javascript var reg = new RegExp("a"); console.log(reg); // "/a/" console.log(typeof reg); //object ``` ``` =javascript var reg = new RegExp("a"); //創建好規則:用來檢查字串裡是否有a var str = "a"; //要檢查的字串 ``` <問題>如何用正則表達式檢查字串? test() - 使用這個方法可以用來檢查一個字串是否符合正則表達式的規則, 如果符合則返回true,否則返回false ``` =javascript var result = reg.test(str); console.log(result); //true console.log(reg.test("abc")); //true console.log(reg.test("bcabc")); //true console.log(reg.test("bcdef")); //false 因為沒有規則裡要包含的字母a console.log(reg.test("Abc")); //false 嚴格區分大小寫 ``` ``` =javascript var reg = new RegExp("a","i"); //忽略大小寫 console.log(reg.test("abc")); //true console.log(reg.test("Abc")); //true ``` ### 使用字面量創建正則表達式 語法: var 變數 = /正則表達式/匹配模式 (注意:他們不是字串!!!!)[很重要] ``` =javascript var reg = new RegExp("a","i");跟 var reg = /a/i; 等價 ``` #### 1.創建一個正則表達式,檢查一個字串中是否"""有a或b""" ``` =javascript 方法一: var reg = /a|b/; c.f:var reg = /ab/;(且的關係) 方法二: var reg = /[ab]/; []裡的內容也是或的關係 console.log(reg.test("abc")); //true console.log(reg.test("cde")); //false console.log(reg.test("ac")); //true ``` #### 2.創建一個正則表達式,檢查是否一個字串中是否有字母 ``` =javascript var reg = /[a-z]/; //任意小寫字母 console.log(reg.test("cde")); //true console.log(reg.test("1688888")); //false var reg = /[A-Z]/; //任意大寫字母 console.log(reg.test("A")); //true var reg = /[A-z]/; //任意字母,不區分大小寫 console.log(reg.test("A")); //true console.log(reg.test("b")); //true ``` #### 3.檢查一個字串中是否含有abc或adc或aec ``` =javascript var reg = /a[bde]c/; console.log(reg.test("adc")); //true console.log(reg.test("azc")); //false console.log(reg.test("aeec")); //false ``` #### 4.[^ ] 除了 - 只要有ab以外的字符就返回true,即使仍有ab也無所謂 ``` =javascript var reg = /[^ab]/; console.log(reg.test("c")); //true console.log(reg.test("ab")); //false console.log(reg.test("abc")); //true ``` #### 5.創建一個正則表達式,檢查是否一個字串中是否有數字 ``` =javascript var reg = /[0-9]/; console.log(reg.test("1688888")); //true console.log(reg.test("q68a8f8")); //true ``` ### 字串與正則表達式相關的方法 #### 1.search() - 搜索字串中是否有指定內容 ``` =javascript var str = "hello abc hello abc"; var result = str.search("abc"); console.log(result); //6 是找到第一個指定內容的索引值 result = str.search("abcd"); console.log(result); //-1 result = str.search(/a[bef]c/); console.log(result); //6 ``` #### 2.match() - 根據正則表達式,從一個字串中將符合條件的內容提取出來 - 默認情況下match只會找到第一個內容,就停止以後的檢索 - match()會將匹配到的內容封裝到一個陣列中返回,即使只有查詢到一個結果 ``` =javascript var str = "1a2b3c4d5e6f7"; result = str.match(/[A-z]/); console.log(result); //"a" 默認情況下match只會找到第一個內容 result = str.match(/[A-z]/g); console.log(result); //"a,b,c,d,e,f" console.log(result[2]); //"c" 因為返回的是陣列 ``` #### 3.replace() - 只替換第一個 - 可以將字串中指定內容替換為新內容 - 參數:(都是字串) 1.被替換的內容,可以接受一個正則表達式作為參數 2.新內容 ``` =javascript result = str.replace("a","@_@"); console.log(result); //"1@_@2b3c4d5e6f7" str = "1a2a3aaa4d5e6f7"; console.log(result); //"1@_@2a3aaa4d5e6f7" 只會替換第一個匹配部分 result = str.replace(/a/g,"@_@"); console.log(result); //"1@_@2@_@3@_@@_@@_@4d5e6f7" ``` #### 4.split() - 可以將一個字串拆分為一個陣列 - 方法中可以傳遞一個正則表達式作為參數,這樣可根據正則表達式拆分字串 ``` =javascript var str = "1a2b3c4d5e6f7"; 根據任意字母將字串拆分 var result = str.split(/[A-z]/); console.log(result); //1,2,3,4,5,6,7 ``` ### 正則表達式語法:量詞 - 通過量詞可以設置一個內容出現的次數 - 量詞只對它前邊的一個內容起作用 - {n}正好出現n次 - {m,n} 出現m-n次 - {m,} 出現m次以上 - \+ 至少一個,相當於{1,} - \* 0或多個,相當於{0,} //有或沒有都沒差 - ? 0或1個,相當於{0,1} ``` =javascript var reg = /a{3}/; //在{}內填入出現次數 a得連續出現3次 var reg = /ab{3}/; //abbb var reg = /(ab){3}/; //ababab var reg = /ab{3}c/; //abbbc var reg = /b{3}/; //bbbb true var reg = /ab{1,3}c/; //abc true; //abbc true; //abbbc true; //abbbbc false var reg = /ab{3, }c/; //至少3個以上的b var reg = /ab+c/; //ac false; //abc true; ``` #### 創建一個正則表達式檢查一個字串是否含有aaa ``` =javascript var reg = /a{3}/; //必須有三個緊鄰的a字符 console.log(reg.test("abc")); //false console.log(reg.test("aaabc")); //true console.log(reg.test("aabac")); //false ``` #### 以a字符開頭,然後立刻結束 ``` =javascript var reg = /^a$/; console.log(reg.test("aaa")); //false ``` #### 以a字符開頭,或以a字符結尾 ``` =javascript var reg = /^a|a$/; console.log(reg.test("aaa")); //true ``` ### 正則表達式語法:加轉譯字符 ``` =javascript var reg = /\./; reg = /\\/; //本身就代表\\ console.log(reg.test("b.")); //true console.log(reg.test("\\.")); //true ``` 注意:使用構造函數時,由於它的參數是一個字串,而\是字串中轉譯字符, 如果要使用\則需要使用\\來代替 reg = new RegExp("\."); //如果要出現\則需要使用\\來代替 \w - 任意字母、數字、_ [A-z0-9_] \W - 除了(大寫)任意字母、數字、_ [^A-z0-9_] \d - 任意數字 [0-9] \D - 除了任意數字 [^0-9] \s - 空格 \S - 除了空格 \b - 單詞邊界 //單詞child就是獨立存在,不與其他字母緊鄰(避免與"包含"概念混淆) \B - 除了單詞邊界 #### 創建一個正則表達式檢查一個字串中是否有單詞child ``` =javascript reg = /child/; console.log(reg.test("hello children")); //true reg = /\bchild\b/; console.log(reg.test("hello child ren")); //true console.log(reg.test("hello children")); //false ``` #### 接收一個用戶輸入的姓名 ``` =javascript var name = prompt("請輸入你的用戶名:"); //假如你在輸入前邊多打了空格 var name = " username "; <問題>該如何去除掉字串中的空格? 就是用""(空串)去替換空格 name = name.replace(/\s/g,""); //注意:不用全局,只會替換一個空格 //這樣會引發另一個問題,就是把包括輸入中間無誤的空格也一同去掉 <修改>如何只將開頭跟結尾的空格去掉? 1.去除開頭的空格 name = name.replace(/^\s*/,""); //發現全局匹配無效,因為只有開頭第一個空格才算開頭 2.去除結尾的空格 name = name.replace(/\s*$/,""); //發現全局匹配無效,因為只有結尾最後一個個空格才算結尾 3.合併 name = name.replace(/^\s*|\s*$/g,""); //一定全局匹配,否則去除第一個空格就將結果返回 console.log(name); ``` ### 正則表達式練習 #### 1.創建一個正則表達式,用來檢查一個字串是否是一個手機 09xx-xxx-xxx (10位09開頭(後面8位)的全數字) ``` =javascript var reg = /^09[0-9]{8}$/; //加^與$必須完全匹配 var str = "0938713200"; var result = reg.test(str); console.log(result); ``` #### 2.郵件的正則 <老師的規則> 任意數字字母下划線.任意數字字母下划線@任意字母數字.任意字母(2-5位).任意字母(2-5位) ``` =javascript var emailReg = /^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,5}){1,2}$/; var email = "abc@abc.com.123"; console.log(emailReg.test(email)); ``` ## 七、宿主對象 ### 什麼是DOM? Dom(全稱Document Object Model文檔對象模型) - 透過DOM對html進行操作 <問題>何謂文檔對象模型? 1.文檔:整個的html網頁文檔 2.對象:把網頁中的每一個部分都轉換為物件 3.模型:使用模型來表示物件間的關係,這樣方便我們獲取物件。 ![](https://i.imgur.com/DQoVo7l.png) ### 節點(Node) 1. 節點是構成我們**網頁最基本的組成部分**,網頁中的每一個部分都可以稱為一個節點。比如:html標籤、屬性、文本、注釋、整個文檔等都是一個節點。 2. 雖然都是節點,但是實際上他們的具體類型是不同的。 3. 節點的類型不同,屬性和方法也不盡相同。 4. 常用節點分為四類: 文檔節點:整個html文檔 元素節點:html文檔中的html標籤 屬性節點:元素的屬性 文本節點:html標籤中的文本內容 ### DOM操作 #### DOM查詢 ``` =javascript 1.getElementsByTagName() - 通過標籤名獲取多個元素節點對象 2.getElementsByName() - 通過name屬性獲取多個元素節點對象 比如:form表單元素中radio,checkbox....的name屬性 3.獲取body var body = document.body; //document中有一個body屬性,它保存的是body的引用 4.獲取html根標籤 var html = document.documentElement; console.log(html); //[object HTMLHtmlElement] 5.獲取document.all代表頁面中所有的元素 var all = document.all; console.log(all.length); for(let i=0;i<all.length; i++){ console.log(all[i]); } 6.獲取class為box1中的所有的div .box1 div document.querySelector(".box1 div"); //使用querySelector可以複合式選取 7.獲取文本節點 var fc = li.firstChild; console.log(fc.nodeValue); //取文本節點的值(文字) ``` #### DOM新增 ``` =javascript 1. createElement() - 用於創建一個"""元素"""節點對象, 他需要一個標籤名作為參數,將會根據該標籤名創建元素節點對象, 並將創建好的對象作為返回值返回 var li = document.createElement("li"); 2. createTextNode() - 創建"""文本"""節點對象,需要一個文本內容作為參數,將會根據該內容創建文本節點, 並將新的文本節點返回 var text = document.createTextNode("這裡是放文本內容"); //這裡的多個被查找元素都是以[html collection]類陣列形式返回 //innerHTML用於獲取元素"""內部"""html代碼,對於自結束標籤,這個屬性沒有意義 //如果需要讀取該元素節點屬性,直接使用元素.屬性名 比如:元素.id, 元素.name, 元素.value ``` ### 子節點操作 注意空白會被當作文本節點 #### 查詢子節點 ``` =javascript 1.childNodes - 表示當前節點的所有子"""節點"""(會獲取包含文本節點)[很重要!!] - 根據DOM標籤間空白也會當成文本節點[很重要!!] c.f children屬性 - 可以獲取當前元素的所有子"""元素""" - 就不會包括空白!!!!(推薦使用) 2.firstChild - 表示當前節點的第一個子節點 c.f firstElementChild屬性 - 可以獲取當前元素的第一個子"""元素""" - 就不會包括空白!!!!(推薦使用) 3.lastChild - 表示當前節點的最後一個子節點 c.f lastElementChild屬性 - 可以獲取當前元素的最後一個子"""元素""" - 就不會包括空白!!!!(推薦使用) ``` #### 新增子節點 ``` =javascript 1.父節點.appendChild(子節點) - 把新的子節點添加到指定節點 2.父節點.insertBefore(新子節點,舊子節點) - 在指定的子節點前面插入新的子節點 ``` #### 刪除子節點 ``` =javascript removeChild() - 刪除子節點 <運用> 子節點.parentNode.removeChild(子節點) ``` #### 修改子節點 ``` =javascript 父節點.replaceChild(新子節點,舊子節點) - 替換子節點 ``` ### 父節點和兄弟節點的操作 注意空白會被當作文本節點 #### 查詢父節點和兄弟節點 ``` =javascript 1.parentNode (c.f:子元素,這裡是沒有s,因為一次只有一個父元素) - 表示當前節點的所有子"""節點"""(會獲取包含文本節點)[很重要!!] - 根據DOM標籤間空白也會當成文本節點[很重要!!] c.f children屬性 - 可以獲取當前元素的所有子"""元素""" - 就不會包括空白!!!!(推薦使用) 2.previousSibling - 表示當前節點的前一個兄弟節點 c.f previousElementSibling屬性 - 可以獲取當前元素的前一個兄弟"""元素""" - 就不會包括空白!!!!(推薦使用) 3.nextSibling - 表示當前節點的後一個兄弟節點 c.f nextElementSibling屬性 - 可以獲取當前元素的最後一個兄弟"""元素""" - 就不會包括空白!!!!(推薦使用) ``` ### 操縱子節點CSS樣式 1.通過style屬性設置和讀取的都是內聯樣式,**無法**讀取樣式表中的樣式 - 設置元素的樣式 語法: 元素.style.樣式名 - 獲取元素的當前顯示的樣式(唯讀) 語法: getComputedStyle() - 這個方法是window的方法,可以直接使用 - 需要兩個參數 第一個,要獲取樣式的元素 第二個,可以傳遞一個偽元素,一般都傳null - 該方法返回一個物件,物件中封裝了當前元素對應的樣式 - 如果獲取的樣式沒有設置,則會獲取到真實的值,而**不是默認值** 比如:沒有width,它就不會獲取到auto,而是一個長度 ``` =javascript var obj = getComputedStyle(box1, null); console.log(obj.width); function getStyle(obj, name){ return getComputedStyle(obj, null)[name]; //不可用.因為name是""變量"" } alert(getStyle(box1, width)); ``` ### 什麼是事件? 1.事件,就是文檔或瀏覽器窗口中發生的一些特定的交互瞬間。 2.JS和html之間的交互是通過事件實現的。 3.代表性事件:點擊、移動鼠標、按下鍵盤鍵。(DOM event) 4.注意script標籤下的地方,必須在body內容加載完後加入, 否則瀏覽器不認識該物件會報錯 (很重要!!) 5.常用事件介紹: - onclick - onload - 會在整個頁面加載完之後才觸發 (根據文檔,image,layer跟window支持) 為window綁定一個onload事件 ``` =javascript window.onload = function(){ alert("hello"); } ``` onload也為了避免第4點想避免的錯誤,可以因應的做法。 但是以性能觀點而言,沒有必要。因為等於先跑完script內的內容, 才執行body的內容,使頁面加載變慢(一般來說不會使用) ### 什麼是BOM? 瀏覽器對象模型 BOM可以使我們通過JS來操作瀏覽器 c.f DOM操作網頁,BOM操作瀏覽器 1.在BOM中為我們提供了一組物件,用來完成對瀏覽器的操作 2.BOM對象有: - Window - 代表的是整個瀏覽器的窗口,同時window也是網頁中的全局對象 - Navigator - 代表的當前瀏覽器的信息,通過對象可以來識別**不同的瀏覽器** - appName (時代的產物,現在不太使用) - 返回瀏覽器名稱 (比如:safari,Microsoft Internet Explorer(IE), netscape(火狐瀏覽器,chrome)…) - userAgent是一個字串,這個字串中包含有用來描述瀏覽器信息的內容, 不同的瀏覽器會有不同的userAgent - 在IE11中已經將微軟和IE相關的標示去除了, 所以我們基本已經不能通過UserAgent來識別一個瀏覽器是否是IE了 - 如果通過UserAgent不能判斷,還可以通過一些瀏覽器中特有的對象, 來判斷瀏覽器的信息(比如:ActiveXObject) - History - 代表瀏覽器的**歷史紀錄**,可以通過該對象來操作瀏覽器的歷史紀錄 - 由於隱私原因,該對象不能獲取到具體的歷史紀錄, 只能操作瀏覽器**向前**或**向後**翻頁 - 而且該操作只在**當次訪問有效** - Location - 代表當前瀏覽器的地址欄信息(url),通過Location可以獲取地址欄信息, 或者操作瀏覽器**跳轉**頁面 - Screen - 代表用戶的屏幕的信息,通過該對象可以獲取到用戶的**顯示器**相關信息 - 這些BOM對象在瀏覽器中都是作為window對象的屬性保存的 - 可以通過window對象來使用,也可以直接使用 ### Navigator ``` =javascript //方法一:appName過時 console.log(navigator); console.log(navigator.appName); // 方法二:ie沒有特殊識別,無法辨別 console.log(navigator.userAgent); var ua = navigator.userAgent; if(/firefox/i.test(ua)){ alert("firefox"); }else if(/chrome/i.test(ua)){ alert("chrome"); }else if(/msie/i.test(ua)){ alert("IE"); } // 方法三: <針對ie11>因為IE11被動過手腳,轉布林值會是false if(window.ActiveXObject){ alert("你是IE") }else{ alert("你不是IE"); } //方法四:<針對ie11>用in檢查 alert("ActiveXObject" in window); if("ActiveXObject" in window){ alert("你是IE") }else{ alert("你不是IE"); } //<總結>針對所有瀏覽器 var ua = navigator.userAgent; if(/firefox/i.test(ua)){ alert("firefox"); }else if(/chrome/i.test(ua)){ alert("chrome"); }else if(/msie/i.test(ua)){ alert("ie"); }else if("ActiveXObject" in window){ alert("ie11"); } ``` ### History history對象方法 - back() - 加載history列表中的前一個url - forward() - 加載history列表中的後一個url - go() - 加載history列表中的某個具體頁面 - 它需要一個整數作為參數 1 : 表示向前跳轉一個頁面,等價 history.forward(); 2 : 表示向前跳轉兩個頁面 -1 : 表示向後跳轉一個頁面,等價 history.back(); -2 : 表示向後跳轉兩個頁面 - length屬性,可以獲取到當成訪問的鏈接數量 - 只會記錄當次的訪問鏈接數量 ``` =javascript var btn = document.getElementById("btn"); btn.onclick = function(){ // alert(history.length); //1 當前頁面 // history.back(); // history.forward(); history.go(); } ``` ### Location ``` =javascript alert(location); //透過location獲取當前url ``` 如果直接將location屬性修改為一個完整的路徑,或相對路徑; 則我們將會自動跳轉到該路徑,並且生成相應的歷史紀錄 ``` =javascript location = "http://www.google.com"; //可指定url進行跳轉 location = "test01.html"; ``` #### assign() - 加載新內容 - 用來跳轉到其他頁面,作用和直接修改location一樣 ``` =javascript location.assign("http://www.google.com"); ``` #### reload() - 重新加載當前文檔內容 - 如果在方法中傳遞一個true作為參數,則會強制清空 ``` =javascript location.reload(); location.reload(true); //強制清空緩存刷新 ``` #### replace() - 用新的文檔替換當前文檔,調用完畢也會跳轉頁面 - 不會生成歷史紀錄,不能回到上一頁 ``` =javascript location.replace("test01.html") ``` ### 圖片切換練習 1.創建一個裝圖片src的陣列 2.透過點擊事件自增和自減索引值 3.設界限:小於第一張和大過最後一張時要停止 4.再將新的索引值賦值給圖片src屬性值,以此切換圖片