从「闭包」到 思考人生

作者: passMaker | 来源:发表于2018-08-07 12:22 被阅读108次

闭包的定义

A closure is the combination of a function and the lexical environment within which that function was declared. -- MDN
闭包是函数和声明该函数的词法作用域的集合。

A closure is the local variables for a function - kept alive after the function has returned . --javascriptkit
闭包是一个函数的局部变量 ——— 在函数返回后仍然有效

不过关于闭包的定义,我更喜欢知乎上的一个答案,简洁明了,便于理解。

一个持有外部环境变量的函数就是闭包。-- 来自知乎Saviio

参考:什么是闭包

闭包的作用

闭包常常用来「间接访问一个变量」。换句话说,「隐藏一个变量」。

词法作用域 (lexical environment)

之前写过一个关于 JavaScript 作用域链 的博客

  • 函数在执行的过程中,先从自己内部找变量
  • 如果找不到,再从创建当前函数所在的作用域(词法作用域)去找, 以此往上
  • 注意找的是变量的当前的状态

函数连同它作用域链上的要找的这个变量,共同构成闭包

一般情况下使用闭包主要是为了

  • 封装数据
  • 暂存数据

一个典型的闭包案例

function car(){
  var speed = 0
  function fn(){
    speed++
    console.log(speed)
  }
  return fn
}

var speedUp = car()
speedUp()   //1
speedUp()   //2

闭包相关案例

当然,你可以不看这些操蛋该死的案例,直接拖到最后,我们聊聊人生哲理

案例1

如下代码输出是怎样的?
如果想输出3,应该如何修改代码?

var fnArr = [];
for (var i = 0; i < 10; i ++) {
  fnArr[i] =  function(){
    return i
  };
}
console.log( fnArr[3]() ) // 10
//改造 1
var fnArr = []
for (var i = 0; i < 10 ;i++){
  fnArr[i] = (function(j){
    return function(){
      return j
    }
  })(i)
}
console.log(fnArr[3]())


//改造 2 使用立即函数声明表达式
var fnArr = []
for (var i = 0; i < 10 ;i++){
  (function(i){
    fnArr[i] = function(){
      return i
    }
  })(i)
}
console.log(fnArr[3]())


//改造 3  使用ES6 let代替var
var fnArr = []
for (let i = 0; i < 10 ;i++ ){
  fnArr[i] = function(){
    return i
  }
}
console.log(fnArr[3]())

案例2

封装一个 Car 对象

var Car = (function(){
  var speed = 0
  function set(s){
    speed = s
  }
  function get(){
    return speed
  }
  function speedUp(){
    speed++
  }
  function speedDown(){
    speed--
  }
  function speedAdd(a){
    speed = speed+a
  }
  function speedSub(a){
    speed = speed-a
  }
  return {
    set: set,
    get: get,
    speedUp: speedUp,
    speedDown: speedDown,
    speedAdd: speedAdd,
    speedSub: speedSub
  }
})()


//调用
Car.set(30)
Car.get()   //30
Car.speedUp()
Car.get()   //31
Car.speedDown()
Car.get()   //30
Car.speedAdd(10)
Car.get()  //40
Car.speedSub(20)
Car.get()  //20

案例3

如下代码输出是怎样的?
怎样修改代码让其连续输出 0,1,2,3,4

for(var i = 0; i < 5 ;i++){
  setTimeout(function(){
    console.log('delayer: ' + i )
  },0)
}
//输出5个5
//方式1
for(var i = 0; i < 5 ;i++){
    setTimeout((function(j){
      return function(){
      console.log('delayer: ' + j )
    }
  }(i)),0)
}


//方式2 使用立即函数声明表达式
for(var i = 0; i < 5 ;i++){
  (function(j){
    setTimeout(function(){
      console.log('delayer: ' + j )
    },0)
  })(i)
}


//方式3 使用ES6 的let
for(let i = 0; i < 5 ;i++){
  setTimeout(function(){
    console.log('delayer: ' + i )
  },0)
}

该案例即是一个倒数计时器的模型

//倒数计时器
for(var i = 0; i < 5 ;i++){
  (function(j){
    setTimeout(function(){
      console.log('delayer: ' + j )
    },5000 - 1000*j)

案例4

独立作用域 与 活动对象

//如下代码输出为多少
function makeCounter(){
  var count = 0

  return function(){
    return count++
  }
}

var counter = makeCounter()  //生成作用域 有一个活动对象count
var counter2 = makeCounter()  //生成作用域 也有一个活动对象count 和之前的是两个不同的作用域 相互独立

console.log(counter())  //0
console.log(counter())  //1

console.log(counter2())
console.log(counter2())
//所以counter2输出也是0 1 和之前的counter无关

案例5

实现数组按姓名、年纪、任意字段排序
之前在 浅谈数组方法 博客中谈及sort方法的时候也提出过这个案例,即对数组进行排序
这里我们将这个方法进行升级,利用闭包的特性,使我们可以自行输入类型,通过users.sort(byField('field'))来进行排序

var users = [
  { name: 'John', age : 20, company: 'Baidu' },
  { name: 'Pete', age : 18, company: 'Alibaba' },
  { name: 'Ann', age : 19, company: 'Tecent' }
]

function byName(user1 ,user2){
  return user1['name'] > user2['name']
  // return user1.name > user2.name
}

function byAge(user1 ,user2){
  return user1['age'] > user2['age']
  // return user1.age > user2.age
}

function byField(field){
  return function(user1 ,user2){
    return user1[field] > user2[field]
  }
}

users.sort(byName)   //按照名字排序
users.sort(byAge)    //按照年龄排序
users.sort(byField('company'))  //定义按照任意输入的类型进行排序的方法

案例6

定义一个sum函数,实现如下调用

console.log( sum(1)(2) ) // 3
console.log( sum(5)(-1) ) // 4
function sum(a) {
  return function(b) {
    return a + b
  }
}

闭包案例 Github

https://github.com/evenyao/JS-Closure

写在最后 关于最震撼的答案

或许我们不需要真的懂闭包?

假设下面三行代码在一个立即执行函数中
注意:下面三行代码在一个立即执行函数中
注意:下面三行代码在一个立即执行函数中
注意:下面三行代码在一个立即执行函数中

三行代码中,有一个局部变量 local,有一个函数 foo,foo 里面可以访问到 local 变量。
这就是一个闭包:

一个最简单的闭包

第一句是变量声明,第二句是函数声明,第三句是 console.log。
每一句我都学过,为什么合起来我就看不出来是闭包?
答案是,你根本不需要知道闭包这个概念,一样可以使用闭包!

闭包是 JS 函数作用域的副产品。

换句话说,正是由于 JS 的函数内部可以使用函数外部的变量,所以这段代码正好符合了闭包的定义。而不是 JS 故意要使用闭包。

很多编程语言也支持闭包,另外有一些语言则不支持闭包。
只要你懂了 JS 的作用域,你自然而然就懂了闭包,即使你不知道那就是闭包!

关于JavaScript 作用域链

那些所谓的闭包的作用

如果我们在写代码时,根本就不知道闭包,只是按照自己的意图写,最后,发现满足了闭包的定义。
那么请问,这算是闭包的作用吗?

真的写在最后

摘自 方应杭 -- JS 中的闭包是什么?

编程界崇尚以简洁优雅为美,很多时候
如果你觉得一个概念很复杂,那么很可能是你理解错了。

相关文章

  • 从「闭包」到 思考人生

    闭包的定义 A closure is the combination of a function and the ...

  • 2016/12/27

    技术 python中的闭包 从 Web 前端到客户端

  • SWIFT 闭包传值方式

    闭包传值 闭包主要用于回调,这里我们还是模拟从详情界面传值到主界面,首先我们需要在详情界面为闭包取个别名,声明一个...

  • JS闭包从繁到简

    闭包的定义 当一个函数即便在离开了它的词法作用域(Lexical Scope)的情况下,仍然存储并可以存取它的词法...

  • Swift中的闭包

    在Swift中有两种闭包,逃逸闭包(@escaping)和非逃逸闭包(@nonescaping)。从Swift 3...

  • JavaScript闭包

    什么是闭包? 闭包(closure)我们从汉字的字面意思可以理解为:闭:封闭的 闭合 关闭包:包裹 ...

  • swift 非逃逸闭包、逃逸闭包的使用场景

    闭包只有在函数中做参数时才会区分逃逸闭包和非逃逸闭包。Swift 3.0之后,传递闭包到函数中的时候,系统会默认为...

  • Python简明教程第14节:闭包和装饰器

    闭包 首先还是得从基本概念说起,什么是闭包? 维基百科:在计算机科学中,闭包(Closure)是词法闭包(Lexi...

  • 「Python」闭包

    闭包的条件 闭包,从字面意思上可能不太好理解是什么意思,但是从闭包的条件入手会相对比较好理解。闭包需要满足三个条件...

  • swift-闭包

    闭包 闭包定义 闭包简化 - 尾随闭包 闭包参数 闭包返回值 闭包的循环引用

网友评论

    本文标题:从「闭包」到 思考人生

    本文链接:https://www.haomeiwen.com/subject/jxbovftx.html