入門篇大綱
第二部分 函數與數組
1.函數
函數的定義
普通函數
- function 函數名 (表達式1…) { 代碼塊 }
- js是解釋性語言,在當前script標籤代碼執行的開始階段,就會將普通函數放入堆中,也只是將引用放入堆中,函數中的內容並沒有解釋執行,只有當函數執行的時候纔會解析,執行一次函數,解析一次
- 普通函數 function fn() {} 可以在定義之前調用執行
fn(); // 可以在定義之前調用
function fn() {
console.log('普通函數');
}
fn(); // 也可以在定義之後調用
- 函數重名的危險
- 普通函數和匿名函數重名,匿名函數會覆蓋普通函數,原因是普通函數在script標籤開始時就將函數放入堆中,而匿名函數是在代碼解析的時候才存入堆中,如果名稱相同,就會覆蓋原函數;
- 普通函數和變量重名,變量會覆蓋普通函數;
例子:
// 1. 普通函數和匿名函數重名
function fn() {
console.log('我是普通函數');
}
var fn = function() {
console.log('我是匿名函數');
}
fn(); // 輸出:我是匿名函數
// 2. 普通函數和變量重名
function fn() {
console.log('我是普通函數');
}
var fn = 10;
fn(); // 調用失敗,會報fn未定義的錯誤
匿名函數
- 定義: var 變量 = function() { 代碼塊}
- 將匿名函數賦值給變量,執行變量函數就是執行這個匿名函數,匿名函數不能再定義該函數之前進行調用
var fn = function() {
console.log('匿名函數');
}
fn(); // 只能在定義之後調用匿名函數
- 不建議使用匿名函數,通常使用addEventListener() 方法替代
// 兼容性極好 ie6+, 但是不建議使用
btn.onclick = function() {
console.log(this);
}
// 通常用下面方式代替,兼容性 ie8+
btn.addEventListener('click', clickHandler);
function clickHandler(e) {
console.log(e);
}
構造函數
- 定義:var 變量 = new Function(參數字符串, 執行語句代碼塊字符串)
- 使用全字符串,參數在最前面,後面的字符串內容是執行代碼的語句
- 缺點:代碼會做2次執行,第一次會將函數中的字符串解析爲普通代碼,第二次執行該代碼,因此,效率極低
- 優點:可以用任何語言傳入該js代碼並且執行
var fn = new Function("a", "console.log(a)");
fn(10); // 輸出 10
自執行函數
- 定義:(function () { 代碼塊}) ()
- 自執行函數只能執行一次,執行完成後再也找不到,變成孤兒對象(但是有引用,不能被回收)
(function () {
console.log('自執行函數');
})();
變量的作用域
全局變量
- 所謂全局變量就是在script標籤中直接使用var定義的變量
- 當在script中定義了變量,在定義前調用是undefined,這是因爲定義變量是在內存中開闢了該變量的存儲位置,並在代碼解釋執行時將值存在棧中,如果提前調用,計算機在內存中找不到對應的存儲位置,所以會報undefined
console.log(s); // 報錯:s is not defined
var s = 100;
- 如果在上一個script中調用下一個script中定義的變量,因爲沒有開闢存儲空間,所以永遠報錯
<script type="text/javascript">
console.log(s); // 報錯:s is not defined
</script>
<script type="text/javascript">
var s = 100;
</script>
- 一旦定義變量完成後,在後面的script中都是可以任意調用的,因爲這些變量都是全局變量
<script type="text/javascript">
var s = 100;
console.log(s); // 輸出: 100
</script>
<script type="text/javascript">
console.log(s); // 輸出: 100
</script>
局部變量
- 所謂局部變量就是在函數中使用var定義的變量
- 函數中的定義的局部變量是有作用域的,他的範圍僅限於該函數內部,函數運行完成後,函數內定義的變量將會自動銷燬
var s;
function fn() {
var c = 10; // 這是局部變量
c += 3;
console.log(c); // 輸出:13
// 可以在函數中修改全局變量的值,修改後,全局調用有效
s == 10;
console.log(s) // 輸出:10
}
fn();
局部變量和全局變量重名
- 如果在函數中定義了某個局部變量名,那麼在該函數中所有的這個變量都是局部變量,不能通過直接使用變量名的方法調用到外部同名的全局變量
例子:
var s = 10;
function fn() {
var s = 20;
s += 20;
window.s += 10; // 想要在函數內調用與局部變量同名的全局變量,需要加上window前綴
console.log(s); // 輸出:40
console.log(window.s); // 輸出:20
}
fn();
典型例子
var s = 10;
function fn() {
console.log(s); // 輸出:undefined
s += 20;
console.log(s); // 輸出:NaN 因爲是 undefined += 10
var s = 20; // 函數內一旦定義,在其前後調用都爲佈局變量
}
fn();
參數
定義
由於js是一種弱類型語言,在使用參數時需要注意以下兩點
- 參數類型:因爲參數類型不能固定,如果是對外開放的函數參數,一定需要在代碼中判斷輸入參數的類型
- 初始化:在ES5中函數的參數不能設置初始化值,所以需要在執行代碼時給他自定義一個默認的初始值;而在ES6中是可有初始化參數設置,直接可以使用,比如
function(a, b, type='+')
,參數type默認是"+"
實參和形參
對象參數
如果參數爲對象,傳入的是引用對象的地址
function fn(o) {
var obj = {a: 1};
console.log(o === obj); // false
console.log(o === obj3); // true
}
var obj3 = {a: 3};
fn(obj3);
函數參數–回調
參數如果傳入的是一個函數名,在當前函數中運行了這個參數,這就是回調
function fn1(o, fn){
o.a += 1;
fn(o); // 執行回調
console.log(o.a); // 輸出: 12
}
function fn2(_o){
_o.a += 10;
}
var obj = {a: 1};
fn1(obj, fn2);
函數執行自己–遞歸
var sum1 = 0;
var i = 0;
function fn() {
if (i === 100){
return; // 跳出函數外
}
sum += i;
i++;
fn(); // 執行回調
}
return
注意事項
- return語句會終止函數的執行並返回函數的值,return是JavaScript裏函數返回值的關鍵字
- 一個函數內處理的結果可以使用return返回,這樣在調用函數的地方就可以用變量接受返回結果
- return關鍵字內任何類型的變量數據或表達式都可以進行返回,甚至什麼都不返回也可以
- return也可以作爲阻止後續代碼執行的語句被使用
function abc() { if (bool) return}
這樣可以有效的控制語句在一定的條件下執行,不過一定要與break區分,break是用在條件當中,跳出的也僅僅是條件和循環,但是return卻可以跳出函數,僅僅是針對函數使用,如果沒有函數是不能使用return的
應用場景
- 返回一個數據
function createObj(_a) {
return {a: _a}; // 返回一個數據對象
}
console.log(createObj(3) === createObj(3)); // 輸出:false
- 工廠模式或者單例模式
var box;
function createBox(_a, _b) {
if (!box) {
box = {};
}
box.a = _a;
box.b = _b;
return box;
}
console.log(createBox(3, 5) === createBox(10, 20)); // 輸出: true
- 通過參數傳入的對象
function setObjProper(obj) {
obj = obj || {}; // 寬模式
obj.n = 3;
return obj;
}
var obj = setObjProper({});
var obj1 = setObjProper();
var obj2 = {a: 5};
console.log(obj2 === setObjProper(obj2)); // 輸出:true
console.log(obj, obj1); // 輸出:{n: 3} {n: 3}
- 如果參數一個函數,返回的是回調函數的結果
function fn1(fn) {
var obj = {};
return fn(obj); // 返回回調函數的結果
}
function fn2(obj) {
obj.a = 10;
return obj;
}
console.log(fn1(fn2)); // 輸出:{a: 10}
- 返回的是一個私密的對象 (閉包)
function fn() {
return function () {
console.log('aaa');
}
}
fn()(); // 輸出: aaa
- 返回一個數組,返回多個元素
- 返回一個對象,返回多個元素
- 返回一個函數體
- 跳出
2.數組
定義
- 數據將無序的數據做有序的排列,存儲在一個變量中
- 原生JS中沒有數組類型,原生JS中的數據可以存儲多個不同類型的數據(弱類型)
- Array.isArray(arr) 是判斷arr是否爲數組
- 數組中,標識元素所在的位置,叫做下標,也叫做索引
- 數組中,標識元素的內容,arr[0]這個就叫做下標變量
數組的創建
- 字面量創建
var arr = [1, 2, 3, 4, 5]
- 對象構造函數創建
var arr = new Object([1, 2, 3, 4, 5])
- 構造函數創建
var arr = new Array()
注意:- 創建數組時,有且僅有一個數值類型的參數,這個參數就是數組的長度
- 創建數組時,有且僅有一個非數組類型的參數或者有兩個或兩個以上的參數,這些參數都是數組的元素
數組的長度
- 數組的長度是可以設置的
- 將數組的長度設置爲0,表示清空數組
- 如果數組的長度小於原數組長度,意味着將數組從0位截取到指定位置
var arr = [1, 2, 3, 4, 5];
arr[arr.length-1] = 6; // 將數組中最後一個元素5替換爲6
arr[arr.length] = 6; // 給數組的尾部添加一個新元素6
數組的遍歷
- for循環遍歷
var arr = [1, 2, 3, 4, 5];
for (let i = 0; i < arr.length; i++) {
console.log(i + '=====' + arr[i]);
// 輸出: 0=====1
// 1=====2
// 2=====3
// 3=====4
// 4=====5
}
- for in 遍歷 (能遍歷出數組中所有的對象)
var arr = [1, 2, 3, 4, 5];
arr.key = 100;
for (let prop in arr) {
console.log(prop + '=====' + arr[prop]);
// 輸出: 0=====1
// 1=====2
// 2=====3
// 3=====4
// 4=====5
// key=====100
}
- forEach遍歷
forEach遍歷當前數組,沒有返回值,不會返回新數組
var arr = [1, 2, 3, 4, 5];
arr.forEach(function(t, i, arr)) {// 參數爲:元素值,元素下標,數組對象
console.log(t, i, arr)
})
- map遍歷
map遍歷數組,將當前數組中的元素返回給新數組,不適用return時,新數組的長度與原數組相同,但是每個元素都是undefined
var arr = [1, 2, 3, 4, 5];
arr = arr.map(function (t) {
if (t % 2 === 0) { // 數組元素中偶數元素 + 1
return t + 1;
}
return t;
});
console.log(arr); // 輸出:[1, 3, 3, 5, 5]
冒泡排序
var arr = [6, 3, 4, 5, 7, 1, 2, 9, 0, 8];
// 要點1: 外層循環從後向前
for (var i = arr.length; i > 0; i--) {
// 要點2:內層循環從前向後
for (var j = 0; j < arr.length; j++) {
// 要點3:只判斷內層循環的當前爲和下一位的大小,然後互換
if (arr[j] > arr[j+1]) { // 升序: j>j+1 降序:j<j+1
var temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
console.log(arr); // 輸出:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
選擇排序
var arr = [6, 3, 4, 5, 7, 1, 2, 9, 0, 8];
// 要點1:外層循環從頭到尾
for (var i = 0; i < arr.length; i++) {
// 要點2:每次初始最小值是當前循環所在的值
var min = i;
// 要點3:從當前所在值的下一項到尾部循環
for (var j = min + 1; j < arr.length; j++) {
// 要點4:比較大小,找出後面元素中最小值所在的下標
if (arr[min] > arr[j]) { // 升序:min>j 降序:min<j
min = j;
}
}
// 要點5:互換當前下標值和最小值的下標值
var temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
console.log(arr) // 輸出:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
數組的方法
push
var arr = [1, 2, 3, 4, 5];
// 向數組尾部添加元素
var len = arr.push(6); // 返回數組的新長度
// 典型應用
var len = arr.push(6, 7, 8);
var len2 = arr.push([6, 7, 8]);
console.log(len, len2) // 輸出:8 9
pop
// 刪除數組尾部的元素
var t = arr.pop(); //返回被刪除的元素
// 典型應用
var arr = [-2, -1, 0, 1, 2, 3, 4];
var arr1 = [];
var t;
while (t = arr.pop()) { // 會獲取arr中0以後的所有元素,並倒序排列
arr1.push(t);
}
console.log(arr1); // [4, 3, 2, 1]
shift
// 刪除數組的頭部元素
var t = arr.shift(); // 返回被刪除的元素
unshift
var arr = [1, 2, 3, 4, 5];
// 向數組頭部添加元素
var len = arr.unshift(); // 返回數組的新長度
var len = arr.unshift(0); // [0, 1, 2, 3, 4, 5]
// 儘量減少unshift的使用,從數組頭部添加元素費性能
join
var arr = [1, 2, 3, 4];
console.log(arr.join()); // 與arr.toString()相同
console.log(arr.join('#')); // 設置一個符號,用這個符號鏈接數組的每一個元素,形成一個新字符串
console.log(arr.join('')); // 元素緊密相連
concat
var arr = [1, 2, 3, 4];
var arr1 = arr.concat(); // 沒有參數數,表示複製數組
var arr2 = arr.concat(5, 6); // 將arr數組鏈接 5,6兩個元素,形成新數組,原數組不變
var arr3 = arr.concat([5, 6, 7]); // 將arr數組和數組[5, 6, 7]合併形成新數組
var arr4 = [5, 6, 7];
var arr5 = arr.concat(arr4); // 將兩個數組鏈接形成新數組
splice
var arr = [1, 2, 3, 4, 5];
arr.splice(); // 數組插入刪除替換元素,並且返回被刪除元素組合成新數組
var arr1 = arr.splice(); // 創建一個新的空數組返回
var arr1 = arr.splice(3); // 從下標是3開始刪除到尾部
var arr1 = arr.splice(0); // 將arr的所有元素導入arr1中,清空arr
var arr1 = arr.splice(0, 2); // 從arr數組的下標0開始刪除2位元素
var arr1 = arr.splice(0, 0, -1); // 在第0位插入一個-1
var arr1 = arr.splice(-1, 0, -1); // 在第-1位(倒數第1位)插入一個-1
var arr1 = arr.splice(arr.length, 0, -1); // 在數組尾部插入一份-1
var arr1 = arr.splice(1, 2, -1, -2); // 從第1位開始替換兩個元素爲-1, -2
slice
var arr = [1, 2, 3, 4, 5];
arr.slice(); // 數組截取元素,並且返回被截取元素組合成新數組,原數組不變
var arr1 = arr.slice(); // 複製arr的所有元素給arr1,和原數組沒有引用關係
var arr1 = arr.slice(0); // 複製arr的所有元素給arr1,和原數組沒有引用關係
var arr1 = arr.slice(3); // 將數組從下標3開始到結尾截取形成新數組
var arr1 = arr.slice(-2); // 將數組從倒數第2位開始到結尾截取形成新數組
var arr1 = arr.slice(3, 4); // 將數組從下標3開始到下標4截取形成新數組
var arr1 = arr.slice(-2, 4); // 將數組從倒數第2位開始到下標4截取形成新數組
var arr1 = arr.slice(-2, -1); // 將數組從倒數第2位開始到倒數第1位截取形成新數組
indexOf
var arr = [1, 2, 3, 4, 5];
arr.indexOf(); // 在數組中查找元素,返回查找到元素的下標,沒有找到返回-1
console.log(arr.indexOf(2)); // 從0開始向後查找2
console.log(arr.indexOf(2, 3)); // 從下標3開始向後查找2
lastIndexOf
var arr = [1, 2, 3, 4, 5];
arr.lastIndexOf(); // 在數組中從後向前查找元素,返回查找到元素的下標,沒有找到返回-1
console.log(arr.lastIndexOf(2)); // 從後向前查找2
console.log(arr.lastIndexOf(2, 3)); // 從下標3開始向前查找2
reverse
var arr = [1, 2, 6, 4, 3, 5];
// 將原數組倒序改變,返回的新數組和原數組是引用關係
var arr1 = arr.reverse();
console.log(arr, arr === arr1); // 輸出: [5, 3, 4, 6, 2, 1] true
sort
var arr = [1, 12, 6, 4, 3, 5];
// 將原數組中的元素按位排序,原數組發生改變,返回的新數組和原數組是引用關係
var arr1 = arr.sort();
console.log(arr, arr === arr1); // 輸出: [1, 12, 3, 4, 5, 6] true
// sort(function(pre, next){}); 可以實現升序降序排列
// 升序排序 輸出:[1, 3, 4, 5, 6, 12]
arr.sort(function(pre, next) {
return pre - next;
});
// 降序排序 輸出:[12, 6, 5, 4, 3, 1]
arr.sort(function(pre, next) {
return next-pre;
});
some
判斷數組中的所有元素是否滿足某條件,只要有一個滿足條件返回true, 否則返回false
var arr = [1, 3, 5, 2, 7, 8, 0, 6]
var boolean = arr.some(function(t) {
t ++;
return t > 5;
})
console.log(boolean); // 輸出: true
every
判斷數組中的所有元素是否滿足某條件,只有當全部滿足返回true,否則返回false
var arr = [1, 3, 5, 2, 7, 8, 0, 6]
var boolean = arr.every(function(t) {
t ++;
return t > 5;
})
console.log(boolean); // 輸出: false
filter
判斷數組中的所有元素是否滿足某條件,並返回滿足條件的所有元素組成的新數組,原數組元素不變
var arr = [1, 3, 5, 2, 7, 8, 0, 6]
var arr1 = arr.filter(function(t) {
t ++;
return t > 5;
})
console.log(arr1); // 輸出: [5, 7, 8, 6]
reduce
// 如果沒有initValue值,sum就是第0項的值,item就是第一項值,所以index是從1開始
var s = arr.reduce(function(sum, item, index)) {
console.log(sum, item, index);
return sum + item;
}, initValue); // 這裏第二個參數是初始值,如果設置這個初始值,index就從0開始
from
var div = document.getElementsByTagName('div');
// es5:將對象轉換成數組
var arr = Array.prototype.slice.call(div);
// es6: 將對象轉換成數組
var arr = Array.from(div);
參數數組
- 在函數中,參數如果寫在所有參數的最前面,那就是必要參數
- es5中所有的參數一般都需要按位填寫,如果沒有填寫,就會變成undefined
- 多填進入的參數值不會被直接獲取到
- 如果一個函數的參數不定,我們一般在函數定義時不寫參數,在函數體中使用參數數組arguments進行處理
arguments典型案例
// 1. 求若干數的最大值
function max() {
var arr = Array.from(arguments);
return arr.reduce(function(p1, p2){
return p1 > p2 ? p1 : p2;
});
}
console.log(max(1, 2, 3, 6, 3, 2, 6, 8, 9)); // 輸出:9
// 2.函數回調
function fn() {
console.log(arguments.callee); // 獲取當前執行的函數
console.log(arguments.callee.a); // 獲取屬性值
console.log(arguments.callee.caller); // 回調當前函數的父級函數
}
fn.a = 10; // 給對象添加屬性
fn(2, 3, 4); // 執行函數
function fn2(fn) {
fn();
}
fn2(fn); // 執行父級函數
二維數組
var arr = [
[1, 2, 3, 4, 5],
[1, 2, 3, 4, 5],
[1, 2, 3, 4, 5],
[1, 2, 3, 4, 5],
[1, 2, 3, 4, 5],
]
對象數組
var arr = [
{a: 1, b: 2, c: 3},
{a: 1, b: 2, c: 3},
{a: 1, b: 2, c: 3},
]