作用域和闭包

发布时间 2023-11-06 20:53:34作者: unuliha

一、作用域

var可以重复声明,重复声明时实际是跳过声明处理,继续执行赋值操作。

宽松模式下,a=2如果找不到a的声明,会在全局声明一个a;严格模式下严格模式禁止自动或隐式地创建全局变量。

function foo(a) {
    console.log(a + b);
    b = a;
}
foo(2); // ReferenceError: b is not defined,直接使用b时作用域内找不到b,所以报引用错误

function foo(a) {
    console.log(a + b);
    let b = a;
}
foo(2); // ReferenceError: Cannot access 'b' before initialization, let声明不会变量提升

function foo(a) {
    console.log(a + b);
    var b = a;
}
foo(2); // NaN, var声明存在变量提升,用b时未赋值但已声明,不会报错,返回undefined

function foo(a) {
    var b = a;
}
foo(2);
console.log(b); // ReferenceError: b is not defined, var在foo函数内部定义的变量,只在foo内部能访问

function foo(a) {
    b = a;
}
foo(2);
console.log(b); // 2,b是定义在全局的,外部能够访问到

function foo(a) {
    b = a;
}
var b = 3;
foo(2);
console.log(b); // 2,foo内部修改了外部的b

function foo() {
    function bar(a) {
      i = 3;
      console.log(a + i);
    }
    for (var i=0; i<10; i++) {};
    bar(2); // 5,这里的i是foo内部for循环定义的i
}
foo();
console.log(i); // ReferenceError: i is not defined,外部访问不到内部的i

function foo() {
    function bar(a) {
      i = 3;
      console.log(a + i);
    }
	bar(2);
}
foo(); // 5
console.log(i); //3,i在全局定义

(function foo(){
    var a = 3;
    console.log(a);
})();
foo; //ReferenceError: foo is not defined,上面为函数表达式,外部访问不到foo

a=1;
var b=2;
let c=3;
console.log(window.a,window.b,window.c);; // 1,2,undefined,let声明的变量不会挂到window上

function process(data) {
    // 在这里做点有趣的事情
}
// 在这个块中定义的内容完事可以销毁!因为someReallyBigData只在块作用域内有效
{
    let someReallyBigData = { .. };
    process(someReallyBigData);
}
var btn = document.getElementById("my button");
btn.addEventListener("click", function click(evt){
    console.log("button clicked"); // click形成了一个覆盖整个作用域的闭包
}, /*capturingPhase=*/false );

foo();
function foo() {
    console.log(a); // undefined
    var a = 2;
}
console.log(a); // undefined,a在foo函数作用域内

foo(); // TypeError,能找到foo,因为变量提升,但是是undefined,直接调用报类型错误
bar(); // ReferenceError,表达式后面的具名函数不能被提升
var foo = function bar() {
  // ...
};

foo(); // 1
var foo; // 函数声明优先级高于变量,重复声明被忽略
function foo() {
  console.log(1);
}
foo = function() {
  console.log(2);
};

foo(); // 3 可重复赋值声明,后面会覆盖前面
function foo() {
    console.log(1);
}
var foo = function() {
    console.log(2);
};
function foo() {
    console.log(3);
}

二、闭包

无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

function foo() {
    var a = 2;
    function bar() {
      console.log(a);
    }
    return bar;
}
var baz = foo(); // 因为foo内部的bar被传出,bar引用了变量a,引擎不知道什么时候会用到a,因此不会销毁foo的内部作用域
baz(); // 2 bar()依然持有对foo内部作用域的引用,而这个引用就叫作闭包。

for (var i=1; i<=5; i++) {
    setTimeout(function timer() {
      console.log(i);
    }, i*1000 );
} // 以每秒一次的频率输出五次6,放入event loop时i*1000已经被转换为时间,但最后打印的i用的是全局定义的

for (var i=1; i<=5; i++) {
    (function() {
      setTimeout(function timer() {
          console.log(i);
      }, i*1000 );
    })();
} // 效果同上,空IIFE里面找不到i,同样是去外部找

for (var i=1; i<=5; i++) {
    (function() {
      var j = i;
      setTimeout(function timer() {
          console.log(j);
      }, j*1000 );
    })();
} // 每隔一秒依次输出递增的i

for (var i=1; i<=5; i++) {
    (function(j) {
      setTimeout(function timer() {
          console.log(j);
      }, j*1000 );
    })(i);
} // 结果同上,正常执行

for (var i=1; i<=5; i++) {
    let j = i; // 是的,闭包的块作用域!
    setTimeout( function timer() {
      console.log(j);
    }, j*1000 );
} // 每次循环创建一个单独的块,存放循环体的内容,因此在块内部j是唯一的

for (let i=1; i<=5; i++) {
    setTimeout(function timer() {
      console.log(i);
    }, i*1000 );
} // 效果同上,正常执行