笔记 05 JS 函数 – 闭包和引用, arguments

原文地址
arguments 自动更新参考

函数

函数是JavaScript中的一等对象
=> 可以把函数像其它值一样传递。

1 闭包和引用

闭包是 JavaScript 一个非常重要的特性
=> 当前作用域总是能够访问外部作用域中的变量
ES5 (JavaScript 中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数)

1 模拟私有变量

/**
 * Counter 函数返回两个闭包,函数 increment 和函数 get
 *
 * 这两个函数都维持着 对外部作用域 Counter 的引用,
 * 因此总可以访问此作用域内定义的变量 count.
 */
function Counter(start) {
    var count = start;
    return {
        increment: function() {
            count++;
        },

        get: function() {
            return count;
        }
    }
}

var foo = Counter(4);
foo.increment();
foo.get(); // 5

2 JavaScript 中不可以对作用域进行引用或赋值 不可以在外部访问私有变量

/**
 * 不会改变定义在 Counter 作用域中的 count 变量的值,
 * 因为 foo.hack 没有 定义在那个作用域内。
 * 它将会创建或者覆盖[全局变量 count]。
 */
var foo = new Counter(4);
foo.hack = function() {
    count = 1337;
};

3 循环中的闭包

/**
 * 不会输出数字 0 到 9,而是会输出数字 10 十次
 *
 * 当 console.log 被调用的时候,匿名函数保持对外部变量 i 的引用,
 * 此时 for循环已经结束,
 * i 的值被修改成了 10.
 */
for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

/**
 * 匿名包装器 - 自执行匿名函数
 * 外部的匿名函数会立即执行,并把 i 作为它的参数,
 * 此时函数内 e 变量就拥有了 [i 的一个拷贝]。
 *
 * 当传递给 setTimeout 的匿名函数执行时,
 * 它就拥有了对 e 的引用,
 * 而这个值是不会被循环改变的。
 */
for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);
        }, 1000);
    })(i);
}
// 等同效果
for(var i = 0; i < 10; i++) {
    setTimeout((function(e) {
        return function() {
            console.log(e);
        }
    })(i), 1000)
}

2 arguments

JavaScript 中每个函数内都能访问一个特别变量 arguments。
这个变量维护着所有传递到这个函数中的参数列表。

arguments 变量不是一个数组(Array)。
尽管在语法上它有数组相关的属性 length
但它不从 Array.prototype 继承
实际上它是一个对象(Object

无法对 arguments 变量使用标准的数组方法,
比如 push, pop 或者 slice。
虽然使用 for 循环遍历也是可以的,
但是为了更好的使用数组方法,
最好把它转化为一个真正的数组

注意: 由于 arguments 已经被定义为函数内的一个变量。
因此通过 var 关键字定义 arguments 或者将 arguments 声明为一个形式参数,
都将导致原生的 arguments 不会被创建。

1 转化为数组

// 转化为一个真正的数组
// 这个转化比较慢,在性能不好的代码中不推荐这种做法。
Array.prototype.slice.call(arguments);

2 传递参数

// 将参数从一个函数传递到另一个函数的推荐做法
function foo() {
    bar.apply(null, arguments);
}
function bar(a, b, c) {
    // 干活
}
/**
 * 创建一个快速的解绑定包装器
 */

function Foo() {}

Foo.prototype.method = function(a, b, c) {
    console.log(this, a, b, c);
};

// 创建一个解绑定的 "method"
// 输入参数为: this, arg1, arg2...argN
Foo.method = function() {

    // 结果: Foo.prototype.method.call(this, arg1, arg2... argN)
    Function.call.apply(Foo.prototype.method, arguments);
};

// 同等效果
Foo.method = function() {
    var args = Array.prototype.slice.call(arguments);
    Foo.prototype.method.apply(args[0], args.slice(1));
};

3 自动更新

arguments 对象为其内部属性以及函数形式参数创建 getter 和 setter 方法。
因此,改变形参的值会影响到 arguments 对象的值,反之亦然。(ES5非严格模式)

function foo(a, b, c) {
    arguments[0] = 2;
    a; // 2

    b = 4;
    arguments[1]; // 4

    var d = c;
    d = 9;
    c; // 3
}
foo(1, 2, 3);

'use strict';
function foo(num1,num2){
console.log(num1 === arguments[0]); // true
console.log(num2 === arguments[1]); // true
num1 = 'a';
arguments[1] = 'b';
console.log(num1 === arguments[0]); // false
console.log(num2 === arguments[1]); // false;
}
foo(1,2);

4 性能 ES5

arguments 对象总会被创建,
除了两个特殊情况 – 作为局部变量声明作为形式参数

arguments 的 getters 和 setters 方法总会被创建;
因此使用 arguments 对性能不会有什么影响。

ES5 提示: 这些 getters 和 setters 在严格模式下(strict mode)不会被创建。

除非是需要对 arguments 对象的属性进行!!!多次访问

// strict mode 模式下 arguments 的描述有助于我们的理解,请看下面代码
// 阐述在 ES5 的严格模式下 `arguments` 的特性
function f(a) {
  "use strict";
  a = 42;
  return [a, arguments[0]];
}
var pair = f(17);
console.log(pair[0] === 42); // true
console.log(pair[1] === 17); // true

arguments.callee
显著的影响现代 JavaScript 引擎的性能

ES5 提示: 在严格模式下,arguments.callee 会报错 TypeError,因为它已经被废除了

/**
 * foo 不再是一个单纯的内联函数(解析器可以做内联处理)
 *
 * 它需要知道它自己和它的调用者
 *
 * 这不仅抵消了内联函数带来的性能提升,
 * 而且破坏了封装,
 * 因此现在函数可能要依赖于特定的上下文。
 */

function foo() {
    // 使用此函数对象和调用函数对象做某事
    arguments.callee; // do something with this function object
    arguments.callee.caller; // and the calling function object
}

function bigLoop() {
    for(var i = 0; i < 100000; i++) {
        foo(); // Would normally be inlined...
    }
}