第七章 函数表达式 函数表达式是干什么的?用来创建函数的。创建函数的方法有:函数声明 、函数表达式 。
函数声明,有个特征:函数声明提升 。函数表达式并没有。
函数表达式的语法形式:有两种?一种,创建一个函数表达式,没有给这个函数表达式起名字。这个函数就叫做匿名函数(拉姆达函数) ;另一种,创建一个函数表达式,并给这个函数表达式起了个名字。这个函数就叫做命名函数表达式 。
1.1 匿名函数 举个例子:
1
2
3
4
var sayHi = function ( ) {
alert("Hi!" );
}
1.2 命名函数表达式 举个例子。
先介绍下递归 ,在第五章的函数的属性和方法 中,讲到函数内部的对象arguments的属性callee 时并没有搞懂。arguments是传给函数的参数所在地,callee是指向拥有这个arguments对象的函数(指针)。(callee不知道什么意思,但像caller,召唤者)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
function factorial (num ) {
if (num < 1 ){
return 1 ;
} else {
return num * factorial(num - 1 );
}
}
var anotherFactorial = factorial;
factorial = null ;
alert(anotherFactorial(4 ));
function factorial (num ) {
if (num < 1 ) {
return 1 ;
} else {
return num * arguments .callee(num - 1 );
}
}
var anotherFactorial = factorial;
factorial = null ;
alert(anotherFactorial(4 ));
貌似用arguments.callee很好的解决了递归问题,但是在严格模式下,并不能访问arguments.callee!!!此时用命名函数表达式可以解决这个问题。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var factorial = (function f (num ) {
if (num < 1 ) {
return 1 ;
} else {
return num * f(num - 1 );
}
});
var anoterFactorial = factorial;
factorial = null ;
alert(anoterFactorial(4 ));
alert(f(4 ));
函数表达式定义的函数怎样才能运行?可以让一个指针指向它,或者创建就立即执行。要不像上边的f(num),在全局下就顶是没有定义。
2. 闭包
闭包是指有权访问另一个函数作用域中的变量的函数
再进行下长句分析,闭包是函数。
创建闭包的常见方式:在一个函数内部创建另一个函数。
理解闭包就要理解第四章中的执行环境及作用域 当中的一系列概念。
最基本的概念:执行环境 ,所有“活动”都有其执行环境。最外层的执行环境就叫做全局执行环境 。 每个执行环境都有之关联的变量对象 ,其中保存着在执行环境中定义的变量和函数。浏览器的全局执行环境的变量对象就是window对象 。 每个函数也有自己的执行环境,它的变量对象 也叫做活动对象 ?? 书中说是当代码在一个执行环境中执行时,会创建变量对象的一个作用域链 ,其实感觉应该表达为创建一个该执行环境可以访问到的所有变量对象的一个作用域链 ,该链就把所有变量对象链起来了。(注意这儿是变量对象,是自身及其他执行环境的变量对象,而不是说某个变量、某个函数)
书中P179值得多看几遍。
可以把作用域链看做一个栈,其中保存的都是变量对象。先压哪些变量对象入栈、再压哪些变量对象入栈。
没有闭包,只有一个函数,执行到函数时,对于该函数的作用域链,先压该函数的活动对象(变量对象),再压全局的变量对象。在函数中访问某个变量时,就沿着这个链一步步去查。若是在该函数的活动对象上找见了,就不会再去全局的变量对象上找了。
若有闭包。(闭包 有人理解为 必须是在一个函数内部,并且访问了其包含函数的变量;但按照这书上的定义,闭包有权访问另一个函数上的变量就可。具体见这里 的讨论)对于包含函数和上边的解释一样。对于闭包,闭包的作用域链,先压闭包的活动对象,再压包含函数的变量对象,最后压全局的变量对象。
看几个例子。没有闭包的情况。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
var name = "Window" ;
function getName ( ) {
alert(name);
}
getName();
var name = "Window" ;
function getName ( ) {
alert(name);
var name;
}
getName();
var name = "Window" ;
function getName ( ) {
alert(name);
var name = "Local" ;
}
getName();
var name = "Window" ;
function getName ( ) {
var name = "Local" ;
alert(name);
}
注意在一个执行环境中有了var xxx,那么xxx就已经挂在该执行环境的变量对象上了。要是你在该执行环境中var xxx 之前使用xxx,实际上相当于该执行环境中已经声明了xxx,只不过是还未赋值,所以是undefined。
看几个例子。有闭包。(我理解的函数中的函数?)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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
var name = "Window" ;
function getName ( ) {
alert(name);
return function ( ) {
alert(name);
}
}
getName()();
var name = "Window" ;
function getName ( ) {
alert(name);
return function ( ) {
alert(name);
}
var name = "Local" ;
}
getName()();
var name = "Window" ;
function getName ( ) {
alert(name);
var name = "Local" ;
return function ( ) {
alert(name);
}
}
getName()();
var name = "Window" ;
function getName ( ) {
var name = "Local" ;
alert(name);
return function ( ) {
alert(name);
}
}
getName()();
var name = "Window" ;
function getName ( ) {
alert(name);
return function ( ) {
alert(name);
var name = "Local-Local" ;
}
}
getName()();
var name = "Window" ;
function getName ( ) {
alert(name);
return function ( ) {
var name = "Local-Local" ;
alert(name);
}
}
getName()();
var name = "Window" ;
function getName ( ) {
alert(name);
return function (name ) {
alert(name);
}
}
getName()();
为什么会想这么多。因为今天看到一个题目:1
2
3
4
5
6
7
8
9
var z = 10 ;
function foo ( ) {
console .log(z);
}
(function (funArg ) {
var z = 20 ;
funArg();
})(foo);
开始想成了外边是一个匿名函数,并且立即执行了,把foo传进去,错误的把funArg()当成了匿名函数中存在的一个闭包……以为funArg()先去自身的变量对象上找z,没有;再去其包含函数(function(){…})上找z,找到了,是20,那么就打印出20…… 这个问题还不是说什么情况下才是闭包?而是说函数名只是个指针 。首先声明了一个foo函数,该函数的变量对象上啥也没,但它能访问到全局执行环境的变量对象上的z,是10。而执行匿名函数时,只是相当于 var funArg = foo; funArg也指向了该函数,它并不是外边匿名函数的闭包。实际上如下图所示:
若是像这样的话:1
2
3
4
5
6
7
var z = 10 ;
(function ( ) {
var z = 20 ;
(function ( ) {
console .log(z)
})();
})();
有两个匿名函数,并且都立即执行了。内部的匿名函数的作用域链0号指向自己的活动对象(无z),1号指向外部匿名函数的活动对象(有z),2号指向全局的变量对象(有z 但找不到这儿了)。这儿的匿名函数就是闭包。
2.1 闭包与变量 闭包可以访问到的变量都是存在它的作用域链链起来的各个变量对象 上,并不是某个变量或者某个函数。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 createFunctions ( ) {
var result = new Array ();
for (var i = 0 ; i < 10 ; i++) {
result[i] = function ( ) {
return i;
};
}
return result;
}
var functions = createFunctions();
functions[0 ]();
functions[1 ]();
functions[2 ]();
functions[9 ]();
这就是使用闭包访问变量时要注意的地方。可能和自己想要的结果不同。书中的解决方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function createFunctions ( ) {
var result = new Array ();
for (var i = 0 ; i < 10 ; i++) {
result[i] = function (num ) {
return function ( ) {
return num;
}
}(i);
}
return result;
}
var functions = createFunctions();
functions[0 ]();
functions[1 ]();
functions[2 ]();
functions[9 ]();
这里 大家讨论上边的解法是不是为了闭包而闭包。不用里层的匿名函数,直接返回num就好了。但是我认为,这只是为了统一例子内容。上边两个的result都是存的是函数,而这里的result存的是数字。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createFunctions ( ) {
var result = new Array ();
for (var i = 0 ; i < 10 ; i++) {
result[i] = function (num ) {
return num;
}(i);
}
return result;
}
var results = createFunctions();
result;
2.2 闭包中this对象
匿名函数的执行环境具有全局性,因此其this对象通常指向window。
其实不光是匿名函数,在全局作用域下定义的函数,在其中使用this,也指的是window。应该考虑到对象时才有意思。在对象中定义函数,在这个函数中使用this,那指的就是这个对象的实例。要是在这个对象的函数中还有匿名函数,或者命名函数表达式(即有函数表达式) ?然后再在函数表达式中使用this,就是window而不是对象的实例。
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
var name = "Window" ;
function getName ( ) {
var name = "Local" ;
alert(this .name);
}
var name = "The Window" ;
var object = {
name : "My Object" ,
getNameFunc : function ( ) {
return function ( ) {
return this .name;
};
}
};
alert(object.getNameFunc()());
var name = "The Window" ;
var object = {
name : "My Object" ,
getNameFunc : function ( ) {
return (function f ( ) {
return this .name;
});
}
};
alert(object.getNameFunc()());
这是因为内部函数表达式上的变量对象上已经有了自己的this和arguments,不会再往上找其包含函数的变量对象的this了。
若是想要在闭包中使用包含函数的this:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var name = "The Window" ;
var object = {
name : "My Object" ,
getNameFunc : function ( ) {
var that = this ;
return function ( ) {
return that.name;
};
}
};
alert(object.getNameFunc()());
这种方式之后很常见。刚看了看Vue的例子,里边就有这种使用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
new Vue() {
el:xx,
data:{
xx:xx;
},
methods:{
getAnswer: function () {
// 虽然这里的this 在Vue中已经被处理过了,但这里赋值给vm意思和上边的差不多
var vm = this
if (this.question.indexOf('?') === -1) {
vm.answer = 'Questions usually contain a question mark. ;-)'
return
}
vm.answer = 'Thinking...'
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
}
}
}
书中最后一个例子挺有意思。1
2
3
4
5
6
7
8
9
10
11
12
13
var name = "The Window" ;
var object = {
name : "My Object" ,
getName : function ( ) {
return this .name;
}
};
object.getName();
(object.getName)();
(object.getName = object.getName)();
前两个好理解。注意要把函数名当做是指针!!!
第三个要注意的是括号括起来()是一个表达式,一个赋值表达式,将object.getName(是个指针),赋值给了object.getName(也是个指针),最后这个表达式会返回一个函数,就是后边object.getName所指向的函数!!!(就相当于 var a; 控制台返回undefined,然后 a = 4; 控制台返回4)再跟着使用()就是调用函数,就等于是在全局直接调用了这个函数。所以this是window。
这样画图应该不太对?栈内存应该在一块儿。object用到了它自己的一块。这样画图更清晰一点。
3. 使用函数表达式中匿名函数可以模仿块儿级作用域 块儿级作用域 ,也叫作私有作用域 。比如一个for循环,其他语言会把在其中定义的变量只用在for循环内,循环结束后这些变量也就被销毁了。但是JavaScript中并没有这样的私有作用域。但是可以用匿名函数模拟私有作用域。
定义一个匿名函数,并且立即执行。1
2
3
4
5
6
7
8
(function ( ) {
})();
function ( ) {
}();
这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。一般来说,我们都应该尽量少向全局作用域中添加变量和函数。
4. 私有变量 JS没有私有成员 ?? JS对象属性是公有的 ?? 怎样理解?私有成员说的是什么呢?对象属性,对象的属性?
JS中有私有变量 的概念。私有变量是在函数中定义的变量,所谓私有,是指不能在函数外部访问这些变量。私有变量包括:函数的参数 、局部变量 、在函数内部定义的其他函数 。
想要在函数外访问到这些私有变量,即用一些公有方法 ,访问私有变量,这样的公有方法被称作特权方法 。
可以使用函数表达式 创建特权方法。
4.1 法一:在构造函数中使用匿名函数,闭包,访问私有变量 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
function Person (name ) {
this .getName = function ( ) {
return name;
};
this .setName = function (value ) {
name = value;
}
}
var person = new Person("DaShuaiBi" );
alert(person.getName());
person.setName("HeiHeiHei" );
alert(person.getName());
function Person (name ) {
var name = "局部变量" ;
this .getName = function ( ) {
return name;
};
this .setName = function (value ) {
name = value;
}
}
var person = new Person("DaShuaiBi" + "参数进来的" );
alert(person.getName());
person.setName("HeiHeiHei" );
alert(person.getName());
缺点:和之前使用构造函数创建对象的缺点类似。每个实例的方法都是不一样的,浪费空间。1
2
3
var person2 = new Person("ErShuai" );
person.getName == person2.getName;
4.2 法二:利用匿名函数创建私有作用域,在私有作用域定义私有变量,在其中再创建函数,这些函数就是特权方法了。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(function ( ) {
var name = "" ;
Person = function (value ) {
name = value;
};
Person.prototype.getName = function ( ) {
return name;
};
Person.prototype.setName = function (value ) {
name = value;
}
})();
var person1 = new Person("DaShuai" );
alert(person1.getName());
person1.setName("HeiHeiHei" );
alert(person1.getName());
优点:解决了使用构造函数,在闭包中访问私有变量时,每个实例方法都是独立的。1
2
var person2 = new Person("ErShuai" );
alert(person1.getName == person2.getName);
缺点:每个实例都没有自己的私有变量。就类似使用原型模式创建对象时的问题。但是可惜的是,那时在实例中可以覆盖 掉原型上的同名属性。这里不行了。1
2
alert(person2.getName());
alert(person1.getName());
问,可以像之前使用组合使用构造函数和原型模式创建对象 那样,在这里创建特权方法吗?貌似是不行的,私有变量,特权方法,和之前的对象还是很不一样的。比如1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(function ( ) {
Person = function (name ) {
};
Person.prototype.getName = function ( ) {
return name;
};
Person.prototype.setName = function (value ) {
name = value;
}
})();
这样呢?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Person = function (value ) {
var name = value;
Person.prototype.getName = function ( ) {
return name;
};
Person.prototype.setName = function (value ) {
name = value;
}
};
var person1 = new Person("DaShuai" );
alert(person1.getName());
person1.setName("HeiHeiHei" );
alert(person1.getName());
var person2 = new Person("ErShuai" );
alert(person1.getName == person2.getName);
alert(person2.getName());
alert(person1.getName());
书中还有两部分讲为单例 创建私有变量和特权方法的模块模式 和增强的模块儿模式 。但不知道具体的使用方式、场景???
还有整个私有变量 和特权方法 这块儿感觉比较乱……
2017.03.16