图片 1

JavaScript 深入之参数按值传递

Posted by

JavaScript 深入之参数按值传递

2017/05/23 · JavaScript
· 参数

原文出处: 冴羽   

ES6 变量声明与赋值:值传递、浅拷贝与深拷贝详解

2017/08/16 · JavaScript
· es6

原文出处: 王下邀月熊   

ES6
变量声明与赋值:值传递、浅拷贝与深拷贝详解归纳于笔者的现代
JavaScript
开发:语法基础与实践技巧系列文章。本文首先介绍
ES6 中常用的三种变量声明方式,然后讨论了 JavaScript
按值传递的特性,最后介绍了复合类型拷贝的技巧;有兴趣的可以阅读下一章节
ES6
变量作用域与提升:变量的生命周期详解。

定义

在《JavaScript高级程序设计》第三版 4.1.3,讲到传递参数:

ECMAScript中所有函数的参数都是按值传递的。

什么是按值传递呢?

也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。

变量声明与赋值

ES6 为我们引入了 let 与 const
两种新的变量声明关键字,同时也引入了块作用域;本文首先介绍 ES6
中常用的三种变量声明方式,然后讨论了 JavaScript
按值传递的特性以及多种的赋值方式,最后介绍了复合类型拷贝的技巧。

按值传递

举个简单的例子:

var value = 1; function foo(v) { v = 2; console.log(v); //2 }
foo(value); console.log(value) // 1

1
2
3
4
5
6
7
var value = 1;
function foo(v) {
    v = 2;
    console.log(v); //2
}
foo(value);
console.log(value) // 1

很好理解,当传递 value 到函数 foo 中,相当于拷贝了一份
value,假设拷贝的这份叫 _value,函数中修改的都是 _value
的值,而不会影响原来的 value 值。

变量声明

在 JavaScript 中,基本的变量声明可以用 var 方式;JavaScript 允许省略
var,直接对未声明的变量赋值。也就是说,var a = 1 与 a =
1,这两条语句的效果相同。但是由于这样的做法很容易不知不觉地创建全局变量(尤其是在函数内部),所以建议总是使用
var 命令声明变量。在 ES6 中,对于变量声明的方式进行了扩展,引入了 let 与
const。var 与 let 两个关键字创建变量的区别在于, var
声明的变量作用域是最近的函数块;而 let
声明的变量作用域是最近的闭合块,往往会小于函数块。另一方面,以 let
关键字创建的变量虽然同样被提升到作用域头部,但是并不能在实际声明前使用;如果强行使用则会抛出
ReferenceError 异常。

引用传递

拷贝虽然很好理解,但是当值是一个复杂的数据结构的时候,拷贝就会产生性能上的问题。

所以还有另一种传递方式叫做按引用传递。

所谓按引用传递,就是传递对象的引用,函数内部对参数的任何改变都会影响该对象的值,因为两者引用的是同一个对象。

举个例子:

var obj = { value: 1 }; function foo(o) { o.value = 2;
console.log(o.value); //2 } foo(obj); console.log(obj.value) // 2

1
2
3
4
5
6
7
8
9
var obj = {
    value: 1
};
function foo(o) {
    o.value = 2;
    console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2

哎,不对啊,连我们的红宝书都说了 ECMAScript
中所有函数的参数都是按值传递的,这怎么能按引用传递成功呢?

而这究竟是不是引用传递呢?

var

var 是 JavaScript 中基础的变量声明方式之一,其基本语法为:

var x; // Declaration and initialization x = “Hello World”; //
Assignment // Or all in one var y = “Hello World”;

1
2
3
4
5
var x; // Declaration and initialization
x = "Hello World"; // Assignment
 
// Or all in one
var y = "Hello World";

ECMAScript 6 以前我们在 JavaScript 中并没有其他的变量声明方式,以 var
声明的变量作用于函数作用域中,如果没有相应的闭合函数作用域,那么该变量会被当做默认的全局变量进行处理。

function sayHello(){ var hello = “Hello World”; return hello; }
console.log(hello);

1
2
3
4
5
function sayHello(){
var hello = "Hello World";
return hello;
}
console.log(hello);

像如上这种调用方式会抛出异常: ReferenceError: hello is not defined,因为
hello 变量只能作用于 sayHello
函数中,不过如果按照如下先声明全局变量方式再使用时,其就能够正常调用:

var hello = “Hello World”; function sayHello(){ return hello; }
console.log(hello);

1
2
3
4
5
var hello = "Hello World";
function sayHello(){
return hello;
}
console.log(hello);

第三种传递方式

不急,让我们再看个例子:

var obj = { value: 1 }; function foo(o) { o = 2; console.log(o); //2 }
foo(obj); console.log(obj.value) // 1

1
2
3
4
5
6
7
8
9
var obj = {
    value: 1
};
function foo(o) {
    o = 2;
    console.log(o); //2
}
foo(obj);
console.log(obj.value) // 1

如果 JavaScript
采用的是引用传递,外层的值也会被修改呐,这怎么又没被改呢?所以真的不是引用传递吗?

这就要讲到其实还有第三种传递方式,叫按共享传递。

而共享传递是指,在传递对象的时候,传递对象的引用的副本。

注意:
按引用传递是传递对象的引用,而按共享传递是传递对象的引用的副本!

所以修改 o.value,可以通过引用找到原值,但是直接修改
o,并不会修改原值。所以第二个和第三个例子其实都是按共享传递。

最后,你可以这样理解:

参数如果是基本类型是按值传递,如果是引用类型按共享传递。

但是因为拷贝副本也是一种值的拷贝,所以在高程中也直接认为是按值传递了。

所以,高程,谁叫你是红宝书嘞!

let

在 ECMAScript 6 中我们可以使用 let 关键字进行变量声明:

let x; // Declaration and initialization x = “Hello World”; //
Assignment // Or all in one let y = “Hello World”;

1
2
3
4
5
let x; // Declaration and initialization
x = "Hello World"; // Assignment
 
// Or all in one
let y = "Hello World";

let 关键字声明的变量是属于块作用域,也就是包含在 {} 之内的作用于。使用
let
关键字的优势在于能够降低偶然的错误的概率,因为其保证了每个变量只能在最小的作用域内进行访问。

var name = “Peter”; if(name === “Peter”){ let hello = “Hello Peter”; }
else { let hello = “Hi”; } console.log(hello);

1
2
3
4
5
6
7
var name = "Peter";
if(name === "Peter"){
let hello = "Hello Peter";
} else {
let hello = "Hi";
}
console.log(hello);

上述代码同样会抛出 ReferenceError: hello is not defined 异常,因为 hello
只能够在闭合的块作用域中进行访问,我们可以进行如下修改:

var name = “Peter”; if(name === “Peter”){ let hello = “Hello Peter”;
console.log(hello); } else { let hello = “Hi”; console.log(hello); }

1
2
3
4
5
6
7
8
var name = "Peter";
if(name === "Peter"){
let hello = "Hello Peter";
  console.log(hello);
} else {
let hello = "Hi";
  console.log(hello);
}

我们可以利用这种块级作用域的特性来避免闭包中因为变量保留而导致的问题,譬如如下两种异步代码,使用
var 时每次循环中使用的都是相同变量;而使用 let 声明的 i
则会在每次循环时进行不同的绑定,即每次循环中闭包捕获的都是不同的 i
实例:

for(let i = 0;i < 2; i++){
setTimeout(()=>{console.log(`i:${i}`)},0); } for(var j = 0;j <
2; j++){ setTimeout(()=>{console.log(`j:${j}`)},0); } let k = 0;
for(k = 0;k < 2; k++){
setTimeout(()=>{console.log(`k:${k}`)},0); } // output i:0 i:1 j:2
j:2 k:2 k:2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for(let i = 0;i < 2; i++){
        setTimeout(()=>{console.log(`i:${i}`)},0);
}
 
for(var j = 0;j < 2; j++){
        setTimeout(()=>{console.log(`j:${j}`)},0);
}
 
let k = 0;
for(k = 0;k < 2; k++){
        setTimeout(()=>{console.log(`k:${k}`)},0);
}
 
// output
i:0
i:1
j:2
j:2
k:2
k:2

深入系列

JavaScript深入系列目录地址:。

JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

本系列:

  1. JavaScirpt 深入之从原型到原型链
  2. JavaScript
    深入之词法作用域和动态作用域
  3. JavaScript 深入之执行上下文栈
  4. JavaScript 深入之变量对象
  5. JavaScript 深入之作用域链
  6. JavaScript 深入之从 ECMAScript 规范解读
    this
  7. JavaScript 深入之执行上下文
  8. JavaScript 深入之闭包

    1 赞 收藏
    评论

图片 1

const

const 关键字一般用于常量声明,用 const
关键字声明的常量需要在声明时进行初始化并且不可以再进行修改,并且 const
关键字声明的常量被限制于块级作用域中进行访问。

function f() { { let x; { // okay, block scoped name const x = “sneaky”;
// error, const x = “foo”; } // error, already declared in block let x =
“inner”; } }

1
2
3
4
5
6
7
8
9
10
11
12
13
function f() {
  {
let x;
    {
      // okay, block scoped name
const x = "sneaky";
      // error, const
      x = "foo";
    }
    // error, already declared in block
let x = "inner";
  }
}

JavaScript 中 const 关键字的表现于 C
中存在着一定差异,譬如下述使用方式在 JavaScript 中就是正确的,而在 C
中则抛出异常:

# JavaScript const numbers = [1, 2, 3, 4, 6] numbers[4] = 5
console.log(numbers[4]) // print 5 # C const int numbers[] = {1, 2,
3, 4, 6}; numbers[4] = 5; // error: read-only variable is not
assignable printf(“%dn”, numbers[4]);

1
2
3
4
5
6
7
8
9
# JavaScript
const numbers = [1, 2, 3, 4, 6]
numbers[4] = 5
console.log(numbers[4]) // print 5
 
# C
const int numbers[] = {1, 2, 3, 4, 6};
numbers[4] = 5; // error: read-only variable is not assignable
printf("%dn", numbers[4]);

从上述对比我们也可以看出,JavaScript 中 const
限制的并非值不可变性;而是创建了不可变的绑定,即对于某个值的只读引用,并且禁止了对于该引用的重赋值,即如下的代码会触发错误:

const numbers = [1, 2, 3, 4, 6] numbers = [7, 8, 9, 10, 11] //
error: assignment to constant variable console.log(numbers[4])

1
2
3
const numbers = [1, 2, 3, 4, 6]
numbers = [7, 8, 9, 10, 11] // error: assignment to constant variable
console.log(numbers[4])

我们可以参考如下图片理解这种机制,每个变量标识符都会关联某个存放变量实际值的物理地址;所谓只读的变量即是该变量标识符不可以被重新赋值,而该变量指向的值还是可变的。

JavaScript 中存在着所谓的原始类型与复合类型,使用 const
声明的原始类型是值不可变的:

# Example 1 const a = 10 a = a + 1 // error: assignment to constant
variable # Example 2 const isTrue = true isTrue = false // error:
assignment to constant variable # Example 3 const sLower = ‘hello
world’ const sUpper = sLower.toUpperCase() // create a new string
console.log(sLower) // print hello world console.log(sUpper) // print
HELLO WORLD

1
2
3
4
5
6
7
8
9
10
11
# Example 1
const a = 10
a = a + 1 // error: assignment to constant variable
# Example 2
const isTrue = true
isTrue = false // error: assignment to constant variable
# Example 3
const sLower = ‘hello world’
const sUpper = sLower.toUpperCase() // create a new string
console.log(sLower) // print hello world
console.log(sUpper) // print HELLO WORLD

而如果我们希望将某个对象同样变成不可变类型,则需要使用
Object.freeze();不过该方法仅对于键值对的 Object 起作用,而无法作用于
Date、Map 与 Set 等类型:

# Example 4 const me = Object.freeze({name: “Jacopo”}) me.age = 28
console.log(me.age) // print undefined # Example 5 const arr =
Object.freeze([-1, 1, 2, 3]) arr[0] = 0 console.log(arr[0]) //
print -1 # Example 6 const me = Object.freeze({ name: ‘Jacopo’, pet: {
type: ‘dog’, name: ‘Spock’ } }) me.pet.name = ‘Rocky’ me.pet.breed =
‘German Shepherd’ console.log(me.pet.name) // print Rocky
console.log(me.pet.breed) // print German Shepherd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Example 4
const me = Object.freeze({name: “Jacopo”})
me.age = 28
console.log(me.age) // print undefined
# Example 5
const arr = Object.freeze([-1, 1, 2, 3])
arr[0] = 0
console.log(arr[0]) // print -1
# Example 6
const me = Object.freeze({
  name: ‘Jacopo’,
pet: {
    type: ‘dog’,
    name: ‘Spock’
  }
})
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); }

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;

1
var a=b=1;

但是在连续赋值中,会发生引用保留,可以考虑如下情景:

var a = {n:1}; a.x = a = {n:2}; alert(a.x); // –> undefined

1
2
3
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]

1
2
3
4
5
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}

1
2
3
a.x  =  a  = {n:2}
              │      │
      {n:1}<──┘      └─>{n:2}

Deconstruction: 解构赋值

解构赋值允许你使用类似数组或对象字面量的语法将数组和对象的属性赋给各种变量。这种赋值语法极度简洁,同时还比传统的属性访问方法更为清晰。传统的访问数组前三个元素的方式为:

var first = someArray[0]; var second = someArray[1]; var third =
someArray[2];

1
2
3
var first = someArray[0];
var second = someArray[1];
var third = someArray[2];

而通过解构赋值的特性,可以变为:

var [first, second, third] = someArray; // === Arrays var [a, b] =
[1, 2]; console.log(a, b); //=> 1 2 // Use from functions, only
select from pattern var foo = () => { return [1, 2, 3]; }; var [a,
b] = foo(); console.log(a, b); // => 1 2 // Omit certain values var
[a, , b] = [1, 2, 3]; console.log(a, b); // => 1 3 // Combine
with spread/rest operator (accumulates the rest of the values) var [a,
…b] = [1, 2, 3]; console.log(a, b); // => 1 [ 2, 3 ] //
Fail-safe. var [, , , a, b] = [1, 2, 3]; console.log(a, b); // =>
undefined undefined // Swap variables easily without temp var a = 1, b =
2; [b, a] = [a, b]; console.log(a, b); // => 2 1 // Advance deep
arrays var [a, [b, [c, d]]] = [1, [2, [[[3, 4], 5],
6]]]; console.log(“a:”, a, “b:”, b, “c:”, c, “d:”, d); // => a: 1
b: 2 c: [ [ 3, 4 ], 5 ] d: 6 // === Objects var {user: x} = {user:
5}; console.log(x); // => 5 // Fail-safe var {user: x} = {user2: 5};
console.log(x); // => undefined // More values var {prop: x, prop2:
y} = {prop: 5, prop2: 10}; console.log(x, y); // => 5 10 //
Short-hand syntax var { prop, prop2} = {prop: 5, prop2: 10};
console.log(prop, prop2); // => 5 10 // Equal to: var { prop: prop,
prop2: prop2} = {prop: 5, prop2: 10}; console.log(prop, prop2); // =>
5 10 // Oops: This doesn’t work: var a, b; { a, b } = {a: 1, b: 2}; //
But this does work var a, b; ({ a, b } = {a: 1, b: 2}); console.log(a,
b); // => 1 2 // This due to the grammar in JS. // Starting with {
implies a block scope, not an object literal. // () converts to an
expression. // From Harmony Wiki: // Note that object literals cannot
appear in // statement positions, so a plain object // destructuring
assignment statement // { x } = y must be parenthesized either // as ({
x } = y) or ({ x }) = y. // Combine objects and arrays var {prop: x,
prop2: [, y]} = {prop: 5, prop2: [10, 100]}; console.log(x, y); //
=> 5 100 // Deep objects var { prop: x, prop2: { prop2: { nested: [
, , b] } } } = { prop: “Hello”, prop2: { prop2: { nested: [“a”, “b”,
“c”]}}}; console.log(x, b); // => Hello c // === Combining all to
make fun happen // All well and good, can we do more? Yes! // Using as
method parameters var foo = function ({prop: x}) { console.log(x); };
foo({invalid: 1}); foo({prop: 1}); // => undefined // => 1 // Can
also use with the advanced example var foo = function ({ prop: x, prop2:
{ prop2: { nested: b } } }) { console.log(x, …b); }; foo({ prop:
“Hello”, prop2: { prop2: { nested: [“a”, “b”, “c”]}}}); // => Hello
a b c // In combination with other ES2015 features. // Computed property
names const name = ‘fieldName’; const computedObject = { [name]: name
}; // (where object is { ‘fieldName’: ‘fieldName’ }) const { [name]:
nameValue } = computedObject; console.log(nameValue) // => fieldName
// Rest and defaults var ajax = function ({ url = “localhost”, port: p =
80}, …data) { console.log(“Url:”, url, “Port:”, p, “Rest:”, data); };
ajax({ url: “someHost” }, “additional”, “data”, “hello”); // => Url:
someHost Port: 80 Rest: [ ‘additional’, ‘data’, ‘hello’ ] ajax({ },
“additional”, “data”, “hello”); // => Url: localhost Port: 80 Rest:
[ ‘additional’, ‘data’, ‘hello’ ] // Ooops: Doesn’t work (in traceur)
var ajax = ({ url = “localhost”, port: p = 80}, …data) => {
console.log(“Url:”, url, “Port:”, p, “Rest:”, data); }; ajax({ },
“additional”, “data”, “hello”); // probably due to traceur compiler But
this does: var ajax = ({ url: url = “localhost”, port: p = 80}, …data)
=> { console.log(“Url:”, url, “Port:”, p, “Rest:”, data); }; ajax({
}, “additional”, “data”, “hello”); // Like _.pluck var users = [ {
user: “Name1” }, { user: “Name2” }, { user: “Name2” }, { user: “Name3” }
]; var names = users.map( ({ user }) => user ); console.log(names);
// => [ ‘Name1’, ‘Name2’, ‘Name2’, ‘Name3’ ] // Advanced usage with
Array Comprehension and default values var users = [ { user: “Name1” },
{ user: “Name2”, age: 2 }, { user: “Name2” }, { user: “Name3”, age: 4 }
]; [for ({ user, age = “DEFAULT AGE” } of users) console.log(user,
age)]; // => Name1 DEFAULT AGE // => Name2 2 // => Name2
DEFAULT AGE // => Name3 4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
var [first, second, third] = someArray;
// === Arrays
 
var [a, b] = [1, 2];
console.log(a, b);
//=> 1 2
 
 
// Use from functions, only select from pattern
var foo = () => {
return [1, 2, 3];
};
 
var [a, b] = foo();
console.log(a, b);
// => 1 2
 
 
// Omit certain values
var [a, , b] = [1, 2, 3];
console.log(a, b);
// => 1 3
 
 
// Combine with spread/rest operator (accumulates the rest of the values)
var [a, …b] = [1, 2, 3];
console.log(a, b);
// => 1 [ 2, 3 ]
 
 
// Fail-safe.
var [, , , a, b] = [1, 2, 3];
console.log(a, b);
// => undefined undefined
 
 
// Swap variables easily without temp
var a = 1, b = 2;
[b, a] = [a, b];
console.log(a, b);
// => 2 1
 
 
// Advance deep arrays
var [a, [b, [c, d]]] = [1, [2, [[[3, 4], 5], 6]]];
console.log("a:", a, "b:", b, "c:", c, "d:", d);
// => a: 1 b: 2 c: [ [ 3, 4 ], 5 ] d: 6
 
 
// === Objects
 
var {user: x} = {user: 5};
console.log(x);
// => 5
 
 
// Fail-safe
var {user: x} = {user2: 5};
console.log(x);
// => undefined
 
 
// More values
var {prop: x, prop2: y} = {prop: 5, prop2: 10};
console.log(x, y);
// => 5 10
 
// Short-hand syntax
var { prop, prop2} = {prop: 5, prop2: 10};
console.log(prop, prop2);
// => 5 10
 
// Equal to:
var { prop: prop, prop2: prop2} = {prop: 5, prop2: 10};
console.log(prop, prop2);
// => 5 10
 
// Oops: This doesn’t work:
var a, b;
{ a, b } = {a: 1, b: 2};
 
// But this does work
var a, b;
({ a, b } = {a: 1, b: 2});
console.log(a, b);
// => 1 2
 
// This due to the grammar in JS.
// Starting with { implies a block scope, not an object literal.
// () converts to an expression.
 
// From Harmony Wiki:
// Note that object literals cannot appear in
// statement positions, so a plain object
// destructuring assignment statement
//  { x } = y must be parenthesized either
// as ({ x } = y) or ({ x }) = y.
 
// Combine objects and arrays
var {prop: x, prop2: [, y]} = {prop: 5, prop2: [10, 100]};
console.log(x, y);
// => 5 100
 
 
// Deep objects
var {
  prop: x,
  prop2: {
    prop2: {
      nested: [ , , b]
    }
  }
} = { prop: "Hello", prop2: { prop2: { nested: ["a", "b", "c"]}}};
console.log(x, b);
// => Hello c
 
 
// === Combining all to make fun happen
 
// All well and good, can we do more? Yes!
// Using as method parameters
var foo = function ({prop: x}) {
  console.log(x);
};
 
foo({invalid: 1});
foo({prop: 1});
// => undefined
// => 1
 
 
// Can also use with the advanced example
var foo = function ({
  prop: x,
  prop2: {
    prop2: {
      nested: b
    }
  }
}) {
  console.log(x, …b);
};
foo({ prop: "Hello", prop2: { prop2: { nested: ["a", "b", "c"]}}});
// => Hello a b c
 
 
// In combination with other ES2015 features.
 
// Computed property names
const name = ‘fieldName’;
const computedObject = { [name]: name }; // (where object is { ‘fieldName’: ‘fieldName’ })
const { [name]: nameValue } = computedObject;
console.log(nameValue)
// => fieldName
 
 
 
// Rest and defaults
var ajax = function ({ url = "localhost", port: p = 80}, …data) {
  console.log("Url:", url, "Port:", p, "Rest:", data);
};
 
ajax({ url: "someHost" }, "additional", "data", "hello");
// => Url: someHost Port: 80 Rest: [ ‘additional’, ‘data’, ‘hello’ ]
 
ajax({ }, "additional", "data", "hello");
// => Url: localhost Port: 80 Rest: [ ‘additional’, ‘data’, ‘hello’ ]
 
 
// Ooops: Doesn’t work (in traceur)
var ajax = ({ url = "localhost", port: p = 80}, …data) => {
  console.log("Url:", url, "Port:", p, "Rest:", data);
};
ajax({ }, "additional", "data", "hello");
// probably due to traceur compiler
 
But this does:
var ajax = ({ url: url = "localhost", port: p = 80}, …data) => {
  console.log("Url:", url, "Port:", p, "Rest:", data);
};
ajax({ }, "additional", "data", "hello");
 
 
// Like _.pluck
var users = [
  { user: "Name1" },
  { user: "Name2" },
  { user: "Name2" },
  { user: "Name3" }
];
var names = users.map( ({ user }) => user );
console.log(names);
// => [ ‘Name1’, ‘Name2’, ‘Name2’, ‘Name3’ ]
 
 
// Advanced usage with Array Comprehension and default values
var users = [
  { user: "Name1" },
  { user: "Name2", age: 2 },
  { user: "Name2" },
  { user: "Name3", age: 4 }
];
 
[for ({ user, age = "DEFAULT AGE" } of users) console.log(user, age)];
// => Name1 DEFAULT AGE
// => Name2 2
// => Name2 DEFAULT AGE
// => Name3 4

数组与迭代器

以上是数组解构赋值的一个简单示例,其语法的一般形式为:

[ variable1, variable2, …, variableN ] = array;

1
[ variable1, variable2, …, variableN ] = array;

这将为variable1到variableN的变量赋予数组中相应元素项的值。如果你想在赋值的同时声明变量,可在赋值语句前加入var、let或const关键字,例如:

var [ variable1, variable2, …, variableN ] = array; let [
variable1, variable2, …, variableN ] = array; const [ variable1,
variable2, …, variableN ] = array;

1
2
3
   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

1
2
3
4
5
6
7
   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”

1
2
3
   var [,,third] = ["foo", "bar", "baz"];
    console.log(third);
    // "baz"

而且你还可以通过“不定参数”模式捕获数组中的所有尾随元素:

var [head, …tail] = [1, 2, 3, 4]; console.log(tail); // [2, 3,
4]

1
2
3
var [head, …tail] = [1, 2, 3, 4];
    console.log(tail);
    // [2, 3, 4]

当访问空数组或越界访问数组时,对其解构与对其索引的行为一致,最终得到的结果都是:undefined。

console.log([][0]); // undefined var [missing] = [];
console.log(missing); // undefined

1
2
3
4
5
   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

1
2
3
4
5
6
7
8
9
10
11
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 } = robotB;
console.log(nameA); // “Bender” console.log(nameB); // “Flexo”

1
2
3
4
5
6
7
8
var robotA = { name: "Bender" };
var robotB = { name: "Flexo" };
var { name: nameA } = robotA;
var { name: nameB } = robotB;
    console.log(nameA);
    // "Bender"
    console.log(nameB);
    // "Flexo"

当属性名与变量名一致时,可以通过一种实用的句法简写:

var { foo, bar } = { foo: “lorem”, bar: “ipsum” }; console.log(foo); //
“lorem” console.log(bar); // “ipsum”

1
2
3
4
5
var { foo, bar } = { foo: "lorem", bar: "ipsum" };
    console.log(foo);
    // "lorem"
    console.log(bar);
    // "ipsum"

与数组解构一样,你可以随意嵌套并进一步组合对象解构:

var complicatedObj = { arrayProp: [ “Zapp”, { second: “Brannigan” } ]
}; var { arrayProp: [first, { second }] } = complicatedObj;
console.log(first); // “Zapp” console.log(second); // “Brannigan”

1
2
3
4
5
6
7
8
9
10
11
var complicatedObj = {
      arrayProp: [
        "Zapp",
        { second: "Brannigan" }
      ]
    };
var { arrayProp: [first, { second }] } = complicatedObj;
    console.log(first);
    // "Zapp"
    console.log(second);
    // "Brannigan"

当你解构一个未定义的属性时,得到的值为undefined:

var { missing } = {}; console.log(missing); // undefined

1
2
3
var { missing } = {};
    console.log(missing);
    // undefined

请注意,当你解构对象并赋值给变量时,如果你已经声明或不打算声明这些变量(亦即赋值语句前没有let、const或var关键字),你应该注意这样一个潜在的语法错误:

{ blowUp } = { blowUp: 10 }; // Syntax error 语法错误

1
2
   { blowUp } = { blowUp: 10 };
    // Syntax error 语法错误

为什么会出错?这是因为JavaScript语法通知解析引擎将任何以{开始的语句解析为一个块语句(例如,{console}是一个合法块语句)。解决方案是将整个表达式用一对小括号包裹:

({ safe } = {}); // No errors 没有语法错误

1
2
   ({ safe } = {});
    // No errors 没有语法错误

默认值

当你要解构的属性未定义时你可以提供一个默认值:

var [missing = true] = []; console.log(missing); // true var {
message: msg = “Something went wrong” } = {}; console.log(msg); //
“Something went wrong” var { x = 3 } = {}; console.log(x); // 3

1
2
3
4
5
6
7
8
9
var [missing = true] = [];
    console.log(missing);
    // true
var { message: msg = "Something went wrong" } = {};
    console.log(msg);
    // "Something went wrong"
var { x = 3 } = {};
    console.log(x);
    // 3

由于解构中允许对对象进行解构,并且还支持默认值,那么完全可以将解构应用在函数参数以及参数的默认值中。

function removeBreakpoint({ url, line, column }) { // … }

1
2
3
function removeBreakpoint({ url, line, column }) {
      // …
    }

当我们构造一个提供配置的对象,并且需要这个对象的属性携带默认值时,解构特性就派上用场了。举个例子,jQuery的ajax函数使用一个配置对象作为它的第二参数,我们可以这样重写函数定义:

jQuery.ajax = function (url, { async = true, beforeSend = noop, cache =
true, complete = noop, crossDomain = false, global = true, // …
更多配置 }) { // … do stuff };

1
2
3
4
5
6
7
8
9
10
11
jQuery.ajax = function (url, {
      async = true,
      beforeSend = noop,
      cache = true,
      complete = noop,
      crossDomain = false,
      global = true,
      // … 更多配置
    }) {
      // … do stuff
    };

同样,解构也可以应用在函数的多重返回值中,可以类似于其他语言中的元组的特性:

function returnMultipleValues() { return [1, 2]; } var [foo, bar] =
returnMultipleValues();

1
2
3
4
function returnMultipleValues() {
return [1, 2];
    }
var [foo, bar] = returnMultipleValues();

相关文章

Leave a Reply

电子邮件地址不会被公开。 必填项已用*标注