大城小筑


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

js怪癖-变量、闭包相关

发表于 2017-08-11

变量的作用范围

  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 this 的accessor 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

js怪癖-undefined和null及引申

发表于 2017-08-07

undefined 和 null

  1. undefined 是JavaScript语言本身分配的,如果一个变量还没有被初始化,那么他的值就是 undefined
  2. null 是被开发者用来明确指出某个值是缺失的,例如,对于JSON.stringify()
  3. 如果需要知道变量 x 是否有值,通常情况下,你需要同时,校验 undefined 和 null。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    if (x != null) {
    // 有值的操作 (false, -0, +0, NaN)都被认为是有值
    } else {
    // 没有值时,执行的操作
    }
    ```
    相等于
    ```javascript
    if (v !== undefined && v!== null) {
    // 有值
    } else {
    // 没值
    }

    另外一种逻辑

    1
    2
    3
    4
    5
    if (x == null) {
    // 没值
    } else {
    // 有值
    }

隐式类型转换

0. 提要

  1. 自动转换为布尔值通常不会引起问题,而且往往很有用

1. 隐式转换为布尔: “ truthy ” 和 “ falsy ”

  1. 当JavaScript需要一个布尔值时(if 语句),会触发隐式转换为布尔值,Boolean()
  2. 算法
    1. 下面这些值将被转换为false
      1. undefined, null
      2. Boolea: false
      3. Number: -0, +0, NaN
      4. String: ‘’
    2. 其他值都被认为是 true

2. 字符串的隐式转换

  1. 加运算符 +,当其中一个操作数是字符串的时候,就会执行拼接字符串的操作

3. 对象的隐式转换

  1. 只有在 JavaScript 表达式或语句需要用到数字或字符串时,对象才被隐式转换。
    1. 需要转换为数字时
      1. 调用 valueOf(),如果结果是原始值,则将其转换为一个数字
      2. 否则,调用 toString()方法,如果结果时原始值,则将其转换为一个数字
      3. 否则,抛出一个类型错误
    2. 需要转换为字符串的时候
      1. 上面的算法,第一步与第二步调换。

参数的处理

  1. 可以传递任意的参数
    1. 缺失参数的值是undefined
    2. 多出来的参数则被直接忽略掉
    3. ES6: 参数默认值
    4. ES6: 不定参数,必须在最后一个
  2. 所有传递的参数都储存在一个特别的,类数组对象 arguments中
  3. 判断参数是否传递了?

    1. 一个不严谨的方法

      1
      2
      3
      4
      5
      6
      7
      function hasParameter (param) {
      if (param) {
      return 'yse'
      } else {
      return 'no'
      }
      }

      这个情况,对于虚拟值(falsy)的运用是非法的,比如false、0以及空字符串都会被解析为缺失参数

    2. 仅仅判断undefined
      1
      2
      3
      4
      5
      6
      7
      function hasParameter (param) {
      if (typeof param !== undefined) {
      return 'yes'
      } else {
      return 'no'
      }
      }
  4. 参数默认值ES5实现

    1. 简单版,传递虚拟值(falsy): false、0 、空字符等同于没传递,将会使用默认值

      1
      2
      3
      4
      5
      function plus (x, y) {
      x = x || 1
      y = y || 1
      return x * y
      }
    2. 严谨版,typeof x === undefined

  5. 强制执行一定数量的参数

    1. 通过arguments.length验证
      1
      2
      3
      4
      5
      6
      7
      8
      function add (x, y) {
      if (arguments.length > 2) {
      throw new Error('Need at most 2 parameter')
      } else if ( arguments.length < 2) {
      throw new Error('Need at least 2 parameter')
      }
      return x + y
      }
  6. arguments 不是 array

    1. arguments 并不是 array,它只是像array,暂且知道两点方法像array
      1. 获取第i个参数,argument[i]
      2. argument.length
    2. 转换函数
      1
      2
      3
      function fromArray (arrayLikeValue) {
      return Array.prototype.slice.call(arrayLikeValue)
      }
  7. 函数参数的个数

    1. 函数直接传入参数是最常见的
    2. 不过如果参数个数太多。程序的可读性就下降了,调用起来也麻烦 – 比如记不清每个位置的参数含义,记不清顺序等。
    3. 通常函数参数的个数不应该超过4个。
    4. 如果有更多的配置需求呢?把多个参数合成为一组配置,以单一参数传递,JS中就是对象object

Hello World

发表于 2017-08-05

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

Damon

Damon

分享所学所思

3 日志
1 分类
2 标签
© 2017 Damon
由 Hexo 强力驱动
主题 - NexT.Muse