What's the point of document.defaultView?
MDN says:
In browsers returns the window object associated with the document or null if none available.
Code like the following (from PPK's site) makes use of document.defaultView:
function getStyle(el,styleProp)
{
var x = document.getElementById(el);
if (x.currentStyle)
var y = x.currentStyle[styleProp];
else if (window.getComputedStyle)
var y = document.defaultView.getComputedStyle(x,null).getPropertyValue(styleProp);
return y;
}
Code like this can be found in other places, like David Mark's My Library. I'm not sure if people are just copying from PPK or some other source or coming up with this independently, but I don't understand it.
My question is, what is the point of using document.defaultView in cases like this? Wouldn't it be easier to write this as follows:
function getStyle(element, styleProp) {
if (element === ''+element) element = document.getElementById(element);
return element.currentStyle ? element.currentStyle[styleProp] :
getComputedStyle(x,null).getPropertyValue(styleProp);
}
What does document.defaultView.getComputedStyle do that window.getComputedStyle or simply getComputedStyle does not?
cwolves' answer got me thinking in the right direction. The original function is silly, missing the point of defaultView. My proposal above is less silly, but also missing the point of defaultView. Here's my new proposal:
function getStyle(element, styleProp) {
var view = element.ownerDocument && element.ownerDocument.defaultView ?
element.ownerDocument.defaultView : window;
return view.getComputedStyle ?
view.getComputedStyle(element,null).getPropertyValue(styleProp) :
element.currentStyle ?
element.currentStyle[styleProp] : null;
}
The element itself must be passed in, not the id. I think this is probably to be preferred anyway. This gets the document containing the node, and the window associated with it. It has a fallback to the current window's getComputedStyle if ownerDocument or defaultView are broken (I vaguely remember getComputedStyle being around before defaultView). This is probably closer to the intended use of defaultView.
The OP asks the question, "What's the point of document.defaultView", and the answer really doesn't have anything to do with getComputedStyle. The document.defaultView property is simply a way of obtaining the window object if one has a reference to the document object contained in that window. There are cases where the window object you are seeking to reference (or defaultView) is not in the same window scope as the code you are running.
One example of this is if you have a reference to the document object in an iframe, and wish to conveniently get a reference to the window object of that iframe.
Another case might be where you are running in privileged context in the browser scope (eg, chrome code in Firefox), and you happen to have a reference to the document object of a tabbrowser, or another window.
Or, as Dagg Nabbit points out, if in any of these cases you have a reference to an element within the window, you could access the parent window of that element through element.ownerDocument.defaultView
I'm not positive on this, but I imagine that it's the result of fixing a bug from either trying to run code on a detached document (i.e. something that exists in memory but is not in the page) or trying to run on a document in a different window (e.g. an iframe or a popup).
According to your quote, when document.defaultView is run on a document that is NOT the current document, you will get the associated window object, thus document.documentView.getComputedStyle !== getComputedStyle since they are in different contexts.
In short, I believe it's akin to document.window which doesn't exist.
It is simply an abstraction as far as I can tell, just in case any user agents pop up with a DOM implementation, but don't provide a view in the form of a window. See Views in DOM level 2.
According to MDN getComputedStyle article,
In many code samples online, getComputedStyle is used from the
document.defaultView object.
In nearly all cases, this is needless, as
getComputedStyle exists on the window object as well.
It's likely the
defaultView pattern was some combination of
folks not wanting to write a spec for window and
making an API that was also usable in Java.
However, there is a single case where the defaultView's method
must be used: when using Firefox 3.6 to access framed styles.
Related
I did this simple test in Chromium Console:
var e = document.querySelector('#myElement');
e.remove();
//But here, 'e' still references the original node even though its no longer in the DOM.
I don't want this behavior. I would like 'e' to act more like a Weak Reference, whereby it would change to NULL or UNDEFINED or something indicating the node has been destroyed.
I understand that the problem is due to the fact that the node technically still exists even though its not part of the DOM anymore. I speculate it will hang around until the browser deems an appropriate time to garbage collect it.
So instead of simply doing .remove(), is there a way to really destroy/delete the node so that all variables referencing it will become undefined or null or some effect that can be detected later?
Thanks in advance!
Also, I speculate this behavior will vary highly between browsers. So any feedback that mentions Non-Chromium browsers is highly welcome also. :)
If a standalone variable holds a reference to an object, and that variable can still be referenced, the object will not get picked up by the garbage collector's mark-and-sweep algorithm, and the object will continue to exist at least as long as the variable can still be referenced.
Given a reference to an object, you cannot destroy it such that other references to the object break.
JavaScript doesn't provide the sort of manual memory control you're looking for, unfortunately.
You can put the element into a WeakSet, after which removing the element from the DOM will eventually cause the element being removed from the WeakSet if nothing else can possibly reference the element - but that's not really what you're looking for.
There do exist WeakRefs, an extremely new API, which allow variables to be specially declared such that what they reference can be garbage collected despite them still being referenceable:
const ref = (() => {
const e = document.querySelector('#myElement');
const ref = new WeakRef(e);
e.remove();
return ref;
})();
With the above, calling ref.deref() will eventually give you undefined if nothing else can reference the element and it has been garbage collected. (Until it gets garbage collected, .deref() will give you the element.)
For example for this.parentNode I would like to just write this.p or instead of
document.getElementById('someid') just write document.g('someid'). Of course that are simple examples, I just want to know what is the correct way to do it.
(I know I can use jQuery or Prototype, but I'd like to learn how it is really done in JS)
Although you can prototype on the HTMLElement in many browsers - Internet Explorer (6,7,8) is NOT one of them. AFAIK, IE9 does support this (though I haven't tested it).
For browsers that do handle it, you can do:
HTMLElement.prototype.doHello = function(thing){
console.log(this + ' says: ' + thing)
}
document.body.doHello('hello')
I would strongly suggest not attempting to do this, for a few reasons:
Browser compatibility. While it is possible in several browsers, it isn't possible in IE <= 8.
DOM elements are host objects. Host objects (i.e. those provided by the environment that aren't native JavaScript objects) have no obligation to play by the same rules as native JavaScript objects and other than specified DOM behaviour can essentially do what they like. So, even if some browsers provide an HTMLElement prototype and allow you to augment it, there's no guarantee that it will work as you expect.
Compatibility with other code in your page. If any other code in your page (such as Prototype) messes with the HTMLElement prototype, you risk naming collisions and hard-to-detect bugs.
Instead, I would suggest creating wrapper objects around DOM nodes as jQuery, YUI and other libraries do.
Kangax has written a good article on DOM extensibility, covering all these points and more.
In a word, don't. It is best not to modify objects you don't own.
This is particularly true for HTMLElement, which you cannot modify in some browsers.
This article from perfectionkills.com will probably give you some insight into how it's done, and why you shouldn't do it.
(By the way, jQuery doesn't extend DOM elements. They use DOM wrappers instead.)
This might not be what you are looking for if you want to wrap a global object like document, but you can get a similar effect with custom-elements [1] [2] to create your own HTMLElement-like nodes.
create custom-element
add method to custom-element class
you can call the method
export class CustomElementInput extends HTMLElement {
log(){
alert("log")
}
// you can even overwrite methods like so
remove(){
alert("removing this node")
super.remove()
}
}
customElements.define("custom-element-input", CustomElementInput)
// somewhere else...
// in your HTML something like:
// <custom-element-input></custom-element-input>
const el = document.querySelector("custom-element-input")
el.log() // creates alert()
Let's have the following html:
<input id="txt" type="text" />
Now, in javascript, I can access its value by these following methods.
var value = txt.value;
var value = document.getElementById('txt').value;
So, is there any difference between them?
First, consider what happens when someone look at the code written like this:
var value = txt.value;
There's exactly ZERO hint here that txt is actually 1) a global variable; 2) a DOM Element. Unless one scans the whole scope of a function (that means the function itself, the parent function it's defined in, its own parent etc. ... right till the outmost - global - scope). That means to understand the code, one has to check its whereabouts. Apparently that doesn't make the code nice to work with.
To illustrate this, consider what happens here:
function someAsyncFunc() {
var txt = 'someText';
// some lines of code
someAsyncAction(function() {
var value = txt.value;
});
}
Here we have a function used as a callback in some asynchronous action. Technically, it doesn't have txt as a local variable to this specific function - but reference to a global variable is still shadowed by txt defined in its parent. The point is, it's way too easy to introduce subtle - yet very nasty - bugs by this potential clash of names.
That's for theory and reasoning, now about cross-platform practice. The whole idea of injecting the DOM Elements into the global scope (by making namespace resolver scan both window and document) for many years was considered, well, a bad idea - a shortcut that'll save you a couple of keystrokes when you write the code, but will more than compensate when someone (or you 6 months after) will debug the code. That's why, I suppose, Gecko engine didn't make such an injection in the standards mode - only in quirks.
Still, this idea has made its way in HTML5 Standard:
*Named objects [...] are those that are either:
a, applet, area, embed, form, frameset, img, or object elements that have a name content attribute whose value is name, or
HTML elements that have an id content attribute whose value is name.
There was a lengthy discussion about the correctness of such approach - which ultimately resulted in the approach staying as is. And the key reason was given by MS reps:
Our data suggests that making this quirks only would break a large
number of web sites.
Yes, yet another victory for the bad practice used so often that it became a common practice. That's why Firefox 14+ supports this 'feature' as well. Still, the mere support of it doesn't make it right.
Hopefully the title of the question is clear, but:
What happens when the following happens:
<div id="parent">
<img src="..." id="myImage"/>
</div>
<script>
var i=document.getElementById('myImage');
document.getElementById('parent').innerHTML='';
// variable i should not be 'undefined' right? chrome debugger says nope!
</script>
I have tested this in chromes debugger, and it appears that the reference still "lives" on even after the DOM object no longer exists... Am I correct? Doing a console.log(i) (for the above example), still returns the object and all its properties... Shouldnt the garbage collector kick in and 'undefined' this reference since it no longer exists in the DOM?
Does anyone know how something like this is handled across other browsers (more specifically, IE6...) besides chrome?
The reason I am asking is because we globally store some references (depending on user actions), to be used if the user performs another action later on... And we basically need a way to test if the DOM object still exists or not..
I have created a small jsfiddle to make everything clear: http://jsfiddle.net/9HCKt/
Thanks.
UPDATE: I see now the question wasn't quite clear... How do I test to see if this object still exists in the DOM in a cross browser compatible way?
Your fiddle makes things worse :) Why you need to know type of removed element? It will be object before it removed, and after too. If question what happens: Your element is Element. You can check it with i instanceof Element. It's just disconnected from DOM. You actually can connect it again, in other or the same place of DOM and it will be legal. If question how to check element is connected to DOM: just check if it's parent defined.
if (i.parentElement) {
// i connected to DOM
} else {
// i disconnected
};
Think about: when you add new element with document.createElement, your new element not attached yet. But it obviously not undefined.
if you can use jQuery,
$(document).has($(i)).length
should be 1 if i is still in the DOM, 0 if not.
Doing a console.log(i) (for the above example), still returns the
object and all its properties... Shouldnt the garbage collector kick
in and 'undefined' this reference since it no longer exists in the
DOM?
In fact, with your i variable, you are maintaining a reference to the object, preventing it from being garbage collected.
I'm trying to understand why the following is not working as I would think it does. I'd like to check whether an object is a window. I was thinking that checking the constructors of the current window and another window would work.
So, first creating another window:
var popup = window.open('', '', '');
And then checking with:
popup.constructor === window.constructor;
But for some reason the results vary among browsers:
IE7: Returns true
But that's simply because .constructor === undefined
IE8: Returns false
IE9: Throws an error with no text ("")
Chrome: Returns false
Though in both cases .constructor === DOMWindow - but DOMWindow is not accessible directly like this
Opera: Returns false
Though in both cases .constructor === Object
Firefox: Returns false
Though in both cases .constructor === Window
Why isn't this reliable and working correctly? jQuery simply checks for "setInterval" in window, but I'd like to create a more robust function for checking whether an object is a window.
Every separate window is a separate global context for the interpreter (and yes I'm ignoring IE7 for the moment). Your issue really becomes clear when you think about instanceof:
if (something instanceof Array) { ... }
The problems with that occur when "something" is a value that was constructed in a separate context, like an iframe for example. Each window has it's very own copy of the "Array" constructor, so even though an object may be an Array instance, it's not an instance of the same constructor function.
I suspect that the IE7 behavior is related to some other weirdness that I think is something like window.window being not equal to window. Like, one of them is in fact a reference to something internal. (Maybe it's window not being equal to window.self even though they otherwise behave identically. I don't invest a lot of philosophical thought time in contemplating IE7.)