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

【读书笔记】细读《JavaScript权威指南》(第八章: 函数)

程序员文章站 2024-03-14 23:09:59
...

8.1 函数定义

  • 函数表达式
var f = function fact (x) {
            if (x<=1) return 1;
            else return x * fact(x-1);
        }

如果一个函数定义表达式包含名称,函数的局部作用域将会包含一个绑定到函数对象的名称,或说函数名称将成为函数内部的一个局部变量(通常应用于递归函数)。


  • 函数语句

函数声明语句必须被提前到外部脚本或外部函数作用域的顶部。这也就意味这它们不能出现在循环、条件判断、with等语句块中。这也是es规范没有将函数声明语句归类为语句的原因。

8.2 函数调用

8.2.1 普通的函数调用

var a = f(x);

单独调用函数,计算返回值。普通的函数调用一般不使用this值。在非严格模式下,this指向全局对象;在严格模式下this值为undefined

8.2.2 方法调用

方法是指作为对象属性存在的函数。 与普通的函数调用的唯一区别就是调用上下文(this)指向调用该方法的对象。

方法链(“链式调用”风格的编程)

当方法的返回值是一个对象,这个对象还可以再调用它的方法。这种方法调用序列中(通常称为“链”或“级联”)每次的调用结果都是另外一个表达式的组成部分。

var o = {
    m: function () {
        var self = this;
        console.log(this === o);  // true
        f();

        function f () {
            console.log(this === o);  // false
            console.log(self === o);  // true
        }
    }
}

一个常见的误区就是误认为嵌套函数时this会指向外层函数的上下文。实际上只要记住普通的函数调用里的上下文只有两种可能指向。详见8.2.1小节。

8.2.3 构造函数调用

构造函数调用创建一个新的空对象,这个对象继承来自构造函数的prototype属性,构造函数试图初始化这个新创建的对象,并将其用做调用上下文。

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

如果构造函数没有形参,圆括号是可以省略的。

8.2.4 间接调用

任何函数都可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。call与apply都可以用来间接调用函数,两个方法都允许显式指定this。

8.3 函数的实参与形参

JavaScript的函数调用既不检查参数类型,也不检查传入参数的个数。

8.3.1 可选形参

当调用函数时传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为undefined值。

function getPropertyNames (o, /* optional */ a) {
    a = a || [];
    for (var property in o) a.push(property);
    return a;
}

可选实参要放在参数表后面,注释也是有必要的。

8.3.2 可变长的实参列表: 实参对象

函数体内可用标识符arguments访问实参对象。实参对象是一个类数组对象。

function max (x, y, z) {
    let max = -Infinity;
    for (let i = 0; i < argument.length; i++) {
        if (max < argument[i]) max = argument[i];
    }
    return max;
}

callee和caller属性

实参对象还定义了两个属性。在非严格模式下,es规定callee指代当前正在执行的函数。caller是非标准的,但被大多数浏览器所实现,它指代调用当前正在执行的函数的函数。

var factorial = function (x) {
    if (x <= 1) return 1;
    return x * argument.callee(x-1); // 在匿名函数中递归调用自身
}

8.3.3 将对象属性用做实参

当函数包含超过三个形参时,记住实参的正确顺序就有点困难了。一个解决方案是通过键值对的形式来传入参数,这样参数的顺序就无关紧要了。

8.4 作为值的函数

自定义函数属性

当函数需要一个“静态”变量来在调用时保持某个值不变,最方便的方式就是给函数定义属性,而不是定义全局变量。

function uniqueInteger () {
    return uniqueInteger.counter++;
}

8.5 作为命名空间的函数

定义一个函数用做临时的命名空间,在这个命名空间里定义的变量都不会污染到全局命名空间。

(function() {}());

该函数被称为立即执行函数,最外层的圆括号是必须的。如果不加,解释器会识图将function解析为函数声明语句,加了以后再会正确解析为函数定义表达式。

8.6 闭包

闭包的定义:

函数对象可以通过作用域链相互关联起来,函数体内部的变量可以保存在函数作用域中(看起来是函数将变量“包裹”了起来)。

闭包的主要特性:

捕捉到局部变量和参数,并一直保存下来。

var scope = 'global scope';
function checkscope () {
    var scope = 'local scope';
    function f () { return scope; }
    return f;
}
checkscope()(); // 'local scope'

上面的代码就是使用闭包的一个基本形式,即在函数体外调用其嵌套子函数(前提是该子函数被函数返回)。
从作用域链上来看,调用 f 时闭包所指向的作用域链和定义 f 时的作用域链不同,简单地表示一下,前者是【f 的局部变量保存对象 - 全局对象】,而后者是【f 的局部变量保存对象 - checkscope 的局部变量保存对象 - 全局对象】