简体   繁体   中英

Return in recursive function doesn't exit function

I am having hard time fixing my recursive function, which is a simplified tool for to scan DOM items and return matching element when found somewhere within document.

find: function(selector, element) {
    if(selector !== undefined) {
        if(element === undefined) {
            element = this.e;
        }
        var elements = element.childNodes;
        for(var i = 0; i < elements.length; i++) {
            if(elements[i].nodeType === 1) {
                console.log(elements[i]);
                if(this.has_class(selector, elements[i]) === true) {
                    console.log('YAY, found it', elements[i]);
                    return elements[i];
                } else {
                    if(elements[i].childNodes.length > 0) {
                        this.find(selector, elements[i]);
                    }
                }
            }
        }
    }
    return false;
}

So the function should scan through children (and possibly their children) of given DOM element and return found element, otherwise go deeper and try again.

This is debuggable DEMO .

As you can see in logs, it triggered console.log('found'); but it did not leave function returning it, but continue and eventually returned false (as of not found). How can it be fixed?

 var tools = { e: document.querySelector('.breadcrumbs'), has_class: function(name, element) { if (element.className === name) { return true; } return false; }, find: function(selector, element) { if (selector !== undefined) { if (element === undefined) { element = this.e; } var elements = element.childNodes; for (var i = 0; i < elements.length; i++) { if (elements[i].nodeType === 1) { console.log(elements[i]); if (this.has_class(selector, elements[i]) === true) { console.log('YAY, found it', elements[i]); return elements[i]; } else { if (elements[i].childNodes.length > 0) { this.find(selector, elements[i]); } } } } } return false; } }; console.log(tools.find('test')); 
 <div class="breadcrumbs" data-active="true"> <div class="bc_navigation" onclick="events.bc_toggle(event, this);"> <span class="bc_arrow"></span> </div> <div class="bc_content"> <div class="bc_wrapper"> <div class="step"> <span class="dot"></span><a onclick="events.go_home(event, this);">Landing</a> </div> <div class="step"> <span class="dot"></span><a href="#prematch[prematch-sport][1|0|0|0|0]">Soccer</a> </div> <div class="step"> <span class="dot"></span><a href="#prematch[prematch-group][1|4|0|0|0]">International</a> </div> <div class="step"> <span class="dot"></span><a class="test" href="#prematch[prematch-event][1|4|16|10|0]">Int - World Cup</a> </div> <div class="step"> <span class="dot"></span><a>Russia - Saudi Arabia</a> </div> </div> </div> </div> 

The return exits the call to find where you found the element, but doesn't unwind all the calls that lead up to it.

Instead of

this.find(selector, elements[i]);

...in your else , you need to see if you got the element and, if so, return:

var result = this.find(selector, elements[i]);
if (result) {
    return result;
}

That lets it propagate up the chain.

Updated Live Example:

 var tools = { e: document.querySelector('.breadcrumbs'), has_class: function(name, element) { if (element.className === name) { return true; } return false; }, find: function(selector, element) { if (selector !== undefined) { if (element === undefined) { element = this.e; } var elements = element.childNodes; for (var i = 0; i < elements.length; i++) { if (elements[i].nodeType === 1) { console.log(elements[i]); if (this.has_class(selector, elements[i]) === true) { console.log('YAY, found it', elements[i]); return elements[i]; } else { if (elements[i].childNodes.length > 0) { var result = this.find(selector, elements[i]); if (result) { return result; } } } } } } return false; } }; console.log(tools.find('test')); 
 <div class="breadcrumbs" data-active="true"> <div class="bc_navigation" onclick="events.bc_toggle(event, this);"> <span class="bc_arrow"></span> </div> <div class="bc_content"> <div class="bc_wrapper"> <div class="step"> <span class="dot"></span><a onclick="events.go_home(event, this);">Landing</a> </div> <div class="step"> <span class="dot"></span><a href="#prematch[prematch-sport][1|0|0|0|0]">Soccer</a> </div> <div class="step"> <span class="dot"></span><a href="#prematch[prematch-group][1|4|0|0|0]">International</a> </div> <div class="step"> <span class="dot"></span><a class="test" href="#prematch[prematch-event][1|4|16|10|0]">Int - World Cup</a> </div> <div class="step"> <span class="dot"></span><a>Russia - Saudi Arabia</a> </div> </div> </div> </div> 

This is one of the key things about recursive functions: When they call themselves, they must look at the result of that call and propagate it when appropriate.

Result of the recursive call to find isn't processed. You should check to result value of the recursive call and should return its value when the recursive call located the element:

find: function(selector, element) {
    if(selector !== undefined) {
        if(element === undefined) {
            element = this.e;
        }
        var elements = element.childNodes;
        for(var i = 0; i < elements.length; i++) {
            if(elements[i].nodeType === 1) {
                console.log(elements[i]);
                if(this.has_class(selector, elements[i]) === true) {
                    console.log('YAY, found it', elements[i]);
                    return elements[i];
                } else {
                    var foundElement = this.find(selector, elements[i]);
                    // *** Added this check to return the located element from the recursive call
                    if (foundElement != false) {
                        return foundElement;
                    }
                }
            }
        }
    }
    return false;
}

querySelector potential wasted

Your tools library of functions is a good effort, but it shows a lack of understanding of how querySelector actually works. To demonstrate my point, your entire program is rewritten below

// starting with the Document element, get the first child matching '.breadcrumbs'
const elem =
  document.querySelector ('.breadcrumbs')

// starting with `elem`, get the first child matching '.test'
const child =
  elem.querySelector ('.test')

console.log (child)
// <a class="test" href="#prematch[prematch-event][1|4|16|10|0]">Int - World Cup</a>

 const elem = document.querySelector ('.breadcrumbs') const someChild = elem.querySelector ('.test') console.log (someChild) // <a class="test" href="#prematch[prematch-event][1|4|16|10|0]">Int - World Cup</a> 
 <div class="breadcrumbs" data-active="true"> <div class="bc_navigation" onclick="events.bc_toggle(event, this);"> <span class="bc_arrow"></span> </div> <div class="bc_content"> <div class="bc_wrapper"> <div class="step"> <span class="dot"></span><a onclick="events.go_home(event, this);">Landing</a> </div> <div class="step"> <span class="dot"></span><a href="#prematch[prematch-sport][1|0|0|0|0]">Soccer</a> </div> <div class="step"> <span class="dot"></span><a href="#prematch[prematch-group][1|4|0|0|0]">International</a> </div> <div class="step"> <span class="dot"></span><a class="test" href="#prematch[prematch-event][1|4|16|10|0]">Int - World Cup</a> </div> <div class="step"> <span class="dot"></span><a>Russia - Saudi Arabia</a> </div> </div> </div> </div> 

multiple classes

Above, querySelector already does everything we need it to, but your tools.has_class exhibits another deficiency – elements can have more than one class. Your function would've skipped over a child that had an attribute class="test foo" .

For the sake of discussion, if you had to implement this on your own, you could adapt your has_class function to separate the element's classes by space, then check each class for a match – or you could use Element.classList which already includes a contains function

 const elem = document.querySelector ('.test') console.log (elem.classList) // { DOMTokenList [ "foo", "test", "bar" ] } console.log (elem.classList.contains ('foo')) // true console.log (elem.classList.contains ('test')) // true console.log (elem.classList.contains ('dog')) // false 
 <div class="foo test bar"></div> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM