24

I'm trying to figure out the vanilla equivalent of the following code:

$(document).attr('key', 'value');

So far I've looked into

  • document - it's not an element so you cannot call setAttribute on it
  • document.documentElement - returns the html tag. This is not the same "element" that jquery is targeting
  • $(document)[0] seems to return a shadow element in Chrome Inspector
  • $(document).attr('key', 'somethingUnique') doesn't exist in the Chrome Inspector

Is jQuery creating it's own shadow element mock of the document so it can treat it like a real element? What element is jQuery actually referencing when you do $(document)?

CTS_AE
  • 12,987
  • 8
  • 62
  • 63
  • 1
    There's no vanilla equivalent to `$(document)`, it is a jQuery object. – Teemu May 31 '18 at 19:16
  • 3
    `$(document)` should just be creating a jQuery object with a stack containing just the document element. How jQuery handles the `attr()` method for elements that do not support `setAttribute()` is a different question I'm not familiar with the answer of. – Taplar May 31 '18 at 19:16
  • 4
    Some insight into what jQuery is actually doing. https://j11y.io/jquery/#v=git&fn=jQuery.attr You can see the comment about what it does if attributes are not supported. – Taplar May 31 '18 at 19:23
  • I don't know the answer, but I did look at the jQuery source code, and document is set globally with var document = window.document. I'm not familiar with using attributes on window.document but it appears it can be used to create attributes that can later be applied to elements. – Arlo Guthrie May 31 '18 at 19:35
  • `$(document)[0]` *is* the same as `document`. – Bergi May 31 '18 at 20:47
  • 1
    This is probably one of the cases where you meant [`.prop()` instead of `.attr()`](https://stackoverflow.com/q/5874652/1048572) – Bergi May 31 '18 at 20:48
  • Where have you seen this code? It doesn't really make sense. What are the actual `key` and `value`? – Bergi May 31 '18 at 20:49
  • We were trying to port code from `$(document).data('key', 'val')` I figured if we could set an attribute that it would get picked up via the jQuery data once it was loaded in. Found out that setting a prop with the data prefix doesn't do anything. We're also running jQuery v1.8.1 so I dug into the source with the the awesome tool that Taplar linked. This all ties back to some lazy eventing with some complicated architecture that had a race condition with jQuery being loaded that was ultimately band-aided with a gross setTimeout that I despise and hate. Couldn't change the listener/check code. – CTS_AE May 31 '18 at 21:59
  • I ended up finding out I could do the following, but it didn't fix the chicken/egg problem `jQuery.cache[document[jQuery.expando]].data` you still need jQuery before you can use it of course. The timeout will have to be a temporary fix until a larger fix can be applied in this circumstance. – CTS_AE May 31 '18 at 22:01
  • 3
    Since your real question is about `.data()`, you're really confusing things by asking about `.attr()`. They're not equivalent, because jQuery implements `.data()` internally using hidden data structures, not using attributes of the element. The `data-XXX` attributes are only used to get the initial data value. See https://stackoverflow.com/questions/28335833/get-wrong-value-in-data-attribute-jquery/28335905#28335905 – Barmar Jun 01 '18 at 00:44
  • Your code is missing a closing `'` in `'value);`. – Makyen Jun 01 '18 at 01:26
  • @Barmar I thought it would pull the initial value from the data attribute though, but it doesn't. This surely loops back to how it is a property and not actually an attribute. It just seems odd they would't reuse their own accessor there for obtaining an attribute. – CTS_AE Jun 01 '18 at 09:18
  • It's an attribute, not a property, but that's irrelevant. The point is that the first time you use `.data()` on an element it gets the data from the DOM and caches it, and never looks at the DOM again for that element. You have to be consistent -- either use `.data()` or `.attr()`, don't mix them. – Barmar Jun 01 '18 at 09:21
  • 2
    @CTS_AE you probably should create a new question that explains what you want to achieve and why. Your comments indicate that your question does not really match the problem you want to solve. – t.niese Jun 01 '18 at 10:34

4 Answers4

27

A jQuery results set is an array like object that in general holds DOMElement, but jQuery does not really care about what type the objects in the result set have. Neither the DOMElements nor any other element that is stored within the jQuery result set is somehow mocked/wrapped, they are directly stored in the result set. jQuery tries to figure out what it has to do to those objects by looking at their available functions.

When you call .attr, jQuery checks for each object in the set if it has the function getAttribute if this is the case it assumes that it also has a function setAttribute.

If it does not have a function getAttribute, then it will forward the function call to the .prop() function, and prop will internally just do:

elem[name] = value

So if you pass a plain object to jQuery, then it will just set its property.

var a = {  
}

$(a).attr('test', 'hello world');

console.dir(a) // for `a`  the property `test` is set
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

If you add a getAttribute and setAttribute function to that object then jQuery will call those:

var a = {
  getAttribute() {

  },
  setAttribute() {
    console.log('setAttribute was called')
  }
}

$(a).attr('test', 'hello world');

console.dir(a);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

However, you should always assume, that the way and order how jQuery does these tests might change. Moreover, only rely on it if it is mentioned explicitly in the documentation.

t.niese
  • 39,256
  • 9
  • 74
  • 101
  • This answer showed me I misunderstood jQuery Object, and showing that you can hack the array and so the dom references maybe should be part of this answer more than a separate one. – lucchi Jun 08 '18 at 10:33
  • Very interesting, a new pattern in my toolbox. – lucchi Jun 08 '18 at 10:58
  • @lucchi As I said, I would not necessarily relay on it. It should just illustrate what jQuery in the current version does. Everything that is not documented as official functionality, might change in future versions. – t.niese Jun 08 '18 at 12:58
  • There are lots of prejudgements of jQuery out there (as far as in MDN), I am glad you made me understand some of the underlying reasons behind those "dogmas", since the supposed "bad" things you can do with jQuery are exactly what is not documented by jQuery for it hopefully should change in future versions... – lucchi Jun 08 '18 at 15:44
20

I believe you're incorrect about $(document) not referring to document, thus, the answer is (quite simply):

document['key']  = 'value'

E.g. In Chrome:

> $(document)[0] === document
true

> $(document).attr('blarg', 'narf')
n.fn.init [document, context: document]

> document.blarg
"narf"

> document.foo = 'bar'
"bar"

> document.foo
"bar"
broofa
  • 37,461
  • 11
  • 73
  • 73
  • 1
    I never thought about comparing the returned element from the jQuery selector against the document object. – CTS_AE May 31 '18 at 22:03
  • This example is a little misleading: `[0]` unwraps the first selected element from the jQuery selection object; You'll note however that: `$(document) !== document` and: `$(document).eq(0) !== document` The latter is the syntax for retrieving the jQuery selection at a desired index, without unwrapping it. – Moshe Bildner Jun 01 '18 at 14:26
11

jQuery is just assigning the value to document directly.

$(document).attr('test', 'hello world');
console.log(document['test']); // prints "hello world"
Reactgular
  • 52,335
  • 19
  • 158
  • 208
0

I really thought jQuery would have wrapped DOM elements, since for some reason, I never write var x = $('#x') to reuse it later but recall $.

That´s why I wrote:

Yes it is wrapped

But after reading @t.niese answer here I tried

var x = $('#x')
var y = $('#y')

var ref = x[0]
x[0] = y[0] // hack jQuery Object reference to DOM element

setTimeout(() => x.html('1'), 1000) // actually writes in #y
setTimeout(() => x.html('2'), 2000) // actually writes in #y
setTimeout(() => { x.push(ref) }, 2500) // hack jQuery Object reference to DOM element
setTimeout(() => x.html('3'), 3000) // actually writes in both #x and #y

And understood I don't write var x = $('#x') not because it is a wrapped object, but exactly because it is not a wrapped object.

I thought the entry point of the API was $, but I may see the API like var api = $(), and the entry point as (el) => api.push(el) or (sel) => api.push(document.querySelector(sel))

I can $().push but I can not $().forEach nor shift nor unshift but yes delete an index, also

In the example

setTimeout(() => { x.map((item) => {console.log(item)}) }, 3500)

logs 0 and 1, not the elements. Tested using jQuery version 3.3.1

lucchi
  • 1,006
  • 6
  • 9