JS面试题--作用域、自由变量和闭包

149 阅读7分钟

JavaScript的作用域和闭包是非常重要的概念,它们是理解JavaScript编程语言的核心。

一、定义

作用域:是指变量和函数的可访问范围。在JavaScript中,作用域可以分为全局作用域和局部作用域。全局作用域是在整个程序中都可以访问的变量和函数。而局部作用域是在函数内部定义的变量和函数。

在JavaScript中,变量的作用域是由它们声明的位置决定的。如果变量在函数内部声明,那么它的作用域就是这个函数的局部作用域。如果变量在函数外部声明,那么它的作用域就是全局作用域。

闭包:是指在函数可以访问在它外部定义的变量。当一个函数返回另一个函数时,返回的函数可以访问在其外部定义的变量,这就是闭包。闭包可以用来创建私有变量和函数,保护变量不被外部访问和修改。

在JavaScript中,闭包是由函数和他的作用域链组成的。当函数被定义时,它会创建一个作用域链,这个包含了函数定义时所处的作用域和全局作用域。当函数被调用时,它会在作用域链上查找变量和函数,如果在当前作用域找不到,就会向上一级作用域查找,直到找到为止。如果在全局作用域都找不到,就会抛出一个ReferenceError。

自由变量:在 JavaScript 中,自由变量(free variable)是指在一个函数中使用的,但既不是函数参数也不是函数内部定义的变量。当一个函数引用了一个自由变量时,它会在运行时查找该变量的值。

二、面试题目

1、this的不同应用场景,如何取值?

"this" 是JavaScript中的一个关键字,用于引用当前正在执行的代码中的对象。它的值可以根据上下文而变化,因此在不同的应用场景中,需要采用不同的方法来获取它的值。 以下是一些常见的应用场景和相应的取值方法:

  • 在全局作用域中,this指向全局对象。在浏览器中,全局对象是window对象。
  • 在函数中,this的值取决于函数的调用方式,如果函数作为对象的方法被调用,this就指向该对象。如果函数作为普通函数被调用,this就指向全局对象。
  • 在事件处理程序中,this指向触发事件的元素
  • 在构造函数中,this指向新创建的对象
  • 在箭头函数中,this的值取决于它所在的上下文。如果箭头函数被定义在对象中,this就指向该对象。如果箭头函数被定义在全局作用域中,this就指向全局对象。
  • 在类中,this指向当前实例化的对象。

在JavaScript中,this的值时非常动态的,并且在不同的上下文中会有不同的取值。因此,在使用this时,需要根据具体的场景来确定它的值。可以通过使用箭头函数、bind()、call()、apply()等方法来显式地指定this的值,以确保代码的正确性。

2、手写bind函数 bind()方法用于创建一个新的函数,并将this关键字绑定到指定的对象上。下面是具体的代码实现,使用了ES6语法:

 Function.prototype.mybind = function(context, ...args) {
    const self = this;
    return function (...bindArgs) {
        return self.apply(context, [...args, ...bindArgs]);
    }
 }

使用场景:

  const obj = {
    name: "John",
    greet() {
      console.log(`Hello, ${this.name}`);
    },
  };

  const newObj = {
    name: "Jane",
  };
  const boundGreet = obj.greet.mybind(newObj);
  boundGreet(); // 输出 "Hello, Jane!"

3、实际开发中闭包的应用场景,举例说明

(1)封装私有变量

闭包可以用来封装私有变量,使得这些变量不会被外部访问到。这在模块化开发中非常有用,可以避免变量名冲突和不必要的全局变量污染。

function createCounter() { 
    let count = 0 
    return function() { 
    count++ 
    console.log(count) 
    } 
} 
const counter = createCounter() 
counter() // 1 
counter() // 2 
counter() // 3

这个例子中,中,我们创建了一个 createCounter 函数,它返回一个闭包函数。闭包函数可以访问 createCounter 函数中的 count 变量,但是外部无法直接访问该变量。这样,我们就可以通过闭包来封装一个私有变量 count,并且可以通过返回的函数来实现计数器的功能。

(2)延迟执行

我们可以在闭包函数中保存一些状态,然后在后续的调用中使用这些状态,从而实现一些特殊的功能。

  function debounce(fn, delay) {
    let timer = null;
    return function () {
      const context = this;
      const args = arguments;
      clearTimeout(timer);
      timer = setTimeout(() => {
        fn.apply(context, args);
      }, delay);
    };
  }

  const button = document.querySelector("button");
  button.addEventListener(
    "click",
    debounce(() => {
      console.log("Button clicked!");
    }, 1000)
  );

在上面的例子中,我们创建了一个 debounce 函数,它返回一个闭包函数。闭包函数中保存了一个计时器 timer,当闭包函数被调用时,它会清除之前的计时器,并重新设置一个新的计时器。这样,我们就可以通过闭包来实现一个防抖函数,从而避免在短时间内多次触发同一个事件。

(3)缓存计算结果

可以在闭包函数中保存一些计算结果,然后在后续的调用中直接使用这些计算结果,从而避免重复计算。

  function createCalc() {
    const cache = {};
    return function (num) {
      if (num in cache) {
        console.log("From cache");
        return cache[num];
      } else {
        console.log("Calculating...");
        const result = num * 2;
        cache[num] = result;
        return result;
      }
    };
  }

  const calc = createCalc();
  console.log(calc(2)); // Calculating... 4
  console.log(calc(2)); // From cache 4
  console.log(calc(3)); // Calculating... 6
  console.log(calc(3)); // From cache 6

在上面的例子中,我们创建了一个 createCalc 函数,它返回一个闭包函数。闭包函数中保存了一个缓存对象 cache,当闭包函数被调用时,它会先检查缓存对象中是否已经存在对应的计算结果,如果存在,则直接返回缓存结果;否则,它会计算出结果,并将结果保存到缓存对象中,然后返回计算结果。这样,我们就可以通过闭包来实现一个缓存计算结果的函数,从而避免重复计算。

(4)实现模块化

可以在闭包函数中定义一些私有变量和方法,从而实现一个独立的模块,避免变量名冲突和全局变量污染。

  const myModule = (function () {
    const privateVar = "我是私有变量";

    function privateFunc() {
      console.log("这是一个私有方法");
    }

    return {
      publicVar: "我是公有变量",
      publicFunc: function () {
        console.log("这是一个公有方法");
        console.log(privateVar);
        privateFunc();
      },
    };
  })();
  console.log(myModule.publicVar); //我是公有变量
  myModule.publicFunc() // 这是一个公有方法, 我是私有变量, 这是一个私有方法

在上面的例子中,我们使用立即执行函数来创建一个闭包,闭包中定义了私有变量 privateVar 和私有函数 privateFunc,然后返回一个对象,其中包含一个公共变量 publicVar 和一个公共函数 publicFunc。这样,我们就可以通过闭包来实现一个模块化的功能,从而避免变量名冲突和全局变量污染。

(5)实现函数柯里化 函数柯里化是指将一个多参数函数转化为一系列单参数函数的过程,从而使得函数更加灵活和可复用。

  function add(x) {
    return function (y) {
      return x + y;
    };
  }

  const add5 = add(5);
  console.log(add5(3)); // 8
  console.log(add5(7)); // 12

在上面的例子中,我们创建了一个 add 函数,它返回一个闭包函数。闭包函数中保存了一个参数 x,当闭包函数被调用时,它会返回一个新的函数,这个新函数中保存了另一个参数 y,当新函数被调用时,它会将 x 和 y 相加并返回结果。这样,我们就可以通过闭包来实现一个函数柯里化的功能,从而使得函数更加灵活和可复用。

4、创建10个A标签,点击弹出序号

变量 i的作用域要放在for循环内

image.png