面试-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 上,并将所有参数传递给目标函数。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧