js-闭包

74 阅读5分钟

参考:

概念

  1. 作用域

  2. 作用域变量往上层找

  3. 作用域变量无使用会被销毁

  4. 子函数被使用时父级环境将被保留 -- 闭包

  5. 某函数可以使用其他函数的作用域变量 -- 闭包

  6. 构造函数也是很好的环境例子,子函数被外部使用父级环境将被保留 -- 闭包

  7. 本质上:闭包就是函数内部和函数外部链接的一座桥梁

什么是闭包?

  • 当内部函数引用了外部函数的变量时就产生了闭包

  • 通过chrome工具得知: 闭包本质是内部函数中的一个对象, 这个对象中包含引用的变量属性

闭包需要三个条件

  1. 函数嵌套

  2. 访问所在的作用域

  3. 所在作用域外被调用

闭包的作用

①可以私有化变量;

②延长了局部变量的时间;

闭包的问题

内存溢出:

内存被用满了,程序自动崩溃了。

解决办法:让内部函数变成null

 let f = test();
 f = null; // 释放内存

内存泄露:

占用的内存太多了,没有及时释放,积累到一定程度就会内存溢出。

this指代:

箭头函数

或则重新指代

立即执行函数IIFE

立即执行函数和闭包没啥关系,不过经常和闭包放在一起使用罢了。

两种写法

必须用( )括起来,因为直接写function会报错,

通常情况下前面加个!,前面有代码没有分号就不会报错了

!(function(){})()  // 常用
!(function(){}())

可以与闭包配合起来使用

一般一些js框架都是这样玩的

let add = (function(){
  let count = 0
  return function() {
    console.log(count++)
})()
  
console.log(add)
console.log(add())
console.log(add())
console.log(add())
console.log(add())

for循环作用域和闭包

早期var带来的问题

// for循环条件中的var,定义的是全局的
for(var i=1;i<=5;i++) {
  setTimeout(() => {
    console.log(i)
  }, i*1000)  
}


// 利用闭包可以解决这个问题
for(var i=1;i<=5;i++) {
  (function(j){
    setTimeout(() => {
       console.log(j)
    }, j* 1000)
  })(i)
}

闭包的生命周期

产生:在嵌套的内部函数定义执行完时就产生了,因为有函数提升的。(不是在外部函数调用时,也不是在外部函数定义时);

死亡:在嵌套的内部函数成为垃圾对象时;

function f1() {
  //此时就已经产生闭包(因为函数提升)
  var b = 2
  function fn() {
    b++
    console.log(b)
  }
  return fn
}
var f = f1()
f()
f = null //闭包消失,因为内部函数成为了垃圾对象(没有变量在引用它)

闭包的使用

封装高阶函数

使用闭包返回数组区间元素

let arr = [3, 2, 4, 1, 5, 6];
function between(a, b) {
  return function(v) {
    return v >= a && v <= b;
  };
}
console.log(arr.filter(between(3, 5)));

下例使用闭包按指定字段排序

let lessons = [  {    title: "媒体查询响应式布局",    click: 89,    price: 12  },  {    title: "FLEX 弹性盒模型",    click: 45,    price: 120  },  {    title: "GRID 栅格系统",    click: 19,    price: 67  },  {    title: "盒子模型详解",    click: 29,    price: 300  }];
function order(field) {
  return (a, b) => (a[field] > b[field] ? 1 : -1);
}
console.table(lessons.sort(order("price")));

计数器

  • 不希望变量暴露在外部,即希望只能由内部函数修改
  • 希望多次调用后,能够每次都更新并保存变量的结果

作用:读取函数内部变量,这些变量始终在内存中

function a(){
	var start =0
  function b(){
		return start++  
  }
  retrun b
}

var inc = a()
log(inv())  // 0
log(inv())  // 1
log(inv())  // 2

inc = null // 释放内存

封装对象的私有属性和方法

function Person(name){
	// 私有的属性
  var age
  // 私有的方法
  function setAge(n){
  	age = n
  }
  function getAge(){
  	return age
  }
  return {
  	name:name,
    setAge:setAge,
    getAge:getAge
  }
}
var p1 = new Person("hello")
p1.setAge(18)
console.log(p1.getAge())
p1 = null

定义js模块

往往配合立即执行函数

  • 具有特定功能的js文件
  • 将给所有的数据和功能都封装在一个函数内部(私有的)
  • 只向外暴露一个包含n个方法的对象或函数
  • 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能

Demo

Demo1

如果子函数被使用时父级环境将被保留

function hd() {
  let n = 1;
  return function() {
    let b = 1;
    return function() {
      console.log(++n);
      console.log(++b);
    };
  };
}
let a = hd()();
a(); //2,2
a(); //3,3

Demo2

构造函数也是很好的环境例子,子函数被外部使用父级环境将被保留。这能算是闭包吗?算

function User() {
  let num = 1;
  this.show = function() {
    console.log(num++);
  };
}
let a = new User();
a.show(); //1
a.show(); //2
let b = new User();
b.show(); //1

Demo3

使用外层函数的参数,也

闭包的10种表现形式

1. 返回值 最常见的一种形式

var fn = function(){
	var name = 'mjj'
  return function(){
   return name; 
  }
}

var fnc = fn();
console.log(fnc())

2. 函数赋值 一种变形的形式是将内部函数赋值给一个外部的变量

var fnc;
var fn = function(){
	var name = "mjj";
  var a = function(){
  	return name;
  }
  fnc =a
}

fn();
console.log(fnc())

3. 函数参数

function fnc(f){
  console.log(f())
}
function fn(){
	var name =  "mjj"
  var a  = function(){
  	return name
  }
  fnc(a)
}
fn()

4. IIFE

function fnc(f){
  console.log(f())
}

(function(){
	var name =  "mjj"
  var a  = function(){
  	return name
  }
  fnc(a)
})()

5. 循环赋值

function foo(){
  var arr = []
  for(var i = 0;i<10;i++){
    // 这里没有闭包的效果 结果是10
    // arr[i] = function(){
    //   return i
    // }

    // 闭包做法一
    // (function(i){
    //   arr[i] = function() {
    //     return i
    //   }
    // })(i)

    // 闭包做法二
    // arr[i] = (function(n){
    //   return function(){
    //     return n;
    //   }
    // })(i)
  }
  return arr
}
var bar = foo();
console.log(bar[3]())

getter和setter函数 将要操作的变量保存在函数内部,防止暴露在外部。

var getValue, setVule
(function(){
	var num = 0
  getValue = function(){
  	return num
  }
  setValue = function(v){
  	num = v
  }
})()
console.log(getValue())
setValue(10)
console.log(getValue())

迭代器 类似计数器

var add = function(){
	var num = 0
  return function(){
		return num++  
  }
}
console.log(add())
console.log(add())

// 自己实现一个迭代器
// ["alex","mjj","阿黄"]
function setUp(arr){
	var i = 0
  return function(){
  	return arr[i++]
  }
}
var next = setUp(["alex","mjj","阿黄"])
console.log(next())
console.log(next())

区分首次

var firstLoad = (function(){
	var list = []
  return function(id){
    if(list.indexOf(id) >= 0){
      return false
    }else{
    	list.push(id)
      return true
    }
  }
})()

firstLoad(10) // true
firstLoad(10) // false

缓存机制

// 未加入缓存
function mult(){
// arguments
  var sum =0
  for(var i=0;i<arguments.length;i++){
		sum = sum + arguments[i]
  }
  return sum
}
console.log(mult(1,2,3,4,5,5,4)) //求和
console.log(mult(1,2,3,4,5,5,4)) //求和


// 有缓存机制
{
	key:value
  1,2,3,4,5,5,4:
}

var mult = function(){
	// 缓存对象
  var cache = {};
  var calculate = function(){
  	var sum =0
    for(var i = 0;i<arguments.length;i++){
    	sum = sum +i
    }
    return sum
  }
  return function(){
  	// 对cache对象进行操作
    Array.prototype.join.call(arguments,',')
  }
}()
console.log(mult(1,2,3,4,4,5,5,6,9))
console.log(mult(1,2,3,4,4,5,5,6,9))

img对象图片上限

// new Image() 进行数据上报
// 低版本流浪器在进行数据上报会丢失左右的数据
// 因为http是异步的,还没有完成上报就销毁了作用域了
var report = function (src){
	var img = new Image();
  img.src = src;
}
report("url")
       
// 使用闭包来做图片上传
 var report = function(src){
	var imgs = []
  return function(src){
  	var img = new Image();
    imgs.push(img)
    img.src = src
  }
}()
report("url")