Что на самом деле делает 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%, что именно происходит; это будет иметь больше смысла после небольшой практики (и нескольких ошибок!).
Однако, если у вас есть какие-то конкретные вопросы по этому поводу, просто оставьте комментарий ниже, и я сделаю все возможное, чтобы ответить.
Спасибо за прочтение!