1、回调函数 (同步异步)
同步:在做某一个操作的时候,其他的操作只能等待
- 一行一行代码执行,然后会阻塞代码,在函数中的结果我们可以用return返回。
- alert for
function fn(a, b) {
return a + b;
}
console.log(1);
console.log(2);
alert(3);
console.log(fn(3, 5));
for (var i = 0; i < 3; i++) {
console.log(i, '我是for循环');
}
console.log(4);
异步:在做某一个操作的时候,其他的操作可以继续执行
- 不会阻塞代码执行,结果不能通过return返回出来,而是必须用回调函数接收结果。
- 定时器 事件函数
// 1、定时器
setTimeout(function () {
var a = 3;
var b = 5;
console.log(a + b);
}, 3000);
// 2、事件
document.onclick = function () {
console.log('我点击了');
}
console.log('我走到这里了');
回调函数:将函数做为参数传给另一个函数调用,这个函数就是回调函数。
回调函数是解决异步操作的有效途径 (更好的 Promise async await)
function fn(a, b, callback) {
setTimeout(function () {
var c = a + b;
callback(c);
}, 3000);
}
fn(3, 5, function (result) {
console.log(result);
})
// ----------------------------------------
// 回调函数(运动框架中的回调函数)
move(box, {}, function () { })
setTimeout(function () { }, 3000);
2、自执行函数
1、函数分类
// 函数声明
function fn() { }
// 函数表达式
var fn = function () { }
// 事件函数
document.onclick = function () { }
// 回调函数
setTimeout(function () { }, 3000);
// ---------------------------------------
// 匿名函数自执行
(function () { })();
2、匿名函数自执行格式
自执行函数:IIFE: Immediately Invoked Function Expression(立即调用函数表达式)
自执行函数一定要注意后面加分号
(函数)();
(函数());
// 匿名函数自执行格式:
(function () { })();
(function () { }());
3、匿句函数自执行及好处
// 自执行函数的写法
(function () { })();
(function () { }());
(function () {
console.log('我是匿名函数,我执行了');
})();
// 有参数
(function (a, b) {
console.log(a + b);
})(3, 5);
// 有返回值
var v = (function (a, b) {
return a + b;
})(4, 6);
console.log(v);
// ---------------------------------------
// 好处:
// 1、避免全局污染,节约内存
// 如果函数只需调用一次,就可以声明成匿名函数。匿名函数可以有效的保证在页面上写入 Javascript,而不会造成全局变量的污染
// 2、完美的嵌入代码
// 一个自执行函数就是一个独立的模块,不会和已有代码相互干扰,在给一个不是很熟悉的页面增加 javaScript 时非常有效。
var a = 1;
var b = 2;
(function () {
var a = 3;
var b = 5;
console.log(a + b);
})();
console.log(a + b);
3、闭包
1、闭包概念
- 1、函数嵌套函数
- 2、内部函数可以读取外部函数的变量、参数或者函数
- 3、这个内部函数在包含它的外部函数外面被调用
- 这个函数就是闭包
闭包最大的特点是: 获取到的变量会常驻在内存中; 即它能记住它的诞生环境,这些变量会一直存在内存中。
闭包的作用:沟通函数内部和外部的桥梁;
闭包优点:缓存数据,延伸变量的作用范围。在外部可以访问函数内部变量。
闭包缺点:如果大量的数据被缓存,可能造成内存泄漏,在使用闭包的时候要慎重。
2、闭包的创建
闭包可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。
闭包会发生内存泄漏,每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。
但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。
function fun() {
var i = 0;
function inner() {
i++;
console.log(i);
}
return inner;
}
alter(fun()());//1
alter(fun()());//1
//fn()()的直接调用都是在开辟一个新的地址。
var newFn = fun();
//fun()这是直接调用,把fun()赋给一个变量,地址就不会再改变了,这个时候就会发现输出只是在递增的。
console.log(newFn());//1
console.log(newFn());//2
console.log(newFn());//3
var f1 = fun();
var f2 = fun();
//fun()这是直接调用,每次直接调用都会开辟一个内存空间,名字不同,地址就不同。f1与f2的内存地址不同。
console.log(f1()); //1
console.log(f2()); //1
//f1与f2内存地址不同,因此打印结果相同。因为闭包会将得到的变量、参数等储存在这一地址中。
console.log(f1()); //1
console.log(f1()); //2
console.log(f1()); //3
//f1内存地址相同,因此打印结果递增。因为闭包获取到的变量会常驻在内存中。
function fn() {
var n = 10;
return function () {
n++;
return n;
}
}
var v = fn();//这里fn()代表被调用,并且只调用了1次。 var n = 10只进行一次。
// var v = function () {
// n++;
// return n;
// }
console.log(v()); // 11 第一次调用自身没有n.向上找。 n=11.
console.log(v()); // 12 虽然是用log形式打印出去,但是也是被调用了一次。内存地址相同,闭包获取到的变量会常驻在内存中。内存 n=11 n++ 后n=12
console.log(v()); // 13
2、闭包模拟对象的私有属性
// num这个属性任何时候都可以获取它,任何时候都可以修改它,哪它就是不私有的
// var obj = {
// num: 3
// };
// console.log(obj.num);
// obj.num = 5;
// console.log(obj);
// --------------------------------------
// 闭包模拟对象的私有属性
function fn(n) {
var num = n;
return {
getNum: function () {
return num;
},
setNum: function (val) {
num = val;
}
}
}
// num就是obj的私有属性,只有通过一定的方法才能拿到
var obj = fn(5);
// console.log(obj);
obj.setNum(10);
console.log(obj.getNum()); // 10
2、循环中的闭包
<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);
// }
// }
// 方式一:利用自定义下标
for (var i = 0; i < li.length; i++) {
li[i].index = i; // 自定义下标
li[i].onclick = function () {
console.log(this.index);
}
}
// 方式二:闭包的形式
for (var i = 0; i < li.length; i++) {
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>
4、递归
- 递归函数就是在函数内部调用函数本身,这个函数就是递归函数。
- 递归函数分两步:递和归。
- 递归调用一定要有退出函数
递归函数的作用和循环效果一样,递归的基本思想是把规模大的问题转化为规模小的相似的子问题来解决。
// 求5的阶乘 5*4*3*2*1 = 120
function fn(n) {
if (n <= 1) {
return 1;
}
return n * fn(n - 1);
}
console.log(fn(5));
// 递
// fn(5);
// 5 * fn(4)
// 5 * 4 * fn(3)
// 5 * 4 * 3 * fn(2)
// 5 * 4 * 3 * 2 * fn(1)
// 5 * 4 * 3 * 2 * 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)); // [2, 3, 4, 4, 5, 6, 6, 7, 8]
// 快速排序:找到数组的第一项,其它项依次和这一项进行比较,如果比它小,放到一个数组中,否则放到另一个数组中,并递归调用
function fn(arr) {
if (arr.length <= 1) {
return arr;
}
var num = arr.shift(); // 取得数组的第一项,数组长度减小
var left = []; // 存放比num小的数
var right = []; // 存放大于等于num的数
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 等等,但有些时候我们并不希望在事件持续触发的过程中那么频繁地去执行函数。防抖和节流就能很好的解决这个这个问题。
1、防抖debounce
防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
1、非立即执行
非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n秒内又触发了事件,则会重新计算函数执行时间。
var n = 0;
function fun() {
n++;
console.log(n);
}
// 非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n秒内又触发了事件,则会重新计算函数执行时间。
function debounce(fun, wait) {
var timer;
return function () {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(fun, wait);
}
}
var v = debounce(fn, 1000); // 调用之后返回一个函数
document.onmousemove = v; // 只有鼠标停下超过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;
timer = setTimeout(function () {
timer = null;
}, wait);
if (tag) {
fun();
};
}
}
var v = debounce(fn, 1000);
document.onmousemove = v;
2、节流throttle
节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。可以通过时间戳、定时器两种方式实现。
1、时间戳方式
事件会立即执行,并且间隔 1 秒执行 1 次
var n = 0;
function fn() {
n++;
console.log(n);
}
// 事件会立即执行,并且间隔 1 秒执行 1 次
function throttle(fun, wait) {
var prev = 0; // 上一次的时间戳
return function () {
var now = Date.now(); // 当前的时间戳
if (now - prev > wait) {
fun();
prev = now;
}
}
}
var v = throttle(fn, 1000);
document.onmousemove = v;
2、定时器方式
事件会1秒后执行,并且间隔 1 秒执行 1 次
var n = 0;
function fn() {
n++;
console.log(n);
}
// 事件会1秒后执行,并且间隔 1 秒执行 1 次
function throttle(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的后续参数是以一个数组传入
共同点:调用函数,修改函数中的this,第一个参数都是新的this指向
function fn(a, b) {
console.log(this);
console.log(a, b);
}
fn(3, 5);
fn.call(document, 5, 6);
fn.apply(document, [7, 8]);
// --------------------------------
// 如果第一个参数是null 和 undefined,则this指向window
fn.call(undefined, 5, 6);
// 找出数组的最大值
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));