2009-10-29 4 views
0

Я не очень хорош с чем-либо, кроме базового javascript, поэтому, пожалуйста, простите простой вопрос.Javascript - получить переменную изнутри локальной области функции

Я пользуюсь библиотекой jsDraw2D. Эта библиотека имеет графический объект, который выглядит примерно следующим:

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

Вы использовать его как это:

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

Я хотел бы добавить функцию jsGraphics, так что я могу назвать

gr.getCanvasElement() 

Есть ли способ сделать это без редактирования самой библиотеки?

Я попытался

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

, но это не похоже на работу. У меня есть интуитивное чувство, что его что-то с этим новым ключевым словом, но если бы вы могли объяснить, почему именно это не так полезно.

+0

Вы передаете 'document.getElementById ('canvas')' в 'jsGraphics' именно так? Если вы, вы можете просто вызвать 'document.getElementById ('canvas')', чтобы получить то же самое, что и 'canvasDiv'. –

+0

Хорошо, что я хочу на самом деле не canvasDiv, а ширину/высоту, которая будет использоваться в некоторых упрощающих методах фасада, которые я планирую добавить. Однако на самой странице будет много объектов jsGraphics, и мне придется работать с каждым по очереди. –

ответ

3

Вы не можете просто получить элемент canvasDiv обязательно, потому что, если он никогда не назначается объекту в конструкторе с использованием этого ключевого слова, ссылка на этот объект существует в замыкании, создаваемом самой конструкторской функцией.

Однако вы можете обернуть конструктор в новом конструкторе, а затем установить прототип равны:

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

myJsGraphics.prototype = jsGraphics.prototype; 

Теперь вы должны быть в состоянии получить доступ к элементу, используя новый конструктор:

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

вся закрывающая вещь немного странна, если вы не привыкли к ней, но суть в том, что функции, определенные в определенной области, но доступные в других местах, могут ссылаться на переменные в области, в которой они были определены в любое время. Самый простой пример, когда у вас есть функция, которая возвращает функцию:

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

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

Это ссылка на «Пуск» переменная «покрыла», когда функция возвращается из функции makeIncrementer. Доступ к нему напрямую невозможен. То же самое происходит в конструкторе объекта, где локальные переменные «закрыты» в функции-члены объекта и действуют как частные переменные. Если только метод или переменная не ссылаются на частный член, определенный в конструкторе, вы просто не можете получить к нему доступ.

+3

jsGraphics.call (this); должен быть jsGraphics.call (это, canvasDivElement); – Chii

+0

Спасибо, что указали это, я внес изменения. –

4

Нет, это не использует обычное наследование на основе прототипа на основе JavaScript, оно добавляет отдельную функцию drawLine к каждому экземпляру jsGraphics, каждый с закрытием вокруг собственной переменной canvasDiv.

Как только function jsGraphics() { закрыт }, нет никакого другого способа доступа к переменной canvasDiv вообще, если только одна из функций внутри не обеспечивает доступ к ней. Это часто делается намеренно, чтобы сделать частные переменные, явно чтобы остановить вас на canvasDiv.

+0

Действительно? Я думал, что все возможно в javascript ... Технически я хочу рассматривать его как защищенную переменную, используемую внутри некоторых методов, которые я буду добавлять в будущем, если это облегчит ее работу. –

+1

Да, я предпочитаю дружественный к отладке подход Python, где вы бы установили его как 'this._canvasDiv', и горе тому, кто использует переменную, начиная с подчеркивания, если они не являются функцией-членом (или они действительно знают, что они «делаю»). Однако для этого потребуется изменить функцию конструктора. – bobince

2

Эта техника «частного состояния» стала все более идиоматичной в последние несколько лет. Лично я обнаружил, что это странно ограничивает попытку быстро отладить что-то из консоли или переопределить поведение в сторонней библиотеке.Это один из немногих раз, когда я думаю: «Черт возьми, почему я не могу это сделать с языком». Я использовал this bug в Firefox 2 для хорошего эффекта, когда мне действительно нужно быстро отлаживать «личную переменную».

Мне было бы интересно узнать, когда другие используют эту идиому или когда они ее избегают. (@bobince, я смотрю на тебя).

В любом случае @bobince в значительной степени ответил на ваш вопрос (коротко: Нет, вы не можете получить доступ к переменной canvasDiv из области, в которой вы находитесь).

Однако есть одна вещь, которую вы можете сделать, это компромисс между взломом или редактированием сторонней библиотеки (я всегда ищу для взлома;): вы можете увеличить объект, чтобы сохранить ссылку, которую вы знаете, нужно позже.

Hack 1: если вы контролируете объект Instantiations себя:

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 

Если библиотеку поддерживает какую-либо концепцию сродни деструктор (обожженный на выгрузке страницы или что-то), убедитесь, что обнулять или удалить свойство там, чтобы избежать утечек памяти в IE:

delete gr._canvasDiv; 

иЛИ Hack 2: перезапись конструктор сразу после включения библиотеки:

// 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)) 

Затем войдите в элемент (теперь общедоступный) как gr._canvasDiv. То же самое относится к удалению его при разгрузке страницы.