作用域
函数作用域
- 一个函数执行多次后不是只会产生一个变量,而是产生不同地址的变量,变量之间的数据不会共享
- 函数每次调用都会创建一个新作用域
let q = 1
function a() {
let n = 1
function a_b() {
let m = 1
function a_b_c() {
let l = 1
console.log('n--' + ++n);
console.log('m--' + ++m);
console.log('l--' + ++l);
console.log('q--' + ++q);
}
a_b_c()
}
a_b()
}
a() //n--2 m--2 l--2 q--2
a() //n--2 m--2 l--2 q--3
- 如果子函数被使用时,父级环境将被保留,它本身的环境不被保留
let q = 1
function a() {
let n = 1
return function a_b() {
let m = 1
return function a_b_c() {
let l = 1
console.log('n--' + ++n);
console.log('m--' + ++m);
console.log('l--' + ++l);
console.log('q--' + ++q);
}
}
}
let site = a() //n--2 m--2 l--2 q--2
console.log(site);
//site= ƒ a_b() {
// let m = 1
// return function a_b_c() {
// let l = 1
// console.log('n--' + ++n);
// console.log('m--' + ++m);
// …
site()() //n,m,l,q=2
site()() //n,q=3 m,l=2 原因是 a_b()被外部的变量引用了,所以a的环境没有消失
let q = 1
function a() {
let n = 1
return function a_b() {
let m = 1
return function a_b_c() {
let l = 1
console.log('n--' + ++n);
console.log('m--' + ++m);
console.log('l--' + ++l);
console.log('q--' + ++q);
}
}
}
let siteb = a()() //引用a_b_c
siteb() //n,m,l,q=2
siteb() n,m,q=3 l=2 原因是a_b_c()被引用了,所以他的父级及父级以上的环境就被保留了
构造函数中的作用域
构造函数中的方法 (例如 fn())相当于普通函数中 return 了一个fn(),所以fn也是被引用了,所以他的父级的环境被保留了
function con() {
let a = 1
this.b = 1
this.fn = function() {
console.log(++a);
console.log(++this.b);
}
}
let n = new con()
console.log(n); // con {b: 1, fn: ƒ}
console.log(n.a); //undefine 但如果是构造函数中 this.a 等于1,就会有值
console.log(n.b) //1
n.fn() //a=2
n.fn() //a=3
console.log(n.b) //3
let、const、var的作用域
在{}里面就形成了一个作用域,let、const是块级作用域,所以在作用域外部是访问不到他们声明的变量的,var没有块级作用域,它声明的变量在全局作用域里,会被挂载到window对象上,所以可以访问到
{
let aa = 1
}
console.log(window.aa); //undefined
// console.log(aa); //报错
{
var bb = 2
}
console.log(window.bb); //2
console.log(bb); //2
但是在函数作用域里用 var 和 let/const声明的变量,外部的作用域都访问不到
function yaild() {
let cc = 3
var dd = 4
}
console.log(cc); //报错
console.log(dd); //报错
for循环里的作用域(for , for in ,for of ,for each)
for循环也会是一个作用域,和{}一样,在里面声明的var会被挂载到window上面,let则不会
for (var i = 0; i < 4; i++) {
console.log(i);
}
console.log('全局' + i);//4
for (let i = 0; i < 4; i++) {
console.log(i);
}
console.log('全局' + i);//报错
for循环里面的异步操作(定时器,在for循环里面为dom对象添加事件等)
这里用let 声明的i 有块级作用域,for循环了四次就是生成了4个块级作用域,每个块级作用域里的i分别为 0 1 2 3,每个定时器里面的i也是用的每个作用域里的i,所以能正常打印出 0 1 2 3
for (let i = 0; i < 4; i++) {
// console.log(i);
setTimeout(() => {
console.log(i); //0 1 2 3
}, 1000)
}
console.log('全局' + i); //报错
但是用var 声明的i是被挂载到全局作用域上的,定时器里的i用的都是window.i,所以当定时器执行里面的函数的时候,for循环已经结束,i==4了,所以打印出来的结果是4 4 4 4
for (var i = 0; i < 4; i++) {
// console.log(i);
setTimeout(() => {
console.log(i); //4 4 4 4
}, 1000)
}
//这个全局4会被最先打印出来
console.log('全局' + i); //4
如何用var的时候也要达到正常输出的效果呢?可以在定时器外面在套一层函数,也就是将定时器放入一个函数作用域里面,因为函数里面的变量都不会被挂载到全局上
for (var i = 0; i < 4; i++) {
(function fn(a) {
setTimeout(() => {
console.log(a); //0 1 2 3
}, 1000)
})(i) //这里将for循环里的i赋值给fn()里面的a,也就是a 来保存i的值
// console.log(i);
}
//这个全局4会被最先打印出来
console.log('全局' + i); //4
闭包
闭包指子函数可以访问外部作用域变量的函数特性,即使在子函数作用域外也可以访问。就是子函数可以访问父函数的变量,如果没有闭包那么在处理事件绑定,异步请求时都会变得困难。
根据作用域的特性,如果子函数引用了父函数的变量,那么父函数的作用域也不会消失,所以可能会造成内存泄漏的问题