一.闭包的概念

闭包是这样的一种机制:函数嵌套函数,内部函数可以引用外部函数的参数和变量。参数和变量不会被垃圾回收机制收回。

1.函数嵌套函数

function fn(a){
            return function(){
                //访问道这个a
                console.log(a);
            }
        }
        console.log(fn('hello'));//调用外部的函数
        fn('hello')()//调用内部函数

2.作用域

function fn1(){
            let i = 0  //i不会被垃圾回收机制回收
            return function(){
                i++
                return i
            }
        }
        console.log(fn1()()); //1
        console.log(fn1()()); //1
        let a = fn1()
        console.log(a());//1
        console.log(a());//2
        console.log(a());//3

3.垃圾回收机制有两种:1.标记清除, 2.引用计数

1.标记清除:js会对变量做一个标记yes or no的标签以供js引擎来处理,当变量在某个环境下被使用则标记为yes,当超出改环境(可以理解为超出作用域)则标记为no,然后对有no的标签进行释放。

2.引用计数:对于js中引用类型的变量, 采用引用计数的内存回收机制,当一个引用类型的变量赋值给另一个变量时, 引用计数会+1, 而当其中有一个变量不再等于值时,引用计数会-1, 如果引用计数为0, 则js引擎会将其释放掉。

二.闭包的作用

相比全局变量和局部变量,闭包有两大特点:

1.闭包拥有全局变量的不被释放的特点

2.闭包拥有局部变量的无法被外部访问的特点

闭包的好处:

1.可以让一个变量长期在内存中不被释放

2.避免全局变量的污染,和全局变量不同,闭包中的变量无法被外部使用

3.私有成员的存在,无法被外部调用,只能直接内部调用

闭包可以完成的功能:1.防抖、2.节流、3.函数柯里化

1.防抖

//防抖 避免函数的重复调用 只会调用一次
        function Antishake(fn,wait){ //第一个参数是函数 第二个参数是毫秒值
            let timer = null //声明一个变量来接收延时器 初始值为null
            return function(){
                clearTimeout(timer)
                timer = setTimeout(() => {
                    fn() //调用这函数
                }, wait);
            }
        }
        let an = Antishake(function(){ //用一个变量接收
            console.log('555');
        },2000)
        document.querySelector('div').onmouseenter = ()=>{
            an() //调用一次
        }

2.节流

function throttle(fn,wait){
            let timer = null //节点闸
            return function(){
                if(timer) return //null false 不是null结果减少true 如果上传没有我就直接跳过 没有人我就上去
                timer = setTimeout(() => { //上车了
                    fn()
                    timer = null //做完之后重新关闭节点闸
                }, wait);
            }
        }
        let throttle1 = throttle(()=>{
            console.log('我上车了');
        },2000)
        document.querySelector('div').onclick = ()=>{
            throttle1()
        }

节流和防抖的区别

防抖避免重复执行 只执行一次

节流 减少执行次数 执行多次

3.函数柯里化

函数柯里化 其实就是函数颗粒化 将一个函数变成一个个颗粒可以组装,就是这个里面的多个参数 将他变成一个个的函数来传递这个参数。

简单柯里化函数:

 function fnSum(a,b,c){//求和函数
            return a+b+c
        }
        //简单柯里化 他就是使用了一个函数来改造原本的函数
        function curry(fn){
            return function(a){
                return function(b){
                    return function(c){
                        return fn(a,b,c)
                    }
                }
            }
        }
        //调用 避免了多余的无用参数传递
        let fnCurry = curry(fnSum) //函数
        console.log(fnCurry(1)(2)(3));//6

改进的柯里化函数:

function fnSum(a,b,c){//求和函数
            return a+b+c
        } 
function curry1(fn){
            //接收一个后面的参数 除了fn的参数
            let args = Array.prototype.slice.call(arguments,1)//从下标1开始全部剪切  把Array里面的slice方法加给外部函数的arguments
            return function(){
                let newArg = args.concat(Array.from(arguments)) //将内部函数的参数和外部的参数合并
                return fn.apply(this,newArg) //将内部函数自动指向 传入所有的参数
            }
        }
        let fn2 = curry1(fnSum,1,2) //函数
        console.log(fn2(3)); //6

最终改进的柯里化函数:可以任意组合传参 如果不满足就返回偏函数 如果满足就返回结果

function fnSum(a,b,c){//求和函数
            return a+b+c
        } 
function curry2(fn){
                //接收一个后面的参数 除了fn的参数
                let args = Array.prototype.slice.call(arguments,1)
                return function(){
                    let newArg = args.concat(Array.from(arguments)) //将内部函数的参数和外部的参数合并
                    if(newArg.length < fn.length){ //参数没有到三个 fn.length获取传递的函数的参数个数
                        return curry2.call(this,fn,...newArg) //又套了一个function  这个this指向这个function 如果没有到达会一直套这个方法
                    }else{
                        return fn.apply(this,newArg) //将内部函数自动指向 传入所有的参数 
                    } 
                }
            }
        let fn3 = curry2(fnSum) //函数
        console.log(fn3(1)()(2)()(3)); //6
        console.log(fn3()(1)()(2)()()); //偏函数  function

三.this指向以及this的绑定

1.this指向问题

在构造函数里面this指向对象

function Box(name) {	
      this.name = name;
      this.show = function(){ 
            console.log(this.name);
      }
}
var box = new Box('zhangsan');  

在普通函数里面谁调用就指向谁

function func() {	
        console.log(this);
}
func();  //window对象

在定时器里面this指向window

setInterval(function(){
                console.log(this); //window对象
        }, 2000);	

在事件函数中this指向触发这个事件的标签

box.onclick = function() {
        console.log(this);//this指向box
}     

2.this绑定的方法

bind() 里面传递的是对应指向的对象

call() 里面传递的是指向的对象和参数(会自动调用)

apply() 里面传递时指向的对象及参数数组(会自动调用)

function fn(name){ //函数
            this.name = name
            console.log(this);
        }
        fn('李四') //this指向window
        function Student(){
            this.name = 'hello'
            this.age = 18
        }
        let student = new Student()
        //bind 里面传递的是对应指向的对象
        fn.bind(student)('李四')
        //call 里面传递的是指向的对象和参数
        fn.call(student,'王五') //name参数王五 自动调用方法
        //apply 里面传递时指向的对象及参数数组
        fn.apply(student,['赵六']) //传递一个参数数组 自动调用方法

相同点:call、apply和bind都是JS函数的公有的内部方法,他们都是重置函数的this,改变函数的执行环节。

不同点:bind是创建一个新的函数,而call和aplay是用来调用函数;call和apply作用一样,只不过call为函数提供的参数是一个个的,而apply为函数提供的参数是一个数组。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐