Crockford's Prototypal inheritance - Issues with nested objects

后端 未结 3 961
借酒劲吻你
借酒劲吻你 2020-11-22 01:21

I\'ve been reading \"Javascript: The Good Parts\" by Douglas Crockford - and while it\'s a bit extreme, I\'m on board with a lot of what he has to say.

In chapter

3条回答
  •  野趣味
    野趣味 (楼主)
    2020-11-22 02:11

    I've changed the examples to give you a better demonstration of what is happening here. Demo

    First we create an object with three properties; A number, a string and an object with one property with a string value.

    Then we create a second object from the first using Object.create();

    var obj1 = { 
        num : 1,
        str : 'foo',
        obj : { less: 'more' }
    };
    var obj2 = Object.create( obj1 );
    
    console.log( '[1] obj1:', obj1 );
    console.log( '[1] obj2:', obj2 );
    
    "[1] obj1:"
    [object Object] {
      num: 1,
      obj: [object Object] {
        less: "more"
      },
      str: "foo"
    }
    "[1] obj2:"
    [object Object] {
      num: 1,
      obj: [object Object] {
        less: "more"
      },
      str: "foo"
    }
    

    Looks good right? We have our first object and a second copied object.

    Not so fast; Let's see what happens when we change some of the values on the first object.

    obj1.num = 3;
    obj1.str = 'bar';
    obj1.obj.less = 'less';
    
    console.log( '[2] obj1:', obj1 );
    console.log( '[2] obj2:', obj2 );
    
    "[2] obj1:"
    [object Object] {
      num: 3,
      obj: [object Object] {
        less: "less"
      },
      str: "bar"
    }
    "[2] obj2:"
    [object Object] {
      num: 3,
      obj: [object Object] {
        less: "less"
      },
      str: "bar"
    }
    

    Now again we have our first object, with changes, and a copy of that object. What's happening here?

    Let's check if the objects have their own properties.

    for( var prop in obj1 ) console.log( '[3] obj1.hasOwnProperty( ' + prop + ' ): ' + obj1.hasOwnProperty( prop ) );
    for( var prop in obj2 ) console.log( '[3] obj2.hasOwnProperty( ' + prop + ' ): ' + obj2.hasOwnProperty( prop ) );
    
    "[3] obj1.hasOwnProperty( num ): true"
    "[3] obj1.hasOwnProperty( str ): true"
    "[3] obj1.hasOwnProperty( obj ): true"
    "[3] obj2.hasOwnProperty( num ): false"
    "[3] obj2.hasOwnProperty( str ): false"
    "[3] obj2.hasOwnProperty( obj ): false"
    

    obj1 has all of its own properties, just like we defined, but obj2 doesn't.

    What happens when we change some of obj2's properties?

    obj2.num = 1;
    obj2.str = 'baz';
    obj2.obj.less = 'more';
    
    console.log( '[4] obj1:', obj1 );
    console.log( '[4] obj2:', obj2 );
    for( var prop in obj1 ) console.log( '[4] obj1.hasOwnProperty( ' + prop + ' ): ' + obj1.hasOwnProperty( prop ) );
    for( var prop in obj2 ) console.log( '[4] obj2.hasOwnProperty( ' + prop + ' ): ' + obj2.hasOwnProperty( prop ) );
    
    "[4] obj1:"
    [object Object] {
      num: 3,
      obj: [object Object] {
        less: "more"
      },
      str: "bar"
    }
    "[4] obj2:"
    [object Object] {
      num: 1,
      obj: [object Object] {
        less: "more"
      },
      str: "baz"
    }
    "[4] obj1.hasOwnProperty( num ): true"
    "[4] obj1.hasOwnProperty( str ): true"
    "[4] obj1.hasOwnProperty( obj ): true"
    "[4] obj2.hasOwnProperty( num ): true"
    "[4] obj2.hasOwnProperty( str ): true"
    "[4] obj2.hasOwnProperty( obj ): false"
    

    So, num and str changed on obj2 and not on obj1 just like we wanted, but obj1.obj.less changed when it shouldn't have.

    From the hasOwnProperty() checks we can see that, even though we changed obj2.obj.less, we didn't set obj2.obj first. This means that we are still referring to obj1.obj.less.

    Let's create an object from obj1.obj and assign it to obj2.obj and see if that gives us what we're looking for.

    obj2.obj = Object.create( obj1.obj );
    
    console.log( '[5] obj1:', obj1 );
    console.log( '[5] obj2:', obj2 );
    for( var prop in obj1 ) console.log( '[5] obj1.hasOwnProperty( ' + prop + ' ): ' + obj1.hasOwnProperty( prop ) );
    for( var prop in obj2 ) console.log( '[5] obj2.hasOwnProperty( ' + prop + ' ): ' + obj2.hasOwnProperty( prop ) );
    
    "[5] obj1:"
    [object Object] {
      num: 3,
      obj: [object Object] {
        less: "more"
      },
      str: "bar"
    }
    "[5] obj2:"
    [object Object] {
      num: 1,
      obj: [object Object] {
        less: "more"
      },
      str: "baz"
    }
    "[5] obj1.hasOwnProperty( num ): true"
    "[5] obj1.hasOwnProperty( str ): true"
    "[5] obj1.hasOwnProperty( obj ): true"
    "[5] obj2.hasOwnProperty( num ): true"
    "[5] obj2.hasOwnProperty( str ): true"
    "[5] obj2.hasOwnProperty( obj ): true"
    

    That's good, now obj2 has its own obj property. Let's see what happens when we change obj2.obj.less now.

    obj2.obj.less = 'less';
    
    console.log( '[6] obj1:', obj1 );
    console.log( '[6] obj2:', obj2 );
    
    "[6] obj1:"
    [object Object] {
      num: 3,
      obj: [object Object] {
        less: "more"
      },
      str: "bar"
    }
    "[6] obj2:"
    [object Object] {
      num: 1,
      obj: [object Object] {
        less: "less"
      },
      str: "baz"
    }
    

    So what this all tells us is that, if the property has not yet been changed on the created object, any get requests to the created object for that property will be forwarded to the original object.

    The set request for obj2.obj.less = 'more' from the previous code block first requires a get request for obj2.obj, which doesn't exist in obj2 at that point, so it forwards to obj1.obj and in turn obj1.obj.less.

    Then finally when we read obj2 again, we still haven't set obj2.obj so that get request will be forwarded to obj1.obj and return the setting that we had previously changed, causing the effect that changing a property of the second objects object child seems to change both, but really it is only actually changing the first.


    You can use this function to return a new object completely separated from the original recursively.

    Demo

    var obj1 = { 
        num : 1,
        str : 'foo',
        obj : { less: 'more' }
    };
    var obj2 = separateObject( obj1 );
    
    function separateObject( obj1 ) {
    
        var obj2 = Object.create( Object.getPrototypeOf( obj1 ) );
        for(var prop in obj1) {
            if( typeof obj1[prop] === "object" )
                obj2[prop] = separateObject( obj1[prop] );
            else
                obj2[prop] = obj1[prop];
        }
    
        return obj2;
    }
    
    console.log( '[1] obj1:', obj1 );
    console.log( '[1] obj2:', obj2 );
    for( var prop in obj1 ) console.log( '[1] obj1.hasOwnProperty( ' + prop + ' ): ' + obj1.hasOwnProperty( prop ) );
    for( var prop in obj2 ) console.log( '[1] obj2.hasOwnProperty( ' + prop + ' ): ' + obj2.hasOwnProperty( prop ) );
    
    "[1] obj1:"
    [object Object] {
      num: 1,
      obj: [object Object] {
        less: "more"
      },
      str: "foo"
    }
    "[1] obj2:"
    [object Object] {
      num: 1,
      obj: [object Object] {
        less: "more"
      },
      str: "foo"
    }
    "[1] obj1.hasOwnProperty( num ): true"
    "[1] obj1.hasOwnProperty( str ): true"
    "[1] obj1.hasOwnProperty( obj ): true"
    "[1] obj2.hasOwnProperty( num ): true"
    "[1] obj2.hasOwnProperty( str ): true"
    "[1] obj2.hasOwnProperty( obj ): true"
    

    Let's see what happens when we change some variables now.

    obj1.num = 3;
    obj1.str = 'bar';
    obj1.obj.less = 'less';
    
    console.log( '[2] obj1:', obj1 );
    console.log( '[2] obj2:', obj2 );
    
    "[2] obj1:"
    [object Object] {
      num: 3,
      obj: [object Object] {
        less: "less"
      },
      str: "bar"
    }
    "[2] obj2:"
    [object Object] {
      num: 1,
      obj: [object Object] {
        less: "more"
      },
      str: "foo"
    }
    

    Everything works exactly the way you expected it to.

提交回复
热议问题