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循环内