您的位置:新葡亰496net > 新葡亰官网 > 将元素应用于数组和函数的优雅方法,变量声明

将元素应用于数组和函数的优雅方法,变量声明

发布时间:2019-11-10 05:58编辑:新葡亰官网浏览(180)

    ES6:spread syntax —— JavaScript 将元素应用于数组和函数的优雅方法

    2017/04/27 · JavaScript · es6

    原文出处: deadcoderising   译文出处:胡子大哈   

    新葡亰496net 1

    上一篇文章中,我介绍了一些关于 ES6 解构方法的新特性。

    本文中我们一起来看一下另外一个 JavaScript 新增语法 —— spread syntax(扩展语法)。

    spread syntax 实际上非常简单,假设你的可遍历对象中有一些元素(如数组),你想把这些元素应用于另一个新的数组或者一个函数调用。通常的做法,你会从索引开始,利用循环访问每个元素。但是通过 spread syntax 这个事情就变的很简单了,你可以使用三个点作为前缀,即 ... 应用于可遍历对象上,问题就解决了。

    为了更直观,我们一起看几个用例就明白了。

    ES6:解构——JavaScript 从数组和对象中提取数据的优雅方法

    2017/04/17 · JavaScript · es6

    原文出处: deadcoderising   译文出处:胡子大哈   

    ES6 有很多新特性,它很大程度上提升了 JavaScript 的编程体验,并且也告诉外界,JavaScript 依旧强势。

    其中一个新特性是其对数组和对象的解构,通过解构方法从数组和对象中提取数据变得非常简单和方便。接下来看一下它是如何做到的,我们从数组开始讲起。

    原文出处: 王下邀月熊   

    复制一个数组

    假设有一个数组名字是 names。

    JavaScript

    const names = ['Luke','Eva','Phil'];

    1
    const names = ['Luke','Eva','Phil'];

    如何把 names 里面的元素复制到一个新数组中呢?

    传统的做法是用循环来实现,但是使用 spread syntax,解决方法很简洁。

    JavaScript

    const copiedList = [...names] console.log(copiedList); // ['Luke','Eva','Phil']

    1
    2
    const copiedList = [...names]  
    console.log(copiedList); // ['Luke','Eva','Phil']

    可以看到比循环方法简单的多。

    这里值得一提的是,这里复制的是引用。也就是说如果一个数组中的元素发生改变,那么另一个数组中的元素也相应地发生改变。

    JavaScript

    var initialArray = [{name: "Luke"}]; var copiedArray = [...initialArray]; initialArray[0]['name'] = 'Mark'; console.log(initialArray); //Array [{'name': 'Mark'}] console.log(copiedArray); //Array [{'name': 'Mark'}]

    1
    2
    3
    4
    5
    6
    7
    var initialArray = [{name: "Luke"}];  
    var copiedArray = [...initialArray];
     
    initialArray[0]['name'] = 'Mark';
     
    console.log(initialArray); //Array [{'name': 'Mark'}]  
    console.log(copiedArray); //Array [{'name': 'Mark'}]

    从数组中提取数据

    假设你有如下的数组,里面是几个人的名字:

    JavaScript

    const names = ['Luke', 'Eva', 'Phil'];

    1
    const names = ['Luke', 'Eva', 'Phil'];

    接下来,使用解构从里面提取数据。

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

    连接数组

    spread syntax 另一个用法是连接数组,做法是把你想要扩展的数组放到一起。如下:

    JavaScript

    const concatinated = [...names, ...names]; console.log(concatinated); // ['Luke','Eva','Phil', 'Luke','Eva','Phil']

    1
    2
    const concatinated = [...names, ...names];  
    console.log(concatinated); // ['Luke','Eva','Phil', 'Luke','Eva','Phil']

    从数组中取元素

    首先从最基本的开始——提取数组中第一个元素。

    JavaScript

    const [first] = names; console.log(first); // 'Luke'

    1
    2
    const [first] = names;  
    console.log(first); // 'Luke'

    ok,下面分析一下这个语法都做了什么。把一个变量用中括号括起来,表示我们想要取得 names 数组中的第一个元素,并且把它分配给指定的变量,本例中即变量 first新葡亰496net,。

    那么现在想要提取几个元素,比如第一个和第二个怎么办呢?很简单,在中括号中添加变量就可以。这样会从数组中顺序提取几个元素分配给指定的变量。

    JavaScript

    const [first, second] = names; console.log(first, second); // 'Luke' 'Eva'

    1
    2
    const [first, second] = names;  
    console.log(first, second); // 'Luke' 'Eva'

    变量声明与赋值

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

    把独立变量扩展到一起

    除了把元素复制到一个新数组中,还可以把独立变量一起扩展到某数组中。下面举个例子,把第一个元素和 names 数组扩展到一起。

    JavaScript

    const first = ['Emily', ...names]; console.log(first); // ['Emily','Luke','Eva','Phil']

    1
    2
    const first = ['Emily', ...names];  
    console.log(first); // ['Emily','Luke','Eva','Phil']

    还可以把独立变量放到 names 的后面。

    JavaScript

    const last = [...names, 'Emily']; console.log(last); // ['Luke','Eva','Phil', 'Emily']

    1
    2
    const last = [...names, 'Emily'];  
    console.log(last); // ['Luke','Eva','Phil', 'Emily']

    元素缺失时的默认值

    以上面的数组为例,如果我们要取 4 个值,而数组中只有 3 个值会发生什么呢?

    JavaScript

    const [first, second, third, fourth] = names; console.log(fourth); // undefined

    1
    2
    const [first, second, third, fourth] = names;  
    console.log(fourth); // undefined

    这种场景下,fourthunderfined

    这在很多场景下都是我们不想见到的,所以可以当数组中没有那么多的值的时候,我们可以提前给变量赋上默认值。

    JavaScript

    const [first, second, third, fourth='Martin'] = names; console.log(fourth); // 'Martin'

    1
    2
    const [first, second, third, fourth='Martin'] = names;  
    console.log(fourth); // 'Martin'

    变量声明

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

    在函数调用中使用 spread syntax

    你已经掌握了如何在数组中运用 spread syntax,现在我们来看一下如何在函数调用中使用。

    假设我们有个简单函数 —— printer —— 接受三个参数,并且打印出来。

    JavaScript

    const printer = (name1, name2, name3) => { console.log(`Names: ${name1}, ${name2} and ${name3}`); };

    1
    2
    3
    const printer = (name1, name2, name3) => {  
        console.log(`Names: ${name1}, ${name2} and ${name3}`);
    };

    依照 printer 函数定义,可以使用 spread syntax 把数组元素应用于 printer 函数。

    JavaScript

    printer(...names); // Names: Luke, Eva and Phil

    1
    printer(...names); // Names: Luke, Eva and Phil

    和数组的用法一样,可以把独立变量一起输出。我们添加 ‘Emily’ 作为 printer 函数的第一个参数,后面跟着 ...names

    JavaScript

    printer('Emily', ...names); // Names: Emily, Luke and Eva

    1
    printer('Emily', ...names); // Names: Emily, Luke and Eva

    如果传递给函数过多的参数,那么超过函数参数个数的元素将会被忽略掉。

    跳过数组中的元素

    学会了如何按顺序从数组中提取数据。现在有这样的场景:想要跳过数组中的某个元素取值,这样就可以避免取到不想取的值。解构方法中提供了很好的解决方案。

    JavaScript

    var [first, , second] = names; console.log(first, second); // 'Luke' 'Phil'

    1
    2
    var [first, , second] = names;  
    console.log(first, second); // 'Luke' 'Phil'

    通过简单的添加逗号,就可以避免分配相应的数组元素,直接跳到下一个元素了。如果想要跳过多个元素呢?也很简单,多加几个逗号就可以了。

    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);

    Bonus:spread syntax 应用于对象字面值!

    这个特征是基于 ECMAScript的附加特征。但是目前使用它需要 babel 插件,叫做:babel-plugin-transform-object-rest-spread。

    通过 spread syntax 这种变体,你可以把两个对象扩展到一起。假设你有两个对象包含了个人信息 —— nameAndAgeabout

    JavaScript

    const nameAndAge = { name: 'Luke', age: 24, } const about = { work: 'Developer', hobby: 'Skydiving', }

    1
    2
    3
    4
    5
    6
    7
    8
    9
        const nameAndAge = {  
          name: 'Luke',
          age: 24,
        }
     
        const about = {  
          work: 'Developer',
          hobby: 'Skydiving',
        }

    接下来用 spread syntax 把两个对象合并到一起。

    JavaScript

    const person = { ...nameAndAge, ...about, } console.log(person); //{ // "age": 24, // "hobby": "Skydiving", // "name": "Luke", // "work": "Developer" //}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
      const person = {  
          ...nameAndAge,
          ...about,
        }
     
        console.log(person);  
        //{
        //  "age": 24,
        //  "hobby": "Skydiving",
        //  "name": "Luke",
        //  "work": "Developer"
        //}

    OK,这篇文章介绍了 spread syntax 的用法。后面我们会继续介绍 ES6 新特性,敬请继续关注!

    1 赞 1 收藏 评论

    新葡亰496net 2

    分配数组中剩下的给某元素

    到现在,已经知道了如何从数组中提取单个元素,那么对于想要取数组中的后面连续部分的元素怎么办呢?看下面的解构代码。

    JavaScript

    var [first, ...rest] = names; console.log(rest); // ['Eva','Phil']

    1
    2
    var [first, ...rest] = names;  
    console.log(rest); // ['Eva','Phil']

    通过在最后一个变量前加 ... 标记,这个意思是分配数组中剩下的所有元素给 rest 变量。

    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

    解构对象

    ok,数组的解构已经都学会了,下面看一下从对象中提取数据,假设有如下描述一个人的对象。

    JavaScript

    const person = { name: 'Luke', age: '24', facts: { hobby: 'Photo', work: 'Software Developer' } }

    1
    2
    3
    4
    5
    6
    7
    8
    const person = {
        name: 'Luke',
        age: '24',
        facts: {
            hobby: 'Photo',
            work: 'Software Developer'
        }
    }

    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() 也只能防止顶层属性被修改,而无法限制对于嵌套属性的修改,这一点我们会在下文的浅拷贝与深拷贝部分继续讨论。

    从对象中提取数据

    依然从最基本的开始,提取从 person 中提取 nameage

    JavaScript

    const {name, age} = person; console.log(name, age); // 'Luke' '24'

    1
    2
    const {name, age} = person;  
    console.log(name, age); // 'Luke' '24'

    可以看到,和从数组中提取数据的语法都是一样的,唯一的不同是把方括号替换成了花括号。

    变量赋值

    提取嵌套值

    假设想要提取对象结构中深层次的值该怎么处理?比如 person 中的 hobby。代码如下。

    JavaScript

    const {facts: {hobby}} = person; console.log(hobby); // 'Photo'

    1
    2
    const {facts: {hobby}} = person;  
    console.log(hobby); // 'Photo'

    通过冒号可以描述对象中的路径,这样就可以取到对象中深层的嵌套值了。

    按值传递

    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 来访问相同的变量时同样会得到变化后的值。

    数据缺失时的默认值

    如在解构数组时的处理方案一样,当想要抽取的值不存在时,也可以给对象里的值赋默认值。如下面代码,想要提取 hometown 属性,并且给定 Unknown 默认值。

    JavaScript

    const {hometown = 'Unknown'} = person; console.log(hometown); // 'Unknown'

    1
    2
    const {hometown = 'Unknown'} = person;  
    console.log(hometown); // 'Unknown'

    连续赋值

    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}

    解构函数参数

    在结束本文之前,我们来看最后一个例子——解构函数参数。

    假设你有一个函数,接受一个对象作为参数。那么你可以直接在参数列表中对对象进行解构。例如下面这个 toString 函数,打印出 nameage

    JavaScript

    const toString = ({ name, age }) = > { return` $ { name } is $ { age } years old`; } toString(person); // Luke is 24 years old

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const toString = ({
        name, age
    }) = > {
        return` $ {
            name
        }
        is $ {
            age
        }
        years old`;
    }
    toString(person); // Luke is 24 years old

    不过要提醒大家的是,这不是一个好的编程习惯,如果别人使用你的函数,很容易造成误解,调试起来特别不方便,这里只是告诉大家可以这样进行解构而已。

    ok,那么到现在对于数组和对象的解构问题大家应该都学会了,后面也还会介绍一些 JavaScript 的一些新特性,欢迎大家对我保持关注。

    如果你认为文章中还需要注意什么,或者添加什么,请让我知道。

    1 赞 1 收藏 评论

    新葡亰496net 3

    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();

    将元素应用于数组和函数的优雅方法,变量声明与赋值。Three Dots

    Rest Operator

    在 JavaScript 函数调用时我们往往会使用内置的 arguments 对象来获取函数的调用参数,不过这种方式却存在着很多的不方便性。譬如 arguments 对象是 Array-Like 对象,无法直接运用数组的 .map() 或者 .forEach() 函数;并且因为 arguments 是绑定于当前函数作用域,如果我们希望在嵌套函数里使用外层函数的 arguments 对象,我们还需要创建中间变量。

    function outerFunction() { // store arguments into a separated variable var argsOuter = arguments; function innerFunction() { // args is an array-like object var even = Array.prototype.map.call(argsOuter, function(item) { // do something with argsOuter }); } }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function outerFunction() {  
       // store arguments into a separated variable
    var argsOuter = arguments;
    function innerFunction() {
          // args is an array-like object
    var even = Array.prototype.map.call(argsOuter, function(item) {
             // do something with argsOuter              
          });
       }
    }

    ES6 中为我们提供了 Rest Operator 来以数组形式获取函数的调用参数,Rest Operator 也可以用于在解构赋值中以数组方式获取剩余的变量:

    function countArguments(...args) { return args.length; } // get the number of arguments countArguments('welcome', 'to', 'Earth'); // => 3 // destructure an array let otherSeasons, autumn; [autumn, ...otherSeasons] = cold; otherSeasons // => ['winter']

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function countArguments(...args) {  
    return args.length;
    }
    // get the number of arguments
    countArguments('welcome', 'to', 'Earth'); // => 3  
    // destructure an array
    let otherSeasons, autumn;  
    [autumn, ...otherSeasons] = cold;
    otherSeasons      // => ['winter']  

    典型的 Rest Operator 的应用场景譬如进行不定数组的指定类型过滤:

    function filter(type, ...items) { return items.filter(item => typeof item === type); } filter('boolean', true, 0, false); // => [true, false] filter('number', false, 4, 'Welcome', 7); // => [4, 7]

    1
    2
    3
    4
    5
    function filter(type, ...items) {  
    return items.filter(item => typeof item === type);
    }
    filter('boolean', true, 0, false);        // => [true, false]  
    filter('number', false, 4, 'Welcome', 7); // => [4, 7]  

    尽管 Arrow Function 中并没有定义 arguments 对象,但是我们仍然可以使用 Rest Operator 来获取 Arrow Function 的调用参数:

    (function() { let outerArguments = arguments; const concat = (...items) => { console.log(arguments === outerArguments); // => true return items.reduce((result, item) => result item, ''); }; concat(1, 5, 'nine'); // => '15nine' })();

    1
    2
    3
    4
    5
    6
    7
    8
    (function() {
    let outerArguments = arguments;
    const concat = (...items) => {
        console.log(arguments === outerArguments); // => true
    return items.reduce((result, item) => result item, '');
      };
      concat(1, 5, 'nine'); // => '15nine'
    })();

    Spread Operator

    Spread Operator 则与 Rest Opeator 的功能正好相反,其常用于进行数组构建与解构赋值,也可以用于将某个数组转化为函数的参数列表,其基本使用方式如下:

    let cold = ['autumn', 'winter']; let warm = ['spring', 'summer']; // construct an array [...cold, ...warm] // => ['autumn', 'winter', 'spring', 'summer'] // function arguments from an array cold.push(...warm); cold // => ['autumn', 'winter', 'spring', 'summer']

    1
    2
    3
    4
    5
    6
    7
    let cold = ['autumn', 'winter'];  
    let warm = ['spring', 'summer'];  
    // construct an array
    [...cold, ...warm] // => ['autumn', 'winter', 'spring', 'summer']
    // function arguments from an array
    cold.push(...warm);  
    cold              // => ['autumn', 'winter', 'spring', 'summer']  

    我们也可以使用 Spread Operator 来简化函数调用:

    class King { constructor(name, country) { this.name = name; this.country = country; } getDescription() { return `${this.name} leads ${this.country}`; } } var details = ['Alexander the Great', 'Greece']; var Alexander = new King(...details); Alexander.getDescription(); // => 'Alexander the Great leads Greece'

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class King {  
    constructor(name, country) {
    this.name = name;
    this.country = country;    
       }
       getDescription() {
    return `${this.name} leads ${this.country}`;
       }
    }
    var details = ['Alexander the Great', 'Greece'];  
    var Alexander = new King(...details);  
    Alexander.getDescription(); // => 'Alexander the Great leads Greece'  

    还有另外一个好处就是可以用来替换 Object.assign 来方便地从旧有的对象中创建新的对象,并且能够修改部分值;譬如:

    var obj = {a:1,b:2} var obj_new_1 = Object.assign({},obj,{a:3}); var obj_new_2 = { ...obj, a:3 }

    1
    2
    3
    4
    5
    6
    var obj = {a:1,b:2}
    var obj_new_1 = Object.assign({},obj,{a:3});
    var obj_new_2 = {
      ...obj,
      a:3
    }

    最后我们还需要讨论下 Spread Operator 与 Iteration Protocols,实际上 Spread Operator 也是使用的 Iteration Protocols 来进行元素遍历与结果搜集;因此我们也可以通过自定义 Iterator 的方式来控制 Spread Operator 的表现。Iterable 协议规定了对象必须包含 Symbol.iterator 方法,该方法返回某个 Iterator 对象:

    interface Iterable { [Symbol.iterator]() { //... return Iterator; } }

    1
    2
    3
    4
    5
    6
    interface Iterable {  
      [Symbol.iterator]() {
        //...
        return Iterator;
      }
    }

    该 Iterator 对象从属于 Iterator Protocol,其需要提供 next 成员方法,该方法会返回某个包含 done 与 value 属性的对象:

    interface Iterator { next() { //... return { value: <value>, done: <boolean> }; }; }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    interface Iterator {  
      next() {
         //...
         return {
            value: <value>,
            done: <boolean>
         };
      };
    }

    典型的 Iterable 对象就是字符串:

    var str = 'hi'; var iterator = str[Symbol.iterator](); iterator.toString(); // => '[object String Iterator]' iterator.next(); // => { value: 'h', done: false } iterator.next(); // => { value: 'i', done: false } iterator.next(); // => { value: undefined, done: true } [...str]; // => ['h', 'i']

    1
    2
    3
    4
    5
    6
    7
    var str = 'hi';  
    var iterator = str[Symbol.iterator]();  
    iterator.toString(); // => '[object String Iterator]'  
    iterator.next();     // => { value: 'h', done: false }  
    iterator.next();     // => { value: 'i', done: false }  
    iterator.next();     // => { value: undefined, done: true }  
    [...str];            // => ['h', 'i']

    我们可以通过自定义 array-like 对象的 Symbol.iterator 属性来控制其在迭代器上的效果:

    function iterator() { var index = 0; return { next: () => ({ // Conform to Iterator protocol done : index >= this.length, value: this[index ] }) }; } var arrayLike = { 0: 'Cat', 1: 'Bird', length: 2 }; // Conform to Iterable Protocol arrayLike[Symbol.iterator] = iterator; var array = [...arrayLike]; console.log(array); // => ['Cat', 'Bird']

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function iterator() {  
    var index = 0;
    return {
        next: () => ({ // Conform to Iterator protocol
          done : index >= this.length,
          value: this[index ]
        })
      };
    }
    var arrayLike = {  
      0: 'Cat',
      1: 'Bird',
      length: 2
    };
    // Conform to Iterable Protocol
    arrayLike[Symbol.iterator] = iterator;  
    var array = [...arrayLike];  
    console.log(array); // => ['Cat', 'Bird']  

    arrayLike[Symbol.iterator] 为该对象创建了值为某个迭代器的属性,从而使该对象符合了 Iterable 协议;而 iterator() 又返回了包含 next 成员方法的对象,使得该对象最终具有和数组相似的行为表现。

    Copy Composite Data Types: 复合类型的拷贝

    Shallow Copy: 浅拷贝

    顶层属性遍历

    浅拷贝是指复制对象的时候,指对第一层键值对进行独立的复制。一个简单的实现如下:

    // 浅拷贝实现 function shadowCopy(target, source){ if( !source || typeof source !== 'object'){ return; } // 这个方法有点小trick,target一定得事先定义好,不然就不能改变实参了。 // 具体原因解释可以看参考资料中 JS是值传递还是引用传递 if( !target || typeof target !== 'object'){ return; } // 这边最好区别一下对象和数组的复制 for(var key in source){ if(source.hasOwnProperty(key)){ target[key] = source[key]; } } } //测试例子 var arr = [1,2,3]; var arr2 = []; shadowCopy(arr2, arr); console.log(arr2); //[1,2,3] var today = { weather: 'Sunny', date: { week: 'Wed' } } var tomorrow = {}; shadowCopy(tomorrow, today); console.log(tomorrow); // Object {weather: "Sunny", date: Object}

    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
    // 浅拷贝实现
    function shadowCopy(target, source){
    if( !source || typeof source !== 'object'){
    return;
        }
        // 这个方法有点小trick,target一定得事先定义好,不然就不能改变实参了。
           // 具体原因解释可以看参考资料中 JS是值传递还是引用传递
    if( !target || typeof target !== 'object'){
    return;
        }  
        // 这边最好区别一下对象和数组的复制
    for(var key in source){
    if(source.hasOwnProperty(key)){
                target[key] = source[key];
            }
        }
    }
     
    //测试例子
    var arr = [1,2,3];
    var arr2 = [];
    shadowCopy(arr2, arr);
    console.log(arr2);
    //[1,2,3]
     
    var today = {
        weather: 'Sunny',
        date: {
            week: 'Wed'
        }
    }
     
    var tomorrow = {};
    shadowCopy(tomorrow, today);
    console.log(tomorrow);
    // Object {weather: "Sunny", date: Object}

    Object.assign

    Object.assign() 方法可以把任意多个的源对象所拥有的自身可枚举属性拷贝给目标对象,然后返回目标对象。Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象身上。注意,对于访问器属性,该方法会执行那个访问器属性的 getter 函数,然后把得到的值拷贝给目标对象,如果你想拷贝访问器属性本身,请使用 Object.getOwnPropertyDescriptor() 和Object.defineProperties() 方法。

    注意,字符串类型和 symbol 类型的属性都会被拷贝。

    注意,在属性拷贝过程中可能会产生异常,比如目标对象的某个只读属性和源对象的某个属性同名,这时该方法会抛出一个 TypeError 异常,拷贝过程中断,已经拷贝成功的属性不会受到影响,还未拷贝的属性将不会再被拷贝。

    注意, Object.assign 会跳过那些值为 将元素应用于数组和函数的优雅方法,变量声明与赋值。null 或 undefined 的源对象。

    Object.assign(target, ...sources)

    1
    Object.assign(target, ...sources)
    • 例子:浅拷贝一个对象

    var obj = { a: 1 }; var copy = Object.assign({}, obj); console.log(copy); // { a: 1 }

    1
    2
    3
    var obj = { a: 1 };
    var copy = Object.assign({}, obj);
    console.log(copy); // { a: 1 }
    • 例子:合并若干个对象

    var o1 = { a: 1 }; var o2 = { b: 2 }; var o3 = { c: 3 }; var obj = Object.assign(o1, o2, o3); console.log(obj); // { a: 1, b: 2, c: 3 } console.log(o1); // { a: 1, b: 2, c: 3 }, 注意目标对象自身也会改变。

    1
    2
    3
    4
    5
    6
    7
    var o1 = { a: 1 };
    var o2 = { b: 2 };
    var o3 = { c: 3 };
     
    var obj = Object.assign(o1, o2, o3);
    console.log(obj); // { a: 1, b: 2, c: 3 }
    console.log(o1);  // { a: 1, b: 2, c: 3 }, 注意目标对象自身也会改变。
    • 例子:拷贝 symbol 类型的属性

    var o1 = { a: 1 }; var o2 = { [Symbol("foo")]: 2 }; var obj = Object.assign({}, o1, o2); console.log(obj); // { a: 1, [Symbol("foo")]: 2 }

    1
    2
    3
    4
    5
    var o1 = { a: 1 };
    var o2 = { [Symbol("foo")]: 2 };
     
    var obj = Object.assign({}, o1, o2);
    console.log(obj); // { a: 1, [Symbol("foo")]: 2 }
    • 例子:继承属性和不可枚举属性是不能拷贝的

    var obj = Object.create({foo: 1}, { // foo 是个继承属性。 bar: { value: 2 // bar 是个不可枚举属性。 }, baz: { value: 3, enumerable: true // baz 是个自身可枚举属性。 } }); var copy = Object.assign({}, obj); console.log(copy); // { baz: 3 }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var obj = Object.create({foo: 1}, { // foo 是个继承属性。
        bar: {
            value: 2  // bar 是个不可枚举属性。
        },
        baz: {
            value: 3,
            enumerable: true  // baz 是个自身可枚举属性。
        }
    });
     
    var copy = Object.assign({}, obj);
    console.log(copy); // { baz: 3 }
    • 例子:原始值会被隐式转换成其包装对象

    var v1 = "123"; var v2 = true; var v3 = 10; var v4 = Symbol("foo") var obj = Object.assign({}, v1, null, v2, undefined, v3, v4); // 源对象如果是原始值,会被自动转换成它们的包装对象, // 而 null 和 undefined 这两种原始值会被完全忽略。 // 注意,只有字符串的包装对象才有可能有自身可枚举属性。 console.log(obj); // { "0": "1", "1": "2", "2": "3" }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var v1 = "123";
    var v2 = true;
    var v3 = 10;
    var v4 = Symbol("foo")
     
    var obj = Object.assign({}, v1, null, v2, undefined, v3, v4);
    // 源对象如果是原始值,会被自动转换成它们的包装对象,
    // 而 null 和 undefined 这两种原始值会被完全忽略。
    // 注意,只有字符串的包装对象才有可能有自身可枚举属性。
    console.log(obj); // { "0": "1", "1": "2", "2": "3" }
    • 例子:拷贝属性过程中发生异常

    var target = Object.defineProperty({}, "foo", { value: 1, writeable: false }); // target 的 foo 属性是个只读属性。 Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4}); // TypeError: "foo" is read-only // 注意这个异常是在拷贝第二个源对象的第二个属性时发生的。 console.log(target.bar); // 2,说明第一个源对象拷贝成功了。 console.log(target.foo2); // 3,说明第二个源对象的第一个属性也拷贝成功了。 console.log(target.foo); // 1,只读属性不能被覆盖,所以第二个源对象的第二个属性拷贝失败了。 console.log(target.foo3); // undefined,异常之后 assign 方法就退出了,第三个属性是不会被拷贝到的。 console.log(target.baz); // undefined,第三个源对象更是不会被拷贝到的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var target = Object.defineProperty({}, "foo", {
        value: 1,
        writeable: false
    }); // target 的 foo 属性是个只读属性。
     
    Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4});
    // TypeError: "foo" is read-only
    // 注意这个异常是在拷贝第二个源对象的第二个属性时发生的。
     
    console.log(target.bar);  // 2,说明第一个源对象拷贝成功了。
    console.log(target.foo2); // 3,说明第二个源对象的第一个属性也拷贝成功了。
    console.log(target.foo);  // 1,只读属性不能被覆盖,所以第二个源对象的第二个属性拷贝失败了。
    console.log(target.foo3); // undefined,异常之后 assign 方法就退出了,第三个属性是不会被拷贝到的。
    console.log(target.baz);  // undefined,第三个源对象更是不会被拷贝到的。

    使用 [].concat 来复制数组

    同样类似于对于对象的复制,我们建议使用[].concat来进行数组的深复制:

    var list = [1, 2, 3]; var changedList = [].concat(list); changedList[1] = 2; list === changedList; // false

    1
    2
    3
    4
    var list = [1, 2, 3];
    var changedList = [].concat(list);
    changedList[1] = 2;
    list === changedList; // false

    同样的,concat方法也只能保证一层深复制:

    > list = [[1,2,3]] [ [ 1, 2, 3 ] ] > new_list = [].concat(list) [ [ 1, 2, 3 ] ] > new_list[0][0] = 4 4 > list [ [ 4, 2, 3 ] ]

    1
    2
    3
    4
    5
    6
    7
    8
    > list = [[1,2,3]]
    [ [ 1, 2, 3 ] ]
    > new_list = [].concat(list)
    [ [ 1, 2, 3 ] ]
    > new_list[0][0] = 4
    4
    > list
    [ [ 4, 2, 3 ] ]

    浅拷贝的缺陷

    不过需要注意的是,assign是浅拷贝,或者说,它是一级深拷贝,举两个例子说明:

    const defaultOpt = { title: { text: 'hello world', subtext: 'It's my world.' } }; const opt = Object.assign({}, defaultOpt, { title: { subtext: 'Yes, your world.' } }); console.log(opt); // 预期结果 { title: { text: 'hello world', subtext: 'Yes, your world.' } } // 实际结果 { title: { subtext: 'Yes, your world.' } }

    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
    const defaultOpt = {
        title: {
            text: 'hello world',
            subtext: 'It's my world.'
        }
    };
     
    const opt = Object.assign({}, defaultOpt, {
        title: {
            subtext: 'Yes, your world.'
        }
    });
     
    console.log(opt);
     
    // 预期结果
    {
        title: {
            text: 'hello world',
            subtext: 'Yes, your world.'
        }
    }
    // 实际结果
    {
        title: {
            subtext: 'Yes, your world.'
        }
    }

    上面这个例子中,对于对象的一级子元素而言,只会替换引用,而不会动态的添加内容。那么,其实assign并没有解决对象的引用混乱问题,参考下下面这个例子:

    const defaultOpt = { title: { text: 'hello world', subtext: 'It's my world.' } }; const opt1 = Object.assign({}, defaultOpt); const opt2 = Object.assign({}, defaultOpt); opt2.title.subtext = 'Yes, your world.'; console.log('opt1:'); console.log(opt1); console.log('opt2:'); console.log(opt2); // 结果 opt1: { title: { text: 'hello world', subtext: 'Yes, your world.' } } opt2: { title: { text: 'hello world', subtext: 'Yes, your world.' } }

    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
    const defaultOpt = {
        title: {
            text: 'hello world',
            subtext: 'It's my world.'
        }
    };
     
    const opt1 = Object.assign({}, defaultOpt);
    const opt2 = Object.assign({}, defaultOpt);
    opt2.title.subtext = 'Yes, your world.';
     
    console.log('opt1:');
    console.log(opt1);
    console.log('opt2:');
    console.log(opt2);
     
    // 结果
    opt1:
    {
        title: {
            text: 'hello world',
            subtext: 'Yes, your world.'
        }
    }
    opt2:
    {
        title: {
            text: 'hello world',
            subtext: 'Yes, your world.'
        }
    }

    DeepCopy: 深拷贝

    递归属性遍历

    一般来说,在JavaScript中考虑复合类型的深层复制的时候,往往就是指对于Date、Object与Array这三个复合类型的处理。我们能想到的最常用的方法就是先创建一个空的新对象,然后递归遍历旧对象,直到发现基础类型的子节点才赋予到新对象对应的位置。不过这种方法会存在一个问题,就是JavaScript中存在着神奇的原型机制,并且这个原型会在遍历的时候出现,然后原型不应该被赋予给新对象。那么在遍历的过程中,我们应该考虑使用hasOenProperty方法来过滤掉那些继承自原型链上的属性:

    function clone(obj) { var copy; // Handle the 3 simple types, and null or undefined if (null == obj || "object" != typeof obj) return obj; // Handle Date if (obj instanceof Date) { copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { copy = []; for (var i = 0, len = obj.length; i < len; i ) { copy[i] = clone(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]); } return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); }

    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
    function clone(obj) {
    var copy;
     
        // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;
     
        // Handle Date
    if (obj instanceof Date) {
            copy = new Date();
            copy.setTime(obj.getTime());
    return copy;
        }
     
        // Handle Array
    if (obj instanceof Array) {
            copy = [];
    for (var i = 0, len = obj.length; i < len; i ) {
                copy[i] = clone(obj[i]);
            }
    return copy;
        }
     
        // Handle Object
    if (obj instanceof Object) {
            copy = {};
    for (var attr in obj) {
    if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
            }
    return copy;
        }
     
    throw new Error("Unable to copy obj! Its type isn't supported.");
    }

    调用如下:

    // This would be cloneable: var tree = { "left" : { "left" : null, "right" : null, "data" : 3 }, "right" : null, "data" : 8 }; // This would kind-of work, but you would get 2 copies of the // inner node instead of 2 references to the same copy var directedAcylicGraph = { "left" : { "left" : null, "right" : null, "data" : 3 }, "data" : 8 }; directedAcyclicGraph["right"] = directedAcyclicGraph["left"]; // Cloning this would cause a stack overflow due to infinite recursion: var cylicGraph = { "left" : { "left" : null, "right" : null, "data" : 3 }, "data" : 8 }; cylicGraph["right"] = cylicGraph;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // This would be cloneable:
    var tree = {
        "left"  : { "left" : null, "right" : null, "data" : 3 },
        "right" : null,
        "data"  : 8
    };
     
    // This would kind-of work, but you would get 2 copies of the
    // inner node instead of 2 references to the same copy
    var directedAcylicGraph = {
        "left"  : { "left" : null, "right" : null, "data" : 3 },
        "data"  : 8
    };
    directedAcyclicGraph["right"] = directedAcyclicGraph["left"];
     
    // Cloning this would cause a stack overflow due to infinite recursion:
    var cylicGraph = {
        "left"  : { "left" : null, "right" : null, "data" : 3 },
        "data"  : 8
    };
    cylicGraph["right"] = cylicGraph;

    利用 JSON 深拷贝

    JSON.parse(JSON.stringify(obj));

    1
    JSON.parse(JSON.stringify(obj));

    对于一般的需求是可以满足的,但是它有缺点。下例中,可以看到JSON复制会忽略掉值为undefined以及函数表达式。

    var obj = { a: 1, b: 2, c: undefined, sum: function() { return a b; } }; var obj2 = JSON.parse(JSON.stringify(obj)); console.log(obj2); //Object {a: 1, b: 2}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var obj = {
        a: 1,
        b: 2,
        c: undefined,
        sum: function() { return a b; }
    };
     
    var obj2 = JSON.parse(JSON.stringify(obj));
    console.log(obj2);
    //Object {a: 1, b: 2}

    延伸阅读

    • 基于 JSX 的动态数据绑定
    • ECMAScript 2017(ES8)特性概述
    • WebAssembly 初体验:从零开始重构计算模块

      1 赞 收藏 评论

    本文由新葡亰496net发布于新葡亰官网,转载请注明出处:将元素应用于数组和函数的优雅方法,变量声明

    关键词:

上一篇:新葡亰496net:浅谈格式化上下文

下一篇:没有了