重點
更多前端知識 誠邀各位前端從事者愛好者加入前端大佬技術交流社區,本社區主要分享技術堆疊、個人心得、技術交流、問題解惑等前端體系交流
點擊下方文字加入
前端大佬技術交流社區
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=當前觸發事件的某個具體div
this.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=20
function 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 = 100
return 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 = 13
var start_juli = 3
var total = 0
return {
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 * ..n
function 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;
}
重點
更多前端知識 誠邀各位前端從事者愛好者加入前端大佬技術交流社區,本社區主要分享技術堆疊、個人心得、技術交流、問題解惑等前端體系交流
點擊下方文字加入
前端大佬技術交流社區
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/341912.html
標籤:其他
