JS:this指针/闭包/作用域

131 阅读4分钟

this指针/闭包/作用域

一、作用域

1.基本概念

在特定的场景下,特定范围内,查找变量的一套规则。广泛的来说,我们都指的是:词法作用域,静态作用域

  • 分类
    • 全局作用域
    • 函数作用域 在函数内部生命的所有变量,在函数范围内持续可用。
    • 块作用域 es6的概念,是一个用来对之前的最小授权原则进行扩展的工具包,将代码在函数中隐藏信息扩展为块中。
 function showResult(a) {
 let e = 3
  console.log(a + e)
  console.log(a + b)
  console.log(a + d)
  console.log(a + c)
  var b = 3
  let c = 5
}
showResult(2)
结果:
  5
  NaN
//d is not defined
// console.log(a + c)报错 cannot access 'c' before initailization

分析:
1.e属于函数作用域,正常取值正常打印
2.b使用var声明,存在变量提升,在预编译阶段给函数头部先声明了一个var b,但是没有赋值,所以相当于bundefined,按照顺序执行,结果为NaN
3.d 取值是发现未定义
4.c使用let声明,会报错。在console.log(a + c)let c = 5 形成一个暂时性死区,这里c不能使用

暂时性死区:let 是与块级作用域,所以let会绑定这整个区域,让let声明的对象无法再进行其他声明。 函数参数中也是默认使用let声明,所以函数的参数使用默认值也会出现暂时性死区

  • 能够成块级作用域的用法

    • if
    • for
    • {...}

    2.函数表达式

    在js的预编译阶段,对变量进行内存分配->变量提升,值为undefined -> 对所有的非表达式的声明进行提升

   var name = function () {
       console.log('name1')
   }
   function name() {
     console.log('name2')
   }
   name()
   结果:name1

分析:
在预编译阶段时候会var name = undefinedfunction name提升到顶部,然后给name进行赋值function,函数的提升的优先级要更,在编译的执行顺序变成:

 function name() {
      console.log('name2')
    }
 var name = undefined
 name = function () {
        console.log('name1')
    }

3.执行上下文

根据代码编写的逻辑,作用域是一层套一层的,变量也是往上找,就是我们所谓的词法作用域,它关注的是函数在何处声明
上下文关注函数在哪里调用

二、闭包

函数嵌套函数时,内部函数可以访问外部函数作用域的变量,且内部函数可以在全局环境下可访问,形成了一段不被销毁的代码空间,就形成了闭包。

当函数的执行上下文没有在原本的词法作用域内,就形成了闭包。

  function fnGenerator(){
     let num = 1
     return () => {
      console.log(num ++ )
      }
  }
  var fn = fnGenerator()

常见的闭包场景:

  • 函数式编程
  • 浏览器中window下一些event 和 handler
  • setTimeOut 也会形成闭包
  • 发布-订阅

三、this

this的指向是根据执行上下文动态决定的。

  • 简单的指向:匿名函数的this一般指向顶级作用域,window或者Global
  • 对象调用时,绑定在对象上
  • call/apply/bind:,绑定在指定的参数上
  • new关键字,利用构造函数绑定在新创建的对象上
  • 箭头函数,根据外层的规则决定this的指向

优先级;new > call/apply/bind > 对象 例题:

var number = 5;
var obj = {
   number:3,
   fn1:(function(){
      var number;
      this.number *=2;
      number = number *2
      number = 3
      return function(){
        var num = this.number
        this.number = 2
        console.log(num)
        number *= 3
        console.log(number)
      }
   })()
}
var fn1 = obj.fn1
fn1.call(null)
obj.fn1()
console.log(window.number)

打印结果:10 9 3 27 2

分析:
1.当执行var fn1 = obj.fn1时,由于obj.fn1是自执行函数,所以作用域指向顶级作用域。此时代码执行的片段如下:

自执行函数this指向window

      var number;        //number为undefined
      this.number *=2;  //this.number 指的是window的number 所以 Window.number = 10
      number = number *2
      number = 3
      return function(){      //将函数体返回给obj.fn1。同时赋给fn1,形成闭包。
        var num = this.number
        this.number = 2  
        console.log(num)
        number *= 3
        console.log(number)
      }

2.此时fn1已经接受了上一步的函数体,执行函数如下:

fn1.call(null) 相当于直接执行fn1()

        var num = this.number  // this.number = 10 -> num = 10
        this.number = 2    //此处又将window.number 变为 2
        console.log(num) // 打印结果:10
        number *= 3  //因为是闭包,所以作用域向上找。number = 3
        console.log(number)  //打印结果:9

3.obj.fn1() 也是看function(){...}代码段,分析如下:

        var num = this.number  //this指向obj,所以 num = obj.number = 3
        this.number = 2
        console.log(num)  //打印结果:3
        number *= 3   //因为是闭包,所以作用域向上找。上一次执行number已经改变 -> 变成9
        console.log(number) //打印结果:27

4.console.log(window.number),在步骤2时已经改变number值,这里打印为2。

但是如果这里将function变成箭头函数:

var number = 5;
var obj = {
   number:3,
   fn1:(() => {
      var number;
      this.number *=2;
      number = number *2
      number = 3
      return () => {
        var num = this.number
        this.number = 2
        console.log(num)
        number *= 3
        console.log(number)
      }
   })()
}
var fn1 = obj.fn1
fn1.call(null)
obj.fn1()
console.log(window.number)

打印结果就会变成:10 9 2 27 2
这里主要是因为执行obj.fn1()时函数变为了箭头函数,这里的执行上下文变成了全局,所以这里的this指向就变成window,所以num = window.number = 2
如果将fn1 和 obj.fn1 的this指向换一个对象

var number = 5;
var obj = {
   number:3,
   fn1:(function() {
      var number;
      this.number *=2;
      number = number *2
      number = 3
      return function() {
        var num = this.number
        this.number *= 2
        console.log(num)
        number *= 3
        console.log(number)
      }
   })()
}
var obj2 = {
  number:108
}
var fn1 = obj.fn1
fn1.call(obj2)
obj.fn1.call(obj2)
console.log(window.number)

打印结果: 108 9 216 27 10

思考:什么场景下需要考虑this的指向问题?

  • 函数式编程
  • 柯里化
  • 闭包
  • 防抖节流