V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
xuecat
V2EX  ›  JavaScript

js 的 new 和原型链一个问题请教,感谢!!!!

  •  
  •   xuecat · 2018-01-26 14:11:35 +08:00 · 4099 次点击
    这是一个创建于 2253 天前的主题,其中的信息可能已经有所发展或是发生改变。
    function Super() {
        this.val = 1;
        this.arr = [1];
    }
    function Sub() {...}
    
    Sub.prototype = new Super();
    var sub1 = new Sub();
    var sub2 = new Sub();
    
    sub1.val = 2;//不会影响 sub2
    sub1.arr.push(2);//会影响 sub2
    

    上面的代码有点我无法理解,想请教下!!!

    先讲下我的理解:

    new Super后,导致Sub.prototype通过call得到了valarr
    new Sub后的实质变化也只是sub1__proto__指向了Sub.prototype

    也就是说,sub1.val,sub1.arr这都是访问Sub.prototype上的。
    这特么访问就都是一个链上的东西了。
    为何sub1.arr能影响,而sub1.val却不影响呢?
    不能因为一个是值类型,一个是引用类型就有区别吧!!!!

    是 new 的时候对值类型复制了吗?可是我翻了下没这描述。。。。。求解答

    22 条回复    2018-02-15 01:11:50 +08:00
    zzuieliyaoli
        1
    zzuieliyaoli  
       2018-01-26 14:23:47 +08:00
    "new Sub 后的实质变化也只是 sub1 的 __proto__ 指向了 Sub.prototype" 不对,
    应该为 “ sub1、sub2 的__proto__指向了 Sup.prototype ”

    sub1.__proto__.arr === sub2.__proto__.arr // true
    mskf
        2
    mskf  
       2018-01-26 14:24:18 +08:00
    先不管继承,根据你的描述,你的问题应该是这个

    function Super() {
    this.val = 1;
    this.arr = [1];
    }

    var a = new Super(),b= new Super()

    a.arr.push(2)
    b.arr//1,2 为什么这里受影响了

    ---
    首先你要知道
    function Super(){
    this.publicProperty = 1 //公有属性,所有实例公有的
    this.publicProperty = 1 //公有属性,所有实例公有的
    }

    Super.staticProperty =
    zzuieliyaoli
        3
    zzuieliyaoli  
       2018-01-26 14:26:39 +08:00
    #1 我的回复是错,请忽略
    Egg
        4
    Egg  
       2018-01-26 14:27:10 +08:00
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain<br/>
    在这个最下面的地方讲了一下 prototype 说到 new Super() 实际用的是 call 这个方法 也就是说 <br/>
    Super 下 this.val 给 call 到了 sub1 sub2 两个重新定义的变量当中<br/>
    Super 下 this.arr 因为是数组 所以 sub1 sub2 中的 arr 指向依旧是 Super 中的 arr<br/>
    mskf
        5
    mskf  
       2018-01-26 14:27:15 +08:00
    @mskf 妈蛋还没写完,敲 TAB 敲到回车了,继续
    ---
    首先你要知道
    function Super(){
    this.publicProperty = 1 //公有属性,所有实例公有的
    this.publicPropertyArr = [] //公有属性,所有实例公有的
    }

    Super.staticProperty = 2//静态属性,不能用 this 调用

    Super.prototype.prototypeProperty = 3 // 原型属性,实例化时赋予对象

    ---

    你的问题在于 publicProperty 这个属性为什么看上去不是公用的
    因为它是值对象。。。
    xuecat
        6
    xuecat  
    OP
       2018-01-26 14:32:21 +08:00
    @zzuieliyaoli @mskf <br/>
    我先修正俩句
    “ sub1、sub2 的__proto__指向了 Sup.prototype ””
    "也就是说,sub1.val,sub1.arr,sub2.val,sub2.arr 这都是访问 Sub.prototype 上的。"

    我能理解 sub1.arr.push(2)会影响 sub2,不能理解 sub1.val 不影响 sub2

    我认为它们访问的 arr 和 val 来源就是 Sup.prototype 上的,所以都是一样的。

    我如此认为的依据:

    new 的定义:
    ```js
    var obj = {};
    obj.__proto__ = Base.prototype;
    Base.call(obj);
    ```

    也就是说 new Sub 后,实质也就只是让__proto__指向了 prototype,和 call 换上下文。
    bramblex
        7
    bramblex  
       2018-01-26 14:33:14 +08:00
    sub => { __prop__: {val: 1, arr: [1] } }
    sub2 => { __prop__:{val:1, arr:[1] }}
    sub.val => 1
    sub.__prop__ === sub2.__prop__ => true

    sub.val = 2
    sub => { val: 1, __prop__: {val:1, arr:[1] }}
    sub2 => { __prop__:{val:1, arr:[1] }}


    sub.arr.push(2)
    sub => { val: 1, __prop__: {val:1, arr:[1,2] }}
    sub2 => { __prop__:{val:1, arr:[1,2] }}

    你需要知道的是, 这个 val / arr 到底在什么位置
    mskf
        8
    mskf  
       2018-01-26 14:33:28 +08:00   ❤️ 1
    @xuecat
    嗯。。。这样说把,如果 sub1.val 影响了 sub2.val ,是不是可以说明 sub1 和 sub2 的指针指向了同一个地址
    gogo81745
        9
    gogo81745  
       2018-01-26 14:33:56 +08:00 via Android   ❤️ 6
    sub1.var = 2
    这里赋值了,sub1 多了一个叫 var 的私有属性,并不是修改了原型链上的 var
    因此原型链上的 var 没有改变,而下次访问 sub1.var 时直接读私有属性不读原型链上的了

    而对数组的并不是赋值,修改的就是原型链上的数组。
    如果你改成 sub1.arr = [1, 2],那就不会影响 sub2
    Egg
        10
    Egg  
       2018-01-26 14:39:53 +08:00
    @gogo81745 如果一定要用到 push 这个方法的话有什么解决方案吗
    mskf
        11
    mskf  
       2018-01-26 14:40:53 +08:00   ❤️ 1
    @Egg Super.prototype.arr = [1]
    xuecat
        12
    xuecat  
    OP
       2018-01-26 14:42:01 +08:00
    @gogo81745 果然是这样的,确实是多了一个属性。万谢!!!

    他们也确实都访问到原型链上了
    Egg
        13
    Egg  
       2018-01-26 14:47:19 +08:00
    @mskf 谢谢
    gogo81745
        14
    gogo81745  
       2018-01-26 14:50:03 +08:00 via Android
    @Egg
    你是指使用 push,并且不影响原型链上的数组吗?
    sub1.arr = sub1.arr.slice()
    sub1.arr.push(2)
    我想到的最简单的方法就这种,不知道有没有什么奇技淫巧
    Egg
        15
    Egg  
       2018-01-26 14:54:19 +08:00
    @gogo81745 这个也可以 都好神奇 又学到一点新东西
    不过感觉还是在刚开始定义的时候 方法和数组用 prototype 来定义继承会好一点
    KuroNekoFan
        16
    KuroNekoFan  
       2018-01-26 15:21:55 +08:00
    关键是理解原型链,实例和基本类型,引用类型
    bucky
        17
    bucky  
       2018-01-26 15:28:52 +08:00
    还是拥抱 es6 吧
    timwei
        18
    timwei  
       2018-01-26 16:10:22 +08:00
    function Super() {
    this.val = 1;
    this.arr = [1];
    }

    function Sub(arr) {
    Super.call(this, arr)
    }

    Sub.prototype = new Super();
    var sub1 = new Sub();
    var sub2 = new Sub();

    sub1.arr.push(2); // [1,2]
    sub2.arr // [1]
    tjsdtc
        19
    tjsdtc  
       2018-01-26 16:54:09 +08:00
    这是属性设置的屏蔽问题,补充一点:
    现假设设置 foo.a = 1,而 a 不存在 foo 对象中而存在其原型链中时,有三种情况:
    1. a 属性是普通属性,此时会在 foo 对象上生成一个 a (就是楼主的情况)。
    2. a 属性是只读属性( writable: false ),此时设置会直接无效,foo 对象中不会添加,严格模式下报错。
    3. a 属性是一个 setter,则直接调用 setter,foo 对象中不会添加。
    isbase
        20
    isbase  
       2018-01-26 17:43:46 +08:00
    xilixjd
        21
    xilixjd  
       2018-01-26 23:39:30 +08:00
    7 楼完美解释
    yunfeihe
        22
    yunfeihe  
       2018-02-15 01:11:50 +08:00
    ## 关于 sub1.arr.push(2);//会影响 sub2 的问题
    Sub.prototype = new Super();
    这一句,创建了一个新的 Super 实例,并且让 Sub.prototype 指向(并非复制)这个 Super 实例。
    var sub1 = new Sub();
    var sub2 = new Sub();
    new 只是一个语法糖,等同于在一个构造函数里加了两段代码
    var yourFunction = function(){
    this = Object.create(yourFunction.prototype); //new 后隐性执行的代码
    .....
    实际代码
    .....
    return this; //new 后隐性执行的代码
    }
    假如楼主没有在 Sub()函数里写入自定义代码(只是为了便于理解,实际上不可能)则
    var sub1 = new Sub() 等价于 var sub1 = Object.create(Sub.prototype)
    var sub2 = new Sub() 等价于 var sub2 = Object.create(Sub.prototype)
    这样就很容易解释关于 array 的问题了,当执行 arr.push 时,编译器先查找 sub1 本身,然后根据原型链查询由 Object.create(Sub.prototype)创建的对象(也就是 Sub.__proto__所指的对象),这个对象也就是刚开始创建的 Super 实例,而 sub1 和 sub2 指向同一个 Super 实例,所以可以相互影响。

    ## 关于 sub1.val = 2;//不会影响 sub2 这个问题
    楼主应该很容易判断出来,对于一个双层函数,外层函数执行了 var a = 1,当内层函数对 a 这个变量进行操作时,有无在内层函数里同样声明 var a,a 的值可能大不相同(声明了即是内层函数变量,否则是外层)。
    但是对象是没有 var sub1.val 这样的声明语句的,所以当你执行 sub1.val=2 时,不仅仅是赋值,可以理解为是声明且赋值。声明了 sub1 的私有属性 val,并且赋值为 2,覆盖掉了原型的 val。事实上如果楼主尝试 delete sub1.val 并且再次执行 sub1.val 会发现值又等于 1 了。
    ps:楼主要是写过 python3 的闭包函数,上面的话应该很容易理解。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3277 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 13:11 · PVG 21:11 · LAX 06:11 · JFK 09:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.