1、回調函數
回調函數:將函數作為參數傳給另一個函數執行的函數,是解決異步操作的有效途徑。
回調函數是某件事執行完了,再做的事。
- 異步:在做某一個操作的時候,其他的操作可以繼續執行,如:定時器、事件綁定。
- 同步:在做某一個操作的時候,其他的操作只能等待。
setTimeout(function () {
console.log(1);
}, 1000);
console.log(2);
box.onclick = function(){
console.log(3);
}
console.log(4);
2、自執行函數(IIFE函數)
匿名函數,因為沒有名字,所以無法調用,因此匿名函數只能函數自執行。
自執行函數:IIFE,Immediately Invoked Function Expression(立即調用函數表達式)
注意:自執行函數後面一定要加分號。
- (函數)();
- (函數());
(function () {
console.log('我執行了');
})();
(function (a, b) {
console.log(a + b);
})(3, 5);
var v = (function (a, b) {
return a + b;
})(10, 20);
console.log(v);
自執行函數的好處:
- 當給一個不熟悉的環境寫代碼時,用自執行函數包起來,防止變量衝突
- 避免全局變量污染
3、閉包
3.1、閉包概念
- 函數嵌套函數
- 內部函數可以讀取外部函數的變量、參數或者函數
- 這個內部函數在外部函數的外面被調用
滿足以上條件的內部函數,就是閉包
閉包的特點:它能記住它的誕生環境,這些變量會一直存在內存中。
閉包的作用:溝通函數內部和外部的橋樑。
閉包的優點:緩存數據,延伸變量的作用範圍。
閉包的缺點:如果大量的數據被緩存,可能造成內存泄露,在使用閉包的時候要慎重。
function fn() {
var n = 1;
function f() {
n++;
console.log(n);
}
return f;
}
var v = fn();
v(); // 2
v(); // 3
v(); // 4
3.2、閉包模擬對象的私有屬性
// 私有屬性的讀取和修改,必須通過一定的方式來進行
function fn(v) {
var value = v;
// return了一個對象出去,對象中有setValue和getValue兩個屬性
// 常規的對象
// var obj = {
// name = '張三',
// age = 18
// }
return {
setValue: function (n) {
value = n;
},
getValue: function () {
return value;
}
}
}
var o = fn(5);
console.log(o); // {setValue: ƒ, getValue: ƒ}
console.log(o.value); // undefined 無法通過這種方式直接獲取o的value
console.log(o.getValue()); // 獲取值 5
o.setValue(10); // 設置值
console.log(o.getValue()); // 10
3.3、循環中的閉包
<ul>
<li>吃飯</li>
<li>睡覺</li>
<li>打豆豆</li>
</ul>
<script>
// 點擊li,打印它的下標
var li = document.getElementsByTagName('li');
// for (var i = 0; i < li.length; i++) {
// li[i].onclick = function(){
// console.log(i);
// // 點擊任意li,只會打印3,點擊之前,循環已經走完,i已經變為3
// }
// }
// -------------------------------------
// 解決方法1
for (var i = 0; i < li.length; i++) {
li[i].index = i; // 給li自定義屬性並賦值i給自定義屬性
li[i].onclick = function () {
console.log(this.index);
}
}
// ------------------------------------
// 解決方法2(循環閉包)
// for (var i = 0; i < li.length; i++) {
// // 給事件一個自執行函數:li[i].onclick = (function(){})();
// li[i].onclick = (function (i) {
// return function(){
// console.log(i);
// }
// })(i);
// }
// 另外的寫法
for(var i = 0;i < li.length;i++){
(function(i){
li[i].onclick = function(){
console.log(i);
}
})(i)
}
</script>
3.4、閉包面試題
var arr = [];
for (var i = 0; i < 10; i++) {
(function (i) {
arr.push(function () {
console.log(i);
})
})(i);
}
// arr = [f, f, f, f, f, f, f, f, f , f]; f代表是function函數
arr[6](); // 6
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i, new Date());
}, 1000);
}
console.log(i, new Date()); // 先打印已經循環完的i為5,打印當前時間
// 循環速度很快,同時設置了5個定時器,並打印循環完的i=5,打印時間為上面時間+1
var i = 1;
var i = 2;
var add = function () {
var i = 0;
return function () {
i++;
console.log(i);
}
}();
add();
function say() {
var num = 888;
var sayAlert = function () {
alert(num);
}
num++;
return sayAlert;
}
var sayAlert = say();
sayAlert();
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
console.log(object.getNameFunc()());
var v = object.getNameFunc();
console.log(v());
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
var that = this; // object
return function () {
return that.name;
};
}
};
console.log(object.getNameFunc()());
// var v = object.getNameFunc();
// v();
4、遞歸
遞歸函數就是在函數內部調用的函數本身,這個函數就是遞歸函數。遞歸函數分為兩步:遞和歸。
遞歸函數的作用和循環效果一樣,遞歸的基本思想是把大規模的問題轉化為規模小的子問題來解決。
// 求5的階乘 5*4*3*2*1 = 120
function factorial(n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
// console.log(factorial(5));
// 遞
// factorial(5)
// 5 * factorial(4)
// 5 * 4 * factorial(3)
// 5 * 4 * 3 * factorial(2)
// 5 * 4 * 3 * 2 * factorial(1)
// 歸
// 5 * 4 * 3 * 2 * 1
// 5 * 4 * 3 * 2
// 5 * 4 * 6
// 5 * 24
// 120
var arr = [4, 6, 2, 6, 5, 8, 4, 7, 3];
console.log(fn(arr));
// 快速排序
// 取出數組的第一項,其它項依次同第一項比較,如果比它下,放在左邊的數組,如果比它大,放在右邊的數組,遞歸調用剛才的方法
function fn(arr) {
if (arr.length <= 1) {
return arr;
}
var num = arr.shift(); // 取出數組的第一項
var left = []; // 存儲比它小的數
var right = []; // 存儲比它大的數
for (var i = 0; i < arr.length; i++) {
var v = arr[i];
if (v < num) {
left.push(v);
} else {
right.push(v);
}
}
return fn(left).concat(num, fn(right));
}
5、防抖與節流
在前端開發的過程中,我們經常會需要綁定一些持續觸發的事件,如resize、scroll、mousemove等等,但有些時候我們並不希望在事件持續觸發的過程中那麼頻繁的去執行函數。防抖和節流就能很好的解決這個問題。
5.1、防抖debounce
防抖,就是指觸發事件後在n秒內函數只能執行一次,如果n秒內又觸發了事件,則會重新計算函數執行事件。
5.1.1、非立即執行
非立即執行的意思是觸發事件後,函數不會立即執行,而是在n秒後執行,如果在n秒內觸發了事件,則會重新計算函數執行時間。
var n = 0;
function fun() {
n++;
console.log(n);
}
// 封裝
function debounce(fun, wait) { // fun:事件處理函數 wait:等待時間
var timer; // 維護全局純淨,藉助閉包來實現
return function () {
if (timer) { // 如果 wait 時間內容觸發了事件,重新開始計時
clearTimeout(timer);
}
timer = setTimeout(fun, wait); // 執行函數
}
}
var v = debounce(fn, 1000);
document.onmousemove = v;
5.1.2、立即執行
立即執行的意思是觸發事件後,函數會立即執行,然後n秒內不觸發事件,才能繼續執行函數的結果。
var n = 0;
function fun() {
n++;
console.log(n);
}
function debounce(fun, wait) { // fun:事件處理函數 wait:等待時間
var timer; // 維護全局純淨,藉助閉包來實現
return function () {
if (timer) {
clearTimeout(timer)
};
var tag = !timer; // 第一次時,tag是真
timer = setTimeout(function () {
timer = null;
}, wait);
if (tag) {
fun();
};
}
}
var v = debounce(fn, 1000);
document.onmousemove = v;
5.2、節流throttle
節流,就是指連續觸發事件,但在n秒中只執行一次函數。節流會稀釋函數的執行頻率。可以通過時間戳、定時器兩種方式實現。
5.2.1、時間戳方式
事件會立即執行,並且間隔1秒執行1次
var n = 0;
function fn() {
n++;
console.log(n);
}
function throttle(fun, wait) {
var previous = 0;
return function () {
var now = Date.now();
if (now - previous > wait) {
fun();
previous = now;
}
}
}
var v = throttle(fn, 1000);
document.onmousemove = v;
5.2.2、定時器方式
事件會1秒後執行,並且間隔1秒執行1次
var n = 0;
function fn() {
n++;
console.log(n);
}
function throttle(fun, wait) { // fun要執行的函數 wait時間
var timer;
return function () {
if (!timer) {
timer = setTimeout(function () {
fun();
timer = null;
}, wait);
}
}
}
var v = throttle(fn, 1000);
document.onmousemove = v;
6、call與apply
- 函數.call(新的this指向,參數1,參數2,......)
- 函數.apply(新的this指向,[參數1,參數2,.....])
作用:調用函數,並修改函數中的this指向
兩者的區別:call的參數是一個一個轉入的,而apply是以一個數組的方式傳入的
function fn(a, b) {
console.log(this);
console.log(a, b);
}
fn(3, 4); // 普通調用
fn.call(document.body, 7, 8);
fn.apply(document.body, [10, 20]);
// 找出數組的最大值
var arr = [4, 6, 2, 6, 5, 8, 4, 7, 3];
// Math.max(4, 6, 2, 6, 5, 8, 4, 7, 3)
console.log(Math.max.apply(Math, arr));