一、作用域
原理:
- 作用域 => 房子 => 除了对象的{}都构成一个作用域
- 作用域 => 为了区别变量.不同作用域内声明的变量是各不相同的.(就算名字相同).
作用域语法:
- let x = 10; (全局变量).
- if () {块级作用域 let y = 20; (局部变量)}
- for () {块级作用域 let y = 20; (局部变量)}
- switch () {块级作用域 let y = 20; (局部变量)}
- function () {函数作用域 let y = 20; (局部变量)}
- let obj = {不是作用域}
作用域分类
- 全局作用域 => script内部的区域 (局部作用域外面的区域)
- 局部作用 => {}内的区域.
a: 块级作用域 => 函数{}之外的其他大括号.
b: 函数作用域 => 函数{}内部.
- 全局变量: 声明在全局作用域内的变量 => 全局变量不安全, 能不用尽量不用.
- 局部变量: 声明在局部作用域中的变量
二、变量可见性
全局变量任何地方都可以访问.
- 局部变量只能在本局部作用域中访问.
- 全局作用域中无法访问局部变量
- 局部作用域A不能访问局部作用域B中的变量
- 子作用域可以访问父作用域中的变量。
复制代码
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// 全局变量任何地方都可以访问. let z = 10000; // 第一个局部作用域 { let x = 10; // 不能访问其他局部作用域内的变量 // console.log(y); // 可以访问全局变量 // console.log(z); // 可以访问本作用域内的变量 console.log(x); } // 全局作用域中不能访问局部作用域中的变量 // console.log(x); // 第二个局部作用域 { let y = 20; // 不能访问其他局部作用域内的变量 // console.log(x); // 可以访问全局变量 // console.log(z); // 可以访问本作用域内的变量 console.log(y); } // 全局作用域中不能访问局部作用域中的变量 // console.log(y); { // 祖父作用域 let n = 200; { // 父作用域 let m = 100; { // 子作用域内可以访问父作用域内的变量. console.log(m); // 子作用可以访问所有祖先作用域内的变量. console.log(n); } } }
三、作用域链
- 作用域 => 房子 => 地点
- 作用域链 => 作用域套作用域 => 地点链
- 作用域链 => 保证变量的有序访问.(是一种机制)
列如:
广东省广州市海珠区。。。
复制代码
1
2
3
4
5
6
7
8
9
10// let x = 300; { // let x = 200; { let x = 100; { console.log(x); } } }
四、变量查找
我们也可以利用作用域链进行变量查找:
1: 写出作用域链.(使用变量所在代码行的作用域链)
2: 沿着这个作用域链查找变量声明(形参也算声明),找到就停止查找.(就近原则),
如果找到全局作用域就没有变量声明,就报错
- 全局作用域是所有作用域链的顶级作用域.(任何一个作用域链的最后一个作用域都 是全局作用域)
- 函数的形参相当于是声明在函数内部的.
- function fn(x) {}
复制代码
1
2
3
4
5
6
7
81: let x = 10; (全局的x) 2: fn(); let x = 20; (fn内的x) add(); x += 10; (把fn内的x变成30) console.log(x); (打印fn内的变量x,30); console.log(x); (打印fn内的变量x,30);
复制代码
1
2
3
4
5
6
7
8
9
10
11
12let x = 10; function fn () { let x = 20; function add() { x += 10; // add => fn => 全局 console.log(x);// add => fn => 全局 } add(); console.log(x);// fn => 全局 } fn();
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14let x = 10; function fn () { let x = 20; function add(x) { // 这里的x在进行变量查找时,找到的是add内的形参x,而不是fn内的x.(就近原则) x += 10; console.log(x); // 30 } add(x); // 这里打印的x,是fn内的x.fn内的x没有被修过过,因此还是20. console.log(x);// 20 } fn();
五、变量提升
预解析 => (*)
程序运行之前,浏览器会把函数声明和var声明的变量提升到本作用域的最前面. (变量提升)
要点:
- 1:只会提升函数声明和var声明.只提升声明部分,赋值部分不提升.
- 2:如果var变量和函数同名,则提升后,函数覆盖变量.
- 函数声明可以写在函数调用之后,因为预解析时,浏览器会把函数声明放到最前面.
复制代码
1
2
3
4
5
6
7console.log(x); // undefined var x = 10; // 以上代码预解析之后,变成下面的代码: var x; console.log(x); x = 10;
复制代码
1
2
3
4
5
6
7
8console.log(x); // 函数 function x() {}; var x = 10; var x; function x() {}; console.log(x); x = 10
复制代码
1
2
3
4
5
6
7// var a是在fn内声明的,因此只能提升到fn的最前面. function fn() { console.log(a); // undefined var a = 10; } fn(); console.log(a);// 报错.因为a不会提升到全局作用域,这里变量查找找不到a的声明,会报错.
六、块级作用域
- 块级作用域内用var声明的变量,变量提升时,会提升到块级作用域的外面。
- var 不能识别块级作用域.
- 块级作用域是ES6提出的新特性,var不是ES6的语法,因此识别不了块级作用域。
- 函数声明也不识别块级作用域.
- 只有在函数作用域内的var变量和函数才是局部变量.
块级作用域和函数作用域的区别
- let 和 const 能识别块级作用域。var不行。
- 块级作用域是ES6新增的特性。
- 用var 在块级作用域中声明变量,不构成局部变量。
复制代码
1
2
3
4
5
6
7
8
9
10{ console.log(x);// undefined // 块级作用域内用var声明的变量会变成全局变量. var x = 10; // 块级作用域内的函数声明,也会提升到块级作用域的外面. function fn() {} } console.log(x); console.log(fn);
七、循环中的let和var
复制代码
1
2
3
4
5
6<body> <ul> <li>1111</li> <li>2222</li> </ul> </body>
循环中var和let:
- 循环用let声明i,则这个i是一个局部变量.
- 循环多少次,就有多少个块级作用域,每个作用域内有一个不同的i.
- 第一个i是0,第二个i是1,以此类推...
- 循环用var声明i,则这个i是一个全局变量.
- 不管循环多少次,修改的都是全局变量的i.
- 循环时操作元素,但凡在事件中使用了i,如果这个i是var声明的,则事件中访问到的i必定 是循环结束之后的i.
- 循环结束之后的i,必定超出了数组的最大下标.
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22let aLi = document.querySelectorAll('li'); // 循环中有事件,事件中如果使用了循环的i,则不能用var声明。 for (var i = 0; i < aLi.length; i++) { aLi[i].onclick = function() { alert(aLi[i].innerText) } } { let i = 0; // 第一个li的点击事件 aLi[i].onclick = function() { alert(aLi[i].innerText); // 第一个匿名函数 => 第一个块级作用域 => 全局作用域 } } { let i = 1; // 第二个li的点击事件 aLi[i].onclick = function() { alert(aLi[i].innerText); // 第二个匿名函数 => 第二个块级作用域 => 全局作用域 } }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let aLi = document.querySelectorAll('li'); var i = 0; { // 第一个li的点击事件 aLi[i].onclick = function() { alert(aLi[i].innerText); // 第一个匿名函数 => 第一个块级作用域 => 全局作用域 } } var i = 1; { // 第二个li的点击事件 aLi[i].onclick = function() { alert(aLi[i].innerText); // 第二个匿名函数 => 第二个块级作用域 => 全局作用域 } }
- 循环结束之后, i的值变成2. aLi[2]是不存在的.是undefined
- 当执行aLi[2].innerText时,就会报错.
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14let aLi = document.querySelectorAll('li'); for (var i = 0; i < 2; i++) {} { // 第一个li的点击事件 aLi[0].onclick = function() { alert(aLi[i].innerText); // 第一个匿名函数 => 第一个块级作用域 => 全局作用域 } } { // 第二个li的点击事件 aLi[1].onclick = function() { alert(aLi[i].innerText); // 第二个匿名函数 => 第二个块级作用域 => 全局作用域 } }
八、this
复制代码
1
2
3
4<ul> <li>1111</li> <li>2222</li> </ul>
复制代码
1
2
3
4
5
6
7
8let aLi = document.querySelectorAll('li'); // 循环结束,i变成2,导致aLi[i]是undefined for (var i = 0; i < aLi.length; i++) { aLi[i].onclick = function() { alert(aLi[i].innerText) } }
复制代码
1
2
3
4
5
6
7
8
9let aLi = document.querySelectorAll('li'); // 事件句柄内的this => 表示触发事件的标签.(点谁它就是谁) for (var i = 0; i < aLi.length; i++) { aLi[i].onclick = function() { // console.log(this.innerText); alert(this.innerText); } }
复制代码
1
2
3
4
5
6
7
8
9
10
11let aLi = document.querySelectorAll('li'); for (var i = 0; i < aLi.length; i++) { // 通过自定义属性存储下标。(第一个li存0,第二个li存1) aLi[i].index = i; aLi[i].onclick = function() { // 通过当前被点击的li,获取对应的下标 let i = this.index; alert(aLi[i].innerText); } }
九、自执行函数
- 自执行函数 => 匿名函数的调用
- 声明的同时,马上调用.
语法:
1:(function(形参){})(实参)
2:(function(形参){}(实参))
复制代码
1
2
3
4
5
6
7
8// 自执行函数 => 模块化开发时,需要通过自执行函数来进行模块化 => 快速创建一个函数作用域 (function () { console.log(100); })(); (function(){ console.log(1000) }());
十、闭包
- 循环用let => 为了用let把下标0和1分别存储到两个块级作用域中.
- 循环用var => 没办法把0和1分半存储到两个块级作用域中.(因为有变量提升).
- var没办法把变量存储到块级作用域中,但是可以存储到函数作用域中.
- 闭包 => 为了把变量存储到函数作用域中.
- 闭包的两个作用 => 存储,保护. => 存储变量,保护变量
- 作用:为了把一个变量存储在函数作用域中.可以在任意时间访问.(存储,保护)
- 表现:函数套函数.子函数使用了复函数内声明的变量.子函数还可以在任何时间调用. (最重 要的条件).
- 原理:为了保证子函数调用时,沿着作用域链,能访问到父函数内的变量.只能不销毁父 函数内的变量.以便子函数在任何时间访问.
- 缺点:滥用多了会有内存泄漏.
- 工作中实际使用:工作里很少用.模块化项目中,每个模块都是一个闭包.
- 闭 => 保护
- 包 => 数据
10.0
复制代码
1
2
3
4<ul> <li>1111</li> <li>2222</li> </ul>
复制代码
1
2
3
4
5
6
7
8
9let aLi = document.querySelectorAll('li'); for (var i = 0; i < aLi.length; i++) { (function(){ var j = i; aLi[j].onclick = function() { alert(aLi[j].innerText) } })(); }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15(function(){ var j = 0; // 第一个li事件 aLi[j].onclick = function() { alert(aLi[j].innerText); // 第一个匿名函数 => 第一个自执行函数作用域 => 全局作用域 } })(); (function(){ var j = 1; // 第二个li事件 aLi[j].onclick = function() { alert(aLi[j].innerText); // 第二个匿名函数 => 第二个自执行函数作用域 => 全局作用域 } })();
10.1
复制代码
1
2
3
4<ul> <li>1111</li> <li>2222</li> </ul>
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<script> let aLi = document.querySelectorAll('li'); // 循环多少次,就有多少个块级作用域,每个块级作用域内的i都是各不一样的. // for (var i = 0; i < aLi.length; i++) { // show(i); // } // 同一个函数调用多少次,就创建多少个函数作用域,每个函数作用域内的变量都是各不相同的. show(0); show(1); function show(j) { aLi[j].onclick = function() { alert(aLi[j].innerText) } } </script>
十一、闭包的构成
闭包的表现形式 => 函数套函数.
闭包的构成条件:
- 1: 一定是函数套函数.
- 2: 子函数使用父函数内声明的变量.
- 3: 子函数可以在任意时间调用.(最重要).
闭包内的变量特性:
- 1:永不销毁 (全局变量的特性).
- 2:只能通过子函数来访问.(局部变量的特征).
面试特别喜欢问闭包:
- 你怎么理解闭包的? => 除了想看你基础好不好,你的表达能力如何.
- 1: 闭包的作用 => 为了把变量存储到函数作用域中.(存储,保护).
- 2: 闭包的原理 => 自行发挥.
- 3: 工作中闭包的实际应用. => 项目中的每个模块都是一个闭包.
这里不是闭包.
- j存储在了fn中了吗? => 并没有
- 函数内的局部变量会在函数调用结束之后,就会被销毁. => 节约内存.
- 全局变量在程序关闭后才会被销毁,全局变量在程序运行期间,不会被销毁.
把fn的返回值赋值给show.则show和fn就同一个函数.
- show是全局变量.show不会被销毁.
- show不会被销毁,则show可以在任意事件调用.
- show每次调用时,都会访问一个变量j.
- 为了保证show每次都能正常的访问j,浏览器就将j一直保存在内存中,不销毁它.
- 只能通过show的调用来访问j.其他方式不行.
- let show = fn();
- j不会被销毁,但是不能直接访问.
- console.log(j);
复制代码
1
2
3
4
5
6// 如何让这个j不会被销毁呢? function fn() { var j = 100; } fn();
复制代码
1
2
3
4
5
6
7
8// fn返回一个匿名函数 function fn() { let j = 10; return function() { console.log(j); } }
最后
以上就是潇洒小懒虫最近收集整理的关于JavaScript进阶(九)的全部内容,更多相关JavaScript进阶(九)内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复