面试-JS基础知识-作用域和闭包、this

作用域

  • 全局
  • 函数
  • 块级作用域(ES6)比如👇在if大括号内定义了x,在大括号外使用x会报错。

自由变量
一个变量在当前作用域没有定义,但被使用了。
会一层一层往上级作用域寻找。

  • 函数作为返回值
function create(){
  const a = 100
  return function (){
    console.log(a) //a在这里是自由变量,向上寻找
  }
}

const fn = create()
const a = 200
fn() //100
  • 函数作为参数
function print(fn){
  const a = 13
  fn()
}
const a = 100
function fn(){
  console.log(a)
}
print(fn) //100

闭包:闭包是指函数“记住”了它被创建时的环境,即使这个函数在它原本的作用域外执行,它依然可以访问这个环境中的变量.在 JavaScript 中,函数会和它的词法环境(Lexical Environment)绑定在一起,这就是闭包的本质。

自由变量的查找是在函数定义的地方向上级作用域查找,而不是在执行的地方!这就是词法作用域(Lexical Scoping)的概念。

闭包能隐藏数据,不被外界直接访问。如下👇

function createCounter() {
    let count = 0; // 私有变量

    return {
        increment() {
            count++;
            console.log(count);
        },
        decrement() {
            count--;
            console.log(count);
        },
        getCount() {
            return count;
        }
    };
}

const counter = createCounter();
counter.increment(); // 输出: 1
counter.increment(); // 输出: 2
console.log(counter.getCount()); // 输出: 2
counter.decrement(); // 输出: 1

在上面这个例子中,count 变量只存在于 createCounter 的作用域中,外部无法直接访问它。increment、decrement 等方法通过闭包的机制,可以持续访问并修改 count 的值。

闭包在回调函数与事件监听中的应用:

function attachEventListener() {
  let count = 0;
  document.getElementById("myButton").addEventListener("click", function() {
    count++;
    console.log(`Button clicked ${count} times`);
  });
}

attachEventListener();

闭包让你能够保持对某些变量的访问,即使函数已经执行完毕。


  • this取什么值是在函数执行的时候确定的,不是函数定义的时候确定。(和上面自由变量的查找相反,注意一下下)
  • 箭头函数的this永远取上级作用域的值。
  • 使用call(..)可以确保this指向函数本身

this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用

总结
下面的i是全局变量,对于for函数内部的i来说是个自由变量会往上级去找。

for 循环在运行时,它会快速执行,完成所有迭代和代码执行。而在循环中添加的 click 事件监听器虽然在循环时绑定上了,但并不会立即触发。

以至于点击每一个a标签的时候都弹出来10而不是点击第几个弹出数字几。
解决办法:把i定义到for内: for(let i = 0)
或者:使用 立即执行函数表达式 (IIFE)(闭包的经典应用)
通过在循环内使用立即执行函数,将当前的 i 传递进去,从而创建一个新的作用域来保存当前的 i 值。
当使用 var 声明变量时,它会被提升并且是函数作用域的,因此循环结束后所有闭包引用的都是同一个 i

for (var i = 0; i < 10; i++) {
  var a = document.createElement('a');
  a.innerHTML = i + '<br>';
  (function(i) {
    a.addEventListener('click', function (e) {
      e.preventDefault();
      alert(i);
    });
  })(i);
  document.body.appendChild(a);
}


手写bind函数第一个绑定的是this
bind 的功能是:

  • 返回一个新函数。
  • 当调用新函数时,this 指向指定的对象。
  • 可以预置一些参数,调用新函数时这些参数自动传递给原函数。

注意:

  • this要传进去
  • 参数要传进去
  • 返回值要返回回来
    下面的arguments可获取一个函数的所有的参数,它不是数组需要将其转换为数组形式。
    apply第一个参数是this,第二个参数是数组

    手写bind函数的另外一个写法:
Function.prototype.myBind = function (context, ...args) {
    // 保存当前函数,即调用 bind 的函数
    const self = this;

    // 返回一个新函数
    return function (...newArgs) {
        // 将新的参数合并,并通过 apply 调用 self
        return self.apply(context, [...args, ...newArgs]);
    };
};

总结:
bind 函数的实现核心就是通过闭包保存 this 和参数信息,并在执行时将它们传递给原函数。如果你觉得代码有些复杂,建议先理解以下几个关键点:
this 是动态的,可以通过 apply 或 call 方法来改变函数执行时的 this 指向。
闭包可以用来保存 this 和参数,从而在返回的函数中继续使用这些值。
new 调用的特殊情况需要我们使用 instanceof 来判断,并相应处理。

补充


详细解释:
Function.prototype.myBind:

myBind 是我们定义的函数,用于模拟 bind。这里,我们将 myBind 直接扩展到了 Function.prototype,这样所有函数都可以调用这个方法。
保存当前的函数:

const fn = this;
在 myBind 函数中,this 就是要绑定 this 的那个函数,例如上面的 greet。
支持预设参数:

...bindArgs
bindArgs 是绑定时传递的参数。这些参数会在调用时一起传给目标函数。
返回一个新的函数:

我们返回了一个新的函数,这个新函数在调用时会使用我们绑定的 context 作为 this。
处理函数执行时传递的参数:

...callArgs
当实际调用绑定后的函数时,传递的参数会通过 callArgs 传递进去。我们使用了 ... 语法将两个参数数组(bindArgs 和 callArgs)合并。
处理构造函数的兼容:

if (this instanceof fn) {
return new fn(...bindArgs, ...callArgs);
}
如果通过 new 操作符调用绑定后的函数,那么 this 应该指向新创建的对象,而不是绑定的 context。为此,我们检查 this 是否是当前函数的实例。如果是,则使用 new 调用原函数,并忽略绑定的上下文,直接返回新实例。
正常调用:

return fn.apply(context, [...bindArgs, ...callArgs]);
在非构造函数调用的情况下,我们使用 apply 将函数的 this 绑定到指定的 context 上,并将所有参数传递给目标函数。

posted @   一个甜橙子  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示