作用域和闭包

第一章:匿名函数

1
2
3
4
5
6
 var foo =function(){
console.log("我是匿名函数");
}
//我们可以把变量 f 当做一个函数名来调用
foo();
=>我是匿名函数

匿名函数的自执行

作用:把全局的变量封装成局部的 , 保证你的数据不会被不小心更改

1
2
3
4
5
6
7
(function(){
var a = 10;
})();
或者
(function(){
var a = 10;
}());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
alert(a)
a();
var a=3;
function a(){
alert(10)
}
alert(a)
a=6;
a();

=> function a(){
alert(10)
}
=>3
=>6
=>报错,a已经被重新赋值了,不是一个函数

第二章:作用域量

2.1区分全局变量和局部变量:

​ 是否在函数内声明

1
2
3
4
5
6
7
8
9
10
11
1:
console.log(a);
console.log(typeof a);
function foo(){
a = 20; //若没有声明而直接赋值,这个变量就会自动为全局变量,若在严更模式下则会报错
}
foo();
console.log(a);
=>报错 //没有a这个元素
=>undefined //typeof 是个安全的测试方式,就算没有这个元素也不会报错
=>20; //返回的是a这个全局变量
1
2
3
4
5
6
7
8
9
2:	在函数中访问重名的全局变量
var a = 20;
function(){
var a = 10;
console.log(a);
console.log(window.a); //可以理解为全局变量a是window的一个属性
}
=>10; //访问的是局部变量a
=>20; //访问的是全局变量a

2.2块级作用域

1
2
3
4
5
6
var m = 10;
if(m==10){
var n =20;
}
console.log(n);
=>20

2.3执行环境

每个执行环境中都有一个变量对象,这个变量对象决定了变量或者函数能够访问的范围

1
2
3
4
5
var a = 10;	
console.log(a);
//在全局的环境下声明了a,那么在全局的执行环境中就有一个变量对象,这个变量对象把执行环境中定义的变量存储为这个变量对象的一个属性,
//访问对象就相当于访问变量对象的一个属性
//在全局的执行环境下,变量对象就是window对象
1
2
3
4
5
6
7
function foo(){
var a = 20;
console.log(a);
}
//在函数中声明了a,此时a就是局部变量,那么此时在函数的这个执行环境中也有了一个变量对象,这个变量对象在执行环境中定义的变量存储在这个变量对象的一个属性
//访问对象就相当于访问变量对象的一个属性
//在局部的执行环境下,变量对象就是活动对象

2.4作用域链

作用域链与一个执行环境相关,作用域链用于变量查找.

作用:函数内查找变量从里向外查找就是因为作用域链的存在.

每个函数都有一个属性[[scope]],这个属性是不可见的,它指向的是作用域链,作用域链实际也是一个对象,类似于数组,作用于域链中存储的是与每个执行环境相关 变量对象 (函数内部也是活动对象)

内部函数可以访问外部函数的局部变量,即使已经执行结束,是因为作用域链的存在

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
var a = 10;
function foo(){
var a = 20;
function inner(){
console.log(a);
}
return inner;
}
var f = foo();
f();
=> 20 //此时foo()的返回值也就是函数inner()赋值给f,但f还是能够访问到foo中的局部变量a;

若:
var a = 10;
function foo(){
var a = 20;
function inner(){
console.log(a);
}
a = 30;
return inner;
}
var f = foo();
f()
=> 30

第三章:闭包

广义上的闭包:

​ 只要一个函数,访问这个函数外的变量,那么就这个函数就是一个闭包;

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
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<script>
var btns = document.querySelectorAll('button');
for(var i = 0;i<btns.length;i++){
btns[i].onclick=function(){
console.log(i);
}
}
=>3
=>3
=>3
//输出了3个3,是因为点击事件的匿名函数访问的是他外部的局部变量i,并且这个i是最新的(也就是i=3),所以输出的全是3;

//解决方案一:利用闭包
for(var i = 0;i<btns.length;i++){
btns[i].onclick=(function(i){
return function(){
console.log(i); //用一个匿名函数包住,并且返回的也是一个函数,这样返回的函数它访问的局部变量就是它外部的函数(onlick=后面的函数)内的变量i;
}
})(i);
}
=>0;
=>1;
=>2

//解决方案二:用一个变量来存储每次循环的i
for(var i = 0;i<btns.length;i++){
btn[i].index = i;
btn[i].onclick = function(){
console.log(this.index);
}
}
=>0;
=>1;
=>2
//解决方案三: 将var 改为let

狭义上的闭包:

​ 如果一个函数,访问了他的外部函数的局部变量,那么这个函数就是一个闭包;(可以理解为这个函数是在一个函数内声明的函数,即嵌套在一个函数内的函数)

1
2
3
4
5
6
7
8
9
10
11
12
如:					
function foo(){
var b = 20;
function inner(){ //这个匿名函数就是foo函数内部声明的函数;
console.log(b); //它可以访问到他的外部函数(也就是foo函数)的局部变量b,并且这个局部变量是最新的
}
b = 30;
return inner;
}
var f = foo();
f();
=>30; //访问的是最新的局部变量

闭包的特点

​ 闭包可以访问它的外部函数作用域内的任何的变量,而且访问到的变量的值一定是最新的,即使外部函数执行结束了,也能访问外部函数的局部变量.

可以用闭包封装返回值

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
function foo(){
var p = {
name:"李四",
age:40
}
return {
getName:function(){
return p.name;
}
getAge:function(){
return 20;
}
setName:function(name){
p.name = name;
}
addAttr:function(key,value){
p[key] = value;
}
getAttr:function(key){
return p[key];
}
}
}
var p = foo();
console.log(p.getName()); =>李四
console.log(p.getAge()); =>20 //返回值传的是谁就返回谁,达到输出假的值的效果;

p.setName("王先生");
console.log(p.getName()); =>王先生

p.addAttr('sex','男');
console.log(p.getAttr('sex')); =>男

第四章:高阶函数

如果一个函数接受一个或多个函数作为参数,或者可以返回一个函数,则这样的函数就叫高阶函数

自己本身不调用,等到别人来调用

1
2
3
4
5
6
function  foo(f) {
f();
}
foo(function(){
console.log('我是传给别人函数');
})

常见的高阶函数

1.map( )

作用:对数组中的每一项都做出相应的改变并输出改变后的数组;

Array.map(function(){});

map( )内可以传入任何的函数

比如想将将 var arr = [1,2,3,4,5] 数组中的每一项都平方得到一个新的数组[1,4,9,16,25];

( )内可以传入一个函数,如传入一个计算平方的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//若是利用常规方法来将数组arr的每一项都求平方并输出:
var arr = [1,2,3,4,5];
var arr1 = [];
for(var a of arr){
arr1.push(a*a);
}
consoloe.log(arr1);

//使用map()方法:
var arr = [1,2,3,4,5];
var arr1 = arr.map(function(a){ //()内传入一个计算平方的函数,a和上面的a一样,代表的是数组中的每一项;
return a*a;
})
console.log(arr1);
=> [1,4,9,16,25] //返回的是一个数组,数组中的每一项都被平方了

也可以传入Math内置的方法 sqrt( ) ,一个用于计算开平方的函数

1
2
3
var arr = [1,2,3,4,5];
console.log(arr.map(Math.sqrt));
=>[1, 1.4142135623730951, 1.7320508075688772, 2, 2.23606797749979] //返回的是一个数组,数组中的每一项都被开平方了
2.reduce( )

Array.reduce(function(sum,ele,index,array){

},sum0)

sum 归总

sum0归总的初始值

ele 数组中的每一个元素

index 数组汇总每一个元素的下标

1
2
3
4
5
6
var arr = [0,1,2,3];
var value = arr.reduce(function(sum,ele,index){
return sum*ele; //数组中的每一项相乘,并输出相乘之后的积sum
})
console.log(value);
=>6;
3.filter( )

过滤

并不会更改原数组,而是取到你想得到的元素并返回一个数组;

1
2
3
4
5
6
7
var arr = [1,2,3,4,5];
var a1 = arr.filter(function(ele,index,array){
return ele%2==0; //获取数组中的所有值为偶数的项
return index%2==0; //获取数组中所有下标为偶数的项
})
console.log(a1);
=>[2,4]

评论