您的位置:首页 > Web前端 > JavaScript

[置顶] JS--函数表达式

2017-12-12 19:52 169 查看

函数表达式

函数表达式的特征
使用函数实现递归
使用闭包定义私有变量
函数的定义方法有两种:一种是函数声明,另一种是函数表达式

函数声明,它的一个重要特征就是函数声明提升,就是在执行代码之前会先读取函数声明,也就意味着可以把函数声明放在调用它的语句后面
第二种创建函数的方式是使用函数表达式。函数表达式有几种不同的语法形式。下面是最常见的一种形式
         var functionName = function(arg0, arg1, arg2){
                    //函数体
         }
     这种形式看起来好像是常规的变量赋值语句即创建一个函数并将它赋值给变量functionName,这种情况下创建的匿名函数,因为function关键字后面没有标识符。匿名函数的name属性是空字符串。

递归

递归函数是在一个函数通过名字调用自身的情况下构成的。
function factorial(num){
if(num <=1){
return 1;
} else {
return num * factorial(num-1);
}
}
     
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4); //出错

    但是这段代码容易引起错误,当指向原始函数的引用只剩下一个时,factorial不再是函数了,就会出错。在这种情况下,使用arguments.calle可以解决这个问题。
     
function factoral(num){
if(num <= 1){
return 1;
} else{
return num * arguments.calle(num-1);
}
}
     加粗的代码显示,通过使用arguments.calle代替函数名可确保无论怎样调用函数都不会出问题。但是在严格模式下,不能通过脚本访问arguments.calle。不过,可以通过使用命名函数表达式来达成相同的结果。例如:
    
var factorial = (function f(num){
if(num <= 1){
return 1;
} else {
return num * f(num-1);
}
});

闭包

搞不清匿名函数和闭包这两个概念,会经常混用。闭包是指有权访问另一个函数作用域中的变量的函数
创建闭包的常见方式,就是在一个函数内部创建另一个函数,仍以前面的createComparisonFunction()函数为例,注意加粗的代码:

function createComparisonFunction(propertyName){

return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];

if(value1 < value2){
return -1;
}else if (value1 > value2){
return1;
}else {
return 0;
}
}
}
    加粗的那两行代码是内部函数中的代码,这两行代码访问了外部函数中额变量propertyName。即使这个内部函数被返回了,而且是在其他地方被调用了,但它仍然可以访问变量propertyName,是因为内部函数的作用域链中包含createComparisonFunction()的作用域。

   在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。
functtion compare(value1, value2) {
if (value1 < value2){
return -1;
}else if (value1 > value2){
returm 1;
}else {
return 0;
}
}

   后台的每个执行环境都有一个表示变量的对象---变量 对象。在创建compare()函数时,会创建一个预见包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。对于这个例子中compare()函数的执行环境而言,其作用域链中包含两个变量对象:本地活动对象和全局变量对象。

注意:无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后局部活动对象就会被销毁,内存中仅存保存全局作用域(全局执行环境的变量对象),但是,闭包的情况又有所不同。
var compare = createComparisonFunction("name");
var result = compare({ name: "Nicholas"}, {name: "Greg"});

当createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中;直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。

闭包与变量

作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。
function createFunctions(){
var result = new Array();

for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
这个函数会返回一个函数数组。似乎每个函数都应该返回自己的索引值,但是每个函数都会返回10.因为每个函数的作用域链中createFunctions()函数的活动对象,所以它们引用的都是同一个变量i。当createFunctions()函数返回后,变量i的值是10。
但是,可以通过创建另一个匿名函数强制让闭包的行为符合预期
function createFunctions(){
var result = new Array();

for (var i=0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
定义了一个匿名函数,并将立即执行该匿名函数的结果赋值给数组。由于函数参数是按值传递的,所以变量就会将变量i的当前值复制给参数num。

关于this对象

this对象是运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被当作为某个对象的方法调用时,this等于那个对象。

每个函数在被调用时都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止因此永远不可能直接访问外部函数中的这两个变量。不过,把外部作用域中的this对象保存在一个闭包能够访问到变量里,就可以让闭包访问对象了。
var name = "The Window";

var object = {
name : "My Object",

getNameFunc : function(){
  var that = this;
return function(){
  return that.name;
};
}
};

alert(object.getNameFunc()());   //"My Object"

内存泄漏

因为闭包的作用域链中保存着一个HTML元素,那么意味着该元素将无法被销毁
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}
以上代码会导致被占用的内存永远不会被回收

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

element.onclick = function(){
alert(id);
};
element = null;
}
通过把element.id的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。再把element变量设置为null,这样就能够解除对DOM对象的引用。

模仿块级作用域

JavaScript没有块级作用域的概念。也就是说在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。
在JavaScript中,你多次声明了同一个变量;遇到这种情况,它只会对后续的声明视而不见(不过,它会执行后续声明中的变量初始化)。匿名函数可以用来模仿块级作用域并避免这个问题。
用作块级作用域(通常称为私有作用域)的匿名函数的语法如下所示
(function (){
//这里是块级作用域
})();
调用函数的方式是在函数名称后面添加一对圆括号。

私有变量

JavaScript中没有私有成员的概念;所以对象属性都是共有的。任何在函数定义的变量,都可以认为是私有变量。
私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
创建用于访问私有变量的公有方法,我们把有权访问私有变量的私有函数的公有方法称为特权方法
第一种是在构造函数中定义特权方法:
function MyObject(){
//私有变量和私有函数
var privateVariable = 10;

function  privateFunction(){
return false;
}

//特权方法
this.publicMethod = function (){
privateVariable++;
return priateFunction();
};
}
上面的代码:在创建MyObject的实例后,除了使用publicFunction()这个途径外,没有任何方法可以直接访问privateVariable和privateFunction();
利用私有和特权方法,可以隐藏那些不应该被直接修改的数据,例如:
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"
以上代码的构造函数中定义了两个特权方法:getName()和setName()。通过这两个特权方法,有权访问私有变量name。
构造函数模式的缺点就是针对每个实例都会创建同样一组新方法,而使用静态私有变量来实现特权方法就可以避免这个问题。

静态私有变量

(function(){
//私有变量和私有函数
var privateVariable = 10;

function privateFunction(){
return false;
}

//构造函数
MyObject = function (){
};

//公有/特权方法
MyObject.prototype.publicMethod = function(){
privateVariale++;
return privateFunction();
};

})();
这个模式创建了一个私有作用域,并在其中丰庄路一个构造函数及相应的方法。在私有作用域中,首先定义了私有变量和私有函数,然后又定义了构造函数及其共有方法。共有方法是在原型上定义的。这个模式在定义构造函数时并没有使用函数声明,而是使用了函数表达式。函数声明只能创建局部函数。
记住:初始化未经声明的变量,总是创建一个全局变量,因此,MyObject就成了一个全局变量。
这个模式与在构造函数中定义特权方法的主要区别,就在于私有变量和函数是由实例共享的。由于特权方法是在原型上定义的,因此所有实例都使用同一个函数,也就会造成在一个实例上调用setName()会影响所有实例。

模块模式

前面的模式是用于为自定义类型创建私有变量和特权方法的。而这里的模块模式是为单例创建私有变量和特权方法的。也就是说只有一个实例的对象。按照惯例,JavaScript是以对象字面量的方式来创建单例对象的。
var singleton = function(){

//私有变量和私有函数
var privateVariable = 10;

function privateFunction(){
return false;
}

//特权/共有方法和属性
return {
publicProperty: true,

publicMethod : function(){
privateVariable++;
return privateFunction();
}
};
}();
返回的对象字面量中只包含可以公开的属性和方法。

增强的模块模式

增强的模块模式,即在返回对象之前加入对其增强的代码。同时还必须添加某些属性和或方法对其加以增强的情况。
var singleton = function(){
//私有变量和私有函数
var privateVariable = 10;

function privateFunction(){
return false;
}

//创建对象
var object = new CustomType();

//添加特权/公有属性和方法
object.publicProperty = true;

object.publicProperty = function(){
privateVariable++;
return privateFunction();
};

//返回对象
return object;
}();
如果前面演示模块模式的例子中的application对象必须是BaseComponent的实例,那么可以使用以下代码:

var application = function(){
//私有变量和函数
var components = new Array();

//初始化
components.push(new BaseCompenent());

//创建appliction的一个局部副本
var app = new BaseComponent();

//公共接口
app.getComponentCout = function(){
return components.length;
};

app.registerComponent = function(component){
if(typeof component == "object"){
components.push(component);
}
};

//返回这个副本
return app;
}();
重写的应用程序单例中,首先是像前面例子中一样定义了私有变量。主要不同之处在于命名变量app的创建过程,它必须是BaseComponent的实例。这个实例就是application对象的局部变量。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: