Что на самом деле делает var , и почему установка myObject = null фактически не удаляет объект? Эти вопросы связаны с фундаментальной концепцией кодирования, относящейся к тому, какой язык вы предпочитаете: AS3, JavaScript или C #, и могут быть поняты с помощью нескольких общих предметов из шкафа канцелярских товаров.
Что такое переменная?
Давайте начнем с основ. Предположим, вы хотите сохранить возраст вашего друга Билла:
|
1
|
var ageOfBill:Number = 24;
|
(Я собираюсь использовать AS3 для этого примера, но основные понятия одинаковы в JavaScript и C #.
В JavaScript синтаксис практически одинаков, но мы не указываем, что age это число:
|
1
|
var ageOfBill = 24;
|
В C # мы не используем ключевое слово var , но мы указываем тип переменной:
|
1
|
short ageOfBill = 24;
|
Надеюсь, не настолько, чтобы запутаться.)
Так что здесь происходит? Думайте об этом так:
-
var(илиshortв C #) означает «получить новую заметку Post-it». -
ageOfBillозначает «написатьageOfBillсверху, пером». -
= 24означает «напиши24на заметке карандашом».

Что если позже мы поймем, что Билл на самом деле моложе, чем мы думали?
|
1
2
3
|
var ageOfBill = 24;
//…later…
ageOfBill = 20;
|
Это просто означает, что мы нашли нашу заметку ageOfBill , ageOfBill 24 и вместо нее написали 20 .

Мы могли бы написать var снова:
|
1
2
3
|
var ageOfBill:Number = 24;
//…later…
var ageOfBill:Number = 20;
|
… но это не очень хороший код, потому что var говорит: «получить свежую заметку». Если вы сделаете это, компилятор, как правило, выяснит, что вы имеете в виду — то есть, что вы хотите изменить то, что написано в существующей ageOfBill Post-it, вместо того, чтобы фактически получить новую — но он, вероятно, будет жаловаться.
|
1
|
Warning: #3596: Duplicate variable definition.
|
Это зависит от языка и вашей среды программирования.
Так можем ли мы попросить компилятор получить свежую заметку Post-it и написать на ней метку ручкой, не написав на ней что-нибудь карандашом? Возможно, мы могли бы сделать это для друга Билла Марти, чей возраст мы не знаем:
|
1
|
var ageOfMarty:Number;
|
На самом деле (по крайней мере в AS3) это даст свежую заметку Post-it, напишите ageOfMarty сверху, пером … и затем напишите начальное значение по умолчанию 0 там карандашом:

Таким образом, другими словами, мы не можем иметь такую заметку Post-it, если она не принимает какое-то значение.
Хорошо, а что если мы хотим сохранить возраст лучшего друга Билла Теда, который, как мы знаем, ровесник?
|
1
|
var ageOfTed:Number = ageOfBill;
|
Здесь происходит то, что компьютер смотрит на ageOfBill Post-it, затем копирует число, написанное на нем карандашом, в новый Post-it, на котором он пишет ageOfTed поверх ручки в ручке.

Это всего лишь копия; если мы затем изменим значение ageOfBill это не повлияет на ageOfTed :
|
1
|
ageOfBill = 21;
|

Так! Это все довольно просто, и, возможно, даже интуитивно понятно. Теперь поговорим о первой общей болевой точке: массивах.
Что такое массив?
Думайте о массиве как связыватель кольца.

(Я собирался сказать ролодекс …

… но я понял, что никогда не видел ни одного в реальной жизни.)
Каждый лист в переплете похож на одну из заметок Post-it, за исключением того, что в верхней части не написано перо. Вместо этого мы ссылаемся на каждый лист по имени подшивки и номеру страницы листа.
Давайте предположим, что у нас есть массив всех наших друзей в произвольном порядке. Кто на первой странице (страница № 0)?
|
1
|
trace(friends[0]);
|
( trace() просто записывает строку в выходные данные отладки; в JavaScript вы можете использовать console.log() а в C # вы можете использовать Console.WriteLine() для той же цели.)

Это Билл!
Итак, что же делает следующая строка?
|
1
|
var firstFriend:String = friends[0];
|
Он получает новую заметку Post-it (из-за ключевого слова var ), firstFriend записывает в firstFriend , а затем копирует все, что написано на первой странице подшивки, на эту заметку карандашом.

(Помните, String просто означает кусок текста.)
Мы можем переписать то, что написано на любой странице подшивки, как и в заметках Post-it:
|
1
|
friends[0] = «Kyle»;
|

… и, конечно, это не влияет на firstFriend пост-пост.
Связыватель является удачной аналогией, потому что — как и в случае с массивом — вы убираете страницы, добавляете новые и переставляете их. Но помните, что отдельные страницы действуют так же, как заметки Post-it, за исключением того, что у них нет собственных меток пера, только номера страниц.
Надеюсь, все еще довольно просто. Итак, вот интересный вопрос: что происходит, когда вы делаете следующее?
|
1
|
var listOfNames:Array = friends;
|
Э-э …
Вы не можете написать это на пост-это
Я немного обманул здесь, потому что я много говорил о массивах, даже не объясняя, как мы их создали. Итак, давайте займемся этим сейчас.
Предположим, вы вводите:
|
1
|
var friends:Array = [«Bill», «Marty», «Ted»];
|
…Что происходит?
Ну, как обычно, var friends означает, что мы получаем свежую заметку Post-it и пишем friends сверху, пером:

Но что мы пишем на нем карандашом?
Это вопрос с подвохом: мы ничего не пишем.
Видите, Array означает «получить новое связующее кольцо». И ["Bill", "Marty", "Ted"] означает «положить три страницы в переплет с этими именами»:

Вы не можете видеть страницы «Марти» и «Тед», но они полностью там.
А потом? Это просто! Прикрепляем пост-заметку friends к обложке переплета:

Теперь, когда мы пишем:
|
1
|
trace(friends[0]);
|
… мы знаем, что нам нужно найти friends надписью Post-it, а затем посмотреть на то, что написано на первой странице (страница № 0) переплетчика, к которому он прикреплен.
На самом деле существует очень мало типов переменных, в которых значение записывается в заметку Post-it карандашом. В AS3 единственными такими типами (называемыми «примитивами») являются:
-
Number -
String -
int -
uint -
Boolean
Для всего остального — Object , MovieClip , XML и т. Д. — мы прикрепляем заметку Post-it к самому элементу.

(Детали немного отличаются в JavaScript и C #, но в целом применима та же идея.)
Итак, вернемся к нашему предыдущему вопросу. Когда мы печатаем:
|
1
|
var listOfNames:Array = friends;
|
…что происходит?
Опять же, мы знаем, что var listOfNames означает «получить новую заметку Post-it и написать listOfNames поверх пера». И теперь мы знаем, что Array означает, что мы будем прикреплять заметку Post-it к чему-то (переплет), а не писать что-то на Post-it карандашом.
Ранее, когда мы делали нечто подобное, мы копировали содержимое одной заметки Post-it на другую. Так вот, должны ли мы получить новый новый переплет и скопировать в него все страницы из подшивки friends ?
Вообще-то, нет! Все, что мы делаем, — это listOfNames новую listOfNames Post-it к тому же переплету, что и заметка friends Post-it.

Теперь friends и listOfNames ссылаются на один и тот же массив . Так что если мы напишем:
|
1
|
listOfNames[0] = «Emmett»;
|
… тогда friends[0] также будут Emmett , потому что listOfNames[0] и friends[0] ссылаются на одну и ту же страницу в том же переплете! И поскольку эта страница содержит только String (помните, что это «примитивный» тип), то мы просто стерли все, что было написано на этой странице ранее, и вместо этого написали Emmett .
Так что же означает ноль?
При таком взгляде null довольно легко понять. Это утверждение:
|
1
|
friends = null;
|
… просто означает «удалить заметку friends из того, к чему она сейчас привязана».

friends Пост-он все еще существует , он просто ни к чему не прилип. Так что если вы наберете:
|
1
|
trace(friends[0]);
|
или
|
1
|
friends[0] = «Henry»;
|
… тогда вы получите сообщение об ошибке, потому что вы пытаетесь сослаться на первую страницу переплета, на которой friends постят — он застрял — но он ни к чему не привязан!
Итак, чтобы быть ясным, установка friends = null не влияет на связыватель вообще. Вы все еще можете получить к нему доступ через listOfNames . И вы даже можете напечатать:
|
1
|
friends = listOfNames;
|
… чтобы вернуться к старой ситуации:

Вывоз мусора
Как я уже сказал, установка friends = null не влияет напрямую на связыватель, но может иметь косвенный эффект.
Видите ли, если нет заметок Post-it, прикрепленных к переплету вообще, то никто не сможет снова получить доступ к переплету. Это будет просто лежать, совершенно недоступно. Но то, что все эти связующие (и другие объекты) лежат, совершенно заброшенные, — это настоящая трата пространства — они загромождают память компьютера.
Вот где приходит сборщик мусора . Это инструмент, который периодически проверяет наличие любых «потерянных» объектов и выбрасывает их в мусорное ведро — и как только они исчезают, они исчезают навсегда; если массив является сборщиком мусора, то и все его страницы тоже.
Для большинства практических целей это не влияет на вас вообще; объекты будут собирать мусор только в том случае, если они потеряны и не могут быть найдены вашим кодом. Если у вас их много, то вы можете время от времени замечать небольшую задержку, когда сборщик мусора выполняет свою работу (для активного сбора мусора требуется немного времени). Преимущество состоит в том, что это освобождает больше места (памяти) для вашего приложения.
(Если вы хотите узнать больше об этой теме, прочитайте сообщения Дэниела Сидиона о сборке мусора и объединении объектов .)
Массивы объектов
Ладно, есть еще одна важная концепция, и она самая сложная.
Рассмотрим этот фрагмент:
|
1
2
3
4
|
var firstFriendDetails:Array = [«Bill», 20];
var secondFriendDetails:Array = [«Marty», 16];
var thirdFriendDetails:Array = [«Ted», 20];
var allFriends:Array = [firstFriendDetails, secondFriendDetails, thirdFriendDetails];
|
Как, черт возьми, это работает?
Давайте начнем с того, что мы знаем. Первые три строки просты; для каждого мы:
- Получить свежее связующее.
- Вставьте страницу с именем друга, написанным на нем карандашом.
- Для nsert другая страница с возрастом друга, написанным на нем карандашом.
- Получите свежий Post-it и напишите соответствующий ярлык на ручке.
- Прикрепите Post-it к переплету.

Что касается этой строки:
|
1
|
var allFriends:Array = [firstFriendDetails, secondFriendDetails, thirdFriendDetails];
|
… нам понадобится строка и лента.
Мы можем думать об этой одной строке как об эквивалентном фрагменте:
|
1
2
3
4
|
var allFriends:Array = [];
allFriends[0] = firstFriendDetails;
allFriends[1] = secondFriendDetails;
allFriends[2] = thirdFriendDetails;
|
Первая строка проста: получите новую подшивку и новую заметку Post-it, напишите allFriends на allFriends Post-it и приклейте ее к переплету.

Что касается второй строки:
|
1
|
allFriends[0] = firstFriendDetails;
|
Помните, что я сказал, что каждая страница в переплете похожа на заметку Post-it, за исключением того, что ничего не написано пером. Если бы первой страницей была Post-it, то мы просто прикрепили бы ее к передней части переплета firstFriendDetails , верно?

… но это не может быть как на передней части этого переплета, так и внутри другого переплета. Итак, вместо этого мы используем строку:

То же самое для двух других:

Поэтому, когда мы хотим узнать, на что allFriends[2] , мы просто открываем allFriends на этой странице и следуем за строкой, что, конечно, приводит к thirdFriendDetails .
Аналогично, для allFriends[1][0] мы сначала allFriends[1] , к какому связующему allFriends[1] , а затем посмотрим на первую страницу этого связывателя … поэтому allFriends[1][0] — Marty !
Loops
Теперь соберите всю эту информацию и помните об этом при чтении этого фрагмента:
|
01
02
03
04
05
06
07
08
09
10
|
var friends:Array = [«Bill», «Marty», «Ted», «Emmett», «Henry»];
var currentIndex:int = 0;
var currentFriend:String;
while (currentIndex < 5) {
currentFriend = friends[currentIndex];
trace(currentFriend);
}
var lastFriend:String = currentFriend;
trace(lastFriend);
|
Что если мы currentFriend значение currentFriend внутри цикла?
|
01
02
03
04
05
06
07
08
09
10
|
var friends:Array = [«Bill», «Marty», «Ted», «Emmett», «Henry»];
var currentIndex:int = 0;
var currentFriend:String;
while (currentIndex < 5) {
currentFriend = friends[currentIndex];
currentFriend = «Herbert»;
trace(currentFriend);
}
trace(friends[0]);
|
Что, если массив содержит непримитивные объекты (мувиклипы, изображения, массивы, трехмерные объекты и т. Д.)?
|
01
02
03
04
05
06
07
08
09
10
|
var friends:Array = [firstFriend, secondFriend, thirdFriend, fourthFriend, fifthFriend];
var currentIndex:int = 0;
var currentFriend:MovieClip;
while (currentIndex < 5) {
currentFriend = friends[currentIndex];
currentFriend = sixthFriend;
}
//What is the value of friends[0] now?
|
Наконец, что если массив содержит другие массивы, которые сами содержат примитивы?
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
var firstFriendDetails:Array = [«Bill», 20];
var secondFriendDetails:Array = [«Marty», 16];
var thirdFriendDetails:Array = [«Ted», 20];
var fourthFriendDetails:Array = [«Emmett», 50];
var fifthFriendDetails:Array = [«Henry», 36];
var friends:Array = [firstFriendDetails, secondFriendDetails, thirdFriendDetails, fourthFriendDetails, fifthFriendDetails];
var currentIndex:int = 0;
var currentFriend:Array;
while (currentIndex < 5) {
currentFriend = friends[currentIndex];
currentFriend[0] = «John»;
currentFriend = [«Herbert», 29];
}
|
Как вы думаете, что ценность friends[3][0] будет после этого?
Другие биты и кусочки
Вот несколько других важных замечаний, которые вы должны знать:
Объекты
В AS3 и JavaScript объекты похожи на массивы, за исключением того, что каждая страница упоминается по метке, а не по номеру страницы. Таким образом, вы можете ввести:
|
1
2
3
4
|
var detailsOfBill:Object = {};
detailsOfBill.title = «Esquire»;
detailsOfBill.bandName = «Wyld Stallyns»;
detailsOfBill.allNames = [«Bill», «S.», «Preston»];
|
… и это похоже на получение нового переплета, наклеивание detailsOfBill посте на лицевой стороне и заполнение ее тремя страницами. На первой странице title надписи написан пером, а карандашом написано слово Esquire ; вторая страница имеет лейбл bandName в перо и Wyld Stallyns в карандаш. На третьей странице есть надпись allNames но на ней ничего не написано карандашом; вместо этого строка прикрепляет ее к другому обычному переплету, страницы которого не помечены: первая страница говорит о Bill , вторая говорит о S. , а третья говорит о Preston , все карандашом.
(Чтобы сделать вещи еще более запутанными, массивы технически представляют собой особую форму объекта. И если вы считаете, что это плохо, функции также можно рассматривать как тип объекта! Но это тема для будущей статьи …)
Подробнее о сборке мусора
Я сказал, что объекты собирают мусор, если к ним не прикреплены записи Post-it, но это упрощение. Если одна страница связывателя указывает на объект через строку (т. myArray[0] = myObject Если myArray[0] = myObject или аналогичный), то этот объект не будет подвергаться сборке мусора. То же самое касается, если страница связывателя указывает на другой связыватель (массив), или если страница связывателя объекта указывает на другой связыватель объекта … и так далее. Это даже применимо, если единственный способ получить доступ к объекту — через Post-it, прикрепленный к подшивке, у которой одна страница привязана к другой подшивке, столько слоев, сколько вам нужно.
По сути, сборщик мусора собирает элемент, только если он не может быть достигнут через любую другую переменную.
Это объясняет, почему объекты на экране обычно не могут быть собраны. В AS3, если MovieClip или другой тип DisplayObject присутствует в списке отображения, он автоматически добавляется к тому, что по сути является скрытым массивом экранных объектов (доступ к которому можно получить через getChildAt () ). Подобные структуры существуют в JavaScript и C #. Таким образом, если на экране появляется изображение и вы удаляете все ссылки на него из переменных, массивов и объектов, оно все равно не будет собирать мусор, пока вы не удалите его из списка отображения.
Любые вопросы?
Я надеюсь, что это помогает прояснить ситуацию. Это, безусловно, запутанная концепция, когда вы впервые сталкиваетесь с ней: некоторые переменные на самом деле содержат значение , а другие просто содержат ссылку на объект. Не беспокойтесь, если вы не уверены на 100%, что именно происходит; это будет иметь больше смысла после небольшой практики (и нескольких ошибок!).
Однако, если у вас есть какие-то конкретные вопросы по этому поводу, просто оставьте комментарий ниже, и я сделаю все возможное, чтобы ответить.
Спасибо за прочтение!