Skip to content

Commit f6bd10c

Browse files
committed
refactor: Modernise polyfills with Object.hasOwn and ES2024 features
- Implement `Object.hasOwn` polyfill and use it for internal property checks. - Add [`Promise.withResolvers`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers) (ES2024) to the standard library. - Optimise `prepend`, `replaceWith`, and `toggleAttribute` for performance and spec compliance. - Set polyfilled methods to non-enumerable to match native behavior. - Introduce robust `createTextNode` and `createDocumentFragment` utility helpers. - Improve performance by replacing `Array.slice` on arguments with manual loops. (AI generated commit message)
1 parent d8bb199 commit f6bd10c

File tree

1 file changed

+118
-43
lines changed

1 file changed

+118
-43
lines changed

src/lib/polyfill.js

Lines changed: 118 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,62 @@
11
(function () {
2+
// polyfill for Object.hasOwn
3+
4+
(function () {
5+
var oldHasOwn = Function.prototype.call.bind(
6+
Object.prototype.hasOwnProperty,
7+
);
8+
if (oldHasOwn(Object, "hasOwn")) return;
9+
Object.defineProperty(Object, "hasOwn", {
10+
configurable: true,
11+
enumerable: false,
12+
writable: true,
13+
value: function hasOwn(obj, prop) {
14+
return oldHasOwn(obj, prop);
15+
},
16+
});
17+
Object.hasOwn.prototype = null;
18+
})();
19+
220
// polyfill for prepend
321

422
(function (arr) {
523
arr.forEach(function (item) {
6-
if (item.hasOwnProperty("prepend")) {
7-
return;
8-
}
24+
if (Object.hasOwn(item, "prepend")) return;
925
Object.defineProperty(item, "prepend", {
1026
configurable: true,
11-
enumerable: true,
27+
enumerable: false,
1228
writable: true,
1329
value: function prepend() {
14-
var argArr = Array.prototype.slice.call(arguments),
15-
docFrag = document.createDocumentFragment();
30+
var docFrag = createDocumentFragment();
1631

17-
argArr.forEach(function (argItem) {
18-
var node =
19-
argItem instanceof Node
20-
? argItem
21-
: document.createTextNode(String(argItem));
22-
docFrag.appendChild(node);
23-
});
32+
var argLength = arguments.length;
33+
for (var i = 0; i < argLength; i++) {
34+
var argItem = arguments[i];
35+
docFrag.appendChild(
36+
argItem instanceof Node ? argItem : createTextNode(argItem + ""),
37+
);
38+
}
2439

2540
this.insertBefore(docFrag, this.firstChild);
2641
},
2742
});
43+
item.prepend.prototype = null;
2844
});
2945
})([Element.prototype, Document.prototype, DocumentFragment.prototype]);
3046

3147
// polyfill for closest
3248

3349
(function (arr) {
3450
arr.forEach(function (item) {
35-
if (item.hasOwnProperty("closest")) {
36-
return;
37-
}
51+
if (Object.hasOwn(item, "closest")) return;
3852
Object.defineProperty(item, "closest", {
3953
configurable: true,
40-
enumerable: true,
54+
enumerable: false,
4155
writable: true,
4256
value: function closest(s) {
43-
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
57+
var matches = (this.document || this.ownerDocument).querySelectorAll(
58+
s,
59+
),
4460
i,
4561
el = this;
4662
do {
@@ -50,68 +66,81 @@
5066
return el;
5167
},
5268
});
69+
item.closest.prototype = null;
5370
});
5471
})([Element.prototype]);
5572

5673
// polyfill for replaceWith
5774

5875
(function (arr) {
5976
arr.forEach(function (item) {
60-
if (item.hasOwnProperty("replaceWith")) {
61-
return;
62-
}
77+
if (Object.hasOwn(item, "replaceWith")) return;
6378
Object.defineProperty(item, "replaceWith", {
6479
configurable: true,
65-
enumerable: true,
80+
enumerable: false,
6681
writable: true,
6782
value: function replaceWith() {
68-
var parent = this.parentNode,
69-
i = arguments.length,
70-
currentNode;
83+
var parent = this.parentNode;
7184
if (!parent) return;
72-
if (!i)
73-
// if there are no arguments
74-
parent.removeChild(this);
75-
while (i--) {
76-
// i-- decrements i and returns the value of i before the decrement
77-
currentNode = arguments[i];
85+
var viableNextSibling = this.nextSibling;
86+
var argLength = arguments.length;
87+
while (viableNextSibling) {
88+
var inArgs = false;
89+
for (var j = 0; j < argLength; j++) {
90+
if (arguments[j] === viableNextSibling) {
91+
inArgs = true;
92+
break;
93+
}
94+
}
95+
if (!inArgs) break;
96+
viableNextSibling = viableNextSibling.nextSibling;
97+
}
98+
var docFrag = createDocumentFragment();
99+
for (var i = 0; i < argLength; i++) {
100+
var currentNode = arguments[i];
78101
if (typeof currentNode !== "object") {
79-
currentNode = this.ownerDocument.createTextNode(currentNode);
102+
currentNode = createTextNode(currentNode, this.ownerDocument);
80103
} else if (currentNode.parentNode) {
81104
currentNode.parentNode.removeChild(currentNode);
82105
}
83-
// the value of "i" below is after the decrement
84-
if (!i)
85-
// if currentNode is the first argument (currentNode === arguments[0])
86-
parent.replaceChild(currentNode, this);
87-
// if currentNode isn't the first
88-
else parent.insertBefore(this.previousSibling, currentNode);
106+
docFrag.appendChild(currentNode);
107+
}
108+
if (argLength >= 1) {
109+
if (viableNextSibling) {
110+
parent.insertBefore(docFrag, viableNextSibling);
111+
} else {
112+
parent.appendChild(docFrag);
113+
}
114+
} else {
115+
parent.removeChild(this);
89116
}
90117
},
91118
});
119+
item.replaceWith.prototype = null;
92120
});
93121
})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);
94122

95123
// polyfill for toggleAttribute
96124

97125
(function (arr) {
98126
arr.forEach(function (item) {
99-
if (item.hasOwnProperty("toggleAttribute")) {
100-
return;
101-
}
127+
if (Object.hasOwn(item, "toggleAttribute")) return;
102128
Object.defineProperty(item, "toggleAttribute", {
103129
configurable: true,
104-
enumerable: true,
130+
enumerable: false,
105131
writable: true,
106132
value: function toggleAttribute() {
107133
var attr = arguments[0];
108134
if (this.hasAttribute(attr)) {
109135
this.removeAttribute(attr);
136+
return false;
110137
} else {
111-
this.setAttribute(attr, arguments[1] || "");
138+
this.setAttribute(attr, arguments.length >= 2 ? arguments[1] : "");
139+
return true;
112140
}
113141
},
114142
});
143+
item.toggleAttribute.prototype = null;
115144
});
116145
})([Element.prototype]);
117146

@@ -141,4 +170,50 @@
141170
};
142171
}
143172
})();
173+
174+
// polyfill for Promise.withResolvers
175+
176+
if (!Object.hasOwn(Promise, "withResolvers")) {
177+
Object.defineProperty(Promise, "withResolvers", {
178+
configurable: true,
179+
enumerable: false,
180+
writable: true,
181+
value: function withResolvers() {
182+
var resolve, reject;
183+
var promise = new this(function (_resolve, _reject) {
184+
resolve = _resolve;
185+
reject = _reject;
186+
});
187+
if (typeof resolve !== "function" || typeof reject !== "function") {
188+
throw new TypeError(
189+
"Promise resolve or reject function is not callable",
190+
);
191+
}
192+
return {
193+
promise: promise,
194+
resolve: resolve,
195+
reject: reject,
196+
};
197+
},
198+
});
199+
Promise.withResolvers.prototype = null;
200+
}
201+
202+
// utils
203+
204+
function createTextNode(text, doc) {
205+
if (doc === undefined) doc = document;
206+
if (doc !== document)
207+
try {
208+
return new Text(text);
209+
} catch (_) {}
210+
return doc.createTextNode(text);
211+
}
212+
function createDocumentFragment() {
213+
try {
214+
return new DocumentFragment();
215+
} catch (_) {
216+
return document.createDocumentFragment();
217+
}
218+
}
144219
})();

0 commit comments

Comments
 (0)