开发者

Javascript - get a variable from inside the local scope of a function

开发者 https://www.devze.com 2022-12-10 13:45 出处:网络
I am not great with anything beyond basic javascript so please forgive the simple question. I am using the js开发者_C百科Draw2D library.This library has a graphics object that looks something like th

I am not great with anything beyond basic javascript so please forgive the simple question.

I am using the js开发者_C百科Draw2D library. This library has a graphics object that looks something like the following:

function jsGraphics(canvasDivElement) {
  var canvasDiv;
  this.drawLine = drawLine;
  function drawLine(point1, point2) {
    // do things with canvasDiv
  }
}

You use it like this:

var gr = new jsGraphics(document.getElementById('canvas'))
gr.drawLine(new jsPoint(0,0), new jsPoint(10,10))

I would like to add a function to jsGraphics so that I can call

gr.getCanvasElement()

Is there a way to do this without editing the library itself?

I have tried

jsGraphics.prototype.getCanvasElement = function() { return canvasDiv }

but this doesn't seem to work. I have an intuitive feeling that its something with that new keyword but if you could explain why exactly it doesn't that would be helpful too.


Nope, this isn't using the normal JavaScript prototype-based inheritance, it's adding a separate drawLine function to every instance of jsGraphics, each with a closure around its own canvasDiv variable.

Once function jsGraphics() { is closed } there is no further way to access the canvasDiv variable at all, unless one of the functions inside provides access to it. This is often done deliberately to make private variables, explicitly to stop you getting at canvasDiv.


You can't just get to the canvasDiv element necessarily, because if it is never assigned to the object in the constructor using the this keyword, the reference to that object exists in a closure created by the constructor function itself.

You can however wrap the constructor in a new constructor and then set the prototypes equal:

function myJsGraphics(canvasDivElement) {
  this.canvasDiv = canvasDivElement;
  jsGraphics.call(this, cavasDivElement);
}

myJsGraphics.prototype = jsGraphics.prototype;

Now you should be able to access the element using your new constructor:

var obj = new myJsGraphics(document.getElementById('blah-elem'));
elem = obj.canvasDiv;

The whole closure thing is a little weird if you're not used to it, but the gist is that functions defined in a certain scope but available elsewhere can refer to variables in the scope in which they were defined at all times. The easiest example is when you have a function that returns a function:

function makeIncrementer(start) {
  return function () { return start++; };
}

var inc = makeIncrementer(0);
var inc2 = makeIncrementer(0);
inc(); // => 0
inc(); // => 1
inc(); // => 2
inc2(); // => 0

That reference to the "start" variable is "closed over" when the function is returned from the makeIncrementer function. It cannot be accessed directly. The same thing happens in an object's constructor, where local variables are "closed" into the member functions of the object, and they act as private variables. Unless there was a method or variable reference to a private member defined in the constructor, you just can't get access to it.


This "private state" technique has become more and more idiomatic in the last few years. Personally I've found it oddly limiting when trying to quickly debug something from the console or override behavior in a 3rd party library. It's one of the few times I think "Damn it, why can't I do this with the language". I've exploited this bug in Firefox 2 to good effect when I've really needed to debug a "private variable" quickly.

I'd be curious to know when others use this idiom or when they avoid it. (@bobince I'm looking at you).

Anyway @bobince has pretty much answered your question (nutshell: No, you can't access the canvasDiv variable from the scope you are in).

However, there is one thing you can do that is a tradeoff between a hack or editing the 3rd-party library (I always go for the hack ;): you can augment the object to hold a reference you know you will need later.

Hack 1: if you control the object instantiations yourself:

var canvas = document.getElementById('canvas');
var gr = new jsGraphics(canvas);
gr._canvasDiv = canvas; // Augment because we'll need this later

// Sometime later...

gr._canvasDiv; // do something with the element

If the library supports some concept akin to a destructor (fired on unload of the page or something), be sure to null out or delete your property there too, to avoid memory leaks in IE:

delete gr._canvasDiv;

OR Hack 2: Overwrite the constructor just after including the library:

// run after including the library, and before
// any code that instantiates jsGraphics objects
jsGraphics = (function(fn) {
    return function(canvas) {
        this._canvasDiv = canvas;
        return fn.apply(this, arguments)
    }
}(jsGraphics))

Then access the element (now public) as gr._canvasDiv. Same note about deleting it on page unload applies.

0

精彩评论

暂无评论...
验证码 换一张
取 消