【问题标题】:JS Inheritance: calling the parent's function from within the child's functionJS继承:从子函数中调用父函数
【发布时间】:2015-05-22 16:47:09
【问题描述】:

JS对象模型一定有什么我不明白的地方。

来自这些资源:

我收集了我认为或认为是对象模型的准确心理表示。这里是:


所有对象都有一个属性,文档将其称为[[Prototype]][[Prototype]] 可以被认为是对对象父级的引用。更准确地说:

对 [parent's] 原型对象的引用被复制到 新实例的内部 [[Prototype]] 属性。 (source 1)

您可以使用Object.getPrototypeOf(child) 访问子级的[[Prototype]] 属性,此处返回的值将是对父级原型的引用(不是其内部[[Prototype]] 属性,而是其实际原型)

obj.prototype 不同于对象的内部[[Prototype]] 属性。它的作用类似于用于制作此精确对象实例的蓝图,而其[[Prototype]] 属性指向用于制作其父对象实例的蓝图。

  • Parent.prototype === Object.getPrototypeOf(child); //true

详细说明:

  • 如果您向child.prototype 添加一个函数,该函数将对child 及其任何子级可用。

  • 如果向parent.prototype 添加一个函数,相当于向Object.getPrototypeOf(child) 添加一个函数,那么该函数将对parent 及其所有子项可用,包括child 和它的所有siblings

您可以使用Object.create() 来创建一个具有您想要的任何[[Protoype]] 属性的新对象。因此,您可以将其用作实现继承的一种方式。示例见source 2


考虑到这一点,我想获得一个我自己的工作示例。我的计划是创建一个父“类”,然后创建一个继承自它的子“类”。

我希望子类实现一个方法,该方法重载了父类的方法。需要注意的是,我希望子版本的方法调用父版本的方法,然后做一些额外的事情。

这是我想出的,请参阅下面的相关问题:

var Parent = function() {};

Parent.prototype.myF = function() {
  console.log('derp');
};


function Child() {
  Parent.call(this);
};

//make [[prototype]] of Child be a ref to Parent.prototype
Child.prototype = Object.create(Parent.prototype);

//need to explicitly set the constructor
Child.prototype.constructor = Child;

Child.prototype.myF = function() {
  Object.getPrototypeOf(this).myF();
  console.log("did I derp??");
};

childInstance = new Child();
childInstance.myF();

似乎是这样的,当我尝试重载Parent.myF() 时,在重载它的同时,我实际上是在修改原始函数。似乎是这种情况,因为记录的结果是:

'derp'
'did I derp??'
'did I derp??'

大概'did I derp??' 的第一次出现来自父函数的修改版本,我不是的意思,那么第二个版本来自子函数。

谁能详细说明为什么会这样?

【问题讨论】:

  • 问题写得非常好。
  • 确认 OP 在 chrome 中的输出,与 firefox 相同。 @Ankit:您是否在 Mozilla Firefox 开发版中检查了右侧的红色“2”? prntscr.com/78578d
  • 您的理解有一处错误。 obj.prototype 不是一回事。只有函数具有.prototype 属性。对象具有.constructor 属性。所以真的是obj.constructor.prototype
  • [[Prototype]] can be thought of as a reference to the object's parent - 实际上,父原型是原型链的一部分,当调用子函数时会检查该原型链。所有祖先的原型都构成了这条链的一部分,一直追溯到根Object。因此,如果对child.Foo() 的调用没有调用子实例或子原型上的任何内容,则解释器将检查parent.prototype.Foo(),如果存在,将调用它。如果不存在,我们将检查grandParent.prototype.Foo()等...
  • @LukeP 原型继承是关于扩充的。看这一行:Child.prototype = Object.create(Parent.prototype); 通过这样做,您已经引入了所有父原型的方法,但是您通过添加 myF() 扩展了子原型的方法。这不是在引用父原型,而是在扩展它。

标签: javascript oop inheritance prototype


【解决方案1】:

很好的问题,需要进行一些测试和研究才能找到答案。

识别奇怪的行为

我稍微更改了您的代码,以找出何时调用了哪个函数:

var Parent = function() {};

Parent.prototype.myF = function() {
  console.log('derp');
};


function Child() {
  Parent.call(this);
  this.name = 'Test'; // this is a new test property
};

//make [[prototype]] of Child be a ref to Parent.prototype
Child.prototype = Object.create(Parent.prototype);

//need to explicitly set the constructor
Child.prototype.constructor = Child;

Child.prototype.myF = function() {
  console.log(this); // here I want to find out the context, because you use it in the next line
  Object.getPrototypeOf(this).myF();
  console.log("did I derp??");
};

childInstance = new Child();
childInstance.myF();

您可以查看 JSFiddle 并亲自尝试:http://jsfiddle.net/Lpxq78bs/

代码中的关键行是这样的:

Child.prototype.myF = function() {
  Object.getPrototypeOf(this).myF(); // this one
  console.log("did I derp??");
};

在执行console.log(this); 以找出this 指的是什么之后,我看到它在did I derp?? 的第一个输出和第二个输出之间发生了变化。

我得到以下输出:

Object { name: "Test" }
Object { constructor: Child(), myF: window.onload/Child.prototype.myF() }
"derp"
"did I derp??"
"did I derp??"

解释输出

由于我在Child 构造函数中添加了一个“名称”属性,因此只有在我查看Child 的实例而不是其.prototype 时才会出现它。

所以输出的第一行意味着当前的this上下文确实是childInstance但第二个既不是childInstance,也不是Parent.prototype

  1. 呼叫(childInstance 中的myF):this 指的是childInstanceObject.getPrototypeOf(this).myF(); 然后查找childInstance[[Prototype]]这是Child.prototype,而不是Parent.prototype。 输出:“我是不是发疯了??”

  2. 调用(Child.prototypemyF)this指的是Child.prototype,也就是childInstances[[Prototype]]属性。所以Object.getPrototypeOf(this).myF(); 的第二次调用最终返回Parent.prototype(有点)。输出:“我是不是发疯了??”

  3. 调用(Parent.prototypeParent.prototype 实例的myFObject.create 创建):最后调用父级上的myF。输出:'derp'

由于您的console.log("did I derp??") 位于myF 函数调用之后,输出顺序相反。下图说明了代码的遍历方式:

所以你对Object.getPrototypeOf(this).myF(); 所指的假设是错误的。

ES5 中的解决方案

@LukeP: https://jsfiddle.net/Lpxq78bs/28/

ES6 中的替代解决方案

为了避免这种混淆,并且由于您使用的是经典继承模式,您可以查看ES6 Classes。以下是您尝试做的一个粗略示例:

class Parent {
    constructor() {
      
    }
    
    myF() {
        console.log('derp');
    }
}

class Child extends Parent {
    constructor() {
        super();
    }

    myF() {
        super.myF();
        console.log("did I derp??");
    }
}

var childInstance = new Child();
childInstance.myF();

我希望这有助于理解发生了什么。

【讨论】:

  • 为什么在所有情况下输出总是did i derp
  • 所以输出序列是...did I derp?? - did I derp?? - derp 还是相反?根据这个答案,它看起来像这样..但正在记录相反的情况。你能解释一下怎么做吗?
  • @The Warlock 我刚刚添加了一张图片,希望能让它更清晰。由于console.log 出现在函数调用之后,因此最后一个函数首先显示其console.log('derp')
  • 太棒了!再次阅读您的答案后,我有点想通了,但图形确实有帮助!
  • @nils 我很抱歉,但是我在 jsFiddle 中犯了一个错误,您能否将 ES5 解决方案链接更新为:jsfiddle.net/Lpxq78bs/28 -- 调用超级函数时,正如 HMR 在 cmets 中指出的那样,您需要覆盖“this”的默认值才能访问实例变量。
【解决方案2】:

您的代码按预期工作,您得到的输出是因为Object.getPrototypeOf,可以解释为

第 1 步:当您调用 childInstance.myF(); 时,它会转到下面的代码,其中 this 指的是 childInstance 本身

Child.prototype.myF = function() {
  Object.getPrototypeOf(this).myF();
  console.log("did I derp??");
};

然后Object.getPrototypeOf 返回childInstance.[[Prototype]] 这是Child.prototype 并再次调用myF 方法(在方法执行后留下第二行打印)但下一次this 将引用childInstance.[[Prototype]]

第 2 步:在此调用中,this 指向 childInstance.[[Prototype]](或 Child.prototype 对象本身)

Child.prototype.myF = function() {
  Object.getPrototypeOf(this).myF();
  console.log("did I derp??");
};

现在 Object.getPrototypeOf 返回 childInstance.[[Prototype]].[[Prototype]]( 即 Child.prototype.[[Prototype]]) 即 Parent.prototype 并再次调用 myF 方法,但这次 myF 方法将打印 derp 因为 myF 方法 Parent.prototype正在被调用。之后,第二行将打印 did i derp

第 3 步:然后函数返回第 1 步执行第二行,再次打印 did i derp

【讨论】:

  • 很好的解释,只有一个小错误:在第2步中,this指向Child.prototypechildInstance.[[Prototype]],它们是对同一事物的引用。 Child.[[Prototype]] 此时只是空的。可以在浏览器中查看:Object.getPrototypeOf(Child);
【解决方案3】:

如果您必须在 ES5 中使用继承,我制作了一个包装器,可以轻松扩展和调用父方法。无需框架。

在这里修改工作代码:https://jsfiddle.net/wcrwLmrk/13/

下面是代码的工作原理:

/* extend the base class to the new parent class and pass 
an object with the new properties and methods */
var ParentClass = Class.extend(  
{ 
    // reset the constructor to the correct constructor
    constructor: function() 
    { 

    },

    callName: function(arg)
    { 
        console.log('parent ' + arg); 
    }
}); 

/* extend the parent class to the new child class and pass 
an object with the new properties and methods */
var ChildClass = ParentClass.extend(  
{ 
    // reset the constructor to the correct constructor
    constructor: function() 
    { 
        ParentClass.call(this); 
    },

    callName: function(arg)
    { 
        // child method code
        console.log('child ' + arg);

        /* call parent method by passing the method name and any params */ 
        this.super('callName', arg); 
    }
}); 

现在我们可以创建一个新的子类 调用超类方法:

var child = new ChildClass(); 
child.callName('name');

这里是项目中需要包含的基类:

var Class = function() 
{ 

}; 

Class.prototype =  
{ 
    constructor: Class, 

    /* this is an alias for callParent. this will 
    allow child classes to call super methods. 
    @param (string) method name 
    @param [(mixed)] addasmany args as the super 
    method needs 
    @return (mixed) the super method return value */
    super: function()
    { 
        var parent = this.parent; 
        if(parent)
        { 
            var args = [].slice.call(arguments), 

            // this will remove the first arg as the method
            method = args.shift(); 
            if(method)
            { 
                var func = parent[method]; 
                if(typeof func === 'function')
                { 
                    return func.apply(this, args);
                } 
            } 
        }
        return false;
    }
}; 

/* this will return a new object and extend it if an object it supplied. 
@param [(object)] object = the extending object 
@return (object) this will return a new object with the 
inherited object */ 
var createObject = function(object) 
{ 
    if(!Object.create) 
    { 
        var obj = function(){}; 
        obj.prototype = object;
        return new obj(); 
    } 
    else 
    { 
        return Object.create(object); 
    } 
}; 

/* this will extend an object and return the extedned 
object or false.  
@param (object) sourceObj = the original object 
@param (object) targetObj = the target object */ 
var extendObject = function(sourceObj, targetObj) 
{ 
    if(typeof sourceObj !== 'undefined' && typeof targetObj !== 'undefined') 
    { 
        for(var property in sourceObj) 
        { 
            if(sourceObj.hasOwnProperty(property) && typeof targetObj[property] === 'undefined') 
            { 
                targetObj[property] = sourceObj[property]; 
            } 
        } 

        return targetObj; 
    } 
    return false; 
}; 

var extendClass = function(sourceClass, targetClass) 
{ 
    /* if we are using a class constructor function 
    we want to get the class prototype object */  
    var source = (typeof sourceClass === 'function')? sourceClass.prototype : sourceClass, 
    target = (typeof targetClass === 'function')? targetClass.prototype : targetClass;

    if(typeof source === 'object' && typeof target === 'object') 
    { 
        /* we want to create a new object and add the source 
        prototype to the new object */ 
        var obj = createObject(source); 

        /* we need to add any settings from the source that 
        are not on the prototype */ 
        extendObject(source, obj); 

        /* we want to add any additional properties from the 
        target class to the new object */ 
        for(var prop in target) 
        { 
            obj[prop] = target[prop]; 
        } 

        return obj; 
    } 
    return false; 
}; 

/* this will allow the classes to be extened. 
@param (object) child = the child object to extend 
@return (mixed) the new child prototype or false */ 
Class.extend = function(child)
{  
    if(!child)
    { 
        return false; 
    } 

    var parent = this.prototype; 

    /* the child constructor must be set to set 
    the parent static methods on the child */ 
    var constructor = child && child.constructor? child.constructor : false; 
    if(child.hasOwnProperty('constructor') === false)
    { 
        constructor = function()
        {
            var args = arguments; 
            parent.constructor.apply(this, args); 
        }; 
    }

    constructor.prototype = extendClass(parent, child); 
    constructor.prototype.parent = parent;

    /* this will add the static methods from the parent to 
    the child constructor. */ 
    extendObject(this, constructor); 
    return constructor; 
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-03
    • 2022-12-04
    • 2014-10-31
    • 2013-02-26
    • 2021-08-24
    • 2012-04-11
    相关资源
    最近更新 更多