# 【尚硅谷】JavaScript基礎&實戰 筆記
###### tags: `coding` `javascript`
## 一、JavaScript簡介
### JavaScript的起源
在JS出現前是將資料傳送至服務器,再將結果回傳。但當時網速慢,導致使用者體驗不佳,為改進此問題而產生的。JavaScript出現主要是用於處理前端網頁驗證。例如:用戶名的長度、密碼的長度、郵箱的格式。
演變至今,
實際上,如今網速已經改進許多,JS誕生的初衷已不再重要。JS的運用也變得廣泛,包括:遊戲、後端…。
為了確保不同瀏覽器上運行的JavaScript標準一致,所以幾個公司共同訂了JS的標準命名為ECMAScript。
### 實現
ECMAScript是一個標準,而這個標準需要由各廠商去實現。不同瀏覽器廠商對該標準會有不同的實現。
| 瀏覽器 | JavaScript實現方式 |
| ------- | ------------------ |
| Firefox | SpiderMonkey |
| Safari | JavaScriptCore |
| Chrome | v8 |

### 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)

> [字面量表示如何表達這個值,一般除去表達式,給變量賦值時,等號右邊都可以認為是字面量。
字面量分為字符串字面量(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></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("* "); //空格還是用 控制
}
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} `);
}
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( )中常用的原型方法

#### 在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();
```

### 陣列的遍歷
#### 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.模型:使用模型來表示物件間的關係,這樣方便我們獲取物件。

### 節點(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屬性值,以此切換圖片