重點
更多前端知識 誠邀各位前端從事者愛好者加入前端大佬技術交流社區,本社區主要分享技術棧、個人心得、技術交流、問題解惑等前端體系交流
點擊下方文字加入
前端大佬技術交流社區
1. 函數的定義和調用
1.1 函數的定義方式
-
方式1 函數聲明方式 function 關鍵字 (命名函數)
function f1() {console.log('命名函數') }
-
方式2 函數表達式(匿名函數)
var f2 = function () {console.log('匿名函數') }
-
方式3 new Function()
/*參數1:函數形參參數2:函數形參參數3:函數體*/ var f3 = new Function('m', 'n', 'console.log(m-n)') // 傳入實參 f3(4,2)
注意
- Function 里面參數都必須是字符串格式
- 第三種方式執行效率低,也不方便書寫,因此較少使用
1.2 函數的調用
// 普通函數:命名函數和匿名函數
function f1() {console.log('人生何處不相逢');
}
var f2 = function () {console.log('寒江孤影,江湖故人,相逢何必曾相識');
}
// 對象中的函數,專業點叫做方法,通過 對象.方法 方式調用
var Person = {speak: function () {console.log('人生處處是歌聲');}
}
// 構造函數:通過 new 關鍵字調用
function Star() { }
new Star()
// 事件處理函數:事件觸發時被調用
element.onclick = function () { }
// 定時器函數:由定時器選擇時機調用
setInterval(function () { }, 1000)
// 立即執行函數:立即調用
(function () {console.log('first off')
}())
2.this
2.1函數內部的this指向
這些 this 的指向,是當我們調用函數的時候確定的。調用方式的不同決定了this 的指向不同
一般指向我們的調用者.
2.2改變函數內部 this 指向
2.2.1 call方法
call()方法調用一個對象。簡單理解為調用函數的方式,但是它可以改變函數的 this 指向
應用場景: 經常做繼承.
var o = {name: 'andy'
}function fn(a, b) {console.log(this);console.log(a+b)
};
fn(1,2)// 此時的this指向的是window 運行結果為3
fn.call(o,1,2)//此時的this指向的是對象o,參數使用逗號隔開,運行結果為3
以上代碼運行結果為:
復習使用this實現繼承
2.2.2 apply方法
apply 方法的用于與call 非常類似,區別在于,不能以參數列表的形式傳遞實參,必須以數組的形式傳遞
var o = {name: 'andy'}function fn(a, b) {console.log(this);console.log(a + b)};// 普通調用fn(1, 2)// call調用,將函數中的this修改為o對象fn.call(o, 1, 2)// apply 調用,區別在于參數列表必須以數組形式傳遞fn.apply(o,[1,2])
可以看到,我們傳入函數的實參是數組,但是函數內部會自動展開為參數列表
利用這一特性,我們可以結合 Math.max 方法求數組的最大值或者最小值
Math.max 方法可以求一組數字的最大值,語法如下
Math.max(1,2,3,4,5)
參數為參數列表形式
利用apply 可以將數組傳入
var numbers = [3, 2, 44, 11, 66, 7]
var max=Math.max.apply(null,numbers)
console.log(max);
也可以利用 ES6 的 Spread syntax 語法
var max=Math.max(...numbers)
2.2.3 bind方法
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
bind() 方法不會調用函數,但是能改變函數內部this 指向
如果只是想改變 this 指向,并且不想調用這個函數的時候,可以使用bind
應用場景:不調用函數,但是還想改變this指向
var o = {name: 'andy'};function fn(a, b) {console.log(this);console.log(a + b);
};
var f = fn.bind(o, 1, 2); //此處的f是bind返回的新函數
f();//調用新函數 this指向的是對象o 參數使用逗號隔開
解惑
上面的代碼
var f = fn.bind(o, 1, 2)
的作用就是將 fn 函數拷貝了一份,名稱叫做 f,同時將函數 f 中的 this 修改為對象o,所以函數 f 與 fn 的函數體是一樣的,只不過內部 this 的指向不同了
如下代碼,才是調用執行函數 f,而上面的代碼僅僅完成上面的任務,但不會執行函數 fn 也不會執行新函數 f
f()
應用
頁面中有1個div,默認為紅色,鼠標懸浮后,變為藍色,3秒之后恢復成紅色
var div = document.querySelector('div')
div.addEventListener('mouseover', function () {this.style.backgroundColor = 'blue'setTimeout(function () {div.style.backgroundColor = 'red'}, 3000);
})
定時器中,this=window,所以不能使用this,必須使用元素名稱div
當然可以使用 var that=this 的方式
但這兩種方式都有問題,如實現下面的效果
當前頁面上有3個div,默認都為紅色,鼠標懸浮到某個div上,顏色變為藍色,3秒后恢復成紅色
var divs = document.querySelectorAll('div')for (var i = 0; i < divs.length; i++) {divs[i].addEventListener('mouseover', function () {// 這里的this=當前觸發事件的某個具體divthis.style.backgroundColor = 'blue'setTimeout(function () {// 下面的代碼應該怎么寫}, 3000);})
}
定時器中的this=window,所以不能使用window
也不能使用divs[i],因為 i 的索引在事件發生時,并不是你想象中的索引
當然可以使用 that=this 的方式,但會多創建局部變量
可以利用bind方法
這里使用bind最合適,因為僅僅是修改了函數中this的指向,并不會馬上執行,3秒之后由系統再次調用
注意:bind 方法會創建一個新函數,所以3秒后調用的是新函數,在新函數中,this=div
問?上面不是需要聲明一個變量接收bind創建的新函數嗎?為什么這里不需要?
同學,請先搞清楚:什么時候需要返回值,什么時候不需要
2.2.4 call、apply、bind三者的異同
-
共同點 : 都可以改變this指向
-
不同點:
- call 和 apply 會調用函數, 并且改變函數內部this指向.
- call 和 apply傳遞的參數不一樣,call傳遞參數使用逗號隔開,apply使用數組傳遞
- bind 不會調用函數, 可以改變函數內部this指向.
-
應用場景
- call 經常做繼承.
- apply經常跟數組有關系. 比如借助于數學對象實現數組最大值最小值
- bind 不調用函數,但是還想改變this指向. 比如改變定時器內部的this指向.
3.嚴格模式
3.1什么是嚴格模式
JavaScript 除了提供正常模式外,還提供了嚴格模式(strict mode)。ES5 的嚴格模式是采用具有限制性 JavaScript變體的一種方式,即在嚴格的條件下運行 JS 代碼。
嚴格模式在 IE10 以上版本的瀏覽器中才會被支持,舊版本瀏覽器中會被忽略。
嚴格模式對正常的 JavaScript 語義做了一些更改:
1.消除了 Javascript 語法的一些不合理、不嚴謹之處,減少了一些怪異行為。
2.消除代碼運行的一些不安全之處,保證代碼運行的安全。
3.提高編譯器效率,增加運行速度。
4.禁用了在 ECMAScript 的未來版本中可能會定義的一些語法,為未來新版本的 Javascript 做好鋪墊。比如一些保留字如:class,enum,export, extends, import, super 不能做變量名
聊聊 TS
3.2開啟嚴格模式
嚴格模式可以應用到整個腳本或個別函數中。因此在使用時,我們可以將嚴格模式分為為腳本開啟嚴格模式和為函數開啟嚴格模式兩種情況。
- 腳本開啟嚴格模式
- 函數開啟嚴格模式
<script>// 在當前腳本中啟用嚴格模式:script開始標記和結束標記之間生效// 'use strict'var name='yhb'age=20function fn(){// 在函數中開啟嚴格模式'use strict'gender='男'}fn()</script>
3.3嚴格模式中的變化
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode
嚴格模式對 Javascript 的語法和行為,都做了一些改變。
'use strict'
num = 10
console.log(num)//嚴格模式后使用未聲明的變量
--------------------------------------------------------------------------------
var num2 = 1;
delete num2;//嚴格模式不允許刪除變量:刪除變量的目的是希望釋放內存,處理方式一般是將變量的值設置為null
--------------------------------------------------------------------------------
function fn() {console.log(this); // 嚴格模式下全局作用域中函數中的 this 是 undefined
}
fn();
--------------------------------------------------------------------------------- function Star() {this.gender = '男';
}
// 不加new 調用Star,則this=undefined
console.log(Star().gender)
// 加new 調用Star,則this=對象
console.log(new Star().gender)
----------------------------------------------------------------------------------
setTimeout(function() {console.log(this); //嚴格模式下,定時器 this 還是指向 window
}, 2000);
另外,嚴格模式下函數的參數名稱應該唯一
// 非嚴格模式下
function f1(m, m) {console.log(m + m)
}
f1(1,2) // 4,分析一下原因為什么事4
嚴格模式下,會報錯
4.高階函數
高階函數是對其他函數進行操作的函數,它接收函數作為參數或將函數作為返回值輸出。
函數作為參數:
函數作為返回值
函數也是一種數據類型,同樣可以作為參數,傳遞給另外一個參數使用。最典型的就是作為回調函數。
同理函數也可以作為返回值傳遞回來
函數作為參數案例:
function fn(m, n, callback) {console.log(m + n)// 函數主體執行結束后才會執行回調函數callback && callback()
}
fn(3, 4, function () {console.log('我就是回調函數')
})
jquery 中大量應用了回調函數,比如動畫完成后執行某個操作
5.閉包
5.1變量的作用域復習
變量根據作用域的不同分為兩種:全局變量和局部變量。
- 函數內部可以使用全局變量。
- 函數外部不可以使用局部變量。
- 當函數執行完畢,本作用域內的局部變量會銷毀。
5.2什么是閉包
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
在JS中,函數每次創建,都會生成閉包(closure),這個閉包包含函數及其詞法環境(大概類似于執行上下文)
閉包是一種執行機制:內部函數總是可以訪問其所在的外部函數中聲明的變量和參數,即使在其外部函數執行結束之后
/*在一個函數中又嵌套函數,在JS中是沒有問題的1)內部作用域可以訪問外部作用域,所以f2中可以訪問f1中的變量2)在全局作用域內,不訪問函數f1中的變量*/function f1() {var m = 100return function() {console.log(m)} }var myfunc=f1()// myfunc 是一個變量,引用了f2的地址,myfunc() 就相當于執行了 f2()myfunc() // f2()// console.log(m)
通過上面案例發現:閉包可以延伸變量的作用范圍。
解惑
一般情況下,下面代碼執行后,外部函數f1就執行完畢了,那么函數內部的成員數據都會被銷毀,包括變量 m,但是因為內部函數f2使用了變量m,而函數f2又被返回給了變量 myfunc,即變量myfuc引用了內部函數f2,此時
f2還沒有執行,所以外部函數f1就不能釋放自己的變量m
f1()
我們可以將 子函數f2 稱作閉包函數(有爭議)
我們再對閉包做一個總結:函數創建時,形成一個閉包,閉包讓內部函數可以訪問外部函數的變量和參數,并且通過向外返回內部函數,使得在函數外部也可以訪問函數內部的數據
閉包的三個特性
1)函數嵌套函數
2)函數內部可以訪問函數外部的變量和參數
3)外部函數執行完畢后,參數和變量不會被垃圾回收機制回收
5.3閉包的案例
- 利用閉包的方式得到當前li 的索引號
for (var i = 0; i < lis.length; i++) {
// 利用for循環創建了4個立即執行函數
// 立即執行函數也成為小閉包因為立即執行函數里面的任何一個函數都可以使用它的i這變量
(function(i) {lis[i].onclick = function() {console.log(i);}})(i);
}
但是,這種案例使用閉包其實并不是最好的解決方案,因為每次循環都要創建一個函數,而且每個i 的值都會被保存,不能釋放,所以執行效率會低
將i的值存儲于li標簽的自定義屬性中的方式更加可取
代碼 (任務單)
- 閉包應用-3秒鐘之后,打印所有li元素的內容
下面代碼遍歷所有li標簽,創建了三個定時器,3秒之后打印每個li標簽元素內容
for (var i = 0; i < lis.length; i++) {(function(i) {setTimeout(function() {console.log(lis[i].innerHTML);}, 3000)})(i);
}
其實,使用我們前面學習的 bind 方法也是可以的
代碼 (任務單)
- 閉包應用-計算打車價格
/*需求分析
打車起步價13(3公里內), 之后每多一公里增加 5塊錢. 用戶輸入公里數就可以計算打車價格
如果有擁堵情況,總價格多收取2塊錢擁堵費*/var car = (function () {var start_price = 13var start_juli = 3var total = 0return {price: function (juli) {if (juli <= start_juli) {total = start_price} else if (juli > 3) {total = start_price + (juli - start_juli) * 5}return total},yondu: function (flag) {return flag ? total + 2 : total}}})()
console.log(car.price(3));
console.log(car.price(10));
console.log(car.yondu(false));
console.log(car.yondu(true));
解惑
1、立即執行函數中的代碼立即執行,不會等待調用,所以 變量 car 引用的是一個對象
2、引用的對象中包含兩個函數 price 和 yongdu,這兩個函數屬于立即執行函數的子函數
3、子函數中引用了外部函數中的變量,所以形成了閉包
4、上面的案例并非非要這么編寫程序,只是為了練習閉包的使用
6.遞歸
6.1什么是遞歸
**遞歸:**如果一個函數在內部可以調用其本身,那么這個函數就是遞歸函數。簡單理解:函數內部自己調用自己, 這個函數就是遞歸函數
**注意:**遞歸函數的作用和循環效果一樣,由于遞歸很容易發生“棧溢出”錯誤(stack overflow),所以必須要加退出條件return。
6.2利用遞歸求1~n的階乘
//利用遞歸函數求1~n的階乘 1 * 2 * 3 * 4 * ..nfunction fn(n) {if (n == 1) { //結束條件return 1;}return n * fn(n - 1);}console.log(fn(3));
6.3利用遞歸求斐波那契數列
// 利用遞歸函數求斐波那契數列(兔子序列) 1、1、2、3、5、8、13、21...
// 用戶輸入一個數字 n 就可以求出 這個數字對應的兔子序列值
// 我們只需要知道用戶輸入的n 的前面兩項(n-1 n-2)就可以計算出n 對應的序列值
function fb(n) {if (n === 1 || n === 2) {return 1;}return fb(n - 1) + fb(n - 2);
}
console.log(fb(3));
6.4利用遞歸遍歷數據
// 我們想要做輸入id號,就可以返回的數據對象var data = [{id: 1,name: '家電',goods: [{id: 11,gname: '冰箱',goods: [{id: 111,gname: '海爾'}, {id: 112,gname: '美的'},]}, {id: 12,gname: '洗衣機'}]}, {id: 2,name: '服飾'
}];
//1.利用 forEach 去遍歷里面的每一個對象function getID(json, id) {var o = {};json.forEach(function(item) {// console.log(item); // 2個數組元素if (item.id == id) {// console.log(item);o = item;return o;// 2. 我們想要得里層的數據 11 12 可以利用遞歸函數// 里面應該有goods這個數組并且數組的長度不為 0 } else if (item.goods && item.goods.length > 0) {o = getID(item.goods, id);}});return o;
}
重點
更多前端知識 誠邀各位前端從事者愛好者加入前端大佬技術交流社區,本社區主要分享技術棧、個人心得、技術交流、問題解惑等前端體系交流
點擊下方文字加入
前端大佬技術交流社區