JS继承:从子函数

时间:2015-05-22 16:47:10

标签: javascript oop inheritance prototype

必须有一些我不了解JS对象模型的东西。

从这些资源:

我已经收集了我的想法或想法,是对象模型的准确心理表征。这是:

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

  

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

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

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

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

详细说明:

  • 如果您向child.prototype添加一项功能,child及其任何一个孩子都可以使用此功能。

  • 如果您向parent.prototype添加一个功能,这相当于向Object.getPrototypeOf(child)添加功能,那么该功能将可用于parent及其所有功能&#39 ; s的孩子,包括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??'的第一次出现来自父母的功能的修改版本,我意味着做,然后是第二版来自孩子的功能。

任何人都可以详细说明为什么会这样吗?

3 个答案:

答案 0 :(得分:6)

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

识别奇怪的行为

我改变了你的代码以找出在以下时调用的函数:

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构造函数添加了'name'属性,因此只有在我查看Child的实例时才会出现,而不是.prototype

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

  1. 致电myF childInstance}:this是指childInstanceObject.getPrototypeOf(this).myF();然后查找[[Prototype]]的{​​{1}}, childInstance,而不是Child.prototype 。 输出:“我做了derp ??”
  2. 致电Parent.prototype的{​​{1}})myF引用Child.prototype,即this [[原型]]财产。所以Child.prototype的第二次调用最终返回childInstances(有点)。输出:“我做了derp ??”

  3. 调用Object.getPrototypeOf(this).myF();创建的Parent.prototype实例myF:最后,调用父项Parent.prototype。输出:'derp'

  4. 由于您的Object.create位于myF函数调用之后,输出的顺序相反。下图说明了代码的遍历方式:

    enter image description here

    因此,您对console.log("did I derp??")所指的内容的假设是错误的。

    ES5中的解决方案

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

    ES6中的替代解决方案

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

    myF

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

答案 1 :(得分:6)

您的代码按预期工作,您获得的输出是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方法(在执行方法后稍后打印第二行)但下次{{} 1}}将引用this

第2步:在此致电childInstance.[[Prototype]]指向this(或childInstance.[[Prototype]]对象本身)

Child.prototype

现在Child.prototype.myF = function() { Object.getPrototypeOf(this).myF(); console.log("did I derp??"); }; 返回Object.getPrototypeOfchildInstance.[[Prototype]].[[Prototype]]Child.prototype.[[Prototype]]并再次调用myF方法,但这次myF方法将打印 derp 因为正在调用Parent.prototype的myF方法。之后,第二行将打印我做了derp

第3步:然后该功能返回到第1步执行第二行,再次打印我做了derp

答案 2 :(得分:0)

如果你必须在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; 
};