正文
}
})
me
.
pet
.
name
=
'Rocky'
me
.
pet
.
breed
=
'German Shepherd'
console
.
log
(
me
.
pet
.
name
)
// print Rocky
console
.
log
(
me
.
pet
.
breed
)
// print German Shepherd
即使是 Object.freeze() 也只能防止顶层属性被修改,而无法限制对于嵌套属性的修改,这一点我们会在下文的浅拷贝与深拷贝部分继续讨论。
变量赋值
按值传递
JavaScript 中永远是按值传递(pass-by-value),只不过当我们传递的是某个对象的引用时,这里的值指的是对象的引用。按值传递中函数的形参是被调用时所传实参的副本。修改形参的值并不会影响实参。而按引用传递(pass-by-reference)时,函数的形参接收实参的隐式引用,而不再是副本。这意味着函数形参的值如果被修改,实参也会被修改。同时两者指向相同的值。我们首先看下 C 中按值传递与引用传递的区别:
void
Modify
(
int
p
,
int
*
q
)
{
p
=
27
;
// 按值传递 - p是实参a的副本, 只有p被修改
*
q
=
27
;
// q是b的引用,q和b都被修改
}
int
main
()
{
int
a
=
1
;
int
b
=
1
;
Modify
(
a
,
&
b
);
// a 按值传递, b 按引用传递,
// a 未变化, b 改变了
return
(
0
);
}
而在 JavaScript 中,对比例子如下:
function
changeStuff
(
a
,
b
,
c
)
{
a
=
a *
10
;
b
.
item
=
"changed"
;
c
=
{
item
:
"changed"
};
}
var
num
=
10
;
var
obj1
=
{
item
:
"unchanged"
};
var
obj2
=
{
item
:
"unchanged"
};
changeStuff
(
num
,
obj1
,
obj2
);
console
.
log
(
num
);
console
.
log
(
obj1
.
item
);
console
.
log
(
obj2
.
item
);
// 输出结果
10
changed
unchanged
JavaScript 按值传递就表现于在内部修改了 c 的值但是并不会影响到外部的 obj2 变量。如果我们更深入地来理解这个问题,JavaScript 对于对象的传递则是按共享传递的(pass-by-sharing,也叫按对象传递、按对象共享传递)。最早由Barbara Liskov. 在1974年的GLU语言中提出;该求值策略被用于Python、Java、Ruby、JS等多种语言。该策略的重点是:调用函数传参时,函数接受对象实参引用的副本(既不是按值传递的对象副本,也不是按引用传递的隐式引用)。 它和按引用传递的不同在于:在共享传递中对函数形参的赋值,不会影响实参的值。按共享传递的直接表现就是上述代码中的 obj1,当我们在函数内修改了 b 指向的对象的属性值时,我们使用 obj1 来访问相同的变量时同样会得到变化后的值。
连续赋值
JavaScript 中是支持变量的连续赋值,即譬如:
var a=b=1;
但是在连续赋值中,会发生引用保留,可以考虑如下情景:
var
a
=
{
n
:
1
};
a
.
x
=
a
=
{
n
:
2
};
alert
(
a
.
x
);
// --> undefined
为了解释上述问题,我们引入一个新的变量:
var
a
=
{
n
:
1
};
var
b
=
a
;
// 持有a,以回查
a
.
x
=
a
=
{
n
:
2
};
alert
(
a
.
x
);
// --> undefined
alert
(
b
.
x
);
// --> [object Object]
实际上在连续赋值中,值是直接赋予给变量指向的内存地址:
a
.
x
=
a
=
{
n
:
2
}
│
│
{
n
:
1
}
<
──┘
└─
>
{
n
:
2
}
Deconstruction: 解构赋值
解构赋值允许你使用类似数组或对象字面量的语法将数组和对象的属性赋给各种变量。这种赋值语法极度简洁,同时还比传统的属性访问方法更为清晰。传统的访问数组前三个元素的方式为:
var
first
=
someArray
[
0
];
var
second
=
someArray
[
1
];
var
third
=
someArray
[
2
];
而通过解构赋值的特性,可以变为:
数组与迭代器
以上是数组解构赋值的一个简单示例,其语法的一般形式为:
[ variable1, variable2, ..., variableN ] = array;
这将为variable1到variableN的变量赋予数组中相应元素项的值。如果你想在赋值的同时声明变量,可在赋值语句前加入var、let或const关键字,例如:
var
[
variable1
,
variable2
,
...,
variableN
]
=
array
;
let
[
variable1
,
variable2
,
...,
variableN
]
=
array
;
const
[
variable1
,
variable2
,
...,
variableN
]
=
array
;
事实上,用变量来描述并不恰当,因为你可以对任意深度的嵌套数组进行解构:
var
[
foo
,
[[
bar
],
baz
]]
=
[
1
,
[[
2
],
3
]];
console
.
log
(
foo
);
// 1
console
.
log
(
bar
);
// 2
console
.
log
(
baz
);
// 3
此外,你可以在对应位留空来跳过被解构数组中的某些元素:
var
[,,
third
]
=
[
"foo"
,
"bar"
,
"baz"
];
console
.
log
(
third
);
// "baz"
而且你还可以通过“不定参数”模式捕获数组中的所有尾随元素:
var
[
head
,
...
tail
]
=
[
1
,
2
,
3
,
4
];
console
.
log
(
tail
);
// [2, 3, 4]
当访问空数组或越界访问数组时,对其解构与对其索引的行为一致,最终得到的结果都是:undefined。
console
.
log
([][
0
]);
// undefined
var
[
missing
]
=
[];
console
.
log
(
missing
);
// undefined
请注意,数组解构赋值的模式同样适用于任意迭代器:
function
*
fibs
()
{
var
a
=
0
;
var
b
=
1
;
while
(
true
)
{
yield
a
;
[
a
,
b
]
=
[
b
,
a
+
b
];
}
}
var
[
first
,
second
,
third
,
fourth
,
fifth
,
sixth
]
=
fibs
();
console
.
log
(
sixth
);
// 5
对象
通过解构对象,你可以把它的每个属性与不同的变量绑定,首先指定被绑定的属性,然后紧跟一个要解构的变量。
var
robotA
=
{
name
:
"Bender"
};
var
robotB
=
{
name
:
"Flexo"
};
var
{
name
:
nameA
}
=
robotA
;
var
{
name
:
nameB