Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't prototype-chain objects defined in .NET #38

Closed
DAVco4 opened this issue Jun 17, 2016 · 7 comments
Closed

Can't prototype-chain objects defined in .NET #38

DAVco4 opened this issue Jun 17, 2016 · 7 comments
Labels

Comments

@DAVco4
Copy link

DAVco4 commented Jun 17, 2016

I'm having trouble creating a prototype chain of objects that I have defined on the .NET side. I'm creating my objects using the pattern described under the Building and instance class header.

Regardless of what other ObjectInstances I pass through the constructor and instance function constructors (on the .NET side of the code) I always end up with objects that have an Object prototype rather than the specific object that I wanted the new instance to 'prototype' from.

Is this currently possible to achieve with Jurassic, or am I going about this the wrong way?

@paulbartrum
Copy link
Owner

There shouldn't be any limitations on how you construct your prototype chain. Can you post sample code that reproduces the problem?

@DAVco4
Copy link
Author

DAVco4 commented Jun 18, 2016

Hi Paul,

If it should be achievable, I'm almost certain that I'm doing something wrong here. If I want instances of OuterObject to have the prototype InnerObject, where should I 'set' that prototype?

I have attempted the following:

  • Setting the third parameter of OuterObjectConstructor's base constructor to new OuterObjectInstance(new InnerObjectInstance(engine.Object.InstancePrototype)
  • returning new OuterObjectInstance(new InnerObjectInstance(this.InstancePrototype, i), i) from the Construct function
  • Making OuterObjectInstance inherit (on the .NET side) from the InnerObjectInstance class.
  • All combinations of the above.

I fully expect this to be a hugely obvious thing that I've overlooked :)

public class OuterObjectConstructor : ClrFunction
{
    public OuterObjectConstructor(ScriptEngine engine)
        : base(engine.Function.InstancePrototype, "OuterObject", new OuterObjectInstance(engine.Object.InstancePrototype))
    {
    }

    [JSConstructorFunction]
    public OuterObjectInstance Construct(int i)
    {
        return new OuterObjectInstance(this.InstancePrototype, i);
    }

public class OuterObjectInstance : ObjectInstance
{

    public OuterObjectInstance(ObjectInstance prototype)
        : base(prototype)
    {
        this.PopulateFunctions();
    }

    public OuterObjectInstance(ObjectInstance prototype, int i)
        : base(prototype)
    {
    }

    [JSFunction(Name = "OuterFunction")]
    public string OuterFunction()
    {
        return "Outer function";
    }
}

@paulbartrum
Copy link
Owner

Your code looks correct. What makes you think it's wrong?

var engine2 = new ScriptEngine();
engine2.SetGlobalValue("O", new OuterObjectConstructor(engine2));
Console.WriteLine("instance: {0}", engine2.Evaluate<string>("var x = new O(); Object.getOwnPropertyNames(x)"));
Console.WriteLine("prototype: {0}", engine2.Evaluate<string>("x = Object.getPrototypeOf(x); Object.getOwnPropertyNames(x)"));
Console.WriteLine("prototype.prototype: {0}", engine2.Evaluate<string>("x = Object.getPrototypeOf(x); Object.getOwnPropertyNames(x)"));
Console.WriteLine("Anything else? {0}", engine2.Evaluate<string>("x = Object.getPrototypeOf(x); x"));

Gives this output:

instance:
prototype: OuterFunction,constructor
prototype.prototype: hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,valueOf,toString,constructor
Anything else? null

This is exactly what I would expect. The instance itself has no properties defined, the instance prototype has two (including the OuterFunction function), then you have the base object prototype which is shared by all objects. It has toString, etc.

@DAVco4
Copy link
Author

DAVco4 commented Jun 18, 2016

I think I've discovered the problem; my outerObject contains all the expected properties inherited from innerObject, but outerObject instanceof InnerObject evaluates to false. I think this is because I'm not using the same constructor instance to create the innerObject instance within the OuterObjectConstructor class, so although they are the same class on the .NET side, on the Javascript side they are entirely different objects, just with identical signatures.

So my question now is; what is the correct way to provide the OuterObjectConstructor with the correct constructor (or a correct instance) of InnerObject such that instanceof will behave as I expect?

Here is the Javascript of what I would like to achieve. Basically; outerObject inherits (prototypes?) from innerObject.

var JSInnerObject = function() {
    this.innerValue = "my inner value";
};

var JSOuterObject = function() {    
    this.outerValue = "my outer value";
};

// OuterObject instances prototype InnerObject
JSOuterObject.prototype = new JSInnerObject();

var jsInnerObject = new JSInnerObject();
var jsOuterObject = new JSOuterObject();

console.log(jsInnerObject instanceof JSInnerObject);
console.log(jsOuterObject instanceof JSOuterObject);
console.log(jsOuterObject instanceof JSInnerObject);

console.log(jsOuterObject.innerValue + " - " + jsOuterObject.outerValue);

And the output;

true
true
true
my inner value - my outer value

And here are the C# classes I'm using to achieve the same thing on the .NET side

InnerObject

using Jurassic;
using Jurassic.Library;

public class InnerObjectConstructor : ClrFunction
{
    public InnerObjectConstructor(ScriptEngine engine)
        : base(engine.Function.InstancePrototype, "InnerObject", new InnerObjectInstance(engine.Object.InstancePrototype))
    {
    }

    [JSConstructorFunction]
    public InnerObjectInstance Construct(int i)
    {
        return new InnerObjectInstance(this.InstancePrototype, i);
    }
}

public class InnerObjectInstance : ObjectInstance
{

    public InnerObjectInstance(ObjectInstance prototype)
        : base(prototype)
    {
        this.PopulateFunctions();
    }

    public InnerObjectInstance(ObjectInstance prototype, int i)
        : base(prototype)
    {
        this["innerValue"] = "my inner value";
    }

    [JSFunction(Name = "innerFunction")]
    public string InnerFunction()
    {
        return "Inner function";
    }
}

OuterObject

using Jurassic;
using Jurassic.Library;

public class OuterObjectConstructor : ClrFunction
{
    public OuterObjectConstructor(ScriptEngine engine)
        : base(engine.Function.InstancePrototype, "OuterObject", new OuterObjectInstance(engine.Object.InstancePrototype))
    {
    }

    [JSConstructorFunction]
    public OuterObjectInstance Construct(int i)
    {
        return new OuterObjectInstance(new InnerObjectInstance(this.InstancePrototype, i), i);
    }
}

public class OuterObjectInstance : ObjectInstance
{

    public OuterObjectInstance(ObjectInstance prototype)
        : base(prototype)
    {
        this.PopulateFunctions();
    }

    public OuterObjectInstance(ObjectInstance prototype, int i)
        : base(prototype)
    {
        this["outerValue"] = "my outer value";
    }

    [JSFunction(Name = "OuterFunction")]
    public string OuterFunction()
    {
        return "Outer function";
    }
}

Setup in Jurassic

js = new ScriptEngine();

js.SetGlobalValue("InnerObject", new InnerObjectConstructor(js));
js.SetGlobalValue("OuterObject", new OuterObjectConstructor(js));

js.Evaluate("var innerObject = new InnerObject(0)");
js.Evaluate("var outerObject = new OuterObject(0)");

js.Evaluate("console.log(innerObject instanceof InnerObject)");
js.Evaluate("console.log(outerObject instanceof OuterObject)");
js.Evaluate("console.log(outerObject instanceof InnerObject)");

js.Evaluate("console.log(outerObject.innerValue + \" - \" + outerObject.outerValue)");

And the output

true
true
false
my inner value - my outer value

After that enormous post, I feel as though I should express my thanks for your excellent library, by the way :)

@paulbartrum
Copy link
Owner

paulbartrum commented Jun 19, 2016

The instanceof operator works by traversing the LHS operand's prototype chain, looking for an object which is equal to the RHS operand's prototype property (this is called InstancePrototype in Jurassic).

Pseudocode for lhs instanceof rhs:

Object.getPrototypeOf(lhs) === rhs.prototype ||
   Object.getPrototypeOf(Object.getPrototypeOf(lhs)) === rhs.prototype ||
   Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(lhs))) === rhs.prototype || ...

So the reason outerObject instanceof InnerObject == false is because InnerObject.prototype never appears in the outerObject prototype chain. Try referencing InnerObject.InstancePrototype in your OuterObjectConstructor.Construct method instead of using new InnerObjectInstance.

@DAVco4
Copy link
Author

DAVco4 commented Jun 19, 2016

Thanks for that Paul.

I managed to get the results I expected, but not quite in the way you suggested. I think this is correct though; when instantiating the OuterObjectConstructor object, I pass in the InnerObjectConstructor and use new OuterObjectInstance(innerObjectConstructor.InstancePrototype) to set the instance prototype object for OuterObjectConstructor. When instantiating an OuterObjectInstance with Construct(), I then use this.InstancePrototype as currently. See the C# code below.

OuterObject

using Jurassic;
using Jurassic.Library;

public class OuterObjectConstructor : ClrFunction
{

    public OuterObjectConstructor(ScriptEngine engine, InnerObjectConstructor innerObjectConstructor)
        : base(engine.Function.InstancePrototype, "OuterObject", new OuterObjectInstance(innerObjectConstructor.InstancePrototype))
    {
    }

    [JSConstructorFunction]
    public OuterObjectInstance Construct(ObjectInstance prot, int i)
    {
        return new OuterObjectInstance(this.InstancePrototype, i);
    }
}

public class OuterObjectInstance : ObjectInstance
{ 

    public OuterObjectInstance(ObjectInstance prototype)
        : base(prototype)
    {
        this.PopulateFunctions();
        this["outerValue"] = "my outer value";
    }

    public OuterObjectInstance(ObjectInstance prototype, int i)
        : base(prototype)
    {   
    }

    [JSFunction(Name = "OuterFunction")]
    public string OuterFunction()
    {
        return "Outer function";
    }
}

InnerObject

using Jurassic;
using Jurassic.Library;

public class InnerObjectConstructor : ClrFunction
{
    public InnerObjectConstructor(ScriptEngine engine)
        : base(engine.Function.InstancePrototype, "InnerObject", new InnerObjectInstance(engine.Object.InstancePrototype))
    {
    }

    [JSConstructorFunction]
    public InnerObjectInstance Construct(int i)
    {
        return new InnerObjectInstance(this.InstancePrototype, i);
    }
}

public class InnerObjectInstance : ObjectInstance
{

    public InnerObjectInstance(ObjectInstance prototype)
        : base(prototype)
    {
        this.PopulateFunctions();
        this["innerValue"] = "my inner value";
    }

    public InnerObjectInstance(ObjectInstance prototype, int i)
        : base(prototype)
    {
    }

    [JSFunction(Name = "innerFunction")]
    public string InnerFunction()
    {
        return "Inner function";
    }
}

Setup code

js = new ScriptEngine();

InnerObjectConstructor innerObjectConstructor = new InnerObjectConstructor(js);
OuterObjectConstructor outerObjectConstructor = new OuterObjectConstructor(js, innerObjectConstructor);

js.SetGlobalValue("InnerObject", innerObjectConstructor);
js.SetGlobalValue("OuterObject", outerObjectConstructor);

js.Evaluate("var innerObject = new InnerObject(0)");
js.Evaluate("var outerObject = new OuterObject(0)");

js.Evaluate("console.log(innerObject instanceof InnerObject)");
js.Evaluate("console.log(outerObject instanceof OuterObject)");
js.Evaluate("console.log(outerObject instanceof InnerObject)");

js.Evaluate("console.log(outerObject.innerValue + \" - \" + outerObject.outerValue)");

And the output

true
true
true
my inner value - my outer value

@paulbartrum
Copy link
Owner

You're right, this is the correct way to do it. Prototypical inheritance is hard! :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants