Yapei Li

专注于前端领域

0%

js高阶3、闭包,作用域,i++,++i

作用域【scope】又叫 【栈内存】

全局作用域:window

私有作用域:函数执行形成私有作用域

块级作用域:使用let 创建变量存在块级作用域

作用域链:当前作用域代码执行的时候遇到一个变量我们首先看一下它是否属于私有变量
如果是当前作用域的私有变量,那么以后再私有作用域中再遇到这个变量都是操作私有的(闭包:私有作用域保护私有变量不受外界干扰);
如果不是私有变量,向其上级作用域查找,也不是上级作用域私有的,继续向上查找,一直找到window全局作用域为止,我们把这种向上一级级查找的机制叫做作用域链
如果全局下有这个变量,操作的就是全局的;
如果全局下没有这个变量:1、设置:给全局对象window增加了属性名;2、获取:报错

查找私有变量

js中有且只有两种私有变量

1. 在私有作用域变量提升阶段声明过的变量或者函数
2. 形参也是私有变量
1
2
3
4
5
6
7
8
function fn(num1,num2){
var total=num1+num2;
return total;
}
var result=fn(100,200)

//totle num1 num2 是私有的
//result fn 是全局的

函数执行的步骤

函数执行形成一个新的私有作用域 ,在私有作用域内:

    1. 形参赋值(附带变量声明与赋值)
    1. 变量提升
    1. 代码自上而下执行
    1. 当前栈内存(私有作用域) 销毁或者不销毁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//=>1.全局下的变量提升:var x;var y; var z; fn=aaafff111;
var x=10,
y=20,
z=30;
function fn(x,y){
//=>私有作用域
//=>3.形参赋值 var x=10 var y=20 (声明x/y都是私有变量)在函数内所有的xy都是私有变量
//变量提升: 按理说应该var x 应该重新变量提升,但是形参x已经存在所以忽略var x,x是10而不是undefined
console.log(x,y,z) //=> 10 (私有的x) 20(私有的y) 30 (z是全局变量)
var x=100;//私有的x=100,不再var
y=200;//私有的y=200
z=300;//全局的z=300
console.log(x,y,z) //100 200 300
}
fn(x,y,z) //=>2.fn执行传递的是实参(实参都是值)fn(10,20,30)
console.log(x,y,z) //10 20 300
1
2
3
4
5
6
7
8
9
10
11
12
13
function fn(b){
//=>私有作用域
//=>形参赋值:var b=1(私有变量);
//=>变量提升:忽略声明function b,但是重新给b赋值(此处赋值操作替换了形参赋值的1)
console.log(b) =>函数本身
function b(){
//私有作用域
//形参赋值、变量提升都没有
console.log(b) //通过作用域链找到上级作用域中的b 输出函数本身
}
b();
}
fn(1)
1
2
3
4
5
6
7
8
function fn(b){
//=>私有作用域
//=>形参赋值:b=1(私有变量);
//=>变量提升:忽略声明b并且没有赋值
console.log(b); // 1
var b=10;
}
fn(1)

如何查找上级作用域:看是在哪定义的

函数执行形成一个私有的作用域(A),A的上级作用域是谁和他在哪执行的没关系,主要看他是在哪定义的,在哪个作用域下定义的,当前A的上级作用域就是谁;

1
2
3
4
5
6
7
8
9
var n=10;
function sum(){
console.log(n)
}
sum() //=>10
~function(){
var n=100;
sum() //=>10 sum 的宿主环境是当前自执行函数形成的私有作用域
}()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//=>window 全局作用域
//=>变量提升 var n; var obj;
//代码执行
var n=10; //=>n=10
var obj={ //=> obj=AAAFFF000 一个内存空间
n:20, //n:2
fn:(function(){ //自执行函数执行的返回结果结果赋值给fn
//形参赋值 没有
//变量提升 var n;
var n=30; //n=30
return function(){
console.log(n)
}
//=> return AAAFFF111
//return 函数(函数是引用数据类型的,所以也是先开辟堆内存,返回堆内存的地址)
})()
//=>fn=AAAFFF111
}

obj.fn() // =>30

{}只是堆内存块级作用域, 不是作用域(作用域必须是函数执行形成) 所以自执行函数的上级作用域是 全局window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//=>window 全局作用域
//=>变量提升 var n; var obj;
//代码执行
var n=10; //=>n=10
var obj={ //=> obj=AAAFFF000 一个内存空间
n:20, //n:2
fn:(function(){ //自执行函数执行的返回结果结果赋值给fn
return function(){
console.log(n)
}
//=> return AAAFFF111
//return 函数(函数是引用数据类型的,所以也是西安开辟堆内存,返回堆内存的地址)
})()
//=>fn=AAAFFF111
}

obj.fn() // =>10

//obj={} {}只是堆内存 不是作用域 所以只执行函数的上级作用域是 全局window
1
2
3
4
5
6
7
8
9
10
11
12
13
var n=10;     //=>n=10
var obj={ //=> obj=AAAFFF000 一个内存空间
n:20, //n:2
fn:(function(n){ //自执行函数执行的返回结果结果赋值给fn
return function(){
console.log(n)
}
})(obj.n) //=> 内存地址在自执行函数执行完才被赋值给变量,此时obj 是 undefined 所以 obj.n 报错

}

obj.fn() //报错 n of undefined

闭包(自执行函数)

函数执行,形成一个私有作用域,保护里面的私有变量不受外界的干扰,这种保护机制就叫**闭包**

但是现在也有很多人认为,函数执行,形成一个不销毁的私有作用域,除了保护私有变量以外,还可以存储一些内容,这样的模式才叫闭包

1
2
3
4
5
6
var utils=(function(){

return {

}
})();

闭包作用(保护):函数执行形成私有作用域,保护里边的私有变量不受外界干扰

真实项目中,我们利用这种机制,实现团队协作开发(避免了多人同一命名,导致代码冲突的问题)

团队协作开发,每个开发者把自己的代码存放在一个私有的闭包当中,防止相互冲突;把需要供别人使用的方法,通过return 或者 window.XXX暴露在全局下即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//=>A
~function(){
//=>A写的代码
function fn(){

}
window.fn=fn
}();
//=>B
~function(){
//=>B写的代码
//B想要调取A写的fn 在A写的代码中写上 window.fn=fn
window.fn() //执行的是A的代码
}();

jQuery源码中也是利用保护机制实现的

1
2
3
4
5
6
7
~function(){
var jQuery=function(){
//...
}

window.$=window.jQuery=jQuery;
}()

闭包作用(保存):形成了不销毁的私有作用域(栈内存),保存里边的内容

函数执行形成一个私有作用域,函数执行完成之后,形成的这个栈内存一般情况下都会自动释放

但是还有特殊情况:函数执行完成,当前私有作用域(栈内存)中的某一部分内容被栈内存以外的其他东西(变量/元素的事件)占用了,当前的栈内存就不能释放掉,也就形成了不销毁的私有作用域(里边的私有变量也不会销毁)(返回了引用数据类型的数据 并被外界引用)

函数执行形成一个私有作用域,如果私有作用域中的部分内容被以外的变量占用了,当前作用域不销毁

形式

函数执行反回了一个引用数据类型堆内存地址(并且堆内存属于这个作用域),在外面有一个变量接受了这个返回值,此时当前作用域就不能销毁(想要销毁,只需要让外面的变量赋值为null,也就不占用当前作用域中的内容了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function fn(){
var i=1;
return function (n){
console.log(n+i++);
}
}
var f=fn(); //fn执行完以后 本该销毁 但是fn内的函数被外面的f占用了所以 fn执行开的栈内存不销毁 `fn是一个不销毁的栈内存 `
f(10) //=>11
fn()(10) //fn()重新开辟一个内存 11
f(20) //22
fn()(20) //21

function fn(i){
return function (n){
console.log(n+(++i));
}
}
var f=fn(10);
f(20) //31
fn(10)(20) //31
f(30) //42
fn(20)(10) //31
f(40) //53
//fn每次执行是重新把所有流程再走一遍(形参赋值 代码提升),和别的fn执行无关

函数执行后return一个对象或者函数(引用数据类型) 返回了一个地址 ,并被外面占用(赋值给了外面的变量)那么这个函数执行形成的栈内存就不销毁了,如果return的是一个基础数据类型则销毁

fn每次执行重新把所有流程再走一遍形参赋值 变量提升),和别的fn执行无关

选项卡闭包解决办法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//选项卡
var tabBox=document.getElementById('tabBox');
var oList=tabBox.getElementsByTagName('li');
var oDivList=tabBox.getElementsByTagName('div');

function changeTab(index){
for( var i=0;i<oList.length;i++){
oList[i].className=oDivList[i].className=null;
}
oList[index].className=oDivList[index].className='select';
}
for(var i=0;i<oList.length;i++){
//直接传i 不行的原因:
// 只是给每一个Li的点击事件 未执行(点击的时候才执行) 存储的就是字符串 所以传i不行
//页面渲染出来 循环就已经结束了 所以 点击li时 i永远等于 oList.length 而且i时全局的;点击的时候执行方法,形成一个私有的作用域,用到了变量i,i不是私有的变量,想全局查找,此时全局的i已经是最后循环的oList.length了

//****所有的事件绑定都是异步编程(绑定方法时并没有执行),当触发点击事件,执行方法时,循环早已经结束
oList[i].onclick=function(){
changeTab(i) //i 不行
}
}

//自定义属性
for(var i=0;i<oList.length;i++){
var oList[i].myIndex=i;
oList[i].onclick=function(){
changeTab(this.myIndex)
}
}

//加一层不销毁的私有作用域 利用闭包
//提高了堆内存的使用 性能不好
for(var i=0;i<oList.length;i++){
oList[i].onclick=(function(i){
//自执行的函数形成的作用域 不销毁
//因为 这个自执行函数把一个引用数据类型赋值给了 li 的点击事件
return function(){
changeTab(i)
}
})(i)
}
for(var i=0;i<oList.length;i++){
~function(i){
//自执行的函数形成的作用域 不销毁
//因为 这个自执行函数把一个引用数据类型赋值给了 li 的点击事件
oList[i].onclick=function(){
changeTab(i)
}
}(i)
}

// 不考虑兼容的情况下 es6中的块级作用域
for(let i=0;i<oList.length;i++){
oList[i].onclick=function(){
changeTab(i)
}
}

obj={} {}只是堆内存 不是作用域、

window既不是关键字也不是保留字

i++和++i的区别

i++:先拿原有i的值和其他值进行运算,运算完成后再自身累加,加括号也是先运算再累加

++i:先自身累加1,然后拿累加完的值和其他值进行运算

1
2
3
4
var i=5;
console.log( 10 + (++i) + (i++) + 5 + (i++) + (++i))
10 + 6 + 6 + 5 + 7 + 9
I=6 I=7 I=8 I=9
1
2
3
4
5
6
7
8
var i=5;
console.log(5+i++) =>10 5+(i++) //加括号也是先运算再累加1
console.log(i) => 6

var i=5
console.log(5+++i) => 报错
console.log(5+ ++i) => 11
console.log(5+ (++i)) => 11