JavaScript 函数

JavaScript 中最有意思莫过于函数了。因为函数本质上是一个对象,都是一个 Function 的实例。因此函数名本质上是指向函数对象的指针。这也就能理解为何 JS 中没有函数重载这个概念。以下两个例子,所定义的函数的方式几乎相差无几。

function sum(num1, num2){
    return num1 + num2;
}

var sum = function(num1, num2){
    return num1 + num2;
}

这两种方式的唯一区别就是,对于前者,在代码开始执行之前,解析器就已经通过一个名为函数声明提升的过程。换句话说,你完全可以先调用这个函数,在定义这个函数。如果采用第二种方式,你不得不先定义函数,在调用该函数,否则会报错。

你甚至还可以这样声明函数(不推荐),如下:

var sum = new Function("num1", "num2", "return num1 + num2");

作为值的函数

因为函数本身只是一个对象,所以函数也可以作为参数被传递给另一个函数。

function sayHello(name){
    alert("Hello, " + name);
}

function sayLove(name){
    alert("I love you, " + name);
}

function callSomeFunction(someFunction, someArgument){
    someFunction(someArgument);
}

callSomeFunction(sayHello, "xiaobai");  //"Hello, xiaobai"
callSomeFunction(sayLove, "xiaobai"); //"I love you, xiaobai"

同样,函数也可以作为一个值返回。

函数的内部属性

在函数内部包含两个特殊的对象:arguments 和 this。其中,arguments 是一个类数组对象,包含着传入函数的所有参数。这个对象有一个名为 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。如下:

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

函数内部另外一个特殊对象是 this,this 是引用的是函数执行的环境对象,如果在全局作用域中调用函数,则 this 对象的引用就是 window。

JavaScript 中还规范化了另一个函数属性:caller。这个属性保存着调用当前函数的函数引用。如果在全局作用域中调用当前函数,它的值为 null。例如:

function outer(flags){
    if(flags == 1){
        return "this is outer";
    }else{
        inner();
    }
}

function inner(){
    return arguments.callee.caller(1);
}

outer(2); // "this is outer"

闭包

首先应该区分一下匿名函数和闭包,function 关键字后边没有标示符而创建的函数称为匿名函数,有时也叫拉姆达函数。匿名函数的 name 属性为空。而闭包是指有权访问另一个函数作用域中的变量的函数。如下所示:

function createComparisonFunction(propertyName){
    return function(object1, object2){
        var value1 = object1[propertyName]; //访问了外部函数的变量
        var value2 = object2[propertyName]; //访问了外部函数的变量

        if(value1 < value2){
            return -1;
        }else if(value1 > value2){
            return 1;
        }else{
            return 0;
        }
    }
}

var nameCompareFunction = createComparisonFunction("name");
var ageCompareFunction = createComparisonFunction("age");

var arr = [{name: "xiaobai", age: 22}, {name: "xiaohei", age: 21}];

arr.sort(nameCompareFunction);  // "xiaobai"
arr.sort(ageCompareFunction);   // "xiaohei"

其中,加入注释的代码访问了外部函数的变量。即使这个函数被返回了,而且在其他地方被调用了,它也能访问该变量。

当函数第一次被调用时,会创建一个执行环境以及相应的作用域链,并将作用域链复制给内部的一个特殊属性(即 [[Scope]] )。然后使用 this、arguments和其他命名参数来初始化函数的活动对象。在作用域链中,外部函数的活动的活动对象始终处于第二位,外部函数的外部函数活动对象处于第三位,以此类推。作用域链本质上是一个指向变量对象的指针列表。

一般来讲,当函数执行完毕之后,其局部活动对象就会被销毁。但是闭包的情况有所不同。

在一个函数内部定义的函数将包含函数(即外部函数)的活动对象添加到它的作用域中。在 createComparisonFunction() 函数中定义的匿名函数的作用域链中,将包含外部函数 createComparisonFunction() 的活动对象。当匿名函数被返回以后,它作用域中依旧包含着 createComparisonFunction() 函数的活动对象。所以,当函数 createComparisonFunction() 执行完后,其活动对象并不会被销毁,因为匿名函数的作用域链仍在引用这个活动对象,所以,本质上只销毁了该执行环境的作用域链。

闭包与变量

作用域链这种机制引出的一个副作用就是,闭包只能取得包含函数中任何变量最后一次保存的值。因为其保存的是整个变量对象。如下所示:

function createFunctions(){
    var result = [];

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

    return result;
}

var functions = createFunctions();
functions.forEach(function(item, index, arr){
   alert(item());
})

在函数 createFunctions() 执行完毕之后,其活动对象中变量 i 的值被保存为10,于是所有匿名函数返回i的时候,都返回了10;

但是我们可以创建另一个匿名函数,让其闭包行为符合我们所期望的。

function createFunctions(){
    var result = [];

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

    return result;
}

this 与闭包

我们知道,this 对象是在函数运行时基于函数的执行环境绑定的,在全局函数中,this 等于 window。当函数作为某个对象的方法调用时,this 就等于那个对象。由于闭包的特殊性,这点需要注意一下。

var name = "The window";
var object = {
    name: "My Object",
    getName: function(){
        return function(){
            return this.name;
        }
    }
};

alert(object.getName()()); //"The window"

在函数被调用时,会自动获得 this 和 arguments 这两个值。因此永远不可能直接访问到外部函数的这两个变量。

模仿块级作用域

匿名函数可以用来模仿块级作用域(通常又称为私有作用域),其语法如下。

(function(){
    //这里是块级作用域
})();

以上代码其实很简单,只是申明了一个匿名函数并立刻执行它。如果不加第一对括号的会被认为是语法错误。

(function(){
    for(var i=0; i< 5; i++){
        alert(i);
    }
})();

alert(i);  // 产生错误

私有变量

JavaScript 中没有私有成员的概念,所有对象属性都是公有的。但是有一个私有变量的概念。在任何函数中定义的变量都可以被认为是私有变量,因为在函数外部无法访问到这些变量。

我们把有权访问私有变量和私有函数的方法称为特权方法。如下所示:

function MyObject(){
    //私有变量和私有方法
    var privateVariable = 10;

    function privateFunction(){
        return false;
    }

    //特权方法
    this.publicMethod = function(){
        privateVariable++;
        return privateFunction;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容