一、作用域
1、我的理解就是变量的可使用范围,这个范围就是作用域。
2、分类:
全局作用域:在代码中任何地方都能访问到的对象拥有全局作用域。
(1)最外层函数和在最外层函数外面定义的变量拥有全局作用域。
(2)所有未定义直接赋值的变量自动声明为用于全局作用域。
(3)所有window对象的属性拥有全局作用域。window对象的内置属性都拥有全局作用域,
例如window.name、window.name、window.localtion、window.top等。
(4)注:块语句(大括号‘{}’中间的语句),如if 和switch条件语句,或for 和whlie循环语句,不像函数,他们不会创建一个新的作用域
全局作用域的弊端:污染全局命名空间,容易近期命名冲突。
var outVariable = "我是最外层变量"; //最外层变量
function outFun() { //最外层函数
var inVariable = "内层变量";
function innerFun() { //内层函数
console.log(inVariable);
}
innerFun();
}
console.log(outVariable); //可以访问: 我是最外层变量
outFun(); //可以访问: 内层变量
console.log(inVariable); //无法访问: inVariable is not defined
innerFun(); //无法访问: innerFun is not defined
function outFun2() {
variable = "未定义直接赋值的变量";
var inVariable2 = "内层变量2";
}
outFun2();//要先执行这个函数,否则根本不知道里面是啥
console.log(variable); //未定义直接赋值的变量
console.log(inVariable2); //inVariable2 is not defined
if (true) {
// 'if' 条件语句块不会创建一个新的作用域
var name = 'Hammad'; // name 依然在全局作用域中
}
console.log(name); // 可以访问:'Hammad'
函数作用域:指声明在函数内部的变量,和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到。
function doSomething(){
var stuName="zhangsan";
function innerSay(){
console.log(stuName);
}
innerSay();
}
console.log(stuName); // 无法访问:脚本错误
innerSay(); //无法访问: 脚本错误
块级作用域:块级作用域可通过let和const声明,所声明的变量在指定块的作用域外无法被访问。
{
var a=1
}
console.log(a)
{
let a=1
}
console.log(a)
二、作用域链
当我们在某个函数内部作用域中查找某个变量时,如果没有找到就会到他的父级作用域中查找,
如果在父级作用域中也没有找到继续向上查找,直到找到全局作用域,如果还没有就放弃。
这种一层一层作用域嵌套关系,就是作用域链。
三、闭包(closure)
- 自由变量:一个变量在当前作用域没有定义,但被使用了,则向上级作用域,一层一层依次寻找,直至找到位置 ,如果全局作用域都没找到,则报错xx
is not defined - 理解:函数嵌套函数,内部函数被外部函数返回并保存下来,就会产生闭包。闭包让你可以在一个内层函数中访问到其外层函数的作用域;能够访问其他函数内部变量的函数,被称为闭包
- 作用:延伸了变量的作用范围;创建私有环境。
- 使用场景:防抖、节流,函数嵌套函数避免污染全局的时候。
- 特点:(1)可以重复利用变量,并且这个变量不会污染全局的一种机制;
(2)这个变量是一直保存在内存中,并不会被垃圾回收机制回收。
(3)每个闭包都有独立的词法作用域(面向对象编程,数据的隐藏和封装) - 缺点:闭包较多的时候,会消耗内存,导致页面性能下降,在IE浏览器中会导致内存泄漏。
function foo() {
var a = 3;
function result() {
console.log(a);
}
return result;
}
var test = foo();
test();
例如上述例子,理论上说,foo函数作用域隔绝了外部环境,所有变量引用都在函数内部完成,foo运行完成以后,内部的变量就应该被销毁的,但是因为使用了闭包,有一个全局作用域的变量test在引用foo内部的result函数,result函数引用始终为3,垃圾运行机制就无法把它销毁。所以闭包本身会造成内部变量常驻内存。
四、闭包相关应用:
1、防抖、节流:(防抖是延迟执行;节流是间隔执行)
(1)防抖:
- 理解:将短时间内多次触发的同一件事实现为在一定时间内只执行一次函数。
- 原理:设置一个定时器,约定在xx毫秒后再触发事件处理,每次触发都会直到xx毫秒内无第二次操作。
- 应用场景:
a、scroll滚动触发;
b、输入实时查询;
c、表单验证;
d、按钮提交。 - 示例代码:
let telInput = document.querySelector("input");
telInput.addEventListener(
"input",
debounce(() => {
console.log("调用查询功能呢");
console.log(new Date().getSeconds());
}, 3000)
);
function debounce(fn, wait) {
let timeOut = null;
return function () {
if (timeOut) clearTimeout(timeOut);
console.log(new Date().getSeconds());
timeOut = setTimeout(fn, wait);
};
}
(2)节流:
- 理解:一定时间内调用一次。(间隔执行)
- 原理:设置一个定时器,约定xx毫秒后执行事件,如果时间到了,那么执行函数并充值定时器。
和防抖的区别在于,防抖每次触发事件都重置定时器,而节流是在定时器时间到了之后再清空定时器。 - 应用场景:
a、提交表单
b、高频监听事件 - 代码示例:
//节流
let box = document.querySelector(".box");
box.addEventListener(
"touchmove",
throttle(() => {
console.log("节流函数执行");
}, 2000)
);
function throttle(event, wait) {
let timeOut = null;
return function () {
if (!timeOut) {
timeOut = setTimeout(() => {
event();
timeOut = null;
}, wait);
}
};
}
2、vue 中的data是个函数 也是利用了闭包的思想
保证每一个组件都有一个私有的作用域,避免了数据的相互污染。
参考:
https://blog.csdn.net/weixin_44247866/article/details/128043543
https://blog.csdn.net/qq1365996192/article/details/123645340
https://blog.csdn.net/weixin_49172439/article/details/125794014