第七章:
在前面已经介绍过了定义函数的两种方式:函数声明、函数表达式
函数声明定义函数的写法存在一个重要的特征就是“函数声明提升”,在执行脚本之前,编译器会先通读一遍脚本,发现函数声明则自动将其提升到脚本的最顶端,优先解析。这也是为什么可以将函数声明放在函数调用之后的原因。
函数表达式的写法则不同,函数表达式本质上并非是创建函数,而是创建了一个变量,变量的赋值使用的是函数。这种赋值式的创建方法创建出的函数其实是一个匿名函数。
递归:
递归的写法本质上就是函数自己通过名字调用自身的一种情况,这种方法在其他的语言中也存在。
function sum(num){
if(num <= 1){
return 1;
}else{
return num * arguments.callee(num-1);
}
}
使用 callee() 方法降低函数的耦合性,callee()方法指向的是调用该方法的函数
var sum = (function f(num){
if(num <= 1){
return 1;
}else{
return num * f(num-1);
}
});
在严格模式下不允许使用arguments.callee()方法,所以可以使用这种命名函数的写法达到同样的效果。
闭包:
( 闭包这个概念在整个JavaScript体系中都比较重要,大家在看着一部分的时候需要好好理解,我个人理解这个概念也相当有限,还是希望大家去看看原书的 P178 )
闭包就是指有权访问另一个函数作用域中的变量的函数。当内部函数被保存到外部时,就会生成闭包。
其实简单的讲就是因为在JavaScript中,允许使用内部函数,也就是函数当中嵌套函数。这些内部函数因为存在于外部函数内部的原因,根据解析的规则可以访问其外部函数所有的局部变量/方法/参数等,当这样的内部参数被包含他们的外部函数之外的函数调用时,就形成了闭包!
闭包的写法也比较多,一般来说满足上面条件的都会形成闭包。具体的实现方法可以参考这位大神的博客!
var Cirle = function(){
var obj = new Object();
obj.PI = 3.1415;
obj.area = function(r){
return this.PI * r * r;
}
return obj;
}
var c = new Cirle();
console.log(c.area(2)); //12.56 (不同浏览器存在精度差)
var cirle = {
PI: 3.14,
area: function(r){
return this.PI * r * r;
}
}
var a = cirle.area(2); //注意这里的引用方式比较特殊
console.log(a); //12.56
这里需要注意第二种闭包创建方式的写法,这里的引用方式比较特殊,是因为prototype属性定义的特殊性。
var dom = function(){};
dom.sayName = function(){
console.log("dom-sayName");
}
dom.prototype.sayAge = function(){
console.log("dom-prototype-sayAge");
}
dom.sayName(); //dom-sayName
dom.sayAge(); //error
var person = new dom();
person.sayName(); //erroy
person.sayAge(); //dom-proyotype-sayAge
在JavaScript中,每个函数都有一个prototype属性,但是对象不存在这个属性。分析上面的代码后得出结论:
- 不使用prototype属性定义的对象方法,是静态方法,只能直接用类名进行调用!另外,此静态方法中无法使用this变量来调用对象其他的属性!
- 使用prototype属性定义的对象方法,是非静态方法,只有在实例化后才能使用!其方法内部可以this来引用对象自身中的其他属性!
var dom = function(){
var Name = "Default";
this.Sex = "Boy";
this.success = function(){
alert("Success");
};
};
console.log(dom.Name); //undefined
console.log(dom.Sex); //undefined
这里使用函数表达式的方法,通过this创建的两个属性和一个方法,这里直接使用函数名点出属性的方式访问的结果为undefined
这是因为当使用这种方式创建函数时,函数的作用域起到了阻隔的作用。现在函数内的方法仅限于函数内的变量/方法引用,不允许外部方法直接调用,想要调用方法必须实例化出一个对象,通过对象进行调用。
闭包的使用:
立即执行函数:
在一个项目中的全部函数,有些函数仅仅需要执行一次,执行完后即销毁,这种情况下可以使用闭包
(function (参数){
//函数执行
})(参数);
这种立即执行函数的写法最典型的一个好处就是其封闭性,该函数体内存在的所有变量和方法都仅在该函数中起作用,外部无法引用其内部的变量,执行完成后立即释放全部资源。不污染全局变量,且函数内变量名可以随意定义
结果缓存:
大型项目中往往会出现许多繁杂的函数,处理时间长,页面响应速度慢,使用闭包将函数的执行结果进行封装缓存,当函数调用时,率先查看缓存中是否存在,不存在再执行函数。
var CachedSearchBox = (function(){
var cache = {},
count = [];
return {
attachSearchBox : function(dsid){
if(dsid in cache){ //如果结果在缓存中
return cache[dsid]; //直接返回缓存中的对象
}
var fsb = new uikit.webctrl.SearchBox(dsid); //新建
cache[dsid] = fsb; //更新缓存
if(count.length > 100){ //保正缓存的大小<=100
delete cache[count.shift()]; //当大于100时将先进去的删除
}
return fsb;
},
clearSearchBox : function(dsid){
if(dsid in cache){
cache[dsid].clearSelection();
}
}
};
})();
CachedSearchBox.attachSearchBox("input");
因为闭包会携带包含他的函数的作用域,所以闭包会比其他的函数占内存,过度使用闭包可能造成页面缓存进度慢,所以大家谨慎使用,但是闭包的概念非常重要!
this对象:
this对象在前面已经有提到过,this是基于运行时函数的执行环境的,this在谁的执行环境中,它就指向谁。
全局环境中this指向window,对象调用时this指向该对象,匿名函数的执行环境具有全局性,所以匿名函数的this通常指向window,不过通过call和apply改变执行环境,this就会改变指向。
内存泄漏:
内存泄漏通常是闭包形成的重要问题,当闭包的作用域链中出现HTML元素时,该元素将无法被销毁,即使闭包不再引用该元素,但闭包的包含元素也会保存这一个引用,所以尽可能在使用HTML元素时,在最后解除对于它的引用!
模仿块级作用域:
使用立即执行函数模仿块级作用域,前面已经介绍过JavaScript中不存在块级作用域。
function Apple(){
for(var i=0; i<10; i++){
console.log(i)
}
console.log(i); //10
}
(function (){
for(var j=0; j<10; j++){
console.log(j);
}
})();
Console.log(j); //error
私有变量:
在函数的内部定义的变量就是私有变量,不允许其他函数访问
特权方法:
把有权访问私有变量的方法称之为特权方法。
其实特权方法就是在函数内部创建一个闭包的方法,将函数内部的属性/方法在闭包的内部获得,因为闭包能够被外部函数获取的特殊性,就形成了外部函数可以获取另一个函数内部属性/方法的情况,也就形成了这种特权方法。
function apple(){
var name = "abc";
var age = 12;
function privation(){
return 1;
}
this.tq = function (){ //特权方法
console.log(name+",,,"+age);
privation();
};
}
function bababan(){
var person = new apple();
person.tq(); //abc12
}
bababan();
静态私有变量:
在私有作用域中也可以构建特权方法,也就是在立即执行函数的内部构建特权方法,这种方法普遍用于封装代码,这种方式封装出来的代码复用性更强,不必担心变量名重复等问题。
(function apple(){
var name = "abc";
var age = 12;
MyObject = function(name){
this.name = name;
}
MyObject.prototype.tq = function (){
var ab = name + age;
return ab;
};
})();
var person = new MyObject();
console.log(person.tq()); //abc12