javascript高级程序设计第七章 函数表达式 笔记

没看懂1
没看懂2
函数有一个name属性,可直接访问到函数名

function sayName(){
    console.log("Hi!yuan");
}
console.log(sayName.name);    //sayName

函数声明定义函数有个重要特性 函数声明提升(可将调用函数代码放在声明函数代码之前)

sayHi();    //Hi!
function sayHi(){
    console.log("Hi!");
}

而用函数表达式定义函数则没有这个特性

sayHi();    //报错,因为sayHI还没被定义,所以没有sayHi这个函数
var sayHi = function(){
	console.log("Hi!")
}

7.1 递归

以下是个递归函数(计算一个整数的阶乘)

function factorial(num){
	if(num <= 1){
		return 1;
	}else{
		return num * factorial(num - 1);
	}
}

若改变该函数的函数名则函数无法使用

function factorial(num){
	if(num <= 1){
		return 1;
	}else{
		return num * factorial(num - 1);
	}
}
var anotherNum = factorial;
factorial = null;    //注销函数名factorial
anotherNum(2);    //报错,factorial不是函数

我们可使用arguments.callee(一个指向正在执行的函数的指针)属性解决这样的耦合问题(牵一发而动全身)
所以第5行可改成

return num * arguments.callee(num - 1); 

但上述方法在严格模式下,不能通过脚本访问arguments.callee属性,
严格模式下可以使用内联命名函数表达式来达成相同的结果

var factorial = (function f(num){
	if(num <= 1){
		return 1;
	}else{
		return num * f(num - 1)
	}
});
var anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(2));    //2

这里的f其实并不是函数名,若是f(2);则会报错,可以将它看作一个中间参数,上面用函数表达式定义的函数的函数名还是factorial,这样就可解决递归函数的耦合问题,至于为什么要用var factorial = (function f(){});这种形式,可能是必须的把(存疑)

7.2闭包

闭包是指有权访问另一个函数作用域中的变量的函数
创建闭包的常见方式,就是在一个函数内部创建另一个函数

了解闭包前必须要了解一个以前了解过的重要概念:作用域链
作用域链在函数被定义时创建,用于寻找使用到的变量的值的一个索引。将函数自身的变量放在最前面,其次时父函数的变量,再是祖父级函数的变量,最后直到全局变量

闭包的作用:一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

闭包的执行原理:函数被调用时会创建一个执行环境以及相对应的作用域链,作用域链有优先级不同的各种变量。在一个函数内使用闭包(创建子函数),这个函数的变量会被闭包调用,所以在其被调用后,作用域链执行环境被回收,但是其变量不会被回收,因为变量可能在接下来被闭包调用,所以变量就被保存下来。只有在闭包被确认销毁后,变量才会被销毁。

闭包的坏处:
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
部分借鉴此处

自己转载的闭包的博客(点击跳转)

7.2.1 闭包与变量(没看懂)

闭包只能取得包含函数中任何变量的最后一个值
闭包所保存的是整个变量对象,而不是某个特殊的变量

function createFunctions(){
     var result = new Array();
     for (var i = 0; i < 10; i++){
         result[i] = function(){
             return i;
         };
     }
     return result;
}

以上代码是将闭包直接赋值给数组

每个函数都返回10,因为每个函数的作用域链中createFunctions()函数返回后,变量i的值时10,所以它们引用的都是同一个变量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;
}

在闭包外面再创建一个匿名函数(这是一个自调用函数,接收一个num参数,调用该函数时将 i 赋值给num),匿名函数内部又有个访问num的闭包,这样一来,result数组中每个函数都有自己num变量的一个副本,因此可以返回各自不同的数值了

i从0到9,每取一个值,都会调用一次匿名函数,此时num被附上i的值,所以每次调用匿名函数都会有num的一个副本,匿名函数内部的闭包自然而然就会取得num的值,则可以返回不同的数值了(理解最明白的一次)

7.2.2 关于this对象

匿名函数的执行环境具有全局性,因此其this对象通常指向window。但有时候由于编写闭包的方式不同,这一点可能不会那么明显

var name = "yuan";
var person = {
	name : "zhang",
	getName : function(){
		return function(){
			return this.name;
		}
	}
}
console.log(person.getName()());    //yuan

console.log(person.getName()()); 这行代码可以拆分为两步,调用person.getName()方法,再调用闭包匿名函数(调用方法后其实返回的是一个函数,再调用闭包才会返回需要的值)

第一步
调用var first = person.getName();方法就相当于

var first = function(){
    return this.name;
}

第二步
var second = first();调用闭包,就相当于

var second = function(){
    return this.name;
}();    //这个叫做自调用函数,定义后跟着自己调用这个函数

所以就相当于在全局作用域中调用 first 函数,所以里面的 this 对象等于 window,那么 返回的是:window.name,就是’The Window’。

每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数的这两个变量(存疑)

把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可让闭包访问对象了

var name = "yuan";
var person = {
	name : "zhang",
	getName : function(){
		var that = this;
		return function(){
			return that.name;
		};
	}
}
console.log(person.getName()());    //zhang

定义匿名函数之前,把this对象赋值给了一个名叫that的变量。在定义了闭包之后,闭包也可以访问这个变量,因为它是我们在包含函数中特意声明的一个变量(函数中声明的变量对于其子函数即闭包来说是一定能使用的)。即使在函数返回后,that也仍然引用着object,所以调用person.getName()()就返回了"zhang"

特殊情况下语法的细微改变也会改变this的值(了解)

var name = "yuan";
var person = {
	name : "zhang",
	getName : function(){
		this.name;
	}
}
person.getName;    //zhang  等价于person.name
(person.getName = person.getName)();    //yuan   等价于window.name

(object.getName = object.getName)();其实就相当于
(function(){ return this.name; })(); 一个函数的自调用,而是在window对象下调用这个函数的,所以当然是window.name

7.2.3 内存泄漏

如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁

function assignHandler(){
    var element = document.getElementById("someElement");
    element.onclick = function(){
        alert(element.id);
    };
}

assignHandler函数内部创建了一个作为element元素事件处理程序的闭包,而这个闭包则又创建了一个循环引用(13章讨论)

匿名函数保存了一个对assignHandler函数的活动对象(这里是element)的引用,因此无法减少element的引用数。只要匿名函数不被销毁,则element的引用数至少为1,所以说element占用的内存不会被回收

function assignHandler(){
    var element = document.getElementById("someElement");
    var id = element.id;
    element.onclick = function(){
        alert(id);
    };
    element = null;    //注销element,回收内存
}

7.3 模仿块级作用域

先讲一下自调用匿名函数形式

函数声明:

(function(){
})();

函数表达式:

var a = function(){
}();

================================================================
在块语句中定义的变量,实际上是在包含函数中而非语句中创建的

function outputNumbers(count){
	for(var i = 0;i < count;i++){
		console.log(i);
	}
	console.log(i);
}
outputNumbers(5);    //0,1,2,3,4,5

有两个console.log(i);但其实第二个吧第一个给覆盖掉了,因为第二个输出结果包含第一个的
第一个输出0,1,2,3,4.第二个输出0,1,2,3,4,5.因为i=4,走了一遍循环再+1到5时,不满足循环条件了,但是跳出循环来到了第二个console.log(i);所以能输出5

在js中,虽然 i 是在循环内部定义的,但是它是在outputNumbers函数的活动对象中,所以能在函数内部任何一处访问它

js不会告诉你是否多次声明了同一个变量,它只会对后续声明视而不见

我们可以封装匿名函数来模仿块级作用域

function outputNumbers(count){
	(function (){
        for(var i = 0;i < count;i++){
		    console.log(i);    //正常输出
		}
	})();	
		console.log(i);    //产生一个错误,找不到i
}
outputNumbers(5);

我们在for循环外部插入了一个自调用的匿名函数,创建了一个私有作用域,则 i 在这个匿名函数外部访问不了了

无论在什么地方,只要临时需要一些变量,就可使用私有作用域

这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数

这种做法可减少闭包占用内存问题,只要函数执行完毕,就可立即销毁其作用域链

(function(){
    var now = new Date();
    if(now.getMonth() == 0 && now.getDate() == 1){
        alert("happy new year!");
    }
})();

以上代码放在全局作用域实现其功能的同时,又不用向全局作用域中添加全局变量

7.4 私有变量

任何在函数中定义的变量,都可认为是私有变量,因为不能再函数外部访问到这些变量
私有变量包括函数参数,局部变量和在函数内部定义的其他函数

有权访问私有变量和私有函数的共有方法成为特权方法

function myObject(){
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //特权方法
   this.publicMethod = function(){
       privateVariable++;
       return privateFunction();
   };
}

在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的所有变量和函数

function Person(name){
    //返回name的值
    this.getName = function(){
        return name;
    };
    //改变name的值
    this.setName = function(value){
        name = value;
    }; 
}
var person = new Person("yuan");
console.log(person.getName());    //yuan
person.setName("zhang");
console.log(person.getName());    //zhang

getName,setName两个都是特权方法,都能访问Person函数的私有变量name

缺点:构造函数模式针对每个实例都会创建同样一组新方法,使用静态私有变量可避免此问题

7.4.1 静态私有变量(没太懂)

在私有作用域中定义私有变量或函数,同样也可创建特权方法

(function(){
	//私有变量,私有函数
	var privateVariable = 10;
	function privateFunction(){
		return false;
	}
	//构造函数
	MyObject = function(){
				
	};
	//特权方法
	MyObject.prototype.publicMethod = function(){
		privateVariable++;
		return privateFunction();
	};
})();

在私有作用域中,定义了私有变量和私有函数,再定义了构造函数及特权方法。

构造函数时没有用var进行函数声明,因为那样会得到局部函数,而我们想创建的是全局函数

这个模式和构造函数主要区别是,私有变量和函数是由实例共享的。由于特权方法在原型上定义,因此所有实例都使用同一个函数。而这个特权方法作为一个闭包,总是保存着对包含作用域的引用

7.4.2 模块模式

前面的模式用于为自定义类型创建私有变量和特权方法

模块模式则是为单例(只有一个实例的对象)创建私有变量和特权方法

var singleton = function(){
    //私有变量,私有函数
	var privateVariable = 10;
	function privateFunction(){
		return false;
	}
	//特权方法和属性
	return {
	    publicProperty : true,
	    publicMethod : function(){
	        privateVariable++;
		return privateFunction();
	};
}();

这个模块模式使用了一个返回对象的匿名函数。匿名函数内部,首先定义了私有变量和函数。再将一个对象字面量作为函数的值返回。返回的对象字面量只包括可公开的属性和方法。这个对象是在匿名函数内部定义的,所以特权方法可访问私有变量和函数。

用处:如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能访问这些私有数据的方法就可使用模块模式

7.4.3 增强的模块模式

在返回对象之前加入对其增强的代码

用处:增强的模块模式合适那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况

var singleton = function(){
    //私有变量,私有函数
	var privateVariable = 10;
	function privateFunction(){
		return false;
	}
	//创建对象
	var object = new CustomType();    //CustonType的实例
	//特权方法和属性
	object.publicProperty = true;
	object.publicMethod = function(){     //特权方法引用单例下的私有变量
	    privateVariable++;
	    return privateFunction();
	};
	return object;
}();

若前面演示模块模式的例子中的application对象必须是BaseComponent的实例,南无就可以使用以下代码

var application = function(){
    //私有变量
    var components = new Array();
    //初始化这个私有变量
    components.push(new BaseComponent());
    //创建application的一个局部副本,因为application是BaseComponent的一个实例,最后也是要将app返回给application的
    var app = new BaseComponent();
    //公共方法
    app.getComponentCount = function(){
        return components.length;
    };
    app.registerComponent = function(component){
        if(type of component == "object"){
            components.push(component);
        }
    };
    //返回这个副本
    return app;
}();

以上代码就是需要创建一个BaseComponent的实例对象。要给这个实例对象添加属性,方法,但是又不想创建新的变量来污染全局变量,所以就创建了这个实例对象的私有变量。又因为这些私有变量只有这个实例对象才能调用,而为了在实例对象外部调用这些私有变量,所以就创建了访问这些私有变量的特权方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值