您的位置:新葡亰496net > 新葡亰官网 > 新葡亰496net学习笔记,浅拷贝与深拷贝详解

新葡亰496net学习笔记,浅拷贝与深拷贝详解

发布时间:2019-11-16 08:36编辑:新葡亰官网浏览(94)

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

    2017/08/16 · JavaScript · es6

    原文出处: 王下邀月熊   

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

    1.Let命令

    Let命令的用法类似于var 但是声明的变量只在代码块内有效

    {

        var a=1;

        let b=0;

    }

    console.log(a) //1

    console.log(b)//  Uncaught ReferenceError: b is not defined 报错


    let不存在变量提升 一定先声明在使用

    console.log(a);//1

    console.log(b);//报错

    var a=1;

    var b=2;


    暂时性死区  es6明确规定 区块中存在let和const 命令,这个区块对于这些声明的变量从一开始就形成了封闭区域,在let声明变量之前 这些变量都是不可用的 学术上也成为‘暂时性死区’

    if(true){

    tmp='abc'

    console.log(tmp);//报错  横线部分就是暂时性死区

    let tmp;

    console.log(tmp);//undefind

    tmp=123;

    console.log(tmp);//123

    }


    let 不可以在一个区域内重复声明一个变量 

    function fun(a){

            let a

    }

    fun();//报错

    function fun(a){

            {

                    let a

              }

    }

    fun();//不报错


    let

    1.作用域在代码快中 外部无法获取let声明的变量

    2.不存在变量提升 var声明变量前 输出变量undefined let声明变量前输出变量 直接报错

    3.let const 这个区块声明变量 从一开始就形成了封闭作用域 凡是在声明之前使用这些变量 就会报错

    也就是说 在let命令声明变量之前 该变量都是不可用的 语法上称为‘暂时性死区

    4.typeof a a是不存在的变量 // undefined

    如果 typeof在死区内 也就是 typeof b  let b = 1 typeof直接报错

    5.不允许重复声明同一个变量 也不能在函数内部重新声明 参数名和函数体内let的变量名 不能相同

    ES6的块级作用域

    1.let实际上为js新增了块级作用域 

    2.{{{{{{作用域可以任意嵌套 let a = 1}} 外层作用域无法读取内层作用域的变量  console.log(a) // 报错}}}}}

    外层作用域可以定义内层同名变量

    不再需要立即执行函数表达式 (function(){}())

    3.ES6函数声明和let类似 允许块级作用域声明函数

    函数调用也只会在当前自己的作用域内找声明的函数调用

    找不到就找外层的 不会找不相干的层

    const命令

    1.const只读 常量不能改变

    新葡亰496net学习笔记,浅拷贝与深拷贝详解。

    const必须赋值 只声明会报错

    并且之后不能重复赋值 不可重复声明

    只在作用域内有效

    ES6共有6种声明方式

    var let const import class function

    顶层对象的属性

    浏览器环境中指的是window对象 node环境中指的是global对象

    var function 声明全局变量 等同于window.变量名

    let const class不是

    global 对象 

    es5 顶层对象 浏览器中是window 

    浏览器和web Worker 里 self也指向顶层对象 但是Node没有self

    Node 顶层对象是global 但其它环境都不支持

    没看懂

    数组的解构赋值

    按照一定模式从数组和对象中提取值 对变量进行赋值 被称为结构

    let [a,b,c] = [1,2,3]

    上面代码表示从数组中提取值 按照对应位置 对变量赋值

    本质上属于’模式匹配‘ 只要等号两边的模式相同 左边的变量就会被赋予对应的值

    let [foo, [[bar], baz]] = [1,[[2],3]] //一一对应

    let [, , third] = ['foo','bar','baz'] //third baz

    let [head, ...tail] = [1, 2, 3, 4];

    head // 1

    tail // [2, 3, 4]

    let [x, y, ...z] = ['a'];

    x // "a"

    y // undefinedz // []

    结构不成功 变量的值就是undefined

    ...z 没有时为空数组  ...是数组

    不完全解构

    let [x, y] = [1, 2, 3];

    x // 1y // 2

    let [a, [b], d] = [1, [2, 3], 4];

    a // 1b // 2d // 4

    按照结构赋值

    let [x,y,z] = new Set(['a','b','c'])

    x //a

    set解构也可以使用数组解构赋值

    解构赋值允许指定默认值

    let [foo = true] = []

    foo // true

    let [x,y='b'] = ['a'] // x = 'a' y = 'b'

    let [x, y = 'b'] = ['a', undefined];  // x='a', y='b'

    赋值 === undefined默认才会生效

    赋值null则默认不生效 =号左侧被赋值null

    有默认值的情况下 赋值的过程

    先按照结构 = 右侧向左侧赋值 当右侧!== undefined时赋值成功 否则是默认值 默认值如果是变量 未被声明会报错

    function f() { console.log('aaa');} 

    let [x = f()] = [1];

    解构赋值过程

    let x;if ([1][0] === undefined)

    { x = f();}

    else { x = [1][0];}

    对象的解构赋值

    与数组的不同之处 数组的元素是按照次序排列的 变量取值由位置决定 而对象的属性没有次序 变量必须与属性同名才能取到正确的值

    对象赋值是先找到对应的属性 对属性值进行赋值 

    对象也可以指定默认值  ===undefined 默认值生效

    let {foo} = {bar: 'bar'} 解构失败 foo//undefined

    let {foo: {bar}} = {baz: 'baz'}; 属性不存在 取子属性会报错

    由于数组本质是特殊对象 因此可以对数组进行对象属性的解构

    let arr = [1,2,3,]

    let {0:first, [arr.length-1]: last} = arr

    first // 1

    last //3

    索引相当于是属性名

    字符串的结构赋值

    字符串会被转换成一个类似数组的对象

    const [a,b,c,d,e] = 'hello'

    类似数组的对象都有一个length属性 因此还可以对这个属性解构赋值

    let {length: len} = 'hello'

    len //5

    数值和布尔值的解构赋值

    数值和布尔值会转为对象

    let {toString: s} = 123

    s === Number.prototype.toString // true

    相当于 123变成了Number对象 s是toString 这个方法

    let {toString: s} = true;

    s === Boolean.prototype.toString // true

    同上 被转换成了Boolean对象 s为方法

    解构赋值的规则是 只要等号右边的值不是对象或数组就将其转换为对象

    undefined 和 null 无法转为对象 所以对他们进行解构赋值 都会报错

    函数参数的结构赋值

    function add([x,y]) {

    return x y

    }

    add([1,2]) // 3

    圆括号的问题

    建议不要再模式中放置圆括号

    不能使用圆括号的情况

    (1) 变量的声明语句

    let [(a)] = [1] 报错

    (2) 函数参数

    函数参数也属于变量声明 因此不能带有圆括号

    (3)赋值语句的模式

    ({p: a}) = {p: 42} 报错

    圆括号不是很理解 奇奇怪怪

    用途

    (1)交换变量

    let x = 1

    let y = 2

    [x,y ] = [y,x]

    (2)从函数返回多个值

    function example() {

     return [1,2,3];

    }

    let [a,b,c] = example()

    function example() {

        return {

        foo: 1,

        bar: 2

    }

    }

    let {foo, bart} = example()

    (3)函数参数的定义

    解构赋值可以方便的将一组参数与变量名对应起来

    // 参数是一组有次序的值function f([x, y, z]) { ... }f([1, 2, 3]);

    // 参数是一组无次序的值function f({x, y, z}) { ... }f({z: 3, y: 2, x: 1});

    (4)提取json数据

    解构赋值对提取json对象中的数据尤其有用

    let jsonData = {

        id: 42,

        status:'OK',

        data: [867,5309]

    }

    let {id, status, data: number} = jsonData;

    (5)函数参数的默认值

    (6)遍历Map结构

    const map = new Map()

    map.set('first','hello')

    map.set('second','world')

    字符串的拓展

    for of 遍历字符串

    let str = 'abc'

    for(let v of str) {

    `    console.log(v)

    }

    // a

    // b

    // c

    at()

    ‘abc’.at(0) // a 

    浏览器支持度不高 建议还是使用charAt()

    includes() 返回布尔 表示是否找到了参数字符串

    startsWith() 返回布尔 表示参数字符串是否在原字符串头部

    endsWith() 返回布尔 表示参数字符串是否在原字符串尾部

    第二个参数标识开始搜索的位置

    let s = 'Hello world!';

    s.startsWith('world', 6) // true

    s.endsWith('Hello', 5) // true

    s.includes('Hello', 6) // false

    上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。

    repeat() 返回一个新字符串 标识将原字符串重复n次

    ‘x’.repeat(3) ‘xxx’

    'hello'.repeat(2) 'hellohello'

    'na'.repeat(0) ''

    参数如果是小数点会被取整

    ‘na’.repeat(2.9) 'nana'

    参数为负数或Infinity 会报错

    参数在0-1和 0到-1之间 以及 NaN等同于0

    如果参数是字符串 会被转换成数字

    ‘na’.repeat('na') '' //'na'转换为字符串是NaN所以结果是‘’

    'na'.repeat('3') // 'nanana'

    padStart 常见用途 为数值补全指定位数 

    下面代码生成10位

    ‘1’.padStart(10,'0') // 0000000001

    '12'.padStart(10,'0') // 0000000012

    ‘123456’.padStart(10, '0') //0000123456

    另一个用途 提示字符串格式

    ‘12’.padStart(10, 'YYYY-MM-DD') // 'YYYY-MM-12'

    '09-12'.padStart(10, 'YYYY-MM-DD') // YYYY-09-12

    数组的扩展 扩展运算符

    主要用于函数调用

    扩展运算符的应用

    复制数组

    const a1 = [1,2]

    const a2 = [...a1]

    const [...a2] = a1

    合并数组

    [1,2].concat(more)

    [1,2,...more]

    [...arr1, ...arr2, ...arr3]

    扩展运算符用于数组赋值 只能放在参数最后一位 否则会报错

    字符串转换数组

    [...'hello']

    // ['h','e','l','l','o']

    Map 和 Set 结构 

    映射 和 集合 // 不是很懂

    Array.from()

    将类似数组的对象和可遍历的对象(包括Map和Set)转为数组

    let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3};

    Array.from(arrayLike) ['a', 'b', 'c']

    NodeList对象[...document.querySelectorAll('div')]

    直接得到一个NodeList集合

    Array.from(NodeList) 转换为真正的数组

    可以将字符串转换为数组

    let str = 'sdfsdf35165'

    Array.from(str)

    // ['s', 'd', 'f'....]

    Array.of() 返回参数组成的数组

    Array.of() // []

    Array.of(undefined) //[undefined]

    Array.of(1) // [1]

    find() 找到第一个符合条件的数组成员

    参数为回调 第一个满足回调返回true的成员 返回该成员

    [1,4,-5,10].find(n => {n<0}) // -5

    回调有三个参数 value 当前值 index 当前索引 arr 原数组 ↓

    [1, 5, 10, 15].find(function(value, index, arr) { return value > 9; }) // 10

    findIndex 与find类似 返回索引

    这两个方法 可以接受第二个参数 find(f,person) 回调中的this指向第二个参数

    function f(v){ return v > this.age; } let person = {name: 'John', age: 20}; [10, 12, 26, 15].find(f, person); // 26

    fill() 给定值填充一个数组

    ['a','b','c'].fill(7) // [7,7,7]

    new Array(3).fill(7) // [7,7,7]

    还可以接受第二第三个参数 起始位置和结束位置(包头不包尾) 

    [‘a’, 'b', 'c'].fill(7,1,2) // ['a', 7, 'c']

    如果参数类型是对象 那么被赋值的是同一个内存地址的对象 而不是深拷贝的对象

    let arr = new Array(3).fill({name: "Mike"});

    arr[0].name = "Ben"; arr

    // [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}] l

    et arr = new Array(3).fill([]);

    arr[0].push(5); arr // [[5], [5], [5]]

    entries() keys() values()

    遍历数组 键值对遍历 键遍历 值遍历

    [1, 2, 3].includes(2) 返回布尔

    数组的空位ES6方法会解析成undefined

    Object.is()

    类似于全等 ===

    区别在于 全等 0 === -0 // true

                    Object.is( 0, -0) // false

                    全等 NaN === NaN // false

                    Object.is(NaN, NaN) // true

    对象的合并  Object.assign() 

    第一个参数 目标对象 后面都是源对象

    如果有同名属性 后面的会覆盖前面的

    const target = { a: 1, b: 1 }; const source1 = { b: 2, c: 2 }; const source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}

    如果只有一个参数 会直接返回该参数

    如果该参数不是对象则会转成对象 然后返回

    undefined和null做参数 会报错

    如果undefined和null不在第一个参数位置 则不会报错

    字符串做参数 会以数组的形式拷贝入对象 数值和布尔 没效果

    const v1 = 'abc';

    const v2 = true;

    const v3 = 10;

    const obj = Object.assign({}, v1, v2, v3);

    console.log(obj); // { "0": "a", "1": "b", "2": "c" }

    Object.assign() 拷贝的属性是有限的 只拷贝源对象自身属性 不拷贝继承属性 不拷贝不可枚举属性

    注意点

    Object.assgin()实现的是浅拷贝 拷贝的是对象的引用

    同名属性会替换

    数组视为对象

    Object.assign([1, 2, 3], [4, 5])

    // [4, 5, 3]

    数组视为对象则是索引做键值做值的对象

    所以4在0的位置 会覆盖1 ,5覆盖2

    函数的处理

    Object.assign()参数如果有函数 则是先运行只会拿到值再复制

    常见用途

    ES6的遍历

    for...in 遍历对象自身和继承的可枚举属性

    Object.keys(obj) 返回一个数组 包含对象自身可枚举属性不含继承的键名

    Object.getOwnPropertyNames(obj)

    返回一个数组 包含对象自身所有属性 包含不可枚举属性的键名

    新葡亰496net,

    1. 属性的简洁表示法

    变量声明与赋值

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

    2.块级作用域

    let实际上为javascript增加了块级作用域。

    function f1(){

    let n=5;

    if(true){

    let n=10;

    }

    console.log(n);//5

    }

    换成var

    function f1(){

    var n=5;

    if(true){

    var n=10;

    }

    console.log(n);//10

    }

    if 内的var n=10 变量提升 替换掉了var n=5;


    es6允许各个块级作用域的任意嵌套

    {{{let a='Hello word!'}

    console.log(a);//报错

    }}

    {{{let a='Hello word!'

        console.log(a);//Hello word!

    }

    }}

    内层作用域可以定义外层作用域相同名称的变量

    {{let a=1}

        let a=1;

    }


      ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

    变量声明

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

    3.const常量

    const 声明一个只读常量。一旦声明 值不能改变。

    const a=12;

    a=13;

    console.log(a);//报错 Uncaught TypeError: Assignment to constant variable.

    上述代码 const 只声明 但是赋值 就会报错。

    const 和 let的作用域一样 只在声明所在的级块作用域内有效。

    if(true){

    const a=1;

    }

    console.log(a);//报错

    es6一共有六种声明变量的方法 var function let const import class


      const foo = 'foo';

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

    4.全局对象的属性

    全局对象是最顶层的对象 在浏览器下指的就是window对象 在Node.js下就指向Global对象

    在es5中 全局对象的属性和全局变量是等价的

    window.a=1;

    console.log(a);//1

    a=2;

    console.log(a);//2

    未声明的全局变量, 自动成为全局对象window的属性, 这被认为是JavaScript语言最大的设计败笔之一。 这样的设计带来了两个很大的问题, 首先是没法在编译时就报出变量未声明的错误, 只有运行时才能知道, 其次程序员很容易不知不觉地就创建了全局变量(比如打字出错) 。 另一方面, 从语义上讲, 语言的顶层对象是一个有实体含义的对象, 也是不合适的。

    ES6  var命令和function命令声明的全局变量, 依旧是全局对象的属性  let命令、const命令、 class命令声明的全局变量, 不属于全局对象的属性。

    var a=1;

    // 如果在Node的REPL环境, 可以写成global.a

    //或者采用通用方法, 写成this.a

    console.log(window.a);//1

    let a=1;

    console.log(window.a);//undefind


      const baz = {foo}

    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

    5.变量的解构赋值

    数组的解构赋值

    老版本的赋值

    var a=1;

    var b=2;

    var c=3;

    es6的赋值允许这样写

    var [a,b,c]=[1,2,3];

    console.log(a);//1

    例子

    let [ , , third] = ["foo", "bar", "baz"];

    third // "baz"

    let [x, , y] = [1, 2, 3];

    x // 1

    y // 3

    let [head, ...tail] = [1, 2, 3, 4];

    head // 1

    tail // [2, 3, 4]

    let [x, y, ...z] = ['a'];

    x // "a"

    y // undefined

    z // []

    解构不成功,变量的值就等于undefined

    解构赋值不仅适用于var 还试用于 let const命令。

    事实上, 只要某种数据结构具有Iterator接口, 都可以采用数组形式的解构赋值。

    function* fibs() {

    var a = 0;

    var b = 1;

    while (true) {

    yield a;

    [a, b] = [b, a b];

    }

    } v

    ar [first, second, third, fourth, fifth, sixth] = fibs();

    sixth // 5

    上面代码中, fibs是一个Generator函数, 原生具有Iterator接口。 解构赋值会依次从这个接口获取值


    解构赋值允许指定默认值

    var [foo=true]=[];

    foo//true

     [x,y='b']=['a'];//x='a' ,y='b'

    [x,y='b']=['a',undefined];//x='a' ,y='b'

    ES6内部使用严格相等运算符(===) , 判断一个位置是否有值。 所以, 如果一个数组成员不严格等于undefined, 默认值是不会生效的.


    var [x='a']=[undefined]; 

    x//a

    var [x='a']=[null];

    x//null

    如果一个数组成员是null, 默认值就不会生效, 因为null不严格等于undefined。


    如果默认值是一个表达式, 那么这个表达式是惰性求值的, 即只有在用到的时候, 才会求值。

    function f1(){

    console.log('aaa');

    }

    let[x=f1()]=[1];

    //x=1;

    等价于

    if([1][0]===undefined){

    x=f1();

    }else{

    x=[1][0];

    }


    默认值可以引用解构赋值的其他变量, 但该变量必须已经声明。

    let [x = 1, y = x] = []; // x=1; y=1

    let [x = 1, y = x] = [2]; // x=2; y=2

    let [x = 1, y = x] = [1, 2]; // x=1; y=2

    let [x = y, y = 1] = []; // ReferenceError

    最后一个表达式之所以会报错, 是因为x用到默认值y时, y还没有声明

      baz  // {foo: 'foo'}

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

    6.对象的解构赋值

    var {foo,roo}={foo:'sss',roo:'xxx'};

    console.log(foo);.//sss

    console.log(roo);//xxx


    var {roo}={foo:'sss',roo:'xxx'};

    console.log(roo);//xxx

    对象的解构赋值与数组的解构赋值不太一样。数组的元素是按照次序排列的,变量的取值按照位置决定。而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。


      上面代码表明,ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。

    变量赋值

    7.字符串的解构赋值

    字符串也可以解构赋值是因为字符串被转换成类似数组的对象。

    var [a,b,c,d,e]='hello';

    console.log(a);//h

    console.log(b);//e

    console.log(c);//l

    console.log(d);//l

    console.log(e);//o

    类似数组对象还有一个length属性,因此对这属性解构赋值。

    let{length:len}='hello'

    console.log(len);//5 相当于把‘hello’的length的值 赋给了len   


      function f (x, y) {
        return {x, y};
      }

    按值传递

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

    8.数值和布尔值的解构赋值

    解构赋值的时候 只要是=号右边的是数值或者布尔值,则右侧会先转换为对象。

    let{toString:s}=123;

    console.log(s===Number.prototype.toString)//true

    let{toString:s}=true;

    console.log(s===Boolean.prototype.toString)//true

    上面代码中, 数值和布尔值的包装对象都有toString属性, 因此变量s都能取到值。解构赋值的规则是, 只要等号右边的值不是对象, 就先将其转为对象。 由于undefined和null无法转为对象, 所以对它们进行解构赋值, 都会报错。

    let{prop:x}=undefined;//报错

    let{prop:y}=null;//报错


      f(1, 2)   // {x: 1, y: 2}

    连续赋值

    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}

      es6中对象的方法可以这样写

    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

      const o = {
        method() {
          return "Hello!";
        }
      };

    数组与迭代器

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

    [ 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

      o.method()  // 'Hello!'

    对象

    通过解构对象,你可以把它的每个属性与不同的变量绑定,首先指定被绑定的属性,然后紧跟一个要解构的变量。

    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 没有语法错误

      function getPoint() {
        const x = 1;
        const y = 10;
        return {x, y};
      }

    默认值

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

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

      getPoint()  // {x: 1, y: 10}

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

       let lastWord = 'last word';

    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 成员方法的对象,使得该对象最终具有和数组相似的行为表现。

      const a = {
        'first word': 'hello',
        [lastWord]: 'world!'
      }

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

      a['first word']  // "hello"

    Shallow Copy: 浅拷贝

      a['last word']  // "world!"

    顶层属性遍历

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

    // 浅拷贝实现 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}

      a[lastWord]  // "world!"

    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 ] ]

      属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]

    浅拷贝的缺陷

    不过需要注意的是,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.'
        }
    }

      const keyA = {a: 1};

    DeepCopy: 深拷贝

      const keyB = {b: 2};

    递归属性遍历

    一般来说,在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;

      const myObject = {
        [keyA]: 'valueA',
        [keyB]: 'valueB'
      };

    利用 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}

      myObject  // {[object Object]: "valueB"}

    延伸阅读

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

      1 赞 1 收藏 评论

    新葡亰496net 1

     

    2. 方法的name属性  

      函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。方法的name属性返回函数名(即方法名)。

      const person = {
        sayName() {
          console.log('hello!');
        }
      }

      person.sayName.name  // "sayName"

     

    3. Object.is()

      比较两个值是否严格相等

      Object.is( 0, -0);  // false

      Object.is(NaN, NaN);  // true

    4. Object.assign()

      用于对象的合并,并把源对象的可枚举属性复制到目标对象中

      const target = {a: 1};

      const source1 = {b: 2};

      const source2 = {c: 3};

      Object.assign(target, source1, source2);

      target  // {a: 1, b: 2, c: 3}

      参数一,是目标对象,后面的参数都是源对象。

      如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

      const target = {
        a: 1,
        b: 2
      }

      const source1 = {
        b: 2,
        c: 2
      };

      const source2 = {
        c: 3
      }

      Object.assign(target, source1, source2)  // {a: 1, b: 2, c: 3}

     

      如果只有一个参数,Object.assign会直接返回该参数。

      const obj = {
        a: 1
      };

      Object.assign(obj)  // {a: 1}

      如果该参数不是对象,则会先转成对象,然后返回。

      typeof Object.assign(2)

      由于undefinednull无法转成对象,所以如果它们作为参数,就会报错。

      如果undefined, null不是Object.assign()方法的首个参数, 则不会报错, 直接跳过。

      const v1 = 'abc';

      const v2 = true;

      const v3 = 10;

      const obj = Object.assign({}, v1, v2, v3);

      obj   // {0: "a", 1: "b", 2: "c"}

      只有字符串的包装对象,会产生可枚举属性。

      1)浅拷贝

        Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

        const obj1 = {a: {b: 1}};

        const obj2 = Object.assign({}, obj1);

        obj1.a.b = 2;

        obj2.a.b  // 2

      2)同名属性的替换

        一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。

        const target = {a: {b: 'c', d: 'e'}};

        const source = {a: {b: 'hello'}}

        Object.assign(target, source)  // {a: {b: 'hello'}}  对象a被整体替换掉

      3)数据的处理

        Object.assign()可以用来处理数组,但是会把数组视为对象

         Object.assign([1, 2, 3], [4, 5])  // [4, 5, 3]

        Object.assign把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1

      4)取值函数的处理

        Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。

        const source = {
          get foo() {
            return 1;
          }
        }

        const target = {}

        Object.assign({}, source)  // {foo: 1}

        source对象的foo属性是一个取值函数,Object.assign不会复制这个取值函数,只会拿到值以后,将这个值复制过去。

       5)常见用途

        ①为对象添加属性 

        class Point {
          constructor(x, y) {
            Object.assign(this, {x, y});
          }
        }  

        通过Object.assign方法,将x属性和y属性添加到Point类的对象实例。

        ②为对象添加方法

        Object.assign(Point.prototype, {
          someMethod(arg1, arg2) {
          },
          anotherMethod() {
          }
        })

        // {someMethod: ƒ, anotherMethod: ƒ, constructor: ƒ}

        ③克隆对象

        function clone (obj) {
          Object.assign({}, obj);
        }

        let origin = {
          name: 'xhk',
          age: 36
        }

        clone(origin)  // {name: "xhk", age: 36}

        将原始对象拷贝到一个空对象,就得到了原始对象的克隆。

        采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。

        function clone (origin) {
          let originProto = Object.getPrototypeOf(origin);
          return Object.assign(Object.create(originProto, origin));
        }

        ④合并多个对象

        将多个对象合并到某个对象。

    1. 属性的可枚举性和遍历

      有四个操作会忽略enumerablefalse的属性。

    • for...in循环:只遍历对象自身的和继承的可枚举的属性。
    • Object.keys():返回对象自身的所有可枚举的属性的键名。
    • JSON.stringify():只串行化对象自身的可枚举的属性。
    • Object.assign(): 忽略enumerablefalse的属性,只拷贝对象自身的可枚举的属性。
    1. 属性的遍历

      ES6 一共有 5 种方法可以遍历对象的属性。

      1) for ... in

      for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)

      2)Object.keys(obj)

      Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

      const obj = {
        name: 'xhk',
        age: 36
      }

      Object.keys(obj)  //  ["name", "age"]

      3)Object.getOwnPropertyNames(obj)

      Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。

      4)Object.getOwnPropertySymbols(obj)
    Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。

      5)Reflect.ownKeys(obj)

      Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

      以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。

    • 首先遍历所有数值键,按照数值升序排列。
    • 其次遍历所有字符串键,按照加入时间升序排列。
    • 最后遍历所有 Symbol 键,按照加入时间升序排列。

     7. Object.getOwnPropertyDescriptors()  

      对象的属性是赋值方法或取值的方法时, 在复制的时候回出现错误, Object.getOwnPropertyDescriptors方法配合Object.defineProperties方法,就可以实现正确拷贝。

      Object.getOwnPropertyDescriptors方法的另一个用处,是配合Object.create方法,将对象属性克隆到一个新对象。这属于浅拷贝。

      let obj = {
        set foo(val) {
          console.log(val);
        }
      }

      const clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

      Object.create() 方法会使用指定的原型对象及其属性去创建一个新的对象。

      Object.getOwnPropertyDescriptors方法可以实现一个对象继承另一个对象。以前,继承另一个对象,常常写成下面这样。

      const obj = {
        __proto__: prot,
        foo: '456'
      }

      //  {     foo:"456"

         __proto__: {

          foo:123

          __proto__:Object

           }

        }

    1. __proto__ 属性(最好不要使用)

      用来读取或设置当前对象的prototype对象。目前,所有浏览器(包括 IE11)都部署了这个属性。

      Object.setPrototypeOf()(写操作)

      Object.getPrototypeOf()(读操作)

      Object.create()(生成操作)

      Object.setPrototypeOf()  用来设置一个对象的__proto__对象, 返回参数对象本身。

      如果第一个参数不是对象,会自动转为对象。但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。

      Object.setPrototypeOf(1, {})  // 1

      Object.setPrototypeOf('foo', {})  // 'foo'

      Object.setPrototypeOf(true, {})  // true

      Object.setPrototypeOf(undefined, {})  // 报错

      Object.setPrototypeOf(null, {})  // 报错

      Object.getPrototypeOf() 用来读取一个对象的原型对象  

      function A() {}

      const a = new A();

      Object.getPrototypeOf(a) === A.prototype  // true

    1. super关键字

      this关键字总是指向函数所在的当前对象, super关键字,指向当前对象的原型对象。

      只有下面的写法可以让Javascript引擎确认:

      const obj3 = {
        foo() {
          return super.foo
        }
      }

      const obj = {
        foo: super.foo
      }  // 报错

      const obj1 = {
        foo: () => super.foo
      } // 报错

      const obj2 = {
        foo: function () {
          return super.foo;
        }
      }  // 报错

      const proto = {
        x: 'hello',
          foo() {
           console.log(this.x);
          }
      }

      const obj = {
        x: 'world',
        foo() {
          super.foo();
        }
      }

      Object.setPrototypeOf(obj, proto);

      obj.foo()  // 'world'

      扩展运算符的解构赋值,不能复制继承自原型对象的属性。

      let o1 = {a: 1}

      let o2 = {b: 2}

      o2.__proto__ = o1;

      let {...o3} = o2

      o3.b // 2

      o3.a // undefined 

      扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。  

      let z = {a: 3, b: 4}

      let n = {...z}

      n   // {a: 3, b: 4}

      上面的例子只是拷贝了对象实例的属性,如果想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法。

      let obj = {
        find() {},
        foo: 'foo'
      }

      const clone2 = Object.assign(
        Object.create(Object.getPrototypeOf(obj)),
        obj
      )

       扩展运算符可以用于合并两个对象

      const a = {
        name: 'xhk'
      }

      const b = {
        wife: 'coco'
      }

      let ab = {...a, ...b}

      ab // {name: 'xhk', wife: 'coco'}

      let cd = Object.assign({}, a, b);

      cd // {name: 'xhk', wife: 'coco'}

      let x = 1;

      let y = 2;

      const z = {
        x = 0;
      }

      const a = {...z, x, y}

      const obj = {
        name: 'xhk',
        age: 36,
        wife: 'coco'
      }

      let b = {
        ...obj,
        age: 24
      }

      b   // {name: "xhk", age: 24, wife: "coco"}

    1. Null传导运算符(?.)

      如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。

      const firstName = message?.body?.user?.firstName || 'default'

      等同于

      const firstName = (message && message.body&& message.body.user && message.body.user.firstName) || 'default'

       上面代码有三个?.运算符,只要其中一个返回nullundefined,就不再往下运算,而是返回undefined

     

    本文由新葡亰496net发布于新葡亰官网,转载请注明出处:新葡亰496net学习笔记,浅拷贝与深拷贝详解

    关键词:

上一篇:新葡亰496net:模块讲解,的一次更新说明

下一篇:没有了