Дом-манипуляция поиск-заказ алгоритм


(это пересечения сообщений из StackOverflow, его предложил я спросил Здесь)

Цель

У меня дом около 70 элементов (div с некоторым материалам) . Мне нужно двигаться и переключать отображение этих слоев довольно много и довольно быстро. Скорость-одна из самых важных вещей. Триггер для перемещения и переключения эти ДИВС-это поисковый запрос, вроде как Гугл мгновенный, за исключением того, что все DOM-элементы я перемещения и переключения загружаются в первый раз (так что больше никаких звонков на сервер).

Реализация

Я реализовал это следующим образом: рядом с дом и передать в JavaScript-массив объектов, представляющих ДИВС вместе с их атрибутами, как положение, содержание и так далее. Этот массив действует как зеркало в дом. Когда пользователь начинает печатать я начинаю цикл через массив и расчета за объект див/, что должно быть сделано к нему. Я вообще-то цикл по этому массиву пару раз: я сначала проверить, если мне нужно посмотреть на объект див/, то я смотрю на объект, то мне нужно смотреть на содержание, то я смотрю на содержание.

Одна из вещей, которую я делаю в эти петли-это установка флагов для работы с DOM. Как я понимаю, чтение и манипулирования и дом-один из самых медленных операций в JavaScript, по сравнению с другими вещами я делаю (циклы чтения и записи атрибутов объектов и т. д.). Я также сделал некоторые профилирования, подтверждающие это предположение. Так на каждом углу я пытался предотвратить "трогательную" дом для повышения производительности. В конце моего алгоритма я еще раз петли, выполнить все необходимые действия дом и сбросить флаги, чтобы сигнализировать их читали. Для кросс-браузерной совместимости я использую jQuery, чтобы на самом деле делать действия дом (выбор, перемещение, переключение). Я не использовать jQuery, чтобы петля над моей массива.

Проблема

Моя проблема сейчас заключается в том, что я думаю, что мой код и структуры данных-это немного некрасиво. У меня это скорее большой многомерный массив с большим количеством атрибутов и флагов. Я неоднократно повторять его функции вызова функции вызова функции. Когда вы сталкиваетесь с проблемами, я могу (до сих пор) несколько легче отлаживать вещи, но это не чувствовать себя хорошо.

Вопрос

Существует ли шаблон проектирования или общее решение для такого рода проблемы? Я подозреваю, что я мог бы реализовать какой-то смарт соединение между блоком и дом, где мне бы не пришлось явно задать флаги и выполнить дом действий, но я не представляю, как такое соединение должно работать, или если это даже хорошая идея или просто усложнять.

Существуют ли другие структуры данных и алгоритмические принципы я недоглядел при решении этой проблемы?

Спасибо!

Примечание: Я не загрязнять глобальное пространство имен, эти функции определены и использованы внутри закрытия.

/**
 * Applies the filter (defined by the currentQuery and to the cats array)
 *
 * -checks whether matching is needed
 * -if needed does the matching
 * -checks whether DOM action is needed
 * -if needed executes DOM action
 *
 * cats is an array of objects representing categories
 * which themselves contain an array of objects representing links
 * with some attributes
 *
 * cats = (array) array of categories through which to search
 * currentQuery = (string) with which to find matches within the cats
 * previousQuery = (string) with previously-typed-in query
 *
 * no return values, results in DOM action and manipulation of cats array
 */
function applyFilter(cats,currentQuery, previousQuery) {
    cats = flagIfMatchingIsNeededForCats(cats,currentQuery,previousQuery);
    cats = matchCats(cats,currentQuery);
    cats = flagIfMatchingIsNeededForLinks(cats,currentQuery,previousQuery);
    cats = matchLinks(cats,currentQuery);
    cats = flagIfDisplayToggleNeeded(cats);
    if ( currentQuery.length > 0 ) {
        cats = flagIfMoveNeeded(cats);
    } else {
        // move everything back to its original position
        cats = flagMoveToOriginalPosition(cats);
    }

    // take action on the items that need a DOM action
    cats = executeDomActions(cats);
}

/**
* Sets a flag on a category if it needs matching, parses and returns cats
*
* Loops through all categories and sets a boolean to signal whether they 
* need matching.
*
* cats = (array) an array with all the category-objects in it
* currentQuery = (string) the currently typed-in query
* previousQuery = (string) the query that was previously typed in
*
* returns (array) cats, possibly in a different state
*/ 
function flagIfMatchingIsNeededForCats(cats,currentQuery,previousQuery) {
    var newQueryIsLonger = isNewQueryLonger(currentQuery, previousQuery);

    // check if matching is necessary for categories
    for (var i = 0; i < cats.length; i++) {
        cats[i].matchingNeeded = isMatchingNeededForCat(
            cats[i].matches
            ,newQueryIsLonger
            ,currentQuery.length
            ,cats[i].noMatchFoundAtNumChars
        );
    }
    return cats;
}

/**
* Whether the new query is longer than the previous one
*
* currentQuery = (string) the currently typed-in query
* previousQuery = (string) the query that was previously typed in
*
* returns (boolean) true/false
*/
function isNewQueryLonger(currentQuery, previousQuery) {
    if (previousQuery == false) {
        return true;
    }

    return currentQuery.length > previousQuery.length
}

/**
* Deduces if a category needs to be matched to the current query
*
* This function helps in improving performance. Matching is done using 
* indexOf() which isn't slow of itself but preventing even fast processes
* is a good thing (most of the time). The function looks at the category,
* the current and previous query, then decides whether
* matching is needed.
*
* currentlyMatched = (boolean) on whether the boolean was matched to the previous query
* newQueryIsLonger = (boolean) whether the new query is longer
* queryLength = (int) the length of the current query
* noMatchFoundAtNumChars = (int) this variable gets set (to an int) for a 
*   category when it switches from being matched to being not-matched. The
*   number indicates the number of characters in the first query that did
*   not match the category. This helps in performance because we don't need
*   to recheck the categoryname if it doesn't match now and the new query is
*   even longer.
*
* returns (boolean) true/false
*/
function isMatchingNeededForCat(currentlyMatched, newQueryIsLonger ,queryLength ,noMatchFoundAtNumChars) {
    if (typeof(currentlyMatched) == 'undefined') {
        // this happens the first time we look at a category, for all 
        // categories this happens with an empty query and that matches with
        // everything
        currentlyMatched = true;
    }

    if (currentlyMatched && newQueryIsLonger) {
        return true;
    }

    if (!currentlyMatched && !newQueryIsLonger) {
        // if currentlyMatched == false, we always have a value for
        // noMatchFoundAtNumChars

        // matching is needed if the first "no-match" state was found 
        // at a number of characters equal to or bigger than 
        // queryLength
        if ( queryLength < noMatchFoundAtNumChars ) {
            return true;
        }
    }

    return false;
}

/**
* Does matching on categories for all categories that need it.
*
* Sets noMatchFoundAtNumChars to a number if the category does not match.
* Sets noMatchFoundAtNumChars to false if the category matches once again.
*
* cats = (array) an array with all the category-objects in it
* currentQuery = (string) the currently typed-in query
*
* returns (array) cats, possibly in a different state
*/
function matchCats(cats,currentQuery) {
    for (var i = 0; i < cats.length; i++) {
        if (cats[i].matchingNeeded) {
            cats[i].matches = categoryMatches(cats[i],currentQuery);

            // set noMatchFoundAtNumChars
            if (cats[i].matches) {
                cats[i].noMatchFoundAtNumChars = false;
            } else {
                cats[i].noMatchFoundAtNumChars = currentQuery.length;
            }
        }
    }
    return cats;
}

/**
* Check if the category name matches the query
*
* A simple indexOf call to the string category_name
*
* category = (object) a category object
* query = (string) the query
*
* return (boolean) true/false
*/
function categoryMatches(category,query) {
    catName = category.category_name.toLowerCase();
    if (catName.indexOf(query) !== -1 ) {
        return true;
    }

    return false;
}

/**
* Checks links to see whether they need matching
*
* Loops through all cats, selects the non-matching, for every link decides
* whether it needs matching
*
* cats = (array) an array with all the category-objects in it
* currentQuery = the currently typed-in query
* previousQuery = the query that was previously typed in
*
* returns (array) cats, possibly in a different state
*/
function flagIfMatchingIsNeededForLinks(cats,currentQuery,previousQuery) {
    var newQueryIsLonger = isNewQueryLonger(currentQuery, previousQuery);
    for (var i = 0; i < cats.length; i++) {
        if (!cats[i].matches) { // only necessary when cat does not match
            for (var k = 0; k < cats[i].links.length; k++) {
                cats[i].links[k].matchingNeeded = isMatchingNeededForLink(
                    cats[i].links[k].matches
                    ,newQueryIsLonger
                    ,currentQuery.length
                    ,cats[i].links[k].noMatchFoundAtNumChars
                );
            }
        }
    }
    return cats;
}

/**
* Checks whether matching is needed for a specific link
*
* This function helps in improving performance. Matching is done using 
* indexOf() for every (relevant) link property, this function helps decide
* whether that *needs* to be done. The function looks at some link 
* properties, the current and previous query, then decides whether
* matching is needed for the link.
*
* currentlyMatched = (boolean) on whether the boolean was matched to the previous query
* newQueryIsLonger = (boolean) whether the new query is longer
* queryLength = (int) the length of the current query
* noMatchFoundAtNumChars = (int) this variable gets set (to an int) for a 
*   link when it switches from being matched to being not-matched. The
*   number indicates the number of characters in the first query that did
*   not match the link. This helps in performance because we don't need
*   to recheck the link properties in certain circumstances.
*
* return (boolean) true/false
*/
function isMatchingNeededForLink(currentlyMatched, newQueryIsLonger ,queryLength ,noMatchFoundAtNumChars) {
    if (typeof(currentlyMatched) == 'undefined') {
        // this happens to a link the first time a cat does not match and
        // we want to scan the links for matching
        return true;            
    }

    if (currentlyMatched && newQueryIsLonger) {
        return true;
    }

    if (!currentlyMatched && !newQueryIsLonger) {
        // if currentlyMatched == false, we always have a value for
        // noMatchFoundAtNumChars

        // matching is needed if the first "no-match" state was found 
        // at a number of characters equal to or bigger than 
        // queryLength
        if ( queryLength < noMatchFoundAtNumChars ) {
            return true;
        }
    }

    return false;
}

/**
* Does matching on links for all links that need it.
*
* Sets noMatchFoundAtNumChars to a number if the link does not match.
* Sets noMatchFoundAtNumChars to false if the link matches once again.
*
* cats = (array) an array with all the category-objects in it
* currentQuery = (string) the currently typed-in query
*
* returns (array) cats, possibly in a different state
*/
function matchLinks(cats,currentQuery) {
    for (var i = 0; i < cats.length; i++) {
        // category does not match, check if links in the category match
        if (!cats[i].matches) {
            for (var k = 0; k < cats[i].links.length; k++) {
                if (cats[i].links[k].matchingNeeded) {
                    cats[i].links[k].matches = linkMatches(cats[i].links[k],currentQuery);
                }

                // set noMatchFoundAtNumChars
                if (cats[i].links[k].matches) {
                    cats[i].links[k].noMatchFoundAtNumChars = false;
                } else {
                    cats[i].links[k].noMatchFoundAtNumChars = currentQuery.length;
                }
            }
        }
    }
    return cats;
}    

/**
* Check if any of the link attributes match the query
*
* Loops through all link properties, skips the irrelevant ones we use for filtering
*
* category = (object) a category object
* query = (string) the query
*
* return (boolean) true/false
*/
function linkMatches(link,query) {
    for (var property in link) {
        // just try to match certain properties
        if (
                !( // if it's *not* one of the following
                    property == 'title'
                    || property == 'label'
                    || property == 'url'
                    || property == 'keywords'
                    || property == 'col'
                    || property == 'row'
                )
        ){
            continue;
        }

        // if it's an empty string there's no match
        if( !link[property] ) {
            continue;
        }

        var linkProperty = link[property].toLowerCase();
        if (linkProperty.indexOf(query) !== -1){
            return true;
        }

    }
    return false;
}

/**
* Flags if toggling of display is needed for a category.
*
* Loops through all categories. If a category needs some DOM
* action (hiding/showing) it is flagged for action. This helps in 
* performance because we prevent unnecessary calls to the DOM (which are 
* slow).
*
* cats = (array) an array with all the category-objects in it
*
* returns (array) cats, possibly in a different state
*/
function flagIfDisplayToggleNeeded(cats) {
    for (var i = 0; i < cats.length; i++) {
        // this happens the first time we look at a category
        if (typeof(cats[i].currentlyDisplayed) == 'undefined') {
            cats[i].currentlyDisplayed = true;
        }

        var visibleLinks = 0;
        // a cat that matches, all links need to be shown
        if (cats[i].matches) {
            visibleLinks = cats[i].links.length;
        } else {
            // a cat that does not match
            for (var k = 0; k < cats[i].links.length; k++) {
                if (cats[i].links[k].matches) {
                    visibleLinks++;
                }
            }            
        }

        // hide/show categories if they have any visible links
        if (!cats[i].currentlyDisplayed && visibleLinks > 0 ) {
            cats[i].domActionNeeded = 'show';
        } else if( cats[i].currentlyDisplayed && visibleLinks == 0 ){
            cats[i].domActionNeeded = 'hide';
        }           
    }
    return cats;
}

/**
* Flags categories to be moved to other position.
*
* Loops through all categories and looks if they are distributed properly. 
* If not it moves them to another position. It remembers the old position so
* it can get the categories back in their original position.
*
* cats = (array) an array with all the category-objects in it
*
* returns (array) cats, possibly in a different state
*/
function flagIfMoveNeeded(cats) {
    var numCats, numColumns, displayedCats, i, moveToColumn, tmp;

    numColumns = getNumColumns(cats);
    numDisplayedCats = getNumDisplayedCats(cats);        
    columnDistribution = divideInPiles(numDisplayedCats, numColumns);

    // optional performance gain: only move stuff when necessary
    // think about this some more

    // we convert the distribution in columns to a table so we get columns
    // and positions
    catDistributionTable = convertColumnToTableDistribution(columnDistribution);

    // sort the categories, highest positions first
    // catPositionComparison is a function to do the sorting with
    // we could improve performance by doing this only once
    cats = cats.sort(catPositionComparison);

    for (i = 0; i < cats.length; i += 1) {
        if( categoryWillBeDisplayed(cats[i]) ){
            tmp = getNewPosition(catDistributionTable); // returns multiple variables
            catDistributionTable = tmp.catDistributionTable;
            cats[i].moveToColumn = tmp.moveToColumn;
            cats[i].moveToPosition = tmp.moveToPosition;
        } else {
            cats[i].moveToColumn = false;
            cats[i].moveToPosition = false;
        }
    }
    return cats;
}

/**
* A comparison function to help the sorting in flagIfMoveNeeded()
*
* This function compares two categories and returns an integer value 
* enabling the sort function to work.
*
* cat1 = (obj) a category
* cat2 = (obj) another category
*
* returns (int) signaling which category should come before the other
*/
function catPositionComparison(cat1, cat2) {
    if (cat1.category_position > cat2.category_position) {
        return 1; // cat1 > cat2
    } else if (cat1.category_position < cat2.category_position) {
        return -1; // cat1 < cat2
    }

    // the positions are equal, so now compare on column, if we need the 
    // performance we could skip this
    if (cat1.category_column > cat2.category_column) {
        return 1; // cat1 > cat2
    } else if (cat1.category_column < cat2.category_column) {
        return -1; // cat1 < cat2
    }

    return 0; // position and column are equal
}

/**
* Checks if a category will be displayed for the currentQuery
*
* cat = category (object) 
*
* returns (boolean) true/false
*/
function categoryWillBeDisplayed(cat) {
    if( (cat.currentlyDisplayed === true  && cat.domActionNeeded !== 'hide')
        ||
        (cat.currentlyDisplayed === false && cat.domActionNeeded === 'show')
    ){
        return true;
    } else {
        return false;
    }
}

/**
 * Gets the number of unique columns in all categories
 *
 * Loops through all cats and saves the columnnumbers as keys, insuring
 * uniqueness. Returns the number of
 *
 * cats = (array) of category objects
 *
 * returns (int) number of unique columns of all categories
 */
function getNumColumns(cats) {
    var columnNumber, uniqueColumns, numUniqueColumns, i;

    uniqueColumns = [];
    for (i = 0; i < cats.length; i += 1) {
        columnNumber = cats[i].category_column;
        uniqueColumns[columnNumber] = true;
    }

    numUniqueColumns = 0;
    for (i = 0; i < uniqueColumns.length; i += 1) {
        if( uniqueColumns[i] === true ){
            numUniqueColumns += 1
        }
    }
    return numUniqueColumns;
}

/**
 * Gets the number of categories that will be displayed for the current query
 *
 * cats = (array) of category objects
 *
 * returns (int) number of categories that will be displayed
 */
function getNumDisplayedCats(cats) {
    var numDisplayedCats, i;

    numDisplayedCats = 0;
    for (i = 0; i < cats.length; i += 1) {
        if( categoryWillBeDisplayed(cats[i]) ){
            numDisplayedCats += 1;
        }
    }
    return numDisplayedCats;
}

/**
 * Evenly divides a number of items into piles
 *
 * Uses a recursive algorithm to divide x items as evenly as possible over
 * y piles.
 *
 * items = (int) a number of items to be divided
 * piles = (int) the number of piles to divide items into
 *
 * return an array with numbers representing the number of items in each pile
 */
function divideInPiles(items, piles) {
    var averagePerPileRoundedUp, rest, pilesDivided;
    pilesDivided = [];

    if (piles === 0) {
        return false;
    }

    averagePerPileRoundedUp = Math.ceil(items / piles);
    pilesDivided.push(averagePerPileRoundedUp);
    rest = items - averagePerPileRoundedUp;

    if (piles > 1) {
        pilesDivided = pilesDivided.concat(divideInPiles(rest, piles - 1)); // recursion
    }

    return pilesDivided;
}

/**
 * Converts a column distribution to a table
 *
 * Receives a one-dimensional distribution array and converts it to a two-
 * dimensional distribution array.
 *
 * columnDist (array) an array of ints, example [3,3,2]
 *
 * returns (array) two dimensional array, rows with "cells"
 * example: [[true,true,true],[true,true,true],[true,true,false]]
 * returns false on failure
 */
function convertColumnToTableDistribution(columnDist) {
    'use strict';
    var numRows, row, numCols, col, tableDist;

    if (columnDist[0] === 'undefined') {
        return false;
    }

    // the greatest number of items are always in the first column
    numRows = columnDist[0];
    numCols = columnDist.length;
    tableDist = []; // we 

    for (row = 0; row < numRows; row += 1) {
        tableDist.push([]); // add a row
        // add "cells"
        for (col = 0; col < numCols; col += 1) {
            if (columnDist[col] > 0) {
                // the column still contains items
                tableDist[row].push(true);
                columnDist[col] -= 1;
            } else {
                tableDist[row][col] = false;
            }
        }
    }
    return tableDist;
}

/**
* Returns the next column and position to place a category in.
*
* Loops through the table to find the first position that can be used. Rows
* and positions have indexes that start at zero, we add 1 in the return 
* object.
*
* catDistributionTable = (array) of rows, with positions in them
*
* returns (object) with the mutated catDistributionTable, a column and a 
* position
*/
function getNewPosition(catDistributionTable) {
    var numRows, row, col, numCols, moveToColumn, moveToPosition;

    numRows = catDistributionTable.length;

    findposition:
    for (row = 0; row < numRows; row += 1) {
        numCols = catDistributionTable[row].length;
        for ( col = 0; col < numCols; col += 1) {
            if (catDistributionTable[row][col] === true) {
                moveToColumn = col;
                moveToPosition = row;
                catDistributionTable[row][col] = false;
                break findposition;
            }
        }
    }

    // zero-indexed to how it is in the DOM, starting with 1
    moveToColumn += 1;
    moveToPosition += 1;

    return {
        'catDistributionTable'  : catDistributionTable
        ,'moveToColumn'         : moveToColumn
        ,'moveToPosition'       : moveToPosition
    };
}

/**
* Sets the target position of a category to its original location
*
* Each category in the DOM has attributes defining their original position.
* After moving them around we might want to move them back to their original
* position, this function flags all categories to do just that.
*
* cats = (array) of category objects
*
* All of the possible return values
*/
function flagMoveToOriginalPosition(cats) {
    for (i = 0; i < cats.length; i += 1) {
        cats[i].moveToColumn = cats.category_column;
        cats[i].moveToPosition = cats.category_position;
    }
    return cats;
}

/**
* Execute DOM actions for the items that need DOM actions
*
* Parses all categories, executes DOM actions on the categories that
* require a DOM action.
*
* cats = (array) an array with all the category-objects in it
*
* no return values
*/
function executeDomActions(cats) {
    for (var i = 0; i < cats.length; i++) {
        var category_id = cats[i].category_id;

        // toggle display of columns
        if (cats[i].domActionNeeded == 'show') {
            showCategory(category_id);
            cats[i].currentlyDisplayed = true;
        }

        if (cats[i].domActionNeeded == 'hide') {
            hideCategory(category_id);
            cats[i].currentlyDisplayed = false;

        }
        cats[i].domActionNeeded = false;

        // for every currentlyDisplayed category move it to new location
        // if necessary
        if (cats[i].currentlyDisplayed && cats[i].moveToColumn !== false) {
            cats[i] = moveCat(cats[i]);
        }
    }
    return cats;
}

/**
* Show a certain category
*
* category_id = (int) the id of the category that needs to be shown
*
* no return values
*/
function showCategory(category_id) {
    $('#' + category_id).show();
}

/**
* Hide a certain category
*
* category_id = (int) the id of the category that needs to be hidden
*
* no return values
*/
function hideCategory(category_id) {
    $('#' + category_id).hide();
}

/**
 * Moves a category to the position set in its attributes
 *
 * A category can have attributes defining the column and position (or row)
 * this function moves the category to the correct column and position.
 *
 * cat = (object) category
 *
 * returns (object) category
 */
function moveCat(cat) {
    var columnSelector, catSelector;
    columnSelector = '#column' + cat.moveToColumn + ' .column_inner' + ' .hiddenblocks';
    catSelector = '#' + cat.category_id;
    $(columnSelector).prepend($(catSelector));

    // reset target coordinates
    cat.moveToColumn = false;
    cat.moveToPosition = false;

    return cat;
}


465
5
задан 9 декабря 2011 в 12:12 Источник Поделиться
Комментарии
2 ответа


  • Во-первых...все это кошки = и вернуть котов путает вещи, поскольку вы никогда не замените кошки -- элементы внутри, что вы связываться. кошки = doSomethingWith(кошки) подразумевает для меня, что doSomethingWith(кошки) клоны массива и не трогать оригинал.

  • Во-вторых, ты слишком гранулирования. Путь вы делаете вещи (делаю все одно, все рядом), но все это требует вашего объекта, чтобы носить все виды флагов для передачи информации между функциями. Однако, вы можете сделать что-то вроде

x(cats)
y(cats)
z(cats)

function x()
for each (item) in cats:
do step 1 to item

function y()
for each (item) in cats:
if item.(step 1 result) says so, do step 2

function z()
for each (item) in cats:
...

что-то вроде

for each (item) in cats:
do step 1 to item
if (step 1 result) says so, do step 2
...

который позволит сохранить промежуточные результаты местных, и, таким образом, исключить необходимость в кучу свои флаги.


  • isMatchingNeededForCat и isMatchingNeededForLink сделать то же самое (с теми же свойствами, даже!), и должны быть объединены в одну функцию.

  • Кроме того, имейте уровней абстракции в виду. На более высоком уровне applyFilter, вас не особо волнует, если согласование требуется для товара. Это оптимизация деталей, которые могут быть отнесены к той точке, где вы на самом деле пытаетесь, чтобы соответствовать категории, в особенности после (если previousQuery и currentQuery достаточно похожие, как код, кажется, предположить, что они есть) он практически не влияет на результат. (isMatchingNeeded может всегда возвращать true, если он хотел, и код должен работать, хотя и медленнее.)

С тех вещей исправлена, и некоторые перестановки параметров и такие, это то, что я за все, вплоть до flagIfDisplayToggleNeeded. Код был уменьшен примерно на 60% (даже больше, если не считать комментариев), и количество "движущихся частей" были исключены.

/**
* Applies the filter (defined by `query`) to the cats array
*
* -finds and flags matching categories, and matching links within categories
* -checks whether DOM action is needed
* -if needed executes DOM action
*
* cats is an array of objects representing categories
* which themselves contain an array of objects representing links
* with some attributes
*
* cats = (array) array of categories through which to search
* currentQuery = (string) with which to find matches within the cats
* previousQuery = (string) with previously-typed-in query
*
* no return values, results in DOM action and manipulation of cats array
*/
function applyFilter(cats, query, previousQuery) {
// previousQuery must be a string (or at least quack like one)
if (previousQuery == undefined || !('length' in previousQuery)) {
previousQuery = '';
}

for (var i = 0; i < cats.length; i++) {
var cat = cats[i];
matchCategory(cat, currentQuery, previousQuery);

var catShouldBeVisible = cat.matches;

if (!cat.matches) {
for (var k = 0; k < cat.links.length; k++) {
matchLink(cat.links[k], currentQuery, previousQuery);
if (cat.links[k].matches) catShouldBeVisible = true;
}
}

if (cat.isCurrentlyDisplayed != catShouldBeVisible) {
cat.domActionNeeded = (catShouldBeVisible ? 'show' : 'hide');
}
}

// Exercise for the reader: see about getting rid of these. :)

if ( query.length > 0 ) {
flagIfMoveNeeded(cats);
} else {
// move everything back to its original position
flagMoveToOriginalPosition(cats);
}

// take action on the items that need a DOM action
executeDomActions(cats);
}

/**
* Attempts to match the given link to the current query, if necessary.
* `cat`'s `matches` and `noMatchFoundAtNumChars` properties will be set accordingly.
*
* cat (object) : the category to check. Will be modified as described below.
* query (string) : the current query.
* previousQuery (string) : the previous query, for which `cat` holds valid results.
*
* No return value. Modifies `cat` to reflect the results of the match attempt.
* The `matches` property will reflect whether the current query matches this item, and
* `noMatchFoundAtNumChars` reflects the current query's length if no match was found.
*/
function matchCategory(cat, query, previousQuery) {
if (isMatchingNeeded(cat, query, previousQuery)) {
var catName = cat.category_name.toLowerCase();
cat.matches = (catName.indexOf(currentQuery) !== -1 );
cat.noMatchFoundAtNumChars = (cat.matches ? false : query.length);
}
}

/**
* Attempts to match the given link to the current query, if necessary.
* `link`'s `matches` and `noMatchFoundAtNumChars` properties will be set accordingly.
*
* link (object) : the link to check. Will be modified as described below.
* query (string) : the current query.
* previousQuery (string) : the previous query, for which `link` holds valid results.
*
* No return value. Modifies `link` to reflect the results of the match attempt.
* The `matches` property will reflect whether the current query matches this item, and
* `noMatchFoundAtNumChars` reflects the current query's length if no match was found.
*/
function matchLink(link, query, previousQuery) {
if (isMatchingNeeded(link, query, previousQuery)) {
var properties = [ 'title', 'label', 'url', 'url', 'keywords', 'col', 'row' ];
link.matches = false;
for (var i = 0; i < properties.length; ++i) {
var property = properties[i];
if (!link[property]) continue;
var linkProperty = link[property].toLowerCase();
if (linkProperty.indexOf(query) !== -1){
link.matches = true;
break;
}
}
link.noMatchFoundAtNumChars = (link.matches ? false : query.length);
}
}

/**
* Returns whether the cached results from the previous query are invalid for the current
* query, and thus need to be rechecked.
*
* item (object) : the item to check. If `item.matches` does not yet exist,
* then `previousQuery` is ignored and the function returns true.
* query (string) : the current query.
* previousQuery (string) : the query for which `item`'s current results are valid.
*
* Return value (boolean) : Whether a match should be retried (and the results saved) for
* the current query.
*
* NOTE: If `item.matches` exists, then either `previousQuery` or `query` must be a
* substring of the other. That is, either `previousQuery.indexOf(query)` or
* `query.indexOf(previousQuery)` must be >= 0. If this is not the case, the result is
* undefined.
*/
function isMatchingNeeded(item, query, previousQuery) {
// If it's never been checked yet, definitely check it now
if (!('matches' in item)) return true;

// If the query is longer, then existing matches need to be rechecked.
// If not, then existing unmatches may need rechecking.
return (
(query.length > previousQuery.length)
? item.matches
: (query.length < item.noMatchFoundAtNumChars)
);
}

Обратите внимание, как я избавилась от flagIfMatchNeededFor*сжала их функциональность в соответствующий матч* функции. Я также циклы внутри applyFilter (и называя isMatchingNeeded для каждого элемента в матче* право, прежде чем я попробовать совпадающие с ней), а не иметь кучу функций, которые перебрать весь массив самостоятельно. Результат был, я могу сохранить промежуточные результаты местных и таким образом избежать того, чтобы установить флаг на объект для каждого. один. шаг. процесса.

Есть и другие изменения, которые могли бы быть полезны. Посмотрим, как мы обгоняем кошка или ссылку , чтобы все эти функции? И как бы 2/3 своей функции принимают либо категории, ссылку или массив одного из двух? Что-то подсказывает мне, что есть возможность здесь, чтобы заказать этот материал в полноценные объекты (в отличие от нынешнего состояния вещей, где объекты просто используются как ассоциативные массивы), и использование принципов ООП для упрощения кода еще больше.

И да, вы можете использовать дома объекты, как обычный JS и объекты в какой-то степени. (Если вы делаете это, хотя, я бы ее ограничить в CSS классы и данные- атрибуты для пользовательских материалов. Добавляя свои собственные свойства, как правило работает, но вам придется беспокоиться о столкновениях имя, между прочим.)


Ой...а я думаю о предметы дом...еще одна вещь, которая может легко влиять на производительность: держать глаз на вашем использования библиотеки jQuery. Используйте его, когда он делает что-то сложное, что вы не хотите делать, но знать, что он делает много лишних хрень за кадром. Например, когда вы говорите $('#' + category_id).скрыть(), несколько моментов:


  1. Построена новая строка.

  2. $ называется, который вызывает $.ФН.инит.

    1. на jQuery.ФН.инит использует регулярное выражение для проверки селектора, и замечает, что это один идентификатор.

    2. Если Вам ПОВЕЗЕТ, jQuery будет называть документ.метода getElementById и быть сделано. Если ты не такой (как правило, если вы используете IE или Опера, который бы confornicate имена и идентификаторы), это поиск документа (что совсем немного медленнее). Обычно вы можете избежать этой медленной поиск по тому, что имена элементов не совпадают код любого элемента.

    3. В любом случае, результат завернутый в новый объект jQuery (по сути, массив-как объект), который возвращается к вам.


  3. $.ФН.скрыть называется.

    1. Он перебирает такие вещи (да-да, ты обходишь массив, даже если он содержит только один элемент!), и вызовы в jQuery.УСБ(элемент, "дисплей") на нем (для того чтобы попасть в предыдущие состояния отображения/скрытия).

    2. Он устанавливает атрибут данных/собственность/что за элемент, так что он может запомнить предыдущее состояние.

    3. Наконец, он говорит элемент.стиль.дисплей = 'никто'.


Все это занимает много времени. Я видел тестов, которые показывают манипуляций с DOM напрямую, а где-то от 3х до 100 раз быстрее, чем с jQuery, в зависимости от того, какой браузер вы используете и что вы делаете с ним. На самом деле, эталоном я просто сделал шоу на jQuery в качестве "92% медленнее", чем чистого ванильного DOM манипуляции в Хроме (то есть 8% от скорости; простой JS-это 11,5 х быстрее). Если вы тратите много времени на настройку элементов, что добавляет вверх.

Вы могли бы избавиться от jQuery и накладные достаточно легко показать/спрятать вещи:

function showCategory(category_id) {
document.getElementById(category_id).style.display = '';
}

function hideCategory(category_id) {
document.getElementById(category_id).style.display = 'none';
}

Если вы хотели, вы могли даже дать ваши детали в параметр domelement собственность, что бы установить на соответствующий элемент (и передать весь товар в Показать/Скрыть функции), и вам даже не придется искать его.


При всем том, что хоть и говорит о производительности,...как вы идете через и почистить код, я предупреждаю вас остерегаться соблазна к преждевременной оптимизации. Особенно смотреть на "Я слышал, что X-это медленный, так что я сделаю все возможное, чтобы избежать Х" типа; это довольно подлый. Вы могли бы узнать, что ты проводишь так много времени, пытаясь избежать х, что ты бы сделал лучше вобще х сразу. Я не профилированный большинство из этого, потому что, честно говоря, моя цель № 1-это не скорость ... я работаю, чтобы устранить ненужные сложности и сделать код более легким в обслуживании. Однако, я готов поспорить, что новый кодекс не значительно медленнее, чем старые, будут загружаться быстрее (так как там гораздо меньше), и намного меньше боли, чтобы работать.

5
ответ дан 10 декабря 2011 в 08:12 Источник Поделиться

@ЧАО: я принял ваш ответ и решил перейти на объектно-ориентированный маршрут. Это сделало мой код короче, читабельнее и понятнее (для меня во всяком случае).

/**
* This is the Searchbox Filtered Search widget. It is used to let people access StartGoogle or
* one of the other search engines.
*
* It can also remove blocks and links that are not related to the typed-in query
*/

COMPANY.WIDGETS.Searchboxfilteredsearch = function () {
'use strict';
var blocks,
Block,
Link,
startHighlightAtChars;

blocks = [];
startHighlightAtChars = 2;

/**
* cleans up the typed in query and does validation for its contents
*
* trims and does a regex to whitelist certain characters
*
* queryString = (string) the string that needs to be cleaned
*
* returns false if the query contains invalid characters
* returns the cleaned up query if the query is valid
*/
function cleanUpQuery(queryString) {
queryString = $.trim(queryString);

if (queryString.length === 0) {
return queryString;
}

// only the following characters are allowed:
// -alphanumerics
// - .
// - ,
// - -
var validChars = new RegExp('^([a-zA-Z0-9&\-,\. ]+)$', 'i');
if (!validChars.test(queryString)) {
return false;
}

queryString = queryString.toLowerCase();

return queryString;
}

/**
* Whether the new query is longer than the previous one
*
* currentQuery = (string) the currently typed-in query
* previousQuery = (string) the query that was previously typed in
*
* returns (boolean) true/false
*/
function isNewQueryLonger(currentQuery, previousQuery) {
if (typeof (previousQuery) === 'undefined') {
return true;
}

if (previousQuery === false && currentQuery.length > 0) {
return true;
}

return currentQuery.length > previousQuery.length;
}

/**
* Constructor function for a block
*
* Can be used to create block objects with methods and properties
*
* blockDetails = (object) containing all the relevant details for creation
* of a Block object
*/
Block = function (blockData) {
// the variables declared here are private to the Block object
var blockId, // the DOM id for this block
name, // the name this block has in the DOM
type, // the type of the block, "category" or "block"
links, // the links array
link, // used to iterate over the links array
doesBlockMatch, // checks whether the block matches
hasMatchingLink, // whether the block has 1 or more matching links
isDisplayed, // whether the block is currently displayed
show, // function that shows the block
hide, // function that hides the block
resetLocation, // resets col and pos
jQueryRef, // the jQuery reference,
createJqueryRef,
getJqueryRef,
highlight, // function to highlight the block
unHighlight, // function to highlight the block
isHighlighted, // whether the block is currently highlighted
applyFilterToLinks, // function to do applyFilter to all links
unHighlightLinks, // function
resetLinks, // function to reset all links
containsMatchingLink, // function, if at least one link matches
block; // reference to "this"

block = this;
blockId = blockData.i;
name = blockData.n.toLowerCase();
type = blockData.t;
this.colOriginal = blockData.c;
this.posOriginal = blockData.p;
this.col = false; // the current or target column
this.pos = false; // the current or target position in the column
hasMatchingLink = true;
isDisplayed = true; // all blocks start off displayed
isHighlighted = false;

links = [];
blockData.links = blockData.l;
if (typeof (blockData.links) !== 'undefined') {
// populate the link array with link objects
for (link = 0; link < blockData.links.length; link += 1) {
links.push(new Link(blockData.links[link], blockId));
}
}

// applies the new query to this block
// this decision tree is a little complex, read carefully
this.applyFilter = function (query) {
// checks block itself
if (doesBlockMatch(query)) {
show();

// block highlighting
if (query.length >= startHighlightAtChars) {
highlight();
} else {
unHighlight();
}

// no links should be highlighted when the block matches
unHighlightLinks();
} else {
// block does not match
unHighlight();

applyFilterToLinks(query);

if (containsMatchingLink()) {
show();
} else {
hide();
}
}

if (query.length === 0) {
resetLinks();
}
};

// checks whether the block itself matches
doesBlockMatch = function (query) {
// empty query
if (query === '') {
return true;
}

// 4th column blocks should always be displayed
if (block.colOriginal === 4) {
return true;
}

// widget-blocks in column 1,2,3 should hide when filtering
if (type === 'widget') {
if (block.colOriginal === 1 ||
block.colOriginal === 2 ||
block.colOriginal === 3) {
return false;
}
}

if (name.indexOf(query) !== -1) {
return true;
}
return false;
};

show = function () {
if (!isDisplayed) {
getJqueryRef().show();
isDisplayed = true;
}
};

hide = function () {
if (isDisplayed) {
getJqueryRef().hide();
isDisplayed = false;
}
};

applyFilterToLinks = function (query) {
for (link = 0; link < links.length; link += 1) {
links[link].applyFilter(query);
}
};

// if at least one link matches return true
containsMatchingLink = function () {
for (link = 0; link < links.length; link += 1) {
if (links[link].matches) {
return true;
}
}
return false;
};

// resets
resetLocation = function () {
};

// create a jQuery object that points to this link
createJqueryRef = function () {
return $('#' + blockId);
};

getJqueryRef = function () {
if (!jQueryRef) {
jQueryRef = createJqueryRef();
}
return jQueryRef;
};

highlight = function () {
// don't highlight widgets
if (type === 'widget') {
return false;
}

// don't highlight blocks in column 4
if (block.colOriginal === 4) {
return false;
}

if (!isHighlighted) {
getJqueryRef().addClass('onthispage-highlighted');
isHighlighted = true;
}
};

unHighlight = function () {
if (isHighlighted) {
getJqueryRef().removeClass('onthispage-highlighted');
isHighlighted = false;
}
};

unHighlightLinks = function () {
for (link = 0; link < links.length; link += 1) {
links[link].unHighlight();
}
};

resetLinks = function () {
for (link = 0; link < links.length; link += 1) {
links[link].reset();
}
};
};

/**
* Constructor function for a block
*
* Can be used to create block objects with methods and properties
*
* blockDetails = (object) containing all the relevant details for creation
* of a Block object
*/
Link = function (linkData, blockId) {
var title, // shown on page
label, // used in backend
url, // minus "http://"
keywords,
linkDmsId,
isHighlighted,
doesLinkMatch, // function
isMatchingNeeded, // function
highlight, // function
previousQuery, // the query that was previously used in filtering
noMatchFoundAtNumChars,
jQueryRef,
getJqueryRef,
createJqueryRef,
queryWasReplaced, // function to check if the query was replaced
link; // reference to "this"

link = this;
title = linkData.t;
label = linkData.l;
url = linkData.u;
keywords = linkData.k;
linkDmsId = linkData.i;
jQueryRef = false;
isHighlighted = false;
previousQuery = false;
noMatchFoundAtNumChars = false;

this.matches = true; // whether the link matches, true on link init

// applies filter on a link using query
this.applyFilter = function (query) {
if (isMatchingNeeded(query)) {
this.matches = doesLinkMatch(query);

if (this.matches) {
noMatchFoundAtNumChars = false;
if (query.length >= startHighlightAtChars) {
highlight();
}
} else {
noMatchFoundAtNumChars = query.length;
}
}

if (!this.matches || query.length < startHighlightAtChars) {
this.unHighlight();
}

// save this for future matching
previousQuery = query;
};

isMatchingNeeded = function (query) {
var matchingNeeded,
newQueryLonger;

newQueryLonger = isNewQueryLonger(query, previousQuery);

matchingNeeded = false;
if (typeof (link.matches) === 'undefined') {
// first time we applyFilter() to a link
matchingNeeded = true;
} else if (link.matches && newQueryLonger) {
matchingNeeded = true;
} else if (!link.matches && !newQueryLonger) {
// this logic is a bit difficult
if (noMatchFoundAtNumChars && (query.length < noMatchFoundAtNumChars)) {
matchingNeeded = true;
}
}

// edge-case: if user pasted a new query over the old query
if (previousQuery !== false && queryWasReplaced(query)) {
matchingNeeded = true;
}

return matchingNeeded;
};

doesLinkMatch = function (query) {
var linkProperties, // array of link properties to check
i, // used to iterate over link properties
matchFound; // whether we found a match

linkProperties = [
title,
label,
url,
keywords
];

matchFound = false;
for (i = 0; i < linkProperties.length; i += 1) {
if (linkProperties[i].toLowerCase().indexOf(query) !== -1) {
matchFound = true;
}

}

return matchFound;
};

highlight = function () {
if (!isHighlighted) {
getJqueryRef().addClass('highlight');
isHighlighted = true;
}
};

this.unHighlight = function () {
if (isHighlighted) {
getJqueryRef().removeClass('highlight');
isHighlighted = false;
}
};

// create a jQuery object that points to this link
createJqueryRef = function () {
var selector;
selector = '#' + blockId + ' .dms_' + linkDmsId;
return $(selector);
};

getJqueryRef = function () {
if (!jQueryRef) {
jQueryRef = createJqueryRef();
}
return jQueryRef;
};

// this function checks if the current query was pasted over the
// previous query
queryWasReplaced = function (currentQuery) {
if ((currentQuery.length - previousQuery.length) >= 0) {
// currentQuery is longer or they're equally long
return (currentQuery.substr(0, previousQuery.length) !== previousQuery);
} else {
// previousQuery is longer
return (previousQuery.substr(0, currentQuery.length) !== currentQuery);
}
};

// resets some attributes so the link will be looked at completely fresh
// in the future
this.reset = function () {
this.unHighlight();
this.matches = true;
noMatchFoundAtNumChars = false;
previousQuery = false;
};
};

/**
* Applies the filter (defined by the currentQuery and to the cats array)
*
* -checks whether matching is needed
* -if needed does the matching
* -checks whether DOM action is needed
* -if needed executes DOM action
*
* cats is an array of objects representing categories
* which themselves contain an array of objects representing links
* with some attributes
*
* cats = (array) array of categories through which to search
* currentQuery = (string) with which to find matches within the cats
* previousQuery = (string) with previously-typed-in query
*
* no return values, results in DOM action and manipulation of cats array
*/
function applyFilter(blocks, currentQuery) {
var i;
for (i = 0; i < blocks.length; i += 1) {
// we're now dealing with a Block object
blocks[i].applyFilter(currentQuery);
}

// move blocks around
}

return {
init: function () {
var form,
item;

form.find('input.query').keyup(function () {
// filteredSearch
applyFilter(blocks, cleanUpQuery($(this).attr('value')));
});

// populate the blocks array with Block objects
for (item = 0; item < filteredSearchItems.length; item += 1) {
blocks.push(new Block(filteredSearchItems[item]));
}
},

};
};

Это работает на JSON-строку вроде:

var filteredSearchItems = [{
i: 'category_38466',
n: 'E-mail',
c: 1,
p: 1,
l: [{
t: 'hotmail',
l: '',
u: 'www.hotmail.com/',
k: 'hotmail,gmail,freemail,yahoomail,yahoo mail,mail,email,e-mail,e mail,spam,aol mail,mailen',
i: '22526'
}, {
t: 'gmail',
l: '',
u: 'www.gmail.com',
k: 'hotmail,gmail,freemail,yahoomail,yahoo mail,mail,email,e-mail,e mail,spam,aol mail,mailen',
i: '22527'
}, {
t: 'yahoo mail',
l: '',
u: 'mail.yahoo.com/',
k: 'hotmail,gmail,freemail,yahoomail,yahoo mail,mail,email,e-mail,e mail,spam,aol mail,mailen',
i: '22528'
}, {
t: 'meer e-mail',
l: '',
u: 'e-mail.startpagina.nl/',
k: '',
i: '22529'
}, {
t: 'anti-spam',
l: '',
u: 'spam.startpagina.nl',
k: '',
i: '22530'
}],
t: 'category'
}, {
i: 'category_38468',
n: 'Zorgverzekering 2012',
c: 1,
p: 2,
l: [{
t: 'zorgverzekering vergelijken',
l: '',
u: 'rajo.linktrackr.com/verzekeringssite-leadsphome ',
k: 'verzekeringssite, ohra zorgverzekering, basisverzekering, eigen risico, vergoedingen, premie, vergelijken, tandartsverzekering afsluiten, huisdierenverzekering, ',
i: '35350'
}, {
t: 'independer.nl zorgverzekering',
l: '',
u: 'www.independer.nl/zorgverzekering/intro.aspx?refer=startpaginazorg2012',
k: '',
i: '35117'
}, {
t: 'ohra zorgverzekering',
l: '',
u: 'ohra.adservinginternational.com/click/1801/1433',
k: 'ohra zorgverzekering, basisverzekering, eigen risico, vergoedingen, premie, vergelijken, tandartsverzekering afsluiten, huisdierenverzekering, ',
i: '35145'
}, {
t: 'zekur zorgverzekering va.73,50',
l: '',
u: 'ad.zanox.com/ppc/?20044408C1043637544T',
k: '',
i: '37972'
}, {
t: 'ditzo zorgverzekering va.78,50',
l: '',
u: 'bluemango.solution.weborama.fr/fcgi-bin/performance.fcgi?ID=297163&amp;A=1&amp;L=601286&amp;C=25475&amp;f=10&amp;P=45394&amp;T=E&amp;W=1&amp;CREA=38170&amp;URL',
k: 'zorgverzekeringen vergelijken, ditzo zorgverzekering, basisverzekering, tandarts verzekering afsluiten, lenzen, overstappen, aanvullende zorgverzekering, besparen',
i: '37904'
}, {
t: 'vergelijk zorgverzekeringen',
l: 'tip',
u: 'www.geld.nl/ziektekostenverzekering_vergelijken',
k: '',
i: '35116'
}],
t: 'category'
}, {
i: 'category_38472',
n: 'Lenen',
c: 1,
p: 3,
l: [{
t: 'geld lenen (afab)',
l: '',
u: 'www.afab.nl/lenen#utm_campaign=SP&amp;utm_medium=tekstlink-cpc&amp;utm_source=startpagina&amp;utm_content=geld-lenen-afab',
k: 'lenen, afab.nl, lening afsluiten, hypotheek, veilige lening &amp; goedkoop geld lenen, persoonlijke lening afsluiten',
i: '27918'
}, {
t: 'online lening aanvragen',
l: '',
u: 'www.lenen-online.nl/#utm_campaign=SP&amp;utm_medium=tekstlink-cpc&amp;utm_source=startpagina&amp;utm_content=online-geld-lenen',
k: '',
i: '38145'
}, {
t: 'lenen bij kredietdesk.nl',
l: '',
u: 'www.kredietdesk.nl/?utm_source=Startpagina&amp;utm_medium=CPC&amp;utm_campaign=KD%2Bgemakspropositie',
k: '',
i: '33344'
}, {
t: 'online lenen - ribank direct',
l: '',
u: 'www.ribankdirect.nl/?utm_source=startpagina&amp;utm_medium=cpc&amp;utm_content=rubriek%2Blenen%2B&amp;utm_campaign=startpagina%2B-%2Blenen',
k: '',
i: '36632'
}, {
t: 'online geld lenen',
l: '',
u: 'www.geldlenen.nl/online-afsluiten-met-of-zonder-advies.php?id=5574&amp;utm_source=Startpagina&amp;utm_medium=CPC&amp;utm_campaign=GL.nl%2BStartpagina',
k: '',
i: '22613'
}, {
t: 'santander',
l: '',
u: 'www.santander.nl/?utm_source=ilse&amp;utm_medium=1x1&amp;utm_campaign=startpagina.nl&amp;utm_content=startpagina.nl',
k: 'santander, lening aanvragen, geld lenen, krediet.',
i: '22610'
}, {
t: 'ohra doorlopend krediet',
l: '',
u: 'ohra.adservinginternational.com//click/1629/1247',
k: 'ohra doorlopend krediet, lening aanvragen, snel geld lenen, lenen, ',
i: '27535'
}, {
t: 'minilening: lenen tot 750,-',
l: '',
u: 'www.minilening.nl/minilening.aspx?trid=7222',
k: 'minilening afsluiten, krediet, geld lenen, ',
i: '22614'
}, {
t: 'lenen.overzicht',
l: '',
u: 'lenen.overzicht.nl/#xtor=SEC-12-[]-[lenen]-[none]-C',
k: '',
i: '22616'
}],
t: 'category'
}, {
i: 'category_38477',
n: 'Banken',
c: 1,
p: 4,
l: [{
t: 'bank of scotland',
l: '',
u: 'bs.serving-sys.com/BurstingPipe/adServer.bs?cn=tf&amp;c=20&amp;mc=click&amp;pli=3086776&amp;PluID=0&amp;ord=%n',
k: 'banken, actie, bank of scotland, sparen, spaarrekening, open bankrekening',
i: '22909'
}, {
t: 'vergelijk bankproducten',
l: '',
u: 'www.mistermoney.nl/vergelijk-banken.asp',
k: '',
i: '35942'
}, {
t: 'ribankdirect.nl - lenen',
l: '',
u: 'www.ribankdirect.nl/?utm_source=Startpagina&amp;utm_medium=CPC&amp;utm_campaign=RD%2Bprijspropositie',
k: 'ribank direct, bankieren, banken, lenen',
i: '33345'
}, {
t: 'inzicht in je financiën',
l: '',
u: 'www.yunoo.nl/?utm_source=Startpagina&amp;utm_medium=tekstlink&amp;utm_campaign=Yunoo%2BStartpagina',
k: '',
i: '22920'
}, {
t: 'ing/postbank',
l: '',
u: 'www.ing.nl/particulier/',
k: 'ing, postbank, pinnen, onlinebankieren,online banieren,pinpas,pincode,ing,edentifier,e-dentifier,frieslandbank,icesave,[ice save], internet bankieren',
i: '22912'
}, {
t: 'online bankieren',
l: '',
u: '/mijn.ing.nl/internetbankieren/SesamLoginServlet',
k: '',
i: '22913'
}, {
t: 'abn amro',
l: '',
u: 'www.abnamro.nl/',
k: 'abn,abnamro,abn-amro,abn amro,sns,snsbank,bank,fortis,pinnen,onlinebankieren,online banieren,pinpas,pincode,ing,edentifier,e-dentifier,frieslandbank,icesave,[ice save]',
i: '22914'
}, {
t: 'online bankieren',
l: '',
u: '/www.abnamro.nl/nl/logon/identification.html',
k: '',
i: '22915'
}, {
t: 'rabobank',
l: '',
u: 'www.rabobank.nl/',
k: 'rabobank, rabo, internetbankieren',
i: '22910'
}, {
t: 'online bankieren',
l: '',
u: '/bankieren.rabobank.nl/klanten/',
k: '',
i: '22911'
}, {
t: 'online bankieren',
l: '',
u: '/www.snsbank.nl/mijnsns/secure/login.html?cmp=2549',
k: '',
i: '22919'
}],
t: 'category'
}];

1
ответ дан 10 января 2012 в 04:01 Источник Поделиться