什么是闭包?闭包的作用?闭包的应用场景? 惰性函数 高阶函数

闭包的概念

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

什么是闭包?

  • 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包最常见的方式就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数局部变量
  • 密闭的容器,类似set/map容器,用来存储数据
  • 闭包是一个对象,存放数据的格式:key:value

闭包形成的条件*

  • 1.函数嵌套

  • 2.内部函数引用外部函数

    function fun(){
        var count = 1;
        function fun2(){//条件1:函数嵌套
        //形成条件2:内部函数引用外部函数
        console.log(count);
        }
    }
    fun();
    

为什么使用闭包?

利用闭包可以突破链作用域,将局部变量传到内部

垃圾回收机制

1.标记清除

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

2.引用计数

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

作用域

全局作用域

不在函数内部作用域和块级内部作用域外的作用域

函数作用域

在函数内部的作用域

块级作用域

在花括号{}内部的作用域 ,

注意

①对象的{}不属于块级作用域,像for(){},if(){},else{},try{},cath(){}等等的花括号才是块级作用域

②对象的{}的作用域是什么作用域取决于对象所处的作用域,比如对象在全局作域 下定义的,那么对象的{}的作用域就是全局作用域

作用域链

内部作用域访问外部作用域的变量,采取的是链式查找的方式来决定取哪个值,采取就近原则的方式向上一级一级的作用域来查找变量值,最顶级是全局作用域,如果到全局作用域也没找值,那么就会报错。

只要是代码,就至少有一个作用域, 如果作用域还有作用域,那么在这个作用域中就又可以诞生一个作用域;

// 作用域链: 内部作用域访问外部作用域的变量,采取的是链式查找的方式来决定取哪个值,这种结构我们称为作用域链
let num = 10;
function fn() { //外部函数
     let num = 20;
     return function fun() { //内部函数
         console.log(num);
     }
}
const funn = fn()
funn() // 20 ,一级一级访问

闭包的作用

  • 拥有全局变量的不被释放的特点
  • 拥有局部变量的无法被外部访问的特点

特性:

函数内再嵌套函数 内部函数可以引用外层的参数和变量 参数和变量不会被垃圾回收机制回收

优点

  • 延长外部函数局部变量的生命周期 ,可以让一个变量长期在内存中不被释放
  • 避免全局变量的污染,和全局变量不同,闭包中的变量无法被外部使用
  • 私有成员的存在,无法被外部调用,只能直接内部调用

坏处

  • 就是消耗内存、不正当使用会造成内存溢出的问题

闭包可以完成的功能

立即执行函数

const a = 666;

(function getA(){
    console.log(a);
})()

模拟块级作用域

for(var i = 0; i < 5; i++) {
    (function (j) {
        setTimeout(() => {
            console.log(j);
        },1000)
    })(i)
}

封装私有变量

function Person(name) {
    this.getName = function(){
        return name
    }
}

防抖

持续触发不执行,不触发后的一段时间执行

**原理 **既然你不想打印之前已经输入的结果,就清除以前触发的定时器,我们应该存储这个timer的变量一直要在内存当中(内存的泄露)【闭包】

//防抖 避免函数的重复调用 只会调用一次
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() //调用一次
  }

节流

持续触发也执行,一段时间内只执行一次

**原理 **当触发事件的时候,我们设置一个定时器,再次触发事件的时候,如果定时器存在,就不执行,直到delay时间后,定时器执行执行函数,并且清空定时器,这样就可以设置下个定时器。

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

函数柯里化

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

let add = (a, b) => a + b
console.log(add(2, 3));
// 函数柯里化
let currying = (a) => (b) => a + b
console.log(currying(2)(3));

数组的哪些方法用到了闭包?

forEach

const arr = [1,2,3];

arr.forEach((item,index) => {
    setTimeout(() => {
        console.log(item);
    },1000)
})

js中常用的高阶函数

条件

  1. 接受一个或多个函数作为输入
  2. 输出一个函数

**高阶函数:**指接受函数作为参数或返回函数的函数。这种函数允许你在 JavaScript 中实现一些高级技巧,比如把函数当作数据处理的基本单元来使用。

  • map:对数组中的每个元素进行操作,并返回一个新的数组。

  • filter:过滤数组中的元素,并返回一个新的数组。

  • reduce:对数组中的所有元素进行迭代,将其归约为单个值。

  • sort:对数组中的元素进行排序。

  • forEach:对数组中的每个元素执行一个操作。

  • some:检查数组中是否有至少一个元素符合某个条件。

  • every:检查数组中的所有元素是否都符合某个条件。

filter

作用:创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
语法:array.filter(function(currentValue,index,arr), thisValue)
返回值:符合条件的元素组成的新数组

let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let newNums = nums.filter((item, index, arr) => {
  return item % 2 === 0;
});
console.log(newNums); // [ 2, 4, 6, 8, 10 ]
console.log(nums); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

map

作用:返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
语法:array.map(function(currentValue,index,arr), thisValue)
返回值:一个新数组

let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let newNums = nums.map((item, index, arr) => {
  return item * 2;
});
console.log(newNums);
// [2,  4,  6,  8, 10, 12, 14, 16, 18, 20]

reduce

作用:统计,累加
语法:array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
   total:必需。初始值,或者结束后的返回值
   currentValue:必需。当前元素
   currentIndex:可选,当前元素索引
   arr:可选,当前元素所属的数组对象
   initialValue:可选,传递给函数的初始值
返回值:累计结果

let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let newNums = nums.reduce((total, item, index, arr) => {
  return total + item;
});
console.log(newNums); //55

find

作用: 方法返回通过测试(函数内判断)的数组的第一个元素的值。
语法:array.find(function(currentValue, index, arr),thisValue)
返回值:返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined。

let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let newNums = nums.find((item, index, arr) => {
  return item === 3;
});
console.log(newNums); // 3

findIndex

和find类似,返回值为第一个符合条件元素的索引值

let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let newNums = nums.findIndex((item, index, arr) => {
  return item === 3;
});
console.log(newNums); // 2

惰性函数

惰性函数(Lazy Function): 惰性函数表示函数执行的分支只会在函数第一次调用的时候执行,在第一次调用过程中,该函数会被覆盖为另一个按照合适方式执行的函数,这样任何对原函数的调用就不用再经过执行的分支了。

惰性函数的本质就是函数重写,所谓惰性载入,指函数执行的分支只会发生一次。那什么时函数重写呢?由于一个函数可以返回另一个函数,因此可以用新的函数在覆盖旧的函数。

function a() {
    console.log('a');
     a = function () {
                console.log('b');
      }
}
a(); //q
a(); //b

第一次调用这个函数时console.log(‘a’)会被执行,打印出a,全局变量a被重定义并被赋予了新的函数,当再一次调用时,console.log(‘b’)被执行。

用处:因为各浏览器之间的行为差异,经常会在函数中包含了大量的if语句,以检查浏览器特性,解决不同浏览器的兼容问题。

惰性函数有两种实现方式

  • 在函数被调用时,再处理函数。函数在第一次调用时,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了
  • 声明函数时就指定适当的函数。

Object与Function 的鸡生蛋与蛋生鸡问题

参考:https://muyiy.cn/blog/5/5.3.html#%E5%BC%95%E8%A8%80

Object()String()等构造器并不是真的由原始构造函数Function()创建,本质上来说,它们都由JavaScript底层实现, 是因为这些构造器的__proto__均指向了构造器Function.prototype

Object.prototype

Object.prototype 表示 Object 的原型对象,其 [[Prototype]] 属性是 null,访问器属性 __proto__ 暴露了一个对象的内部 [[Prototype]] 。 Object.prototype 并不是通过 Object 函数创建的,为什么呢?看如下代码

function Foo() {
  this.value = 'foo';
}
let f = new Foo();
f.__proto__ === Foo.prototype;
// true

实例对象的 __proto__ 指向构造函数的 prototype,即 f.__proto__ 指向 Foo.prototype,但是 Object.prototype.__proto__ 是 null,所以 Object.prototype 并不是通过 Object 函数创建的,那它如何生成的?其实 Object.prototype 是浏览器底层根据 ECMAScript 规范创造的一个对象。

Object.prototype 就是原型链的顶端(不考虑 null 的情况下),所有对象继承了它的 toString 等方法和属性。

Function.prototype

Function.prototype 对象是一个函数(对象),其 [[Prototype]] 内部属性值指向内建对象 Object.prototype。Function.prototype 对象自身没有 valueOf 属性,其从 Object.prototype 对象继承了valueOf 属性。

Function.prototype 的 [[Class]] 属性是 Function,所以这是一个函数,但又不大一样。为什么这么说呢?因为我们知道只有函数才有 prototype 属性,但并不是所有函数都有这个属性,因为 Function.prototype 这个函数就没有。

为什么没有呢,我的理解是 Function.prototype 是引擎创建出来的函数,引擎认为不需要给这个函数对象添加 prototype 属性,不然 Function.prototype.prototype… 将无休无止并且没有存在的意义。

function Object

Object 作为构造函数时,其 [[Prototype]] 内部属性值指向 Function.prototype,即

bject.__proto__ === Function.prototype // true

function Function

Function 构造函数是一个函数对象,其 [[Class]] 属性是 Function。Function 的 [[Prototype]] 属性指向了 Function.prototype,即

Function.__proto__ === Function.prototype // true

Function & Object 鸡蛋问题

instanceof检查的是右操作数的prototype`属性是否在左操作数的原型链上

Object instanceof Function 		// true
Function instanceof Object 		// true

Object instanceof Object 		// true
Function instanceof Function 	// true

Object 构造函数继承了 Function.prototype,同时 Function 构造函数继承了Object.prototype。这里就产生了 鸡和蛋 的问题。

为什么会出现这种问题?

因为 Function.prototypeFunction.__proto__ 都指向 Function.prototype

3个灵魂拷问

Object和Function谁是谁的实例

ObjectFunction的实例

Function.prototype是一个函数?

是的

Function.prototype()可以执行,不会报错,说明Function.prototype真的是一个函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值