js怪癖-变量、闭包相关

变量的作用范围

  1. 在大多数编程语言中,变量的声明周期是“定义此变量的块(block)”。但是在JavaScript,在ES6前,变量的作用域和函数息息相关,而不是大括号
  2. 如何创建块作用域: IIFE

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    function func (x) {
    console.log(tmp) // ReferenceErro: tmp is not defined
    if (x < 0) {
    (function () {
    var tmp
    // 做原本想做的事情
    })()
    }
    }
    ```
    即使用一个IIFE,将tmp的作用域限制在包含它的`if`语句块中
    3. ES6中,let、const解决了这个问题
    ## 闭包
    1. 当函数离开了创建它的位置后,仍然可以获取到该位置上存在的所有变量。
    2. 先来一个闭包
    ```javascript
    function incrementorFactory (start, step) {
    return function () { // 我们定义这个匿名函数叫 (*)
    start += step
    return start
    }
    }

    调用incrementFactory

    1
    2
    3
    var inc = incrementorFactory(10,5)
    inc() // 22
    inc() // 24
  3. 如何解释?
    在运行阶段,内部函数 () 能获取到外部函数 incrementorFactory 的变量 start 与 step,而执行 incrementorFactory 不仅仅返回了 () ,也连带了变量 start 与 step 。存储这两个变量的数据叫做environment,environment 与 object 非常相似,它将键名映射到键值,返回的函数包含了 environment 的引用,它在父级即外部的 environment 时就已经激活。组合(函数 + environment)就叫做闭包。
    名称来源于当 environment “关闭” 函数时,它为变提供了可声明在函数外的值(自有变量)

    1. 当函数被请求,就会为他的参数和局部变量创建一个新的 environment。所有总会有一连串的environment.
    2. f 的 environment
    3. f 的外部 environment
    4. f 的外部的 environment 外部的 environment
    5. …..
    6. 全局environment
    7. 以上是从 f 的 environment开始,完全搜索 environment 链查看的所有变量值。
  4. 无意识共享

    1. 闭包并不是在特定的时间点获得快照(拷贝),他是获取动态的变量

      1
      2
      3
      4
      5
      var result = []
      for (var i = 0; i < 5; i++) {
      result.push(function () {return i}) // (*)
      }
      console.log(result[3]) // 5,但是我们期待是3
    2. 一个解决的方案是通过一个返回值(Immediately Invoked Function Expression)来赋值 i 的当前值

      1
      2
      3
      4
      5
      for (var i = 0; i < 5; i++) {
      (function (j) {
      result.push(function () { return j})
      })(i)
      }

      使用bind函数

      1
      2
      3
      for (var i = 0; i < 5; i++) {
      result.push(function(j) {return j}.bind(null,i))
      }

      使用forEach?? 这个为什么可以?

      1
      2
      3
      4
      var range = [1, 2, 3, 4, 5]
      range.forEach(function (i) {
      result.push(function () {return i})
      })

      ES6的方法

      1
      2
      3
      4
      5
      var result = []
      for (let i = 0; i < 5; i++) {
      result.push(function () {return i}) // (*)
      }
      console.log(result[3]) // 3,问题解决
  5. 私有变量

    1. JavaScript的闭包是一种颇为紧密的封装。可以说,闭包是JavaScript在ES6的private Symbol之前唯一靠谱的 ‘private’ 访问控制的实现模式
    2. 在JavaScript层面没有任何办法可以通过闭包的函数对象取出闭包捕获的变量–除非该函数自己把它返回出来(或者通过其他方式传递出来)。这样闭包就可以精确的控制自己想要暴露出来的信息量
    3. 例如说

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      var o = (function () {
      var a = 1
      var b = 42
      return {
      foo: function () {
      console.log(a + b)
      return b
      }
      }
      })

      这样o.foo所指向的闭包虽然既捕获了 a 也捕获了 b,但是它只返回 b 出来,外界也就只能通过o.foo() 得到变量 b 的值;而且o.foo() 也只让外界能读取变量 b的值,而不能做任何修改,达到了私有访问控制的目的

  6. 当私有变量是对象的时候

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var o = (function () {
    var person = {
    name: 'Vincent',
    age: 24,
    __proto__: null
    }
    return {
    run: function (k) {
    return person[k]
    }
    }
    })()

    这里返回到外界的闭包暴露出了member access的权限,这就允许了许多可能性。当对象person的__proto__没有被设为null时,我们可以通过向Object.prototype注入一个 reuturn thisaccessor property (访问器属性) 来获取 person 的引用。而指定了 __proto__: null 之后就不能用这招了。

  7. 来个官方点的解释
    引用维基百科:闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
  8. 来点与闭包相关的题外话
    在函数内部不使用var/let/const,等于没有声明变量,而是相当于访问了window的属性
    与在window下声明变量的区别在于,访问window的属性可以delete。在全局作用域下直接声明的变量不可以 delete
    闭包的定义: 闭包是由函数和与其相关的参照环境组合而成的实体,函数和环境,缺一不可。
  9. 遇到问题,多看MDN,规范。 博客只能做参考。
  10. 来点高端的,ES5如何实现类的私有变量
    Objects can be produced by constructors,which are functions which initialize object,Constructor provide the feature that classes provide in other languages,including static variables and methods
  11. 定义

    1. instance variables 实例属性

      1
      2
      3
      function Container (name) = {
      this.name = name
      }
    2. In the prototype 在原型上的属性和方法 也称(public methoed 公共方法)
      The prototype mechanism is used fro inheritance.It also conserves memory

      1
      2
      3
      Container.prototype.stamp = function (string) {
      return this.name + string
      }
    3. 私有方法、私有属性。在构造函数中进行var声明

      1
      2
      3
      4
      5
      function Container (param) {
      this.member = param
      var secret =3
      var that = this
      }

      这个构造函数创造了三个私有实例变量: param,secret,that
      它们被附加到对象中,但是他们不能被外部访问,也不能被对象自己的公共方法访问。
      私有方法可以调用私有属性
      公共发放也无法调用私有方法
      为此,我们创建 特权方法

    4. 特权方法(其实就是实例方法)
      function Container(param) {
              function dec() {
                      if (secret > 0) {
                              secret -= 1;
                              return true;
                      } else {
                              return false;
                      }
              }
              this.member = param;
              var secret = 3;
              var that = this;
              this.service = function () {
                      return dec() ? that.member : null;
              };
      }
      
    • 特权方法能够访问私有变量和方法
    • 特权方法也能访问公共方法
    • 公共方法能访问特权方法
    1. Closures

      This pattern of public, private, and privileged members is possible because JavaScript has closures. What this means is that an inner function always has access to the vars and parameters of its outer function, even after the outer function has returned. This is an extremely powerful property of the language. There is no book currently available on JavaScript programming that shows how to exploit it. Most don’t even mention it.

      Private and privileged members can only be made when an object is constructed. Public members can be added at any time.

参考链接:
http://www.crockford.com/javascript/private.html
https://github.com/justjavac/12-javascript-quirks/blob/master/cn/6-the-scope-of-variables.md
https://github.com/justjavac/12-javascript-quirks/blob/master/cn/7-inadvertent-sharing-of-variables-via-closures.md