V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
Tankpt
V2EX  ›  问与答

关于js的原型的一个问题

  •  
  •   Tankpt · 2014-01-23 22:00:48 +08:00 · 3261 次点击
    这是一个创建于 3951 天前的主题,其中的信息可能已经有所发展或是发生改变。
    先附上一段代码
    function fTest(name){}

    fTest.prototype.name = "hello";

    fTest.prototype.setname = function(name){
    this.name = name;
    };

    fTest.prototype.getname = function(){
    console.log(this.name);
    };

    var oTest1 = new fTest();
    var oTest2 = new fTest();

    oTest2.__proto__ === oTest1.__proto__;//返回true


    然后就是我查看了下oTest1中的属性,发现有name,一点疑问,我之前对他的理解都是fTest.prototype.name = "hello";,这句话表明在fTest.prototype中有一个变量为name,然后oTest1和oTest2全部指向他,我的理解是两个共有的一个属性,只要在一个改变了,那另一个去访问也会变化,但是我实际看了下在OTest1对象中有name,这个我在function fTest(name){}中又没进行申明,为啥有这个属性呢?

    我的一点怀疑是不是我这个东西的this出了问题,有点乱指了,一点感觉不知道对不对,还望前辈们指点下问题
    28 条回复    1970-01-01 08:00:00 +08:00
    FrankFang128
        1
    FrankFang128  
       2014-01-23 22:08:36 +08:00
    fTest.prototype.name = "hello";
    这不是 name 的声明吗?
    FrankFang128
        2
    FrankFang128  
       2014-01-23 22:09:49 +08:00
    不太明白你的疑问在哪里。
    你明明声明了为什么觉得自己没声明?
    FrankFang128
        3
    FrankFang128  
       2014-01-23 22:15:37 +08:00
    console.log(oTest1)

    > fTest {name: "hello", setname: function, getname: function}
    > > __proto__: fTest
    > > > constructor: function fTest(name){}
    > > > getname: function (){
    > > > name: "hello"
    > > > setname: function (name){
    > > > __proto__: Object

    oTest1 和 oTest2 的 name 属性都是从 __proto__ 里得到的,而 __proto__ 指向 fTest.prototype,而 fTest.prototype.name = "hello";.
    所以 oTest1 和 oTest2 的 name 属性来自同一个地方,但是 name 不是它们自身的属性
    oTest1.hasOwnProperty('name')
    false
    Tankpt
        4
    Tankpt  
    OP
       2014-01-23 22:45:47 +08:00
    @FrankFang128 嗯,我看了下你第三条回复,完全同意,现在我就是比如调用了下setname方法,用oTest1.setname("hi");我之前的理解是这个操作会改变fTest.prototype中name属性为hi ,然后调用oTest2.getname()返回的也是hi,但是实际测试的时候返回的还是hello,这样感觉就跟name
    然后用oTest1.hasOwnProperty('name')返回的是true,我想问的是是不是在setname中的this出了问题了
    FrankFang128
        5
    FrankFang128  
       2014-01-23 22:49:20 +08:00
    在 context 是 oTest1 的情况下,你只能操作 oTest1 的属性,无法通过 oTest1 改变它的 __proto__ 的属性,也就是说,你添加了一个实例属性,覆盖了『类』属性。
    FrankFang128
        6
    FrankFang128  
       2014-01-23 22:50:22 +08:00
    但是对于没用覆盖过 name 的 oTest2 来说, name 还是 __proto__ 的 name,而不是 its own property。
    FrankFang128
        7
    FrankFang128  
       2014-01-23 22:54:36 +08:00
    应该不能称为「类属性」。 暂时还没想到合适的词。
    在你执行 oTest1.setname 时,在 oTest1 上添加了一个 name,同时 oTest1 的 __proto__ 里也有一个 name。
    根据 JS 查找成员的规则,它如果在 oTest1 上找到了 name, 就不会再去看 oTest1 的 __proto__ 了。
    而 oTest2,因为自身没有 name 属性,所以 JS 引擎会去看它的 __proto__
    FrankFang128
        8
    FrankFang128  
       2014-01-23 22:56:12 +08:00
    已经很久没看面向对象的东西,不过不建议你从面向对象的角度去理解,而是从原型链的角度来理解。
    Tankpt
        9
    Tankpt  
    OP
       2014-01-23 22:57:32 +08:00
    @FrankFang128 嗯嗯。有些明白了。那就是说这个setname方法中的this.name指向的不是fTest.prototype中的name么?
    Tankpt
        10
    Tankpt  
    OP
       2014-01-23 23:00:32 +08:00
    @FrankFang128 嗯,那个js寻找一个属性从实例开始找这点我是明白的,我感觉根据测试的结果来看,这个时候在oTest1实例中就已经有了一个name的属性,然后覆盖了fTest.prototype中的name,然后么就是一下子没想明白这个实例中的name是在哪里加进去的。有点困惑了
    FrankFang128
        11
    FrankFang128  
       2014-01-23 23:01:30 +08:00
    this.name 指向哪里,是不确定的。
    唯一确定的是 JS 引擎的查找规则。
    在你 setname 之前,如果你想读取 oTest1 的 name,那么 JS 引擎首先看 oTest1 自身有么有 name 属性(没有),然后再看 __proto__ 里有没有(有),如果还找不到,它会继续看 __proto__ 的 __proto__。
    但是你 setname 之后, oTest1 就有了「自己的」name 属性。你再让 JS 引擎找 oTest1.name 的时候,它依然用刚才的逻辑找 name。
    现在应该懂了吧。
    FrankFang128
        12
    FrankFang128  
       2014-01-23 23:04:56 +08:00
    这是因为 oTest1.setname 中的 this ,指的是 oTest1。所以 oTest1.setname 会在 oTest1(this)上添加 name 属性。



    如果你运行 oTest1.setname.call(fTest.prototype,'another name'),那么期间的 this 就是 fTest.prototype 了。
    这里涉及的概念是 context。 你可以试着运行下。
    FrankFang128
        13
    FrankFang128  
       2014-01-23 23:07:15 +08:00
    this 和 prototype 就是 JS 最难理解的两个地方啊。
    FrankFang128
        14
    FrankFang128  
       2014-01-23 23:08:37 +08:00
    读 name 的过程和写 name 的过程是不一样的。11楼说的是读的过程。
    FrankFang128
        15
    FrankFang128  
       2014-01-23 23:10:29 +08:00
    而写的过程呢,就简单很多了。
    this.name = 'hi'
    JS 引擎会看 this 有没有 name 这个属性。没有就新建一个 name ,并赋值为1(没有查看__proto__ 的过程);有就直接赋值。
    Tankpt
        16
    Tankpt  
    OP
       2014-01-23 23:11:33 +08:00
    @FrankFang128 嗯嗯。现在prototype自己琢磨了下,然后差不多能画一些图了,前天看到了this的问题,感觉好玄乎,嗯。你刚那么一说,算是明白了,set中的this指向的是oTest1,那就没错了,难得放假有时间整理下东西,谢谢啦。非常感谢你的耐心指导~~
    FrankFang128
        17
    FrankFang128  
       2014-01-23 23:15:08 +08:00
    至于 this 指向哪里,也是不确定的,要看具体环境。
    如果你直接运行一个方法,比如
    function foo(){
    console.log(this) // window
    console.log(this === window) // true
    }

    如果你运行一个 object 的方法,不如

    var obj = {
    foo : function(){
    console.log(this===obj)
    }
    }
    obj.foo() // true

    但是你可以使用 call 来指定 this,比如

    var obj = {
    foo : function(){
    console.log(this===myObj)
    }
    }

    var myObj = {}
    obj.foo.call(myObj) // true

    so...不知道你懂了没。
    FrankFang128
        18
    FrankFang128  
       2014-01-23 23:17:16 +08:00
    你理懂还有段距离吧。 建议你看看李战的《悟透 JS》http://www.cnblogs.com/leadzen/archive/2008/02/25/1073404.html
    是他的博客,后来编成书了。第一部分很不错,后面的就差一点。
    FrankFang128
        19
    FrankFang128  
       2014-01-23 23:24:04 +08:00
    我理解到现在的程度,用了大半年的时间,还是觉得没理解透啊。嘿嘿
    Tankpt
        20
    Tankpt  
    OP
       2014-01-23 23:26:07 +08:00
    @FrankFang128 嗯嗯。所以在试着整理一下自己理解的东西,准备写个文章,然后看看。嗯嗯。这个文章我有看过点,当时太长了,然后就放在了书签里==!
    otakustay
        21
    otakustay  
       2014-01-24 02:34:38 +08:00
    js找属性的逻辑很简单:
    1. 自己有没有这属性,有的返回
    2. 原型有没有这属性,有的返回
    3. 原型的原型有没有这属性,有的返回
    4. 如果原型的原型的...的原型不存在,返回个undefined

    js写属性的逻辑更简单:直接往上面写,和原型没关系

    然后你的代码:
    1. oTest1和oTest2共享一个原型,这个无误
    2. 在没有调用setName前,找oTest1的name找到的是原型上的,oTest2的name也是原型上的,他们一样
    3. oTest1.setName('hello')后,oTest1的name找的是自己的,这是上面说的步骤的第1步。而oTest2找name是原型上的,是上面的第2步。因此它们返回的东西不一样,且你能看到oTest1有name了

    至于你说的oTest1的name变了oTest2会变,这个显然是错的,根据上面找属性的步骤来看,oTest1.setName('hello')改的是oTest1上的东西(在原型的前面),导致原型上的那个name在oTest1读取时被“隐藏”起来了,但仅仅是隐藏,其值是没有被改动的。
    otakustay
        22
    otakustay  
       2014-01-24 02:40:56 +08:00   ❤️ 2
    至于this更简单,就是4种调用方法来决定this是啥
    我想说js其实没有任何难的,无论是this还是原型链还是作用域链,统统简单的要死,根本就是一帮半调子开发者自己玩不出来就到处说这难那难,把人吓得看一眼就觉得复杂没敢认真去理解
    给你看一下我讲课和技术分享时用的一些PPT:https://www.dropbox.com/sh/alem0za5y1y53j4/DpqaYZJeIn
    其中的Brief Javascript.pptx就是js的入门课程,虽然没有对应的讲述会稍微有些难理解,不过你可以看一下,关于原型、作用域、this在这里面都有讲到,我认为这个PPT上的东西都能理解的话,js就算入门了
    想继续深入的可以看所谓闭包.pptx,和闭包、作用域链有关的概念这里面应该全讲清楚了,再往下可以看Inside the browser.pptx和异步编程与浏览器执行模型.pptx,这是浏览器底下的知识了
    sd4399340
        23
    sd4399340  
       2014-01-24 10:15:08 +08:00
    otakustay
        25
    otakustay  
       2014-01-24 11:44:27 +08:00
    @sd4399340 我自己能打开,搞不懂dropbox,给一个skydrive的吧不过可能版本有些老,错误倒是没有:https://skydrive.live.com/redir?resid=556CE34496EA9887%21105
    FrankFang128
        26
    FrankFang128  
       2014-01-24 12:43:07 +08:00
    @otakustay 对啊,JS最难的地方其实在于“怎样用C++/Java/C#的思维方式来思考JS”,可惜很多半路转职的开发者在这一点上越陷越深。


    @Tankpt 不错,都是对的。不过我觉得图画得太复杂了。“对象的 __proto__ 指向它构造函数的 prototype”这一句话就能解释那几张图了。

    oTest1 是个对象,那么它的 __proto__ 指向构造函数 fTest 的 prototype;
    构造函数 fTest 的 prototype 也是个对象,其 __proto__ 指向构造函数 Object 的 prototype;
    依此类推。
    Tankpt
        27
    Tankpt  
    OP
       2014-01-24 14:17:17 +08:00
    @FrankFang128 恩恩。那个图么。就是感觉这么画出来直观点。我怕自己解释不清,哈哈。我刚又看了一遍你们的回复,明白多了。灰常感谢
    Tankpt
        28
    Tankpt  
    OP
       2014-01-25 16:26:47 +08:00
    @FrankFang128 不晓得论坛里怎么私信,今天对那个constructor对象有点疑问了,主要是在实现继承的过程中,发现contructor指向了父类的构造函数,这个从面向对象的理解上,我觉得没问题,就是在实现上有点疑惑,不晓得在哪里改动了,或者说这个constructor属性是在什么情况下会修改,就比如下面的这个段代码
    function SuperType(){
    this.name ="person";
    }

    SuperType.prototype.getname = function(){
    console.log(this.name);
    };

    function Subtype(){
    this.name = "man";
    }

    Subtype.prototype = new SuperType();

    在Subtype.prototype 中的constructor指向了SuperType,我的理解这个不是应该指向subtype么
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2659 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 00:19 · PVG 08:19 · LAX 16:19 · JFK 19:19
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.