欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

《JavaScript权威笔记(第六版)》笔记0x5 函数

程序员文章站 2022-03-17 13:49:33
...

    在JavaScript里,函数即对象,程序可以随意操控它们。比如, JavaScript可以把函数赋值给变量,或者作为参数传递给其他函数。因为函数就是对象,所以可以给它们设置属性,甚至调用它们的方法。

    JavaScript的函数可以嵌套在其他函数中定义,这样它们就可以访问它们被定义时所处的作用域中的任何变量。这意味着JavaScript函数构成了一个闭包(closure),它给JavaScript带来了非常强劲的编程能力。

函数调用

    函数调用

printprops({x:1});
var total=distance(0,0,2,1)+distance(2,1,3,5);
var probability=factorial(5)/factorial(13);

    对于普通的函数调用,函数的返回值成为调用表达式的值。如果该函数返回是因为解释器到达结尾,返回值就是undefined。如果函数返回是因为解释器执行到一条return语句,返回值就是return之后的表达式的值,如果return语句没有值,则返回undefined。

    方法调用

    对方法调用的参数和返回值的处理,和上面所描述的普通函数调用完全一致。但是,方法调用和函数调用有一个重要的区别,即:调用上下文。属性访问表达式由两部分组成:一个对象和属性名称。在像这样的方法调用表达式里,对象o成为调用上下文,函数体可以使用关键字this引用该对象。

var calculator={
    operand1:1,
    operand2:1,
    add:function(){
        this.result = this.operand1+this.operand2;
    }
};
calculator.add();
calculator.result//=>2

    构造函数调用

    如果函数或者方法调用之前带有关键字new,它就构成构造函数调用。

    如果构造函数调用在圆括号内包含一组实参列表,先计算这些实参表达式,然后传入函数内,这和函数调用和方法调用是一致的。但如果构造函数没有形参, JavaScript构造函数调用的语法是允许省略实参列表和圆括号的。凡是没有形参的构造函数调用都可以省略圆括号:

var o=new Object();
var o=new Object;

    构造函数调用创建一个新的空对象,这个对象继承自构造函数的prototype属性。构造函数试图初始化这个新创建的对象,并将这个对象用做其调用上下文,因此构造函数可以使用this关键字来引用这个新创建的对象。注意,尽管构造函数看起来像一个方法调用,它依然会使用这个新对象作为调用上下文。也就是说,在表达式new o.m()中,调用上下文并不是o。
    构造函数通常不使用return关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值。然而如果构造函数显式地使用return语句返回一个对象,那么调用表达式的值就是这个对象。如果构造函数使用return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果。

    间接调用

    JavaScript中的函数也是对象,和其他JavaScript对象没什么两样,函数对象也可以包含方法。其中的两个方法call()和apply()可以用来间接地调用函数。两个方法都允许显式指定调用所需的this值,也就是说,任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。两个方法都可以指定调用的实参。 call()方法使用它自有的实参列表作为函数的实参, apply()方法则要求以数组的形式传入参数。

函数参数

    将对象属性做实参

    当一个函数包含超过三个形参时,对于程序员来说,要记住调用函数中实参的正确顺序实在让人头疼。每次调用这个函数时都要不厌其烦地查阅文档,为了不让程序员每次都翻阅手册这么麻烦,最好通过名/值对的形式来传入参数,这样参数的顺序就无关紧要了。为了实现这种风格的方法调用,定义函数的时候,传入的实参都写入一个单独的对象之中,在调用的时候传入一个对象,对象中的名/值对是真正需要的实参数据。

作为命名空间的函数

    在函数中声明的变量在整个函数体内都是可见的(包括在嵌套的函数中),在函数的外部是不可见的。不在任何函数内声明的变量是全局变量,在整个JavaScript程序中都是可见的。在JavaScript中是无法声明只在一个代码块内可见的变量的,基于这个原因,我们常常简单地定义一个函数用做临时的命名空间,在这个命名空间内定义的变量都不会污染到全局命名空间。

function mymodule(){//模块代码
    //这个模块所使用的所有变量都是局部变量
    //而不是污染全局命名空间
}
mymodule();//不要忘了还要调用这个函数

     这段代码仅仅定义了一个单独的全局变量:名叫"mymodule"的函数。这样还是太麻烦,可以直接定义一个匿名函数,并在单个表达式中调用它:

(function(){//mymodule()函数重写为匿名的函数表达式
//模块代码
}());//结束函数定义并立即调用它

闭包

    函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包”。

    词法作用域规则

var scope="global scope";//全局变量
function checkscope(){
    var scope="local scope";
    function f(){return scope;}
    return f();
}
checkscope()//=>"local scope"

    实现闭包

var uniqueInteger=(function(){
    var counter=0;//函数的私有状态
    return function(){return counter++;};
}());

函数式编程

    使用函数处理数组

var sum=function(x,y){return x+y;};
var square=function(x){return x*x;};
var data=[1,1,3,5,5];
var mean=data.reduce(sum)/data.length;
var deviations=data.map(function(x){return x-mean;});
var stddev=Math.sqrt(deviations.map(square).reduce(sum)/(data.length-1));

    高阶函数

    高阶函数(higher-order function)就是操作函数的函数,它接收一个或多个函数作为参数,并返回一个新函数。

//这个高阶函数返回一个新函数,这个函数将它的实参传入f()
//并返回f的返回值的逻辑非
function not(f){
    return function(){//返回一个新函数
        var result=f.apply(this,arguments);//调用f
        return !result;//对结果求反
    };
}
var even=function(x){//判断奇偶的函数
    return x%2===0;
};
var odd=not(even);
[1,1,3,5,5].every(odd);//=>每个元素都是奇数

     记忆

//返回f()的带有记忆功能的版本
//只有当f()的实参的字符串表示都不相同时它才会工作
function memorize(f){
    var cache={};//将值保存在闭包内
    return function(){//将实参转换为字符串形式,并将其用作缓存的键
        var key=arguments.length+Array.prototype.join.call(arguments,",");
        if(key in cache)return cache[key];
        else return cache[key]=f.apply(this,arguments);
    };
}
/*
memorize()函数创建一个新的对象,这个对象被当做缓存(的宿主)并赋值给一个局部变量,
因此对于返回的函数来说它是私有的(在闭包中)。所返回的函数将它的实参数组转换成字
符串,并将字符串用做缓存对象的属性名。如果在缓存中存在这个值,则直接返回它。
否则,就调用既定的函数对实参进行计算,将计算结果缓存起来并返回
*/
//返回两个整数的最大公约数
function gcd(a,b){
    var t;
    if(a<b)t=b,b=a,a=t;//确保a>=back
    while(b!=0)t=b,b=a%b,a=t;//欧几里得算法求公约数
    return a;
}
var gcdmeno=memorize(gcd);
gcdmeno(85,187);//=>17//当写一个递归函数时,往往需要实现记忆功能
var factorial=memorize(function(n){
    return (n<=1)?1:n*factorial(n-1);
});
factorial(5)//=>120