search

Home  >  Q&A  >  body text

javascript - 一个js继承问题,想不明白啊

两段小代码
第一段:

function Outer() {
    this.a = 1;
}

function Inner() {
}

var outer = new Outer();
Inner.prototype = outer;
var inner = new Inner();

inner.a += 1;
console.log(inner.a, outer.a);

第二段:

function Outer() {
    this.data = {
        a: 1
    };
}

function Inner() {
}

var outer = new Outer();
Inner.prototype = outer;
var inner = new Inner();

inner.data.a += 1;
console.log(inner.data.a, outer.data.a);

请问下为什么两段代码运行结果不一样呢?在第二段代码中,在inner中修改变量为何会影响到outer?

PHPzPHPz2819 days ago483

reply all(6)I'll reply

  • PHP中文网

    PHP中文网2017-04-10 14:57:18

    樓上都沒說到點上。

    簡而言之,

    inner.data.a
    

    結果等價於

    var data = inner.data; 
    data.a
    

    並沒有對 inner 進行任何賦值操作,只是對 inner.data 進行了 GetValue。

    inner.a 在不同上下文當中,含義有差異,主要體現在

    inner.a = ...
    

    並沒有原型鏈尋址的過程。

    詳細原因見下文。


    c.d += bc.d = c.d + b 的縮寫,其中 c.d 的位置必須是一個 LeftHandSideExpression。

    然而 c.d + b 中的 c.dc.d = ... 中的 c.d 是不同的,比如這裏 c.d 是一個 PropertyReferance,

    c.d + b 會對 c.d 進行 GetValue,從而遍歷 c 的原型鏈尋找 屬性名爲 "d" 的值

    c.d = ... 則會對 c 的屬性 "d" 賦值,而 c 又不存在屬性 d,因此創建了新屬性,名爲 d

    至於 c.d.e 當中的 c.d,則始終會被 GetValue,即便是 c.d.e = ... 也仍舊是訪問了原型鏈上的 c.d

    簡而言之這個問題的核心在於屬性尋址的機制:

    MemberExpression . IdentifierName is identical in its behaviour to

    MemberExpression [ <identifier-name-string> ]

    The production MemberExpression : MemberExpression [ Expression ] is
    evaluated as follows:

    Let baseReference be the result of evaluating MemberExpression. Let
    baseValue be GetValue(baseReference)
    . Let propertyNameReference be the
    result of evaluating Expression. Let propertyNameValue be
    GetValue(propertyNameReference). Call CheckObjectCoercible(baseValue).
    Let propertyNameString be ToString(propertyNameValue). If the
    syntactic production that is being evaluated is contained in strict
    mode code, let strict be true, else let strict be false. Return a
    value of type Reference
    whose base value is baseValue and whose
    referenced name is propertyNameString, and whose strict mode flag is
    strict. The production CallExpression : CallExpression [ Expression ]
    is evaluated in exactly the same manner, except that the contained
    CallExpression is evaluated in step 1.

    加粗的操作就是導致這一區別的關鍵,使得 c.d.e 中的 c.d 在任何上下文中都有相同的含義,而單純的 c.dc.d + bc.d = ... 中有了區別。

    具體區別如下:

    The production AssignmentExpression : LeftHandSideExpression =
    AssignmentExpression is evaluated as follows:

    Let lref be the result of evaluating LeftHandSideExpression. Let rref
    be the result of evaluating AssignmentExpression. Let rval be
    GetValue(rref)
    . Throw a SyntaxError exception if the following
    conditions are all true: Type(lref) is Reference is true
    IsStrictReference(lref) is true Type(GetBase(lref)) is Environment
    Record GetReferencedName(lref) is either "eval" or "arguments" Call
    PutValue(lref, rval). Return rval. NOTE When an assignment occurs
    within strict mode code, its LeftHandSide must not evaluate to an
    unresolvable reference. If it does a ReferenceError exception is
    thrown upon assignment. The LeftHandSide also may not be a reference
    to a data property with the attribute value {[[Writable]]:false}, to
    an accessor property with the attribute value {[[Set]]:undefined}, nor
    to a non-existent property of an object whose [[Extensible]] internal
    property has the value false. In these cases a TypeError exception is
    thrown.

    而 PutValue 中:

    Else if IsPropertyReference(V), then If HasPrimitiveBase(V) is false,
    then let put be the [[Put]] internal method of base, otherwise let put
    be the special [[Put]] internal method defined below. Call the put
    internal method using base as its this value, and passing
    GetReferencedName(V) for the property name
    , W for the value, and
    IsStrictReference(V) for the Throw flag.

    可見賦值的時候並不會像 GetValue 時那樣進行原型鏈尋址,而是直接修改了外層對象。

    reply
    0
  • 伊谢尔伦

    伊谢尔伦2017-04-10 14:57:18

    javascript中的继承,归根结底 就是 对象的赋值引用;

    第一段中 a 只是 Number 类型,赋值是按 按值传递的;

    第二段中 data 是 Object 类型, 赋值是 按共享传递的;

    按共享传递的 可以理解为 按安全指针传递,安全指针不能去解除引用和改变对象,但可以去修改该对象的属性值。

    运行下面的代码:

    function Outer() {
        this.data = {
            a: 1
        };
        this.data2 = {
            a: 1
        };
    }
    
    function Inner() {
    }
    
    var outer = new Outer();
    Inner.prototype = outer;
    var inner = new Inner();
    
    inner.data.a += 1;//这种情况 inner.data === outer.data 为 true
    inner.data2 = {a:2};//这种情况 inner.data2 === outer.data2 为 false
    console.log(inner.data === outer.data,inner.data.a, outer.data.a);
    console.log(inner.data2 === outer.data2,inner.data2, outer.data2);
    

    不知道你能不能理解,具体可以看下 javascript 的 赋值策略

    reply
    0
  • 怪我咯

    怪我咯2017-04-10 14:57:18

    因为data是对象,继承了对象,两个a都是这个对象的引用

    reply
    0
  • 巴扎黑

    巴扎黑2017-04-10 14:57:18

    简单数据类型,例如Number,在对象继承的时候,会做值copy,所以你改变父对象或子对象的这些相关属性,其实改变的是各自的属性值
    与此对应的是如果是引用类型,例如object,array,function等,在对象继承的时候,会copy这些对象的引用地址,而不是他们实际的值,这意味着我们在父对象和子对象中的属性,指向的都是同一个引用,所以更改这个引用对象内部的属性,会同时影响父子对象。

    这里有一个例外是不可变数据类型,如String,由于String的不可更改性,尽管继承的时候是引用,但是你修改它的时候其实是新创建了字符串,由于javascript的prototype继承覆盖机制,所以并不会造成引用问题,简化起见,你可以将String理解为简单数据类型。

    另外在你的示范代码中的继承方法其实是有问题的:假设你的Outer内部如果有dom操作行为,那么在继承的时候,由于你实例化了Outer,代码就会被执行,而这次操作是错误的。可以参考 javascript设计模式书中的继承方法, 点击这里,同时它解释了这个方法最后三行的含义,我觉得很好。

    reply
    0
  • 迷茫

    迷茫2017-04-10 14:57:18

    data 是对象,对象是引用类型

    reply
    0
  • 怪我咯

    怪我咯2017-04-10 14:57:18

    bumfod 正解!

    reply
    0
  • Cancelreply