Jak rozbroić zmienną JavaScript?

591

Mam zmienną globalną w JavaScript (właściwie windowwłaściwość, ale nie sądzę, żeby to miało znaczenie), która została już zapełniona przez poprzedni skrypt, ale nie chcę innego skryptu, który uruchomi się później, aby zobaczyć jego wartość lub że był nawet zdefiniowane.

Włożyłem some_var = undefinedi działa w celu testowania, typeof some_var == "undefined"ale naprawdę nie sądzę, że jest to właściwy sposób, aby to zrobić.

Co myślisz?

Guss
źródło

Odpowiedzi:

453

deleteOperator usuwa własności z obiektu. Nie można usunąć zmiennej. Odpowiedź na pytanie zależy więc od sposobu zdefiniowania zmiennej globalnej lub właściwości.

(1) Jeśli jest utworzony var, nie można go usunąć.

Na przykład:

var g_a = 1; //create with var, g_a is a variable 
delete g_a; //return false
console.log(g_a); //g_a is still 1

(2) Jeśli zostanie utworzony bez var, można go usunąć.

g_b = 1; //create without var, g_b is a property 
delete g_b; //return true
console.log(g_b); //error, g_b is not defined

Wyjaśnienie techniczne

1. Korzystanie var

W tym przypadku odwołanie g_ajest tworzone w tym, co specyfikacja ECMAScript nazywa „ VariableEnvironment ”, które jest dołączone do bieżącego zakresu - może to być kontekst wykonania funkcji w przypadku użycia varwewnątrz funkcji (choć może być nieco bardziej skomplikowany jeśli weźmiesz pod uwagę let) lub w przypadku kodu „globalnego” środowisko zmienne jest dołączane do obiektu globalnego (często window).

Odwołania w środowisku VariableEnvironment zwykle nie są usuwalne - proces szczegółowo opisany w ECMAScript 10.5 wyjaśnia to szczegółowo, ale wystarczy powiedzieć, że jeśli twój kod nie jest wykonywany w evalkontekście (z którego korzysta większość konsol programistycznych opartych na przeglądarce), to zmienne zadeklarowane za varpomocą nie mogą zostać usunięte.

2. Bez użycia var

Gdy próbuje przypisać wartość do nazwy bez użycia varsłowa kluczowego, JavaScript próbuje zlokalizować nazwie odniesienie w nazywa ECMAScript Spec „ LexicalEnvironment ”, a główną różnicą jest to, że LexicalEvironment s są zagnieżdżone - czyli LexicalEnvironment ma rodzica ( co specyfikacja ECMAScript nazywa „referencją środowiska zewnętrznego”), a gdy Javscript nie może zlokalizować referencji w środowisku LexicalEenvironment , sprawdza je w macierzystym środowisku LexicalEnvironment (jak szczegółowo opisano w 10.3.1 i 10.2.2.1 ). LexicalEnvironment najwyższego poziomu to „ środowisko globalne”, a to jest powiązane z obiektem globalnym, ponieważ jego odwołania są właściwościami obiektu globalnego. Jeśli więc spróbujesz uzyskać dostęp do nazwy, która nie została zadeklarowana przy użyciu varsłowa kluczowego w bieżącym zakresie lub jakimkolwiek zasięgu zewnętrznym, JavaScript ostatecznie pobierze właściwość z windowobiektu, aby służyć jako tego odniesienia. jak dowiedzieliśmy się wcześniej, na właściwości obiektów mogą zostać usunięte.

Notatki

  1. Ważne jest, aby pamiętać, że vardeklaracje są „podnoszone” - tzn. Zawsze uważa się, że miały miejsce na początku zakresu, w którym się znajdują - choć nie inicjalizacja wartości, którą można wykonać w varinstrukcji - pozostawia się tam, gdzie jest . Zatem w poniższym kodzie aznajduje się odwołanie z VariableEnvironment, a nie windowwłaściwość, a jego wartość będzie 10na końcu kodu:

    function test() { a = 5; var a = 10; }

  2. Powyższa dyskusja dotyczy sytuacji, gdy „tryb ścisły” nie jest włączony. Reguły wyszukiwania są nieco inne, gdy używa się „trybu ścisłego”, a odniesienia leksykalne, które zostałyby rozstrzygnięte we właściwościach okna bez „trybu ścisłego”, spowodują błędy „niezadeklarowanej zmiennej” w „trybie ścisłym”. Naprawdę nie rozumiałem, gdzie to jest określone, ale jak działają przeglądarki.

Dayong
źródło
8
To, co powiedziałeś, jest powszechnym błędnym przekonaniem, ale w rzeczywistości jest niepoprawne - w Javascript nie ma „zmiennych globalnych”. Zmienne zdefiniowane bez wyraźnego zakresu (takie jak używanie varpoza funkcją) są właściwościami „obiektu globalnego”, jakim jest w przeglądarkach internetowych window. Tak więc - var a = 1; delete window.a; console.log(a);z powodzeniem usunie zmienną i spowoduje błąd referencyjny w ostatnim wierszu.
Guss
7
@Guss, Twój kod var a = 1; delete window.a; console.log(a);wyświetla 1.
Dayong
5
Używam Google Chrome v36. Testowałem na innych przeglądarkach. Wygląda na to, że nie jest spójny w różnych przeglądarkach. Chrome i Opera wyświetlały 1, podczas gdy Firefox, Safari i IE 11 na moim komputerze dały błąd.
Dayong
3
Ok, mój błąd. Zobacz ecma-international.org/ecma-262/5.1/#sec-10.5 (podpunkty 2 i 8.c.ii): Podczas uruchamiania mojego testu w konsoli programisty jest ogólnie uważany za „kontekst ewaluacyjny” (choć może nie w Chrome), więc zgłosi błąd. Ten sam kod w kontekście globalnym rzeczywistego dokumentu będzie 1poprawnie wyświetlany we wszystkich przeglądarkach. Przykłady kodu działające w rzeczywistych dokumentach są poprawne. Wybrałem twoją odpowiedź jako poprawną, ale byłbym wdzięczny, jeśli możesz ją edytować, aby zawierała wyjaśnienie window.a = 1; delete window.a;i ewentualnie mechanizm. Mogę to również zrobić, jeśli nie masz nic przeciwko.
Guss
2
@KlaiderKlai tak. Zmienne o zasięgu funkcji są tworzone i niszczone za każdym razem, gdy funkcja jest wykonywana. Prawdopodobnie zamknięcie jest wyjątkiem.
Dayong
278

Odpowiedź @ scunlife zadziała, ale technicznie powinna być

delete window.some_var; 

delete ma być opcją, gdy cel nie jest właściwością obiektu. na przykład,

(function() {
   var foo = 123;
   delete foo; // wont do anything, foo is still 123
   var bar = { foo: 123 };
   delete bar.foo; // foo is gone
}());

Ale ponieważ zmienne globalne są faktycznie elementami obiektu okna, działa.

Gdy zaangażowane są łańcuchy prototypów, użycie delete staje się bardziej złożone, ponieważ usuwa tylko właściwość z obiektu docelowego, a nie prototypu. na przykład,

function Foo() {}
Foo.prototype = { bar: 123 };
var foo = new Foo();
// foo.bar is 123
foo.bar = 456;
// foo.bar is now 456
delete foo.bar;
// foo.bar is 123 again.

Więc uważaj.

EDYCJA: Moja odpowiedź jest nieco niedokładna (patrz „Błędne przekonania” na końcu). Link wyjaśnia wszystkie krwawe szczegóły, ale podsumowanie jest takie, że mogą istnieć duże różnice między przeglądarkami i w zależności od obiektu, z którego usuwasz. delete object.someProppowinny być ogólnie bezpieczne, o ile object !== window. Nadal nie używałbym tego do usuwania zadeklarowanych zmiennych, varchociaż można to zrobić w odpowiednich okolicznościach.

noah
źródło
14
dzięki @jedierikb za link do tego interesującego artykułu. dokładniej w tej części < perfectionkills.com/understanding-delete/#misconceptions > tego artykułu, w której autor stwierdza, że ​​stwierdzenie Noego „usuń ma być zakazem” jest raczej niedokładne, a także doskonałe wyjaśnienie, dlaczego jest niedokładne . (Nie strzelaj do posłańca!)
Rob Wells,
2
W odniesieniu do ostatniego zdania poprawionej odpowiedzi jedyną okolicznością, w której można usunąć zmienne zadeklarowane za pomocą, varjest moment zadeklarowania zmiennej eval.
Stephen Booher
1
W takim przypadku instrukcja usuwania w ogóle nic nie robi. Co tu się dzieje?
Anderson Green,
@ AndersonGreen - odszyfrowane zmienne globalne są tworzone z flagą DontDelete, więc nie można ich usunąć. Ten kod działa dokładnie zgodnie z oczekiwaniami.
RobG
35

Jeśli domyślnie deklarujesz zmienną bez var, właściwym sposobem byłoby użycie delete foo.

Jednak po jego usunięciu próba użycia tego w operacji, takiej jak dodawanie, ReferenceErrorspowoduje wyrzucenie, ponieważ nie można dodać ciągu do niezadeklarowanego, niezdefiniowanego identyfikatora. Przykład:

x = 5;
delete x
alert('foo' + x )
// ReferenceError: x is not defined

W niektórych sytuacjach bezpieczniejsze może być przypisanie go do wartości false, null lub undefined, aby został zadeklarowany i nie generował tego typu błędu.

foo = false

Należy zauważyć, że w ECMAScript null, false, undefined, 0, NaN, lub ''by wszystko aby ocenić false. Po prostu upewnij się, że nie używasz !==operatora, ale zamiast tego !=podczas sprawdzania typu dla booleanów i nie chcesz sprawdzania tożsamości (tak nullby == falsei false == undefined).

Należy również pamiętać, że deletenie „usuwa” odniesień, a jedynie właściwości bezpośrednio na obiekcie, np .:

bah = {}, foo = {}; bah.ref = foo;

delete bah.ref;
alert( [bah.ref, foo ] )
// ,[object Object] (it deleted the property but not the reference to the other object)

Jeśli zadeklarowałeś zmienną var, nie możesz jej usunąć:

(function() {
    var x = 5;
    alert(delete x)
    // false
})();

W nosorożcu:

js> var x
js> delete x
false

Nie można również usunąć niektórych predefiniowanych właściwości, takich jak Math.PI:

js> delete Math.PI
false

Istnieją pewne dziwne wyjątki, deletejak w każdym języku, jeśli dbasz wystarczająco, powinieneś przeczytać:

meder omuraliev
źródło
Dzięki za pełną odpowiedź ze wszystkimi szczegółami. Zaznaczyłem to, ale zaakceptowałem odpowiedź Noego, ponieważ uważam, że w przypadku prostego pytania zwięzłość jest ważniejsza niż ukończenie. Znowu - dzięki za wspaniałą pracę, którą wykonałeś nad odpowiedzią.
Guss,
30
some_var = null;

//or remove it..
delete some_var;
scunliffe
źródło
11
To nie działa, jeśli zakres tego kodu jest funkcją. Zobacz odpowiedź @ noah na prawidłowe rozwiązanie.
Roatin Marth
1
Dzięki za odpowiedź, ale zaakceptowałem odpowiedź Noego, ponieważ lepiej wyjaśnia ona pułapki delete .
Guss,
3
nie martw się ... Dałem „szybką i brudną” prostą odpowiedź - @noah dodał wszystkie szczegóły dla „innych” przypadków, więc też zasługuje na uznanie. ;-)
scunliffe
7
To nie jest poprawne. deletedziała tylko dla nieruchomości. Ustawienie nulltej zmiennej wciąż istnieje.
Derek 朕 會 功夫
1
Ta odpowiedź jest wystarczająca dla najbardziej prawdopodobnego przypadku, w którym sprawdzasz za pomocą „if (some_var) {..}”
BearCode
16

TLDR: proste zdefiniowane zmienne (bez var,let , const) może być usunięty z delete. Jeśli używasz var, let, const- nie mogli być usunięte ani z deleteani Reflect.deleteProperty.

Chrome 55:

simpleVar = "1";
"1"
delete simpleVar;
true
simpleVar;
VM439:1 Uncaught ReferenceError: simpleVar is not defined
    at <anonymous>:1:1
(anonymous) @ VM439:1
var varVar = "1";
undefined
delete varVar;
false
varVar;
"1"
let letVar = "1";
undefined
delete letVar;
true
letVar;
"1"
const constVar="1";
undefined
delete constVar;
true
constVar;
"1"
Reflect.deleteProperty (window, "constVar");
true
constVar;
"1"
Reflect.deleteProperty (window, "varVar");
false
varVar;
"1"
Reflect.deleteProperty (window, "letVar");
true
letVar;
"1"

FF Nightly 53.0a1 pokazuje to samo zachowanie.

Serj.by
źródło
Twoja odpowiedź jest poprawna technicznie, więc masz rację, ale wszystko, co napisałeś, jest objęte wybraną odpowiedzią ze znacznie więcej szczegółów i odniesień do specyfikacji ECMAScript - w przyszłości przydatne byłoby przejrzenie istniejącej odpowiedzi przed opublikowaniem.
Guss,
5
Zgoda. Ale wspomniano tylko o varprzypadku. Dla mnie interesujące było testowanie, udostępnianie leti constprzypadki. Dziękuję za uwagę. Spróbuję być bardziej szczegółowy następnym razem.
Serj.by
4

ECMAScript 2015 oferuje API Reflect. Możliwe jest usunięcie właściwości obiektu za pomocą Reflect.deleteProperty () :

Reflect.deleteProperty(myObject, 'myProp');
// it is equivalent to:
delete myObject.myProp;
delete myObject['myProp'];

Aby usunąć właściwość windowobiektu globalnego :

Reflect.deleteProperty(window, 'some_var');

W niektórych przypadkach właściwości nie można usunąć (gdy właściwości nie można skonfigurować), a następnie funkcja ta zwraca false(podobnie jak operator usuwania ). W innych przypadkach zwraca true:

Object.defineProperty(window, 'some_var', {
    configurable: false,
    writable: true,
    enumerable: true,
    value: 'some_val'
});

var frozen = Object.freeze({ myProperty: 'myValue' });
var regular = { myProperty: 'myValue' };
var blank = {};

console.log(Reflect.deleteProperty(window, 'some_var')); // false
console.log(window.some_var); // some_var

console.log(Reflect.deleteProperty(frozen, 'myProperty')); // false
console.log(frozen.myProperty); // myValue

console.log(Reflect.deleteProperty(regular, 'myProperty')); // true
console.log(regular.myProperty); // undefined

console.log(Reflect.deleteProperty(blank, 'notExistingProperty')); // true
console.log(blank.notExistingProperty); // undefined

W trybie ścisłym istnieje różnica między deletePropertyfunkcją a deleteoperatorem:

'use strict'

var frozen = Object.freeze({ myProperty: 'myValue' });

Reflect.deleteProperty(frozen, 'myProperty'); // false
delete frozen.myProperty;
// TypeError: property "myProperty" is non-configurable and can't be deleted
madox2
źródło
4

Zmienne, w przeciwieństwie do prostych właściwości, mają atrybut [[Konfigurowalny]] , co oznacza niemożność usunięcia zmiennej za pomocą operatora delete . Istnieje jednak jeden kontekst wykonania, na który ta reguła nie ma wpływu. Jest to kontekst ewaluacyjny : dla zmiennych nie ustawiono atrybutu [[Konfigurowalny]].

Eldiyar Talantbek
źródło
3

Oprócz tego, co wszyscy napisali, zauważ również, że deletezwraca wartość logiczną. Może ci powiedzieć, czy usunięcie się powiodło, czy nie.

Testując na Chrome, wszystko oprócz można letbyło usunąć. po deletezwróceniu truefaktycznie je usunął:

implicit_global = 1;
window.explicit_global = 1;
function_set = function() {};
function function_dec() { };
var declared_variable = 1;
let let_variable = 1;

delete delete implicit_global; // true, tested on Chrome 52
delete window.explicit_global; // true, tested on Chrome 52
delete function_set; // true, tested on Chrome 52
delete function_dec; // true, tested on Chrome 52
delete declared_variable; // true, tested on Chrome 52
delete let_variable; // false, tested on Chrome 78
oriadam
źródło
To nie zawsze jest poprawne. Zwłaszcza w Chrome. Firefox zwraca wszystko poprawnie. Nie testowałem w żadnych innych przeglądarkach. Jak dla letVars i constvars to wraca prawda, co oznacza, że zmienna powinna usunięty, ale tak nie jest. Możesz to sprawdzić zarówno w Chrome, jak i FF. FF wydaje się zwracać prawidłowe wartości, podczas gdy Chrome nie. Więc nie wiem, czy naprawdę możesz na tym polegać. Zobaczmy:let letVar = "1"; undefined delete letVar; true letVar "1" typeof letVar; "string" const constVar="1"; undefined delete constVar; true constVar; "1" typeof constVar; "string"
Serj. Do
1
Jak wspomniano poniżej jedierikb, istnieje doskonały artykuł autorstwa kangax perfectionkills.com/understanding-delete, który głównie opisuje, dlaczego i jak deletedziała operator. Ale nie opisuje, dlaczego dosłownie przeciwstawia się sytuacji z funkcjami. Szkoda. Jednak w odniesieniu do zmiennych rzeczy zaczynają się wydawać znacznie wyraźniejsze.
Serj.by
2

Nie możesz usunąć zmiennej, jeśli zadeklarowałeś ją (przy pomocy var x;) podczas pierwszego użycia. Jeśli jednak twoja zmienna x pojawiła się po raz pierwszy w skrypcie bez deklaracji, możesz użyć operatora delete (delete x;), a twoja zmienna zostanie usunięta, bardzo podobnie do usuwania elementu tablicy lub usuwania właściwości obiektu .

Satyapriya Mishra
źródło
1

Jestem trochę zmieszany. Jeśli wszystko, czego potrzebujesz, to aby zmienne nie były przekazywane do innego skryptu, nie ma potrzeby usuwania zmiennej z zakresu. Po prostu zeruj zmienną, a następnie jawnie sprawdź, czy jest ona zerowa. Po co męczyć się z usuwaniem zmiennej z zakresu? W jakim celu nie może tego serwera anulować?

foo = null;
if(foo === null) or if(foo !== null)
designdrumm
źródło
Wymagane jest, aby skrypt zamówienia, który nie jest pod moją kontrolą, nie zobaczyłby istnienia zmiennej - specjalnie dla przypadku OP skrypt docelowy zachowuje nullwartość, której nie chcę wyzwalać.
Guss,
Podczas tworzenia tego pytania nie nadużywano żadnego „zaplecza”. To tylko kilka skryptów na stronie, na których nie mam kontroli nad niczym innym niż ten jeden skrypt.
Guss
Czy oba skrypty znajdują się w tym samym dokumencie, czy w osobnych dokumentach, które jeden ładuje do drugiego? Wspomniałeś o skrypcie zamówienia i skrypcie docelowym. Jeśli jest to kwestia przekazania zmiennej do innego skryptu za pomocą zmiennej get / post, usunęłbym ją z backendu, zanim jakikolwiek skrypt javascript wejdzie w jej posiadanie. Przykładem tego może być php. <?php if(isset($_POST['somevariable']) unset($_POST['somevariable']); if(isset($_GET['somevariable']) unset($_GET['somevariable']); ?>
designdrumm
Widzę. Cóż, jeśli istnieją kontrole i wyważenia wartości zerowej, a następnie ustawienie jej na wartość, z którą skrypt docelowy nic nie zrobi, wydaje się bardziej logiczne niż usunięcie zmiennej z zakresu, ale szukasz odpowiedzi, więc pozwolę koniowi leżeć. Dziękuję za twoje odpowiedzi.
designdrumm
Jedno szybkie pytanie. Czy kiedykolwiek pojawi się skrypt, który zostanie wywołany po twoim, który nie będzie w twojej kontroli, ale nadal będzie potrzebował tej zmiennej? Jeśli tak, to usunięcie zmiennej z zakresu jest złym pomysłem.
designdrumm