JavaScript课程——Day14(回调函数、自执行函数、闭包、递归、防抖与节流、call与apply)
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、循环中的闭包
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));