作用域和自由变量

作用域

  1. 作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。
  2. 分为:

    1. 全局作用域
    2. 函数作用域
    3. 块级作用域(ES6新增)

      自由变量

  3. 一个变量在当前作用域没有定义,但是被使用了。

向上级作用域一层层依次寻找,直到找到为止。

  1. image.png

在最里面一层里,a a1 a2都没定义,就向外层寻找,找到对应的值。

闭包

  1. 作用域应用的特殊情况,有两种表现:

    1. 函数作为参数被传递

      1. // 函数作为返回值
      2. function create () {
      3. let a = 100
      4. return function () {
      5. // 在这里,a是一个自由变量,向外寻找定义,a=100
      6. console.log(a);
      7. }
      8. }
      9. const fn = create()
      10. const a = 200
      11. fn() // 100
    2. 函数作为返回值被返回

      1. // 函数作为参数传递
      2. function print (fn) {
      3. let a = 200
      4. fn()
      5. }
      6. const a = 100
      7. function fn () {
      8. // 在这里,a也是一个自由变量,向外寻找定义,a=100
      9. console.log(a);
      10. }
      11. print(fn) // 100
    3. 自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在函数执行的地方。

      this

  2. 场景和取值:

    1. 作为普通函数——>返回window
    2. 使用 call apply bind——>传入什么参数绑定什么
    3. 作为对象方法被调用——>返回对象本身
    4. 在class方法中调用——>返回实例本身
    5. 箭头函数——>找上级作用域的值来确定
  3. this取什么值是在函数运行的时候确定的,不是函数定义的时候确定的。
  4. demo1

    1. function fn1 () {
    2. console.log(this);
    3. }
    4. fn1() // window
    5. fn1.call({ x: 100 }) // { x: 100 }
    6. const fn2 = fn1.bind({ x: 200 })
    7. fn2() // { x: 200 }
  5. demo2

    1. const zhangsan = {
    2. name: 'zs',
    3. sayHi () {
    4. // this指向当前对象
    5. console.log(this);
    6. },
    7. wait () {
    8. setTimeout(function () {
    9. // 是window调用的定时器,这里的this是window
    10. console.log(this);
    11. })
    12. }
    13. }
  6. demo3

    1. const lisi = {
    2. name: 'ls',
    3. sayHi () {
    4. // this指向当前对象
    5. console.log(this);
    6. },
    7. wait () {
    8. setTimeout(() => {
    9. // 这里使用了箭头函数。向上级作用域查找,指向当前对象
    10. console.log(this);
    11. })
    12. }
    13. }

    相关面试题

  7. this的不同应用场景,如何取值?

    1. 作为普通函数——>返回window
    2. 使用 call apply bind——>传入什么参数绑定什么
    3. 作为对象方法被调用——>返回对象本身
    4. 在class方法中调用——>返回实例本身
    5. 箭头函数——>找上级作用域的值来确定
  8. 手写bind函数

    1. call bind apply都是定义在Function的prototype上
      1. // 手写bind
      2. Function.prototype.myBind = function () {
      3. // 将参数拆解为数组
      4. const args = Array.prototype.slice.call(arguments)
      5. // 获取fn1.bind({ x: 100 }, 10, 20, 30)中的 { x: 100}
      6. // 即bind要绑定的对象。
      7. // 即参数第一项,让第一项弹出,剩下的就是参数。
      8. const t = args.shift()
      9. // fn1.bind({ x: 100 }, 10, 20, 30)中的fn1
      10. const self = this
      11. // 返回一个函数
      12. return function () {
      13. return self.apply(t, args)
      14. }
      15. }
  9. 实际开发中闭包的应用场景,举例说明

    1. 隐藏数据,如做一个简单的cache工具。 ```javascript // 闭包隐藏数据,只提供API function createCache () { // 闭包中的数据,被隐藏,不让外界访问 const data = {} return { set: function (key, val) {
      1. data[key] = val
      }, get: function (key) {
      1. return data[key]
      } } }

const c = createCache() c.set(‘a’, 100) console.log(c.get(‘a’)); // 100 console.log(c.data); // undefined

  1. ```javascript
  2. // 创建十个a标签,点击每个a标签的时候弹出相对应的数字
  3. let i, a
  4. for (i = 0; i < 10; i++) {
  5. a = document.createElement('a')
  6. a.innerHTML = i + '<br>'
  7. a.addEventListener('click', function (e) {
  8. e.preventDefault()
  9. // 这个函数不是立刻执行,点击的时候执行
  10. // 当执行到这行代码的时候,i已经是10了,因为i是全局作用域
  11. alert(i)
  12. })
  13. document.body.appendChild(a)
  14. }
  15. // 最后发现点击哪个标签都弹出1
  16. // 解决方法:在for循环的时候定义i,每次循环都会产生新的块级作用域。