Javascript基礎Day4 函數(下) 作用域(重點) 什麼是作用域,就是一個變數可以生效的範圍 變數不是在所有地方都可以使用的,而這個變數的使用範圍就是作用域 全局作用域 整個頁面起作用,在<script>內都能訪問到; 在全局作用域中有全局對象window,代表一個瀏覽器視窗,由瀏覽器創建 ...
Javascript基礎Day4
作用域(重點)
-
什麼是作用域,就是一個變數可以生效的範圍
-
變數不是在所有地方都可以使用的,而這個變數的使用範圍就是作用域
全局作用域
-
整個頁面起作用,在<script>內都能訪問到;
-
在全局作用域中有全局對象window,代表一個瀏覽器視窗,由瀏覽器創建,可以直接調用;
-
全局作用域中聲明的變數和函數,會作為window對象的屬性和方法保存;
-
變數在函數外聲明,即為全局變數,擁有全局作用域。
var a = 123;//全局變數 function fn() { console.log(a);//123 } fn(); console.log(a);//123
局部作用域
-
局部作用域內的變數只能在函數內部使用,所以也叫函數作用域;
-
變數在函數內聲明,即為局部變數,擁有局部作用域。
function fn() { var b = 456;//局部變數 console.log(b);//456 } fn(); console.log(b);//b is not defined
註:可以直接給一個未聲明的變數賦值(全局變數),但不能直接使用未聲明的變數!因為局部變數只作用於函數內,所以不同的函數可以使用相同名稱的變數。當全局與局部有同名變數的時候,訪問該變數將遵循 "就近原則"。
變數的生命周期
-
全局變數在頁面打開時創建,在頁面關閉後銷毀。
-
局部變數在函數開始執行時創建,函數執行完後局部變數會自動銷毀。
變數使用規則(重點)
-
有了作用域以後,變數就有了使用範圍,也就有了使用規則
-
變數使用規則分為兩種,訪問規則 和 賦值規則
訪問規則
-
當我想獲取一個變數的值的時候,我們管這個行為叫做 訪問
-
獲取變數的規則:
-
首先,在自己的作用域內部查找,如果有,就直接拿來使用
-
如果沒有,就去上一級作用域查找,如果有,就拿來使用
-
如果沒有,就繼續去上一級作用域查找,依次類推
-
如果一直到全局作用域都沒有這個變數,那麼就會直接報錯(該變數 is not defined)
var num = 100 function fn() { var num2 = 200 function fun() { var num3 = 300 console.log(num3) // 自己作用域內有,拿過來用 console.log(num2) // 自己作用域內沒有,就去上一級,就是 fn 的作用域裡面找,發現有,拿過來用 console.log(num) // 自己這沒有,去上一級 fn 那裡也沒有,再上一級到全局作用域,發現有,直接用 console.log(a) // 自己沒有,一級一級找上去到全局都沒有,就會報錯 } fun() }fn()
-
-
變數的訪問規則 也叫做 作用域的查找機制
-
作用域的查找機制只能是向上找,不能向下找
function fn() { var num = 100 } fn() console.log(num) // 發現自己作用域沒有,自己就是全局作用域,沒有再上一級了,直接報錯
賦值規則
-
當你想給一個變數賦值的時候,那麼就先要找到這個變數,在給他賦值
-
變數賦值規則:
-
先在自己作用域內部查找,有就直接賦值
-
沒有就去上一級作用域內部查找,有就直接賦值
-
在沒有再去上一級作用域查找,有就直接賦值
-
如果一直找到全局作用域都沒有,那麼就把這個變數定義為全局變數,在給他賦值
function fn() { num = 100 }fn() // fn 調用以後,要給 num 賦值 // 查看自己的作用域內部沒有 num 變數 // 就會向上一級查找 // 上一級就是全局作用域,發現依舊沒有 // 那麼就會把 num 定義為全局的變數,併為其賦值 // 所以 fn() 以後,全局就有了一個變數叫做 num 並且值是 100console.log(num) // 100
-
常見事件
-
瀏覽器事件
-
onload 載入完畢
-
onscroll 瀏覽器滾動事件
-
onresize 瀏覽器視窗改變事件
-
-
滑鼠事件
-
onclick :點擊事件
-
ondblclick :雙擊事件
-
oncontextmenu`: 右鍵單擊事件
-
onmousedown :滑鼠左鍵按下事件
-
onmouseup :滑鼠左鍵抬起事件
-
onmousemove :滑鼠移動
-
onmouseover :滑鼠移入事件
-
onmouseout :滑鼠移出事件
-
onmouseenter :滑鼠移入事件(不冒泡)
-
onmouseleave :滑鼠移出事件(不冒泡)
-
onselectstart:選中事件(不被 input 和 textarea 標簽支持 )
-
onselect:選中事件(支持 input 和 textarea 標簽)
-
-
鍵盤事件
-
onkeydown 鍵盤按下事件
-
onkeyup 鍵盤釋放事件
-
onkeypress 產生可列印字元事件
註:鍵盤事件綁定的位置,要麼是document,要麼是輸入框
-
-
觸摸事件(移動端)
-
ontouchstart 觸摸開始
-
ontouchmove 觸摸移動
-
ontouchend 觸摸結束
-
-
表單事件
-
onchange 表單改變事件(失去焦點時觸發)
-
oninput 表單輸入事件(輸入時觸發)
-
onsubmit 表單提交事件(點擊submit時觸發)
-
onfocus :獲得焦點事件
-
onblur :失去焦點事件
-
-
其他事件
-
ontransitionend 過渡結束的時候觸發
-
onanimationend 動畫結束的時候觸發
-
自執行函數
要執行一個函數,我們必須要有方法定義函數、引用函數。
匿名函數如何調用?
匿名自執行函數,也叫立即執行函數(IIFE)。
(function () {
console.log(123);
})();
小括弧能把我們的表達式組合分塊,並且每一塊都有一個返回值,這個返回值實際上就是小括弧中表達式的返回值。
自執行函數的好處:獨立的作用域,不會污染全局環境!
傳參:(function (a,b) {
console.log(a + b);
})(2,3);
常見形式:(function () {
console.log(11);
})();
(function(){
console.log(22);
}());
!function() {
console.log(33);
}();
+function() {
console.log(55);
}();
-function() {
console.log(66);
}();
~function() {
console.log(77);
}();
遞歸函數
-
什麼是遞歸函數
-
在編程世界裡面,遞歸就是一個自己調用自己的手段
-
遞歸函數: 一個函數內部,調用了自己,迴圈往複
-
一般來說,遞歸需要有邊界條件、遞歸前進段和遞歸返回段。
-
當邊界條件不滿足時,遞歸前進;當邊界條件滿足時,遞歸返回。
// 下麵這個代碼就是一個最簡單的遞歸函數 // 在函數內部調用了自己,函數一執行,就調用自己一次,在調用再執行,迴圈往複,沒有止盡function fn() { fn()}fn()
-
其實遞歸函數和迴圈很類似
-
需要有初始化,自增,執行代碼,條件判斷的,不然就是一個沒有盡頭的遞歸函數,我們叫做 死遞歸
簡單實現一個遞歸
-
我們先在用遞歸函數簡單實現一個效果
-
需求: 求 1 至 5 的和
-
先算 1 + 2 得 3
-
再算 3 + 3 得 6
-
再算 6 + 4 得 10
-
再算 10 + 5 得 15
-
結束
-
-
開始書寫,寫遞歸函數先要寫結束條件(為了避免出現 “死遞歸”)
function add(n) { // 傳遞進來的是 1 // 當 n === 5 的時候要結束 if (n === 5) { return 5 }} add(1)
-
再寫不滿足條件的時候我們的遞歸處理
function add(n) { // 傳遞進來的是 1 // 當 n === 5 的時候要結束 if (n === 5) { return 5 } else { // 不滿足條件的時候,就是當前數字 + 比自己大 1 的數字 return n + add(n + 1) }} add(1)
-
老王有四個子女,老四比老三小2歲,老三比老二小2歲,老二比老大小2歲,老大現在16歲,問老四幾歲?
function countAge(who) {
if (who == 1) {
return 16; }
else {
return countAge(who - 1) - 2;
}
}
alert(countAge(4)); // 10
註:遞歸函數在運行的時候,每調用一次函數就會在記憶體中開闢一塊空間,記憶體消耗較大,註意防止棧溢出。
遞歸演算法一般用於解決三類問題:
1.數據的定義是按遞歸定義的;
2.問題解法按遞歸演算法實現;
3.數據的結構形式是按遞歸定義的。
構造函數(瞭解)
構造函數:用於創建特定類型的對象。
JS內部構造函數:Object、Number、String、Array、Function、Boolean等等...
當任意一個普通函數用於創建一類對象,並通過new操作符來調用時它就可以作為構造函數。
構造函數一般首字母大寫。
簡單對象(掌握)
-
對象是一個複雜數據類型
-
對象是一組無序的鍵值對,是帶有屬性和方法的集合。
-
通俗講,對象就是無序的數據集合。
-
屬性是與對象相關的值,方法是能夠在對象上執行的動作。
-
對象的作用:用於在單個變數中存儲多個值。
創建一個對象
-
字面量的方式創建一個對象
var obj = { 鍵:值, 鍵:值 ...... }; 鍵:一般用雙引號引起來(不用引號也可以) 值:可以是任意類型的數據 var obj = { name: '小錯', age: 18, sayHi: function (){ alert('hi,大家好'); }}
-
內置構造函數的方式創建對象
var obj2 = new Object(); obj2.name = '小錯'; obj2.age = 18; obj2.sayHi = function (){ alert('hi,大家好'); } console.log( obj2.name ); obj2.sayHi( );
-
操作對象
訪問對象成員: 1. 對象.屬性 對象.方法() 2. 對象[變數或字元串]刪除屬性: delete obj.attr;遍歷對象:{} for / in 迴圈 for (var key in obj){ console.log( obj[key] ); }
{}和new創建對象的對比(瞭解)
字面量的優勢:
它的代碼量更少,更易讀;它可以強調對象就是一個簡單的可變的散列表,而不必一定派生自某個類;對象字面量運行速度更快,因為它們可以在解析的時候被優化——它們不需要"作用域解析"!因為存在我們創建了一個同名構造函數Object()的可能,所以當我們調用Object()的時候,解析器需要順著作用域鏈從當前作用域開始查找,如果在當前作用域找到了名為Object()的函數就執行,如果沒找到,就繼續順著作用域鏈往上找,直到找到全局Object()構造函數為止
構造函數的優勢:
Object()構造函數可以接收參數,通過這個參數可以把對象實例的創建過程委托給另一個內置構造函數(Number()、String()等),並返回另一個對象實例。使用自定義構造函數創建對象,可以通過傳參添加屬性和方法,當需要定義的同類對象較多時,節省了定義對象的代碼量,並且使對象屬性和方法的結構更加清晰
-
數據類型之間存儲的區別(重點)
-
既然我們區分了基本數據類型和複雜數據類型
-
那麼他們之間就一定會存在一些區別
-
他們最大的區別就是在存儲上的區別
-
我們的存儲空間分成兩種 棧 和 堆
-
棧: 主要存儲基本數據類型的內容
-
堆: 主要存儲複雜數據類型的內容
基本數據類型在記憶體中的存儲情況
-
var num = 100
,在記憶體中的存儲情況 -
直接在 棧空間 內有存儲一個數據
複雜數據類型在記憶體中的存儲情況
-
下麵這個 對象 的存儲
var obj = { name: 'Jack', age: 18, gender: '男' }
-
複雜數據類型的存儲
-
在堆裡面開闢一個存儲空間
-
把數據存儲到存儲空間內
-
把存儲空間的地址賦值給棧裡面的變數
-
-
這就是數據類型之間存儲的區別
數據類型之間的賦值
-
基本數據類型之間的賦值
var num = 10var num2 = num num2 = 200 console.log(num) // 10 console.log(num2) // 200
-
相當於是把 num 的值複製了一份一摸一樣的給了 num2 變數
-
賦值以後兩個在沒有關係
-
-
複雜數據類型之間的賦值
var obj = { name: 'Jack' } var obj2 = obj obj2.name = 'Rose' console.log(obj.name) // Roseconsole.log(obj2.name) // Rose
-
因為複雜數據類型,變數存儲的是地址,真實內容在 堆空間 記憶體儲
-
所以賦值的時候相當於把 obj 存儲的那個地址複製了一份給到了 obj2 變數
-
現在 obj 和 obj2 兩個變數存儲的地址一樣,指向一個記憶體空間
-
所以使用 obj2 這個變數修改空間內的內容,obj 指向的空間也會跟著改變了
-
數據類型之間的比較
-
基本數據類型是 值 之間的比較
var num = 1var str = '1' console.log(num == str) // true
-
複雜數據類型是 地址 之間的比較
var obj = { name: 'Jack' } var obj2 = { name: 'Jack' } console.log(obj == obj2) // false
-
因為我們創建了兩個對象,那麼就會在 堆空間 裡面開闢兩個存儲空間存儲數據(兩個地址)
-
雖然存儲的內容是一樣的,那麼也是兩個存儲空間,兩個地址
-
複雜數據類型之間就是地址的比較,所以
obj
和obj2
兩個變數的地址不一樣 -
所以我們得到的就是
false
-
-
函數的參數
-
函數的參數也是賦值的,在函數調用的時候,實參給行參賦值
-
和之前變數賦值的規則是一樣的
-
函數傳遞基本數據類型
function fn(n) { n = 200 console.log(n) // 200 } var num = 100fn(num) console.log(num) // 100
-
和之前變數賦值的時候一樣,在把 num 的值複製了一份一摸一樣的給到了函數內部的行參 n
-
兩個之間在沒有任何關係了
-
-
函數傳遞複雜數據類型
function fn(o) { o.name = 'Rose' console.log(o.name) // Rose } var obj = { name: 'Jack' } fn(obj) //所傳遞的就是obj的引用地址console.log(obj.name) // Rose
-
和之前變數賦值的時候一樣,把 obj 記憶體儲的地址複製了一份一摸一樣的給到函數內部的行參 o
-
函數外部的 obj 和函數內部的行參 o,存儲的是一個地址,指向的是一個存儲空間
-
所以兩個變數操作的是一個存儲空間
-
在函數內部改變了空間內的數據
-
obj 看到的也是改變以後的內容
-
js常見錯誤類型
SyntaxError :語法錯誤
// 1) 變數名不符合規範var 1
// Uncaught SyntaxError: Unexpected numbervar 1a
// Uncaught SyntaxError: Invalid or unexpected token// 2) 給關鍵字賦值function = 5
// Uncaught SyntaxError: Unexpected token =
ReferenceError :引用錯誤(要用的變數沒找到)
// 1) 引用了不存在的變數a()
// Uncaught ReferenceError: a is not definedconsole.log(b)
// Uncaught ReferenceError: b is not defined// 2) 給一個無法被賦值的對象賦值console.log("abc") = 1
// Uncaught ReferenceError: Invalid left-hand side in assignment
TypeError: 類型錯誤(調用不存在的方法)
// 1) 調用不存在的方法123()
// Uncaught TypeError: 123 is not a functionvar o = {}o.run()
// Uncaught TypeError: o.run is not a function// 2) new關鍵字後接基本類型var p = new 456
// Uncaught TypeError: 456 is not a constructor
RangeError: 範圍錯誤(參數超範圍)
// 1) 數組長度為負數[].length = -5
// Uncaught RangeError: Invalid array length// 2) Number對象的方法參數超出範圍var num = new Number(12.34)console.log(num.toFixed(-1))
// Uncaught RangeError: toFixed() digits argument must be between 0 and 20 at Number.toFixed
// 說明: toFixed方法的作用是將數字四捨五入為指定小數位數的數字,參數是小數點後的位數,範圍為0-20.