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

关于 JS 的面向对象,好像有点明白了,又好像模糊。

  •  
  •   cc7756789 · 2015-08-20 16:41:19 +08:00 · 3891 次点击
    这是一个创建于 3382 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我把一些 DOM 方法赋给变量,以供方便使用,但是遇到了错误。

    var gTag = document.getElementsByTagName;
    // undefined
    
    gTag
    // getElementsByTagName ()
    
    gTag ("p")
    // TypeError: 'getElementsByTagName' called on an object that does not implement interface Document.
    

    刚接触 JS 没多久,非常不理解,为什么把这个方法赋给变量,不用括号输入变量名就是直接调用了。
    然后我了解了下 call 和 apply 方法,原来 JS 下有这种好用的机制,一个新对象未定义一个方法,可直接调用旧对象的方法,只需要把新对象作为 this 传给旧对象的方法。

    function Old (){}
    
    Old.prototype = {
        name: "old name",
        run: function (){
            console.log ("Name is: " + this.name );
        }
    }
    
    var o = new Old;
    
    var New = {
        name: "new name"
    };
    
    o.run (New ); // Name is new name
    
    
    var run = o.run;
    run.call (o );  // name is old name
    

    但是我非常不理解为什么把实例的方法赋值给一个变量后会丢失 this 指针。

    这里无意挑起语言的纷争!!

    然后我想起了 Python 的类:

    class A:
        def run (self ):
            return "running"
    
    a = A ()
    run = a.run  
    run ()
    
    A.run ()
    """Traceback (most recent call last ):
    File "<stdin>", line 1, in <module>
    TypeError: unbound method run () must be called with A instance as first argument (got nothing instead )"""
    
    A.run (a )
    

    好像明白了些什么,但是又好像模糊!

    JS 里面把实例方法赋值给变量,以变量再调用,但是 this 会丢失。就好像如 Python 中的 A.run
    未实例化直接使用,也会报错。 JS 在 nodejs 环境下虽然未报错,但是输出的是 Name is undefined 也就是说找不到 this.name ,当成一个普通函数运行了。

    我的理解是否有误?

    现在还是不太明白为什么对实例方法赋值操作 var a = object.method 会丢失 this 。

    16 条回复    2015-08-21 14:56:08 +08:00
    edire
        1
    edire  
       2015-08-20 16:54:09 +08:00
    被你说晕了。真心被你说晕了。我先回答你最后一个问题
    void1900
        2
    void1900  
       2015-08-20 16:55:32 +08:00
    找不到对象
    edire
        3
    edire  
       2015-08-20 17:00:26 +08:00   ❤️ 1
    var a = object.method 会丢失 this 。

    不是会丢失 this 。是这样的你看这个

    var object = {
    name: 'username',
    method: function () {
    console.log (this.name );
    }
    };

    我如果执行 object.method (); 这样的话 他就会输出 username
    如果我先赋值给 a;

    var a = object.method

    那么,其实等于

    var a = function () {
    this.name;
    }

    当前的 this 指向的是 window ,也就是 window.name
    cc7756789
        4
    cc7756789  
    OP
       2015-08-20 17:02:05 +08:00
    o.run (New ); // Name is new name
    这里搞错了

    o.run.call (New ); // Name is new name
    cc7756789
        5
    cc7756789  
    OP
       2015-08-20 17:03:30 +08:00
    @edire 原来如此
    FrankFang128
        6
    FrankFang128  
       2015-08-20 17:03:53 +08:00
    看在用 Java 的角度理解 JavaScript 我就明白了你为什么疑惑了。
    你这样是想不通的。
    请忘掉传统的 OOP ,进入 prototype 的世界。
    FrankFang128
        7
    FrankFang128  
       2015-08-20 17:04:54 +08:00   ❤️ 1
    this 不会丢失, this 永远指向 context , this 不会指向「当前对象」。
    Arrowing
        8
    Arrowing  
       2015-08-20 17:05:03 +08:00
    var gTag = document.getElementsByTagName;
    这一句只是将 getElementsByTagName 这个方法引用赋值给 gTag
    而 js 里是方法执行时确定上下文对象的,当你执行这个方法时,会查找上下文对象,而这里明显缺乏一个上下文对象

    错误提示是 getElementsByTagName 的上下文对象必须是 document 或集成 document 的
    你可以这样:

    var gTag = function (tagName ){
    return document.getElementsByTagName (tagName );
    };
    gTag ('p');

    或者这样:

    var gTag = document.getElementsByTagName.bind (document );
    gTag ('p');
    sudoz
        9
    sudoz  
       2015-08-20 17:07:49 +08:00
    @FrankFang128 是这么回事
    q84629462
        10
    q84629462  
       2015-08-20 17:08:24 +08:00
    [var a = object.method 会丢失 this ]
    并不是丢失了 this :

    var a = {a:function (){console.log (this );}};
    var b = a.a;
    a.a ();
    b ();

    this 一直都在,只是这个 function 的运行环境改变了
    edire
        11
    edire  
       2015-08-20 17:08:42 +08:00
    然后 call o.run.call (New ) 实现了什么 实现了一个

    让 run 这个方法运行的时候作用域 this 指向了 New ,所以 那个地方的 this.name 也就是 "new name"
    WXYOO1
        12
    WXYOO1  
       2015-08-20 18:06:59 +08:00
    问题一:像 @Arrowing 所说的, Js 自带的方法要考虑上下文, document.getElementsByTagName 必须绑在全局上。
    另外扩展 Js 本身的一些方法一般是重新定义一下:比如像一些 ie 兼容的扩展方法等
    var gTag = function (tagName ){
    return document.getElementsByTagName (tagName );
    };
    gTag ('p');


    问题二:两个关注点:
    1.prototype 和 call\apply
    function Old (){}

    Old.prototype = {
    name: "old name",
    run: function (){
    console.log ("Name is: " + this.name );
    }
    }

    var o = new Old;

    var New = {
    name: "new name"
    };

    o.run (New ); // Name is (old old old )/3 遍 name
    //o.run 并没有接受参数的地方,你应该要写成 o.run.call (New );吧?
    //1. o.run (New ); 打印 old name 的原因是 o.run o 从类 Old 定义而来,而 Old 的 prototype.name = “ old name ”;
    //2. 若你本意是 o.run.call (New ) 则打印 Name is new name 此处指针会指向 New 对象。
    latelx
        13
    latelx  
       2015-08-20 18:51:12 +08:00
    你可以用 bind 把上下文绑定在方法上啊

    var run = o.run.bind (o )

    run ();
    YuJianrong
        14
    YuJianrong  
       2015-08-20 19:17:18 +08:00 via iPhone
    对 JS 语法有任何疑问去看语言 spec 是最简单的办法, es6 有点厚就算了,大部分特性去看薄薄的 es3 标志就好了。
    Rube
        15
    Rube  
       2015-08-21 11:09:02 +08:00
    js 作用域链
    bramblex
        16
    bramblex  
       2015-08-21 14:56:08 +08:00
    OwO 不明觉厉
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   945 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 22:30 · PVG 06:30 · LAX 14:30 · JFK 17:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.