this、作用域、闭包

120 阅读6分钟

1.this详解

在JavaScript中,this关键字表示当前函数执行的环境。当一个函数被调用时,JavaScript引擎会为该函数创建一个执行环境,其中包括了函数的变量和对象等,而this关键字则用来指代当前执行环境的上下文。

this关键字在大多数情况下的值由函数的调用方式决定,this不能在执行期间被赋值,并且每次被函数调用时this的值可能会不同。还有一些与this相关的关键字,如call()、apply()、bind()等,用来改变函数的执行环境、实现函数的高阶调用、绑定函数的上下文等。

this的值:当前执行上下文(global、function或eval)的一个属性,在非严格模式下总是指向一个对象,在严格模式下可以是任意值。

简单使用:

let o = {
      color: 'blue',
      sayColor: function() {
        return this.color
      }
    }
 console.log(o.sayColor())//blue

1.1 全局作用域下的this

全局执行环境也就是任何函数体的外部,this都指向全局对象

    console.log(window === this)//true
    a = 10;
    console.log(this.a)//10
    console.log(window.a)//10
    function fun() {
      return this.s
    }
    obj = {
      s: 1,
      b: fun
    }
    var s = 2;
    console.log(obj.b())//1
    console.log(fun())//2
  <button class="btn">btn1</button>
  <button class="btn">btn2</button>
  <button class="btn">btn3</button>
  
  var btns = document.querySelectorAll('.btn')
    for(i of btns) {
      i.onclick = function() {
        console.log(this)//this指向本节点对象
      }
    }

image.png

    var obj3 = {
      fun3:function(){
          console.log(this);
      }
    }

    setInterval(obj3.fun3,1000);     // this指向window对象
    setInterval('obj3.fun3()',1000);  // this指向obj3对象

image.png

1.2 对象方法中的this

在JavaScript中,一个对象可以包含多种方法,在对象的方法中使用this,那么this指向该对象本身

   let obj = {
      num: 1,
      fun:function() {
        console.log(this.num)//1,this指向obj对象
      }
    }
    obj.fun()

1.3 构造函数中的this

在JavaScript中,构造函数用于创建对象,每个构造函数都包含this关键字,其指向新的构造对象

    function Person(id, age) {
      this.id = id
      this.age = age

    }
    let person1 = new Person('01', 18)
    let person2 = new Person('02', 20)
    console.log(person1)
    //this指向新创建的Person对象

1.4 call和apply方法中的this

JavaScript的函数对象有call和apply两个方法,这两个方法允许开发者在调用函数时自定义this的值,call和apply区别于bind,bind不会立即执行,而是返回一个新的函数,apply的第二个参数需要以数组的方式表示

    let obj1 = {
      fun:function() {
        console.log(this)
      }
    }
    let obj2 = {
      s :obj1
    }
    obj1.fun.call(obj2.s)//{fun: f}

1.5 箭头函数中的this

在箭头函数中,this与封闭词法环境的this保持一致,在全局代码中this则为全局对象,如果需要更改函数内部的this指向,就不能使用箭头函数

const obj = {
  fun: function() {
    setTimeout(() => console.log(this) , 100)//this为obj对象
  } 
}
obj.fun()

1.6 函数作为参数和返回值时的this

当函数作为参数或者返回值时this的指向可能会发生变化

let obj = { name: 'Lily' };
var name = 'cat'
function func2() {
    console.log(this.name);//cat,this指向window
}

function func1() {
    func2();
}

func1.call(obj); 
let obj = {
  name: 'lily'
}
var name = 'cat'
function fun() {
  console.log(this.name)//cat,this指向window
}
function fun1() {
  return fun
}
const fun2 = fun1()
fun2()

1.7 事件绑定中的this

在开发中通常需要将函数绑定在DOM元素的事件上,函数中的this则为绑定事件的元素

  <button onclick="console.log(this)">click me</button>
  //this指向button元素

1.8 this可能出现的坑点

var obj = {name: 'jack'}
document.querySelector('.btn5').addEventListener('onclick', function() {
  console.log(this)//this指向触发事件的元素
})
setTimeout(function() {
  console.log(this)//this指向全局对象window,setTimeout不属于任何对象
},1000)

1.9 按绑定形式分类

以上为this在不同场景下的指向介绍,总结为三种绑定方式。1、默认绑定(正常调用时this的指向) 2、隐式绑定(this取决于当前执行上下文环境) 3、显示绑定(通过call,apply,bind改变this指向)

2. 存储空间与context

存储和运算是程序时刻做的事,存储包含数据结构,运算包含算法,在存储与运算过程中变量和执行环境是需要重点学习的。

在JavaScript中变量的作用域分为词法作用域动态作用域。词法作用域是变量只能在定义的代码块中使用,一旦离开代码块,变量就不再存在。动态作用域为变量可以在函数内部中重新定义,并可以在不同的函数调用之间共享。在JavaScript中函数内部的变量为局部变量,函数外部的变量为全局变量,局部变量只能在函数内部被访问和修改,全局变量可以在任何地方被访问和修改。

为解决作用域问题,JavaScript提供了一些特殊的语法结构,如with语句、IIFE(立即执行函数表达式)等,with语句可以简化对全局变量的引用,IIFE可以将函数的作用域限制在当前代码块内部,避免污染全局命名空间。

3. 闭包

3.1 闭包的概念

闭包是指一个函数能够访问并操作其创建时所在词法域中变量的能力。也就是说,闭包就是能够将变量保存到函数外部的一种机制。

在JavaScript可以通过两种方式创建闭包,1.使用嵌套函数 2.使用bind()、call()、apply()等方法。 嵌套函数通常用于实现模块化编程,封装私有属性等场景。而使用bind()、call()、apply()等方法可以用来改变函数执行环境、实现高阶调用等。

闭包有很多应用场景,通过其可以实现私有属性缓存计算结果模拟事件处理程序等。但是闭包也有缺陷,如易导致内存内漏、难以维护等。

let num = 1
function fun() {
  num++
  console.log(num)
}
fun()//2
function fun() {
  let num = 1;
  function fun1() {
    num++;
    console.log(num)
  }
  return fun1
}
fun()()//2
fun()()//2
function fun() {
  let num = 1;
  function fun1() {
    num++;
    console.log(num)
  }
  return fun1
}
let fun2 = fun()
fun2()//2
fun2()//3
fun2()//4

3.2 闭包的使用场景

闭包的使用场景很广泛,在函数式编程思想中闭包的使用场景更广泛。例如,1、函数柯里化 2、单例模式处理、工厂模式处理 3、react hooks

3.2.1函数柯里化

function curry(fn) {
  const arity = fn.length;

  return function curried(...args) {
    if (args.length >= arity) {
      return fn.apply(this, args);
    } else {
      return function (...moreArgs) {
        return curried.apply(this, args.concat(moreArgs));
      };
    }
  };
}

function add(a, b, c) {
  return a + b + c;
}

const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 输出 6

Compose函数

function compose(...funcs: Function[]) {
  if (funcs.length === 0) {
    // 推断参数类型,使其可用于后续推理
    return <T>(arg: T) => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce(
    (a, b) =>
      (...args: any) =>
        a(b(...args))
  )
}

3.2.2 单例模式、工厂模式处理

function Singleton () {}

const getSingletonInstance = (function () {
  let instance = null
  return function () {
    if (!instance) {
      instance = new Singleton()
    }
    return instance
  }
})()

const s1 = getSingletonInstance()
const s2 = getSingletonInstance()

console.log(s1 === s2) // true
function createPerson(name) {
    const privateProperties = {}

    const person = {
        setName(name) {
            if (!name) {
                throw new Error('A person must have a name')
            }
            privateProperties.name = name
        },
        getName() {
            return privateProperties.name
        }
    }

    person.setName(name)
    return person
}

person = createPerson('John')
console.log(person.getName())
// => John
person.setName('Michael')
console.log(person.getName())
// => Michael