Manifest.JS: Ein leichtes Front-End-Strukturgerüst
Umfang der Codeüberprüfung
Mein Ziel bei dieser Überprüfung ist es, umfassende Beobachtungen und Vorschläge zur Verbesserung der Effizienz / Einfachheit des Schreibens des Frontends einer Webanwendung mit diesem Grundgerüst zu erhalten. Ich möchte mich auf das konzentrieren, wie es aussehen soll, und nicht auf die Details dessen, was es unbeabsichtigt tun kann oder nicht. Beschränken Sie den Umfang auf eine Gesamtübersicht, was Zeit sparen sollte, da es sich um einen großen Codeblock für eine Überprüfung handelt.
Der Schwerpunkt sollte auf der Geschwindigkeit der skalierbaren (wartbaren, umstrukturierbaren) Entwicklung, übergreifenden Codemustern und dem Codedesign der resultierenden Anwendungen liegen.
"Es sieht so aus, als ob Sie versuchen, dies zu erreichen, hier haben Sie Erfolg gehabt, hier fehlt es Ihnen, oder hier ist eine umfassende Modifikation, mit der der resultierende Code leichter zu lesen, zu warten und schneller zu entwickeln ist . "
Das Problem Manifest.JS soll angegangen werden
Entwerfen von einseitigen Web-Apps Ich habe zwei Dinge gefunden, die mir an ReactJS nicht gefallen haben (zumindest für meinen typischen Projektmaßstab):
- Musste mehr Code schreiben, als ich grundlegende Dinge erreichen wollte
- Der Transport von Informationen durch die App war mühsam. Sie mussten im Wesentlichen einen Draht durch die Komponenten führen, um Informationen von Punkt A nach Punkt B zu erhalten, wodurch sich das Design eng gekoppelt anfühlte und sich danach nur schwer neu strukturieren ließ
Ich habe mich auch in Bezug auf andere JS-App-Frameworks, die ich ausprobiert habe, so gefühlt. Also schrieb ich zwei ziemlich einfache Klassen, die zusammenarbeiten, um ein Entwicklungsmuster zu erstellen, das ich bevorzugte. Mein Ziel war es, dass ich:
- Konzentrieren Sie sich auf den gesamten Prozess des Aufbaus jeder einzelnen Komponente einer App in modulares JS, ohne sich darum kümmern zu müssen, wie jede Komponente mit der externen Anwendung verbunden ist.
- Sie müssen nicht zwischen mehreren Dateien oder Sprachen wechseln, um JavaScript, HTML und CSS zu bearbeiten, um eine UI-Funktion zu erstellen oder zu verwalten.
Lose Kopplung, Trennung von Bedenken, reiner JS-Workflow, vorhersehbare Projektstruktur, einfacher Datenfluss, nicht mehr Funktionen als erforderlich. Das ist das Ziel, ob dieser Code es erreicht oder nicht, ich bin mir noch nicht sicher.
Hinweis: JSX erledigt einige Aufgaben #2, aber da sich die beiden Sprachen in einer Datei für mich etwas seltsam anfühlten, wollte ich, dass meine Dateien eine einheitliche Sprache sind und nicht wie bei React JSX.
Selbstkritik:
Bisher habe ich einige Selbstkritikpunkte berücksichtigt:
Wenn es darum geht, einen Satz von
Elementsin eine Klasse zu modularisieren , könnte ich eine einzige, festgelegte Methode dafür bereitstellen, damit der Entwickler einen klaren Weg vorwärts hat und keine Freiheit hat, versehentliche Anti-Patterns zu entwickeln, wenn er entscheidet, wie die Komponenten in modular verpackt werden sollen Dateien.Verkettung ist großartig. Ich sollte aktualisieren,
.useum zurückzukehren,thisdamit wir dann eine Aktion wie verketten könnenself.append(new InfoPage().use(subPage, { /* properties */ }).actions.select(true))Erstellen Sie die InfoPage, verwenden Sie die Unterseitenvorlage, übergeben Sie eindeutige Eigenschaften und wählen Sie sie standardmäßig aus. Kann auch dazu führen, dass
actions zurückkehren,Elementdamit sie verkettet werden können.
Komponenten:
- Publisher.js - eine einfache Klasse zum Weiterleiten von Nachrichten zum Implementieren des Pub Sub-Musters, da ich in der Lage sein wollte, von Namespaces getrennte Ereignisse von jedem Ort in der App aus zu senden und sie an einer anderen Stelle zu lesen, z. B.:
publisher.emit("header/select-nav/home", this)Undpublisher.on("header/select-nav/" + name, data => {}). Darüber hinaus unterstütze ich ein drittesboolArgument, um das Senden und Abhören von Ereignissen über einen optional in Socket.io übergebenen Socket zu unterstützenlet publisher = new Publisher(io()), damit ich lokale und Remote-Ereignisse auf die gleiche Weise behandeln kann.
Verwendung:
let publisher = new Publisher(io()) // or let publisher = new Publisher()
publisher.on("namespace1/subnamespace2/event-name", data => {}, false)
// third arg set to true tells the event handler to listen for Socket.io events
- Element.js - ein Wrapper für HTML-Elemente, der die gesamte HTML-Generierung und -Logik der App erleichtert, sodass die mit jeder sichtbaren Komponente der App verknüpfte Logik eng mit ihr verbunden ist, während alle Komponenten einzeln lose miteinander verbunden bleiben . Ich habe auch vor, möglicherweise Unterstützung für das lokale Generieren von CSS-Klassen in jeder Komponente hinzuzufügen.
Verwendung:
new Element("div", { // create a div
id: "header", // set the id
traits: { publisher }, // assign data that will be inherited by any children
data: { name: "primary" }, // assign data to this element only
text: "My header text", // set text
innerHTML: false, // set innerHTML
classes: [], // assign classes
attributes: { // assign attributes
"contenteditable": "true"
},
styles: {}, // assign styles
actions: { // create actions, which will be wrapped in a
show: (self, arg1) => { // wrapper func and receive the Element as
self.clearStyle("opacity") // their first argument, followed by any
self.addClass("visible") // args passed with Element.action.name()
console.log("called with Element.actions.show(arg1)")
},
hide: self => {
self.removeClass("visible") // remove them
self.style("opacity", 0) // or set styles
}
},
callback: self => { // trigger as soon as the element is created
self.append(new Element("div", {
id: "important-icon",
classes: ["hidden", "header-icon"],
actions: {
select: () => {
self.addClass("visible")
self.removeClass("hidden")
self.updateText("Selected") // update text
}
},
ready: self => {
self.on("mouseover", evt => { // handle DOM events
self.addClass("highlight")
})
}
}))
},
ready: self => { // trigger after the element is appended to a parent
self.traits.publisher.on("events/header/" + self.data.name, data => {
self.select("#important-icon").actions.select();
// you could of course apply this listener to the icon itself,
// but the select feature is convenient in some cases
})
}
}).appendTo(document.body)
- Controller.js - Die Validierung von Eingaben während des Datenflusses wird immer wichtiger, je größer eine Anwendung wird. Es sollte also natürlich eine Wahl sein, ob Sie es verwenden möchten, und ich habe es verfügbar gemacht und unterstützt, um den Datenfluss sowohl innerhalb des Elements als auch im Publisher zu validieren . Ich habe noch keinen Code in der Publisher-Unterstützung codiert, aber es funktioniert genauso bei
Element, mitpublisher.use(controller). Ich wollte aber auch, dass ein Pass eine Blueprint-Eingabe an eine Reihe von Elementen übergibt, die dieselben Eigenschaften erfordern, und es ist sinnvoll, dass ein Controller die aktuelle Eingabe überschreiben kann, um das Testen / Debuggen zu vereinfachen. Deshalb habe ich eine hinzugefügtinsertMethode dazu, die (wie Sie im Code sehen werden) für die Vorlage von Elementeigenschaften verwendet werden kann und sollte.
Verwendung:
let page = new Controller({
data: data => { // pass a function to validate data however you want
if (!data.name) return false
else return true
},
traits: true, // pass true to simply ensure a setting is passed
actions: "object", // pass a string to test against typeof
}).insert({ // and insert specific default data
traits: {
publisher
},
actions: {
select: self => {
let target = "header/select-nav/" + self.data.name.toLowerCase()
self.traits.publisher.emit(target, this)
self.addClass("visible")
}
},
ready: self => {
self.traits.publisher.emit("header/add-nav", self)
}
});
Element.js:
import Controller from "/js/classes/controller.js"
function isCyclic(obj) {
var seenObjects = [];
function detect(obj) {
if (obj && typeof obj === 'object') {
if (seenObjects.indexOf(obj) !== -1) {
return true;
}
seenObjects.push(obj);
for (var key in obj) {
if (obj.hasOwnProperty(key) && detect(obj[key])) {
//console.log(obj, 'cycle at ' + key);
return true;
}
}
}
return false;
}
return detect(obj);
}
function isObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
}
function isIterable(item) {
let type = false;
if (isObject(item)) type = 'obj';
else if (Array.isArray(item)) type = 'arr';
return type;
}
function mergeDeeper(source, target) {
let allProps = [];
let sourceProps;
let type;
let targetProps;
if (isObject(source)) {
sourceProps = Object.keys(source);
type = 'obj';
} else if (Array.isArray(source)) {
sourceProps = source;
type = 'arr';
} else {
return source;
}
if (isObject(target)) {
targetProps = Object.keys(target);
} else if (Array.isArray(target)) {
targetProps = target;
} else {
debugger
throw "target missing"
}
sourceProps.forEach(prop => {
allProps.push(prop);
});
targetProps.forEach(prop => {
allProps.push(prop);
});
allProps = [...new Set(allProps)];
let merged
if (type == 'obj') {
merged = {};
} else if (type == 'arr') {
merged = [];
}
allProps.forEach(prop => {
if (type == "obj") {
if (source[prop]) {
if (isIterable(source[prop])) {
if (isIterable(target[prop])) {
merged[prop] = mergeDeeper(source[prop], target[prop])
} else merged[prop] = source[prop]
} else {
merged[prop] = source[prop]
}
} else {
if (source[prop] !== undefined) {
merged[prop] = source[prop]
} else {
merged[prop] = target[prop]
}
}
} else {
let iterable = isIterable(prop);
if (iterable) {
let filler
if (iterable == "obj") filler = {};
else if (iterable == "arr") filler = [];
merged.push(mergeDeeper(prop, filler))
} else {
merged.push(prop)
}
}
})
return merged;
}
const collectChildSelectors = (elementWrapper, selectors) => {
elementWrapper.children.forEach(childWrapper => {
if (childWrapper.element.id) {
selectors[childWrapper.element.id] = childWrapper
}
if (childWrapper.selector) {
selectors[childWrapper.selector] = childWrapper
}
collectChildSelectors(childWrapper, selectors)
})
}
const applySettings = function(newSettings) {
if (!newSettings) throw "bad settings"
let settings = mergeDeeper(newSettings, {
text: false,
innerHTML: false,
classes: [],
actions: {},
data: {},
attributes: {},
styles: {},
traits: {},
id: false,
callback: false,
ready: false,
});
if (settings.id) {
this.element.id = settings.id
this.selector = settings.id
}
if (settings.text) this.element.textContent = settings.text
if (settings.innerHTML) this.element.innerHTML = settings.innerHTML
if (settings.selector) {
this.selector = settings.selector
this.selectors[settings.selector] = this;
}
settings.classes.forEach(className => this.element.classList.add(className))
Object.keys(settings.attributes).forEach(attributeName =>
this.element.setAttribute(attributeName,
settings.attributes[attributeName]))
Object.keys(settings.styles).forEach(styleName =>
this.element.style[styleName] = settings.styles[styleName])
Object.keys(settings.actions).forEach(actionName =>
this.actions[actionName] = () => settings.actions[actionName](this))
Object.keys(settings.data).forEach(propertyName =>
this.data[propertyName] = settings.data[propertyName])
Object.keys(settings.traits).forEach(propertyName =>
this.traits[propertyName] = settings.traits[propertyName])
if (settings.ready) this.ready = settings.ready
if (settings.callback) settings.callback(this);
}
export default class {
constructor(tag, settings) {
this.children = [];
this.data = {}
this.actions = {}
this.traits = {}
this.selectors = {}
this.element = document.createElement(tag)
applySettings.apply(this, [settings])
}
use(arg1, arg2) {
if (arg1 instanceof Controller) {
let controller = arg1;
let settings = arg2;
let mergedSettings = mergeDeeper(settings, controller.insertions);
controller.test(mergedSettings);
applySettings.apply(this, [mergedSettings])
} else if (arguments.length === 1) {
let settings = arg1;
applySettings.apply(this, [settings])
} else {
throw "bad settings passed to Element"
}
return this;
}
addEventListener(event, func) {
this.element.addEventListener(event, func)
}
delete() {
this.parent.removeChild(this.element)
}
style(styleName, value) {
this.element.style[styleName] = value
}
clearStyle(styleName) {
this.element.style[styleName] = ""
}
updateText(text) {
this.element.textContent = text
}
updateAttribute(attributeName, attributeContent) {
this.element.setAttribute(attributeName, attributeContent)
}
addClass(className) {
this.element.classList.add(className)
}
removeClass(className) {
this.element.classList.remove(className)
}
on(evt, func) {
this.element.addEventListener(evt, func)
}
select(id) {
let parts = id.split("#")
let selector = parts[parts.length - 1];
if (!this.selectors[selector]) debugger;
//throw "bad selector " + selector
return this.selectors[selector]
}
appendTo(elementWrapper) {
let element
if (elementWrapper.nodeName) element = elementWrapper
else {
element = elementWrapper.element
this.parent = element
collectChildSelectors(this, elementWrapper.selectors)
Object.keys(elementWrapper.traits).forEach(propertyName =>
this.traits[propertyName] = elementWrapper.traits[propertyName])
}
if (this.ready) this.ready(this)
element.appendChild(this.element)
return this
}
append(elementWrapper) {
let element
let wrapped = false
if (elementWrapper.nodeName) element = elementWrapper
else {
wrapped = true
element = elementWrapper.element
element.parent = this
if (element.id) this.selectors[element.id] = elementWrapper
if (elementWrapper.selector)
this.selectors[elementWrapper.selector] = elementWrapper
this.children.push(elementWrapper)
collectChildSelectors(elementWrapper, this.selectors)
Object.keys(this.traits).forEach(propertyName =>
elementWrapper.traits[propertyName] = this.traits[propertyName])
}
if (elementWrapper.ready) elementWrapper.ready(elementWrapper)
this.element.appendChild(element)
if (wrapped) return elementWrapper
}
}
Controller.js:
export default class {
constructor(settings) {
this.tests = {};
Object.keys(settings).forEach(key => {
let val = settings[key];
if (typeof val == "boolean") {
this.tests[key] = input => {
return input !== undefined
}
} else if (typeof val == "string") {
this.tests[key] = input => {
return typeof input === val
}
} else if (typeof val == "function") {
this.tests[key] = val;
}
})
}
test(obj) {
Object.keys(obj).forEach(key => {
if (!this.tests[key] || !this.tests[key](obj[key])) {
console.log("Controller test failed");
debugger;
}
});
}
insert(insertion) {
this.insertions = insertion;
return this;
}
}
Publisher.js
export default class {
constructor(socket) {
if (socket) this.socket = socket;
this.events = {};
}
on(command, func, socket = false) {
if (!this.events[command]) this.events[command] = [];
this.events[command].push(func);
if (socket && this.socket) socket.on(command, func);
}
emit(command, data = {}, socket = false) {
if (this.events[command]) {
this.events[command].forEach(func => func(data));
}
if (socket && this.socket) socket.emit(command, data);
}
}
Implementierung
app.js::
import Publisher from "/js/classes/publisher.js"
import Controller from "/js/classes/controller.js"
let publisher = new Publisher(io())
import Header from "/js/classes/header/header.js"
import Home from "/js/classes/pages/home/home.js"
import News from "/js/classes/pages/news/news.js"
import Leaderboard from "/js/classes/pages/leaderboard/leaderboard.js"
import Account from "/js/classes/pages/account/account.js"
import Builder from "/js/classes/pages/builder/builder.js"
let header = new Header(publisher)
let page = new Controller({
data: true, // () => { } // validate the value however you choose
traits: true, // It's good to have this capability for debugging
actions: true, // or for if your boss wants all your data interfaces
ready: true // validated because he read it in a hip dev blog
}).insert({ // <- But insertion is the feature you'll be using
traits: { // more often to test input data, debug, and like with
publisher // this case, apply a single input object to multiple
}, // Elements
actions: {
select: self => {
let target = "header/select-nav/" + self.data.name.toLowerCase()
self.traits.publisher.emit(target, this)
self.addClass("visible")
}
},
ready: self => {
self.traits.publisher.emit("header/add-nav", self)
}
});
new Home().use(page, {
data: {
name: "Home",
iconPath: "/assets/home/home-1.png",
cornerPath: "/assets/corners/corner-1.png",
}
}).appendTo(document.body)
new News().use(page, {
data: {
name: "News",
iconPath: "/assets/news/news-1.png",
cornerPath: "/assets/corners/corner-5.png"
}
}).appendTo(document.body)
new Leaderboard().use(page, {
data: {
name: "Leaderboard",
iconPath: "/assets/leaderboard/leaderboard-1.png",
cornerPath: "/assets/corners/corner-3.png",
}
}).appendTo(document.body)
new Account().use(page, {
data: {
name: "Account",
iconPath: "./assets/profile/profile-1.png",
cornerPath: "/assets/corners/corner-4.png",
}
}).appendTo(document.body)
new Builder().use(page, {
data: {
name: "Builder",
iconPath: "./assets/builder/builder-1.png",
cornerPath: "/assets/corners/corner-2.png",
}
}).appendTo(document.body).actions.select()
/js/classes/pages/builder/builder.js::
Hier habe ich eine Art ungerade returnAnweisung im Konstruktor verwendet, nur zu visuellen Zwecken, weil ich sie gerne new ModuleName()in der Datei verwende, in der sie verwendet wird, im Gegensatz zu einem Funktionsaufruf, aber Sie können es so oder so tun.
import Element from "/js/classes/element.js"
import NavBar from "/js/classes/pages/builder/nav-bar.js"
export default class {
constructor() {
return new Element("div", {
id: "builder",
classes: ["page"],
actions: {
select: self => {
let target = "header/select-nav/" + self.data.name.toLowerCase()
self.traits.publisher.emit(target, this)
self.addClass("visible")
}
},
ready: self => {
self.traits.publisher.emit("header/add-nav", self)
self.actions.select()
},
callback: self => {
self.append(new NavBar());
// add more elements
}
})
}
}
/js/classes/pages/header/header.js
import Element from "/js/classes/element.js"
import NavIcon from "./header-nav-icon.js"
export default class {
constructor(publisher) {
return new Element("div", {
id: "header",
traits: { publisher },
ready: self => {
self.append(new Element("div", {
selector: "title-wrapper",
classes: ["title-wrapper"],
ready: self => {
self.append(new Element("div", {
selector: "location-wrapper",
classes: ["location-wrapper"],
ready: self => {
self.traits.publisher.on("header/add-nav", data => {
self.append(new Element("div", {
selector: "location-item-wrapper",
classes: ["location-item-wrapper"],
ready: self => {
self.traits.publisher.on("header/select-nav/" +
data.data.name.toLowerCase(), data => {
self.addClass("visible")
});
self.append(new Element("div", {
id: data.data.name.toLowerCase() + "-nav",
classes: ["location-item", "heading"],
text: data.data.name
}))
self.append(new Element("img", {
classes: ["location-item-icon"],
attributes: {
"src": data.data.iconPath.split(".png")[0] + "-flat.png"
}
}))
self.append(new Element("img", {
selector: "corner",
classes: ["corner"],
attributes: {
"src": data.data.cornerPath
}
}))
}
}))
})
}
}))
self.append(new Element("div", {
selector: "sub-location-wrapper",
classes: ["sub-location-wrapper", "subheading"]
}))
}
}))
self.append(new Element("div", {
selector: "nav-wrapper",
classes: ["nav-wrapper", "center-contents"],
ready: self => {
self.traits.publisher.on("header/add-nav", data => {
console.log("header/add-nav, data", data.data)
console.log("adding nav-item")
self.append(new NavIcon().use({
data: data.data
}))
});
self.append(new Element("div", {
classes: ["title-bg-wrapper"],
ready: self => {
self.append(new Element("img", {
classes: ["title-bg-icon"],
attributes: {
"src": "./assets/header.png"
}
}))
self.append(new Element("div", {
classes: ["title-bg-text"],
innerHTML: "BIDRATE <br/> RENAISSANCE"
}))
}
}))
}
}))
}
}).appendTo(document.body)
}
}
Antworten
Aus einem kurzen Rückblick;
isCyclic-> Ich würde in Betracht ziehen,objinJSON.stringifydie relevante Ausnahme zu werfen und sie zu fangenfunction detectist kein guter Name, es ist okay wegen des Kontexts, könnte aber besser sein//console.log(obj, 'cycle at ' + key);<- schlechter KommentarDer Code verwendet beide
varundsetundconst, es ist ein echter Wert, Code zu analysieren und nurset/ zu verwendenconstfunction isObject(item)<- ein icky Name, da Sie wirklich prüfen, ob es sich um ein Objekt handelt, aber nicht um ein Array (das auch ein Objekt ist), weshalb Sie diese Funktion in nicht verwenden konntenif (obj && typeof obj === 'object')function isIterable(item) {<- sehr kitschig Namen, nimmt der Leser es einen Booleschen Wert zurück, vor allem mit der ersten Zeile istfalsedann aber auch Sie zurückkommenobjoderarres vielleicht nenneniterableTypediese Rückkehrundefined,'obj'oder'arr'?Sie überspringen geschweifte Klammern
isIterable, sollten Sie nichtdebuggergehört nicht in den Produktionscodediese
sourceProps.forEach(prop => { allProps.push(prop); }); targetProps.forEach(prop => { allProps.push(prop); });könnte sein
allProps = allProps.concat(sourceProps).concat(targetProps);Sie wissen, dass nur Object und Array iterierbar sind und dass die Eigenschaft so iterierbar ist
let filler if (iterable == "obj") filler = {}; else if (iterable == "arr") filler = [];kann sein
let filler = iterable=="obj"?{}:[];Im Großen und Ganzen würde ich dies über den ternären Operator nachlesen
if (source[prop] !== undefined) { merged[prop] = source[prop] } else { merged[prop] = target[prop] }könnte kurzgeschlossen und lesbarer sein (für mich);
merged[prop] = source[prop]?source[prop]:target[prop];und in diesem Fall könnte es sogar auf verkürzt werden
merged[prop] = source[prop] || target[prop];Der Code verwendet inkonsistent Semikolons, was beim Lesen sehr ärgerlich ist
Sie sollten einen Namens- / Codierungsstandard auswählen und sich daran halten, bevor das
functionSchlüsselwort verwendet wurde, und jetzt wechselt der Code zu diesem;const collectChildSelectors = (elementWrapper, selectors) => {Nicht sicher, warum Sie nicht alle möglichen Parameter angeben
addEventListeneraddEventListener(event, func) { this.element.addEventListener(event, func) }Wenn Sie die folgenden fünf Schritte mit verschiedenen Parametern ausführen, kann eine Hilfsfunktion verwendet werden, um die Lesbarkeit zu verbessern.
Object.keys(settings.styles).forEach(styleName => this.element.style[styleName] = settings.styles[styleName])
Gesamtübersicht
Beschränken Sie den Umfang auf eine Gesamtübersicht, was Zeit sparen sollte, da es sich um einen großen Codeblock für eine Überprüfung handelt.
Während ich die Module an einen Plunker angeschlossen habe, ist es für mich immer noch schwierig festzustellen, ob das Framework mir bei einem SPA mit begrenztem Code helfen würde. Ich sehe viele Methoden, die selfals ersten (und oft einzigen) Parameter akzeptiert werden. Warum können sie nicht operieren this? Ist der Kontext dafür nicht richtig gebunden?
Ich habe ein Event-Emitter-Modul für ein Interview erstellt. Die Anforderungen klingen wie das Pub-Sub-Muster und ich habe ähnliche Methoden wie der Publisher implementiert. Die Anforderungen erforderten eine Möglichkeit, einen "einmaligen" Handler zu haben, sowie eine Möglichkeit, die Registrierung einer registrierten Handlerfunktion aufzuheben. Sie könnten erwägen, solche Funktionen mit Ihrem Publisher-Modul anzubieten.
Das Fazit lautet: Wenn Sie der Meinung sind, dass Sie mit diesem Framework weniger Code schreiben können als mit vielen anderen Frameworks, verwenden Sie ihn.
Gezieltes JS-Feedback
Ich habe festgestellt, dass das constSchlüsselwort in Ihrem Code nur zweimal vorkommt - für zwei Funktionsausdrücke, dh collectChildSelectorsund applySettings. Es wird empfohlen, constdas Standardschlüsselwort für alle Variablen zu verwenden let. Wenn eine Neuzuweisung erforderlich ist, wechseln Sie zu using . Vermeiden Sie es auch var, es sei denn, es besteht Bedarf an einer globalen Variablen, die jedoch ebenfalls verpönt ist.
Einige Teile des Codes werden ===zum Vergleichen von Werten verwendet, andere jedoch ==. Eine empfohlene Vorgehensweise ist immer ein strikter Typvergleich.
Verwenden Sie zur besseren Lesbarkeit einen konsistenten Anführungszeichenstil für Zeichenfolgenliterale - entweder einfache oder doppelte Anführungszeichen, jedoch nicht beide.
mergeDeeper()könnte den Spread-Operator anstelle von forEach () verwenden -> for sourcePropsund drückentargetProps
allProps.push(...sourceProps, ...targetProps)
Der Name der Funktion isIterableerscheint etwas seltsam, da sie einen String oder einen Booleschen Wert zurückgeben kann. Vielleicht wäre ein passenderer Name iterableType- selbst wenn er zurückkehrt, falsewürde der Anrufer wissen, dass der Wert nicht iterierbar ist.