函数传参
值和引用
JavaScript 中没有指针,引用的工作机制也不尽相同。在 JavaScript 中变量不可能成为指向 另一个变量的引用。
JavaScript 引用指向的是值。如果一个值有 10 个引用,这些引用指向的都是同一个值,它 们相互之间没有引用 / 指向关系。
JavaScript 对值和引用的赋值 / 传递在语法上没有区别,完全根据值的类型来决定。
var a = 2;
var b = a; // b是a的值的一个副本 b++;
a; // 2
b; // 3
var c = [1,2,3];
var d = c; // d是[1,2,3]的一个引用 d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]
上例中 2 是一个标量基本类型值,所以变量 a 持有该值的一个复本,b 持有它的另一个复 本。b 更改时,a 的值保持不变。
c 和 d 则分别指向同一个复合值 [1,2,3] 的两个不同引用。请注意,c 和 d 仅仅是指向值 [1,2,3],并非持有。所以它们更改的是同一个值(如调用 .push(4)),随后它们都指向更 改后的新值 [1,2,3,4]。
由于引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向。
var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]
// 然后
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]
b=[4,5,6] 并不影响 a 指向值 [1,2,3],除非 b 不是指向数组的引用,而是指向 a 的指针, 但在 JavaScript 中不存在这种情况!
函数参数就经常让人产生这样的困惑:
function foo(x) {
x.push( 4 );
x; // [1,2,3,4]
// 然后
x = [4,5,6];
x.push( 7 );
x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // 是[1,2,3,4],不是[4,5,6,7]
我们向函数传递 a 的时候,实际是将引用 a 的一个复本赋值给 x,而 a 仍然指向 [1,2,3]。 在函数中我们可以通过引用x来更改数组的值(push(4)之后变为[1,2,3,4])。但x = [4,5,6] 并不影响 a 的指向,所以 a 仍然指向 [1,2,3,4]。
我们不能通过引用 x 来更改引用 a 的指向,只能更改 a 和 x 共同指向的值。如果要将 a 的值变为 [4,5,6,7],必须更改 x 指向的数组,而不是为 x 赋值一个新的数组。
传参中有this隐式丢失
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a是全局对象的属性 bar(); // "oops, global"
虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时
的 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
一种更微妙、更常见并且更出乎意料的情况发生在传入回调函数时:
var foo = {
bar: function() { return this.baz; },
baz: 1
};
var baz = 2;
(function(fn){
// fn其实引用的是foo
return fn(); // <-- 调用位置! fn和foo.bar指向同一块内存函数。
})(foo.bar); // 2
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一 个例子一样。并且因为this是动态作用域所以调用时候决定值,打印出2
var foo = {
bar: function() { alert(baz); },
baz: 1
};
var baz = 2;
(function(fn){
// fn其实引用的是foo
var baz = 3;
return fn(); // <-- 调用位置!
})(foo.bar); // 2 这里不是this所以是词法作用域解析时候就决定了这里打印2而不是3
使用arguments改变参数指向环境
var foo = {
bar: function() { return this.baz; },
baz: 1
};
var baz = 2;
(function(){
arguments.baz = 3;
return arguments[0](); // arguments是一个结构可以转换为数组的对象。所以这里 arguments[0] 把this上下文指向了 arguments。 不是foo 也不是window。
})(foo.bar); // 3