笔记 05 JS 函数 – 闭包和引用, 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...
}
}