JavaScript权威指南学习笔记(四)函数表达式
主要内容:
* 函数表达式特点
* 函数递归
* 使用闭包的私有变量
函数定义提升:
//函数提升,函数定义是先读取的,不会报错
sayHi();
function sayHi(){
alert("Hi!");
}
//此时会报错
sayHi(); //error – function doesn’t exist yet
var sayHi = function(){
alert("Hi!");
};
理解函数提升是理解函数声明和函数定义区别的关键,下面这些有时会显得奇怪:
//这种写法不行,会报错
if(condition){
function sayHi(){
alert("Hi!");
}
} else {
function sayHi(){
alert("Yo!");
}
}
//这种可以
var sayHi;
if(condition){
sayHi = function(){
alert("Hi!");
};
} else {
sayHi = function(){
alert("Yo!");
};
}
闭包
匿名函数和闭包的术语经常错误地混用。
闭包是什么?
“Closures are functions that have access to variables from another function’s scope.”
- 闭包是函数。
- 这个函数能够访问另外一个函数作用域中的变量。
- 经常通过构造一个函数中的函数来实现。
function createComparisonFunction(propertyName) {
return function(object1, object2){
var value1 = object1[propertyName];//exp1
var value2 = object2[propertyName];//exp2
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}
返回的函数(匿名函数)里面的exp1和exp2访问了外部表达式中的变量,即使内部的函数已经结束了(returned),并且在其它地方使用,它依然对那个变量有访问权。这是因为内部函数的作用域包含createComparisonFunction()的作用域。考虑一下函数第一次被调用的时候发生了什么:
理解作用域链(scope chains)如何构建和使用对理解闭包非常有帮助。
当一个函数被调用时,一个可执行上下文(execution context)被创建了,并且它的作用域也被创建了。内部函数的**对象被参数初始化,外部函数的**对象是作用域链的第二个对象,这个过程会随着所有包含在外面的函数一直持续下去,直到到达全局可执行上下文时才停止。
function compare(value1, value2){
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);
这里在全局可执行上下文定义了一个全局函数compare(),当compare()第一次调用时,一个新的**对象被创建,这个对象包含arguments、value1和value2,全局可执行上下文变量对象紧挨着compare()的可执行上下文的作用域链,它包括this,result和compare。
在代码运行的背后,一个对象代表着每个可执行上下文的变量。全局上下文的变量总是存在,相对的,本地上下文变量的对象就如compare()的对应的那些,只有在函数被调用的时候才存在。当compare()定义时,它的作用域就被创建了,预加载全局变量对象,然后保存在内部的[[Scope]]属性内。当这个函数调用时,一个可执行上下文被创建,然后通过函数[[Scope]]属性中的对象来创建它的作用域链。这之后,一个表现得如变量对象的**对象被构造,然后被添加到上下文作用域链的前端。在这个例子中,compare()函数的可执行上下文在它的作用域有两个变量对象:当前**对象和全局变量对象。记住,作用域链本质上是一组指向对象的指针而不是真正地包含这些对象。
无论什么时候一个变量在函数中被访问时,内部就会根据这个名字在这个作用域里搜索。一旦这个函数结束,当前**的对象被销毁,内存就只剩下了全局的作用域。然而,闭包则表现的不同。
在另一个函数中定义的函数将外部函数的**对象添加到作用域链中,所以,在createComparisonFunction()中,匿名函数的作用域链实际包含了createComparisonFunction()对应的**对象的引用。
var compare = createComparisonFunction("name");
var result = compare({ name: "Nicholas" }, { name: "Greg" });
下图展示了上面两行代码执行时发生了什么:
当匿名函数从createComparisonFunction()返回的时候,它的作用域链就会被初始化,包括createComparisonFunction()的对象和全局对象,这就使得匿名函数可以访问createComparisonFunction()的所有变量。另外一个有趣的副作用就是当createComparisonFunction()结束执行时,从它那里获得的**对象是不会被销毁的,因为在匿名函数中依然有对它的引用。当createComparisonFunction()执行结束时,它的上下文的作用域链被销毁,但是它的**对象在匿名函数被销毁之前始终保存在内存中。
//create function
var compareNames = createComparisonFunction("name");
//call function
var result = compareNames({ name: "Nicholas" }, { name: "Greg"});
//dereference function - 内存被回收
compareNames = null;
在这里,比较函数被创建,保存在变量compareNames中,将变量赋值为null,这样就解引用了这个函数,这样就可以让回收进程将其清除。作用域链将会被destroy,作用域也会被安全的destroy。
由于闭包的出现总会伴随着包含它的函数的作用域,这样会占用很多内存,所以过度使用闭包会造成内存过度消耗,所以作者建议大家在非常必要的时候再使用闭包。
闭包和变量
经典问题:
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
var arr = createFunctions();
for(var i=0;i<10;i++){
alert(arr[i]());
}//10,10,10,10,10,10,10,10,10,10
由于返回数组中每个函数在它的作用域链中都包含createFunctions()的**对象,它们都指向同一个变量i,当createFunctions()函数结束运行时,i的值为10,这时候执行arri时,匿名函数返回的就是最终的i。如果想要每个函数都返回它对应数组的索引值,可以这样做:
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 test = createFunctions();
for(var i=0;i<10;i++){
alert(test[i]())
}//0,1,2,3,4,5,6,7,8,9
这里i按值传递给带num参数的匿名函数,我认为关键是在将带参匿名函数执行的结果赋值给了“result[i]”。
this 对象
在闭包里使用this对象会导致一些复杂的行为。this会根据上下文的运行时来决定的,当在全局函数中使用时,在非strict模式下,this等于window,在strict模式下是未定义的。当在对象的方法中,this指代的是此对象。匿名函数在这种上下文中并不会绑定在某个对象上,这就意味着匿名函数中的this指向window(strict模式下undefined)。如:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //"The Window" (in non-strict mode)
函数一被调用的时候,就马上会自动获得两个变量 this 和 arguments,一个内部函数从来都不能通过外面的函数直接获取到这两个变量。可以通过如下方法获得:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"My Object"
做一点小小的改变可能就达不到预期的结果了:
var name = "The Window";
var object = {
name : "My Object",
getName: function(){
return this.name;
}
};
object.getName(); //"My Object"
(object.getName)(); //"My Object"
(object.getName = object.getName)(); //"The Window" in non-strict mode
最后一行行可以看成下面的形式:
object.getName = function(){
return this.name;//非严格模式下this指向了window
}
## 内存泄露(Memory Leaks)
模仿块级作用域
js里是没有块级作用域的,
function outputNumbers(count){
for (var i=0; i < count; i++){
alert(i);
}
alert(i); //count
}
js对重复声明同一个变量时不会提示信息,它会忽略随后的声明。
利用匿名函数创造块作用域:
(function(){
//block code here
})();
这个语法定义了一个立即执行的匿名函数,这种函数有时被称为immediately invoked function。可以用它模仿块级作用域:
function outputNumbers(count){
(function () {
for (var i=0; i < count; i++){
alert(i);
}
})();
alert(i); //causes an error
}
这个模式也限制了内存问题,因为没有对匿名函数的引用,因此作用域在函数执行完之后就结束了。
私有变量
严格来说,JS没有私有成员;所有的对象属性都是public的。然而,还是有私有变量的概念。函数里的变量通常被认为私有的,因为函数外无法访问到。
封装效果:
function Person(name){
this.getName = function(){
return name;
};
this.setName = function (value) {
name = value;
};
}
var person = new Person("Nicholas");
alert(person.getName()); //"Nicholas"
person.setName("Greg");
alert(person.getName()); //"Greg"
这两个都是闭包。
静态私有变量
上代码:
(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("Nicholas");
alert(person1.getName()); //"Nicholas"
person1.setName("Greg");
alert(person1.getName()); //"Greg"
var person2 = new Person("Michael");
alert(person1.getName()); //"Michael"
alert(person2.getName()); //"Michael"
这里使用了私有作用域,这样内部定义的变量和函数外部访问,除了。。
这里提一点,(这里Person的赋值没有使用var),初始化一个未定义的变量会产生一个全局变量,所以Person就变成了全局变量,这样在私有作用域外面就可以访问到它,(严格模式下这种赋值会报错)
这里的name就成了静态变量,创建私有静态变量允许通过prototypes进行更好的代码复用。
模块模式
单例模式,传统字面表达式:
var singleton = {
name : value,
method : function () {
//method code here
}
};
下面看模块模式(单例模式增强版):
var singleton = function(){
//private variables and functions
var privateVariable = 10;
function privateFunction(){
return false;
}
//privileged/public methods and properties
return {
publicProperty: true,
publicMethod : function(){
privateVariable++;
return privateFunction();
}
};
}();
模块模式利用了匿名函数返回了一个对象。匿名函数中先定义函数和变量,然后,返回一个字面对象。对象只包含public的属性和方法。由于对象定义在匿名函数中,所以所有的公共方法都能访问这些私有变量和函数。本质上,对象表达式定义了单例的公共接口。这对那些需要各种初始化操作和访问私有变量的单例很有用,如:
var application = function(){
//private variables and functions
var components = new Array();
//initialization
components.push(new BaseComponent());
//public interface
return {
getComponentCount : function(){
return components.length;
},
registerComponent : function(component){
if (typeof component == "object"){
components.push(component);
}
}
};
}();
在web级应用中,拥有一个能够处理application级信息的单例是非常普遍的。这段代码构造了一个处理组件的应用。模块模式适合创建一个唯一对象,这个对象只需一些数据来初始化,暴露可以访问内部数据的一些公共接口。因为最终返回的是一个object literal,所以每一个用这种方式构造的单例都是一个对象的实例。单例全局都可以访问。
模块增强模式
待读。。。
var singleton = function(){
//private variables and functions
var privateVariable = 10;
function privateFunction(){
return false;
}
//create object
var object = new CustomType();
//add privileged/public properties and methods
object.publicProperty = true;
object.publicMethod = function(){
privateVariable++;
return privateFunction();
};
//return the object
return object;
}();
上一篇: Socket
下一篇: react 使用 swiper
推荐阅读
-
js权威指南学习笔记(二)表达式与运算符
-
Javascript学习笔记(详)(三)——函数表达式与DOM
-
JavaScript权威指南学习笔记(四)函数表达式
-
Javascript学习笔记之 函数篇(一) : 函数声明和函数表达式_基础知识
-
《HTML5 与CSS3 权威指南》(第四版 上册)学习笔记
-
javascript学习笔记(四)function函数部分_基础知识
-
js权威指南学习笔记(二)表达式与运算符
-
Javascript学习笔记之函数篇(四):arguments 对象_基础知识
-
javascript权威指南 学习笔记之变量作用域分享_javascript技巧
-
javascript权威指南 学习笔记之javascript数据类型_javascript技巧