从「闭包」到 思考人生

作者: 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 中的闭包是什么?

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

    相关文章

      网友评论

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

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