有学有练才叫学习:学而不思则罔,思而不学则殆:学而不习,纸上谈兵,习而不进,画地为牢!

前端之路(高级JS)闭包递归防抖节流

javascript cat 10个月前 (12-02) 81次浏览 已收录 0个评论 扫描二维码

1、回调函数

同步:在做某一个操作的时候,其他的操作只能等待

一行一行代码执行,然后会阻塞代码,在函数中的结果我们可以用return返回( 函数调用后可以用一个变量接收 )

alert for

alert(1);
console.log(2);

function fn() {
    return '他是老王';
}

alert(3);
console.log(fn());

for (var i = 0; i < 3; i++) {
    console.log(i);
}

alert(4);
console.log(i);

异步:在做某一个操作的时候,其他的操作可以继续执行

不会阻塞代码执行,结果不能通过return返回出来,而是必须用回调函数接收结果。

定时器 事件函数

// 定时器异步
console.log(2);
setTimeout(function () {
    console.log(1);
}, 3000);
console.log(3);

// 事件函数异步
document.onclick = function () {
    console.log('点我了');
}
console.log('我执行到这里了');

回调函数:将函数做为参数传给另一个函数调用,这个函数就是回调函数。

回调函数是解决异步操作的有效途径 (更好的 Promise async await 是解决异步的终极方法)

// 函数调用,3s以后拿到返回的值

// 不行
function fn(a, b) {
    setTimeout(function () {
        return a + b;
    }, 3000);
}
var v = fn(3, 5);
console.log(v);

// -----------------------
// 回调函数
function fn(a, b, callback) {
    setTimeout(function () {
        var c = a + b;
        callback(c);
    }, 3000);
}

fn(3, 5, function (val) {
    // console.log('我三秒以后执行了');
    console.log(val);
});
// -----------------------
// 回调函数(运动框架中的回调函数)
move(box, {}, function () { })

setTimeout(function () { }, 3000);

2、自执行函数

1、函数分类

// 普通函数
function fn() { }

// 函数表达式
var fn = function () { }

// 事件处理函数
document.onclick = function () { }

// 回调函数
move(box, 'left', 500, function () { });

// 匿名函数自执行
// 匿名函数自执行后面一定要加分号,否则语法错误
(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、这个内部函数在包含它的外部函数外面被调用

这个函数就是闭包

闭包最大的特点是:它能记住它的诞生环境,这些变量会一直存在内存中

闭包的作用:沟通函数内部和外部的桥梁

闭包优点:缓存数据,延伸变量的作用范围。

闭包缺点:如果大量的数据被缓存,可能造成内存泄漏,在使用闭包的时候要慎重。

function fn() {
    var n = 10;
    return function () {
        n++;
        return n;
    }
}

var v = fn();
console.log(v()); // 11
console.log(v()); // 12
console.log(v()); // 13

2、闭包模拟对象的私有属性

var obj = {
    num: 5
}
// 我们可以直接通过obj来获取或修改这个num,因此num这属性就不私有
console.log(obj.num); // 获取 5
obj.num = 8; // 修改
console.log(obj);

// ------------------------
function fn(n) {
    var num = n;

    return {
        getNum: function () {
            return num;
        },
        setNum: function (val) {
            num = val;
        }
    }
}

var o = fn(5);
console.log(o); // {getNum, setNum}  
// o这个里面没有num这个变量,但是我们却可以使用它,所以称它为私有的

console.log(o.getNum()); // 取
o.setNum(8); // 设置
console.log(o.getNum());

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
// 快速排序
// 先取出数组的第一项,然后用其它项依次和这个第一项进行比较,如果比它小,则放左左边的一个数组中,否则,放到右边的一个数组中
// 然后依次递归调用

function fn(arr) {
    // 如果数组长度小于等于1,则返回数组,不用再比
    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);
        }
    }

    // console.log(left, num, right);
    // console.log(left.concat(num, right));
    return fn(left).concat(num, fn(right));
}

var arr = [4, 6, 2, 6, 5, 8, 4, 7, 3];
console.log(fn(arr));

5、防抖与节流

在前端开发的过程中,我们经常会需要绑定一些持续触发的事件,如 resize、scroll、mousemove 等等,但有些时候我们并不希望在事件持续触发的过程中那么频繁地去执行函数。防抖和节流就能很好的解决这个这个问题。

1、防抖debounce

防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

1、非立即执行

非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n秒内又触发了事件,则会重新计算函数执行时间。

var h1 = document.querySelector('h1');
var n = 0;
function fn() {
    n++;
    h1.innerText = n;
}

// 非立即执行
// 非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n秒内又触发了事件,则会重新计算函数执行时间。
function debounce(fun, wait) { // fun函数   wait时间
    var timer;
    return function () {
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(fun, wait);
    }
}
var v = debounce(fn, 1000);
document.onmousemove = v;
2、立即执行

立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。

var h1 = document.querySelector('h1');
var n = 0;
function fn() {
    n++;
    h1.innerText = 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 h1 = document.querySelector('h1');
var n = 0;
function fn() {
    n++;
    h1.innerText = 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 h1 = document.querySelector('h1');
var n = 0;
function fn() {
    n++;
    h1.innerText = 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(a, b);
    console.log(this);
}

var obj = {
    name: 'zs',
    age: 3
}

fn(3, 5); // window
fn.call(obj, 7, 8);
fn.apply(obj, [10, 20]);

fn.call('小王', 7, 8);
// 如果第一个参数为null和undefined,则this指向window
// 如果第一个参数为其它的字符串、数字、布尔,则会将这个基本值包装成对象(这没有意义)
var arr = [3, 2, 5, 32, 5, 1];

// 求数组的最大值,不可以先排序,再找

console.log(Math.max(3, 2, 5, 32, 5, 1));

// 利用apply的第二个参数是一个数组的特性
console.log(Math.max.apply(Math, arr));
console.log(Math.min.apply(Math, arr));

// es6
console.log(Math.max(...arr));
喜欢 (0)
cat
关于作者:
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址