[javascript] Knockout.js는 준 대형 데이터 세트에서 엄청나게 느립니다.

이제 막 Knockout.js를 시작하고 있습니다 (항상 사용해보고 싶었지만 이제는 변명 할 수 있습니다!)-그러나 테이블을 상대적으로 작은 집합에 바인딩 할 때 정말 나쁜 성능 문제가 발생합니다. 데이터 (약 400 행 정도).

내 모델에는 다음 코드가 있습니다.

this.projects = ko.observableArray( [] ); //Bind to empty array at startup

this.loadData = function (data) //Called when AJAX method returns
{
   for(var i = 0; i < data.length; i++)
   {
      this.projects.push(new ResultRow(data[i])); //<-- Bottleneck!
   }
};

문제는 for위 의 루프가 약 400 행으로 약 30 초 정도 걸린다는 것입니다. 그러나 코드를 다음과 같이 변경하면

this.loadData = function (data)
{
   var testArray = []; //<-- Plain ol' Javascript array
   for(var i = 0; i < data.length; i++)
   {
      testArray.push(new ResultRow(data[i]));
   }
};

그런 다음 for눈 깜짝 할 사이에 루프가 완료됩니다. 즉, pushKnockout의 observableArray개체 방법 이 엄청나게 느립니다.

내 템플릿은 다음과 같습니다.

<tbody data-bind="foreach: projects">
    <tr>
       <td data-bind="text: code"></td>
       <td><a data-bind="projlink: key, text: projname"></td>
       <td data-bind="text: request"></td>
       <td data-bind="text: stage"></td>
       <td data-bind="text: type"></td>
       <td data-bind="text: launch"></td>
       <td><a data-bind="mailto: ownerEmail, text: owner"></a></td>
    </tr>
</tbody>

내 질문 :

  1. 이것이 내 데이터 (AJAX 메서드에서 가져온)를 관찰 가능한 컬렉션에 바인딩하는 올바른 방법입니까?
  2. push바인딩 된 DOM 개체를 다시 빌드하는 것과 같이 호출 할 때마다 무거운 재 계산을 수행 할 것으로 예상 합니다. 이 재 계산을 지연하거나 한 번에 모든 항목을 푸시 할 수있는 방법이 있습니까?

필요한 경우 더 많은 코드를 추가 할 수 있지만 이것이 관련성이 있다고 확신합니다. 대부분의 경우 사이트에서 Knockout 자습서를 따랐습니다.

최신 정보:

아래 조언에 따라 코드를 업데이트했습니다.

this.loadData = function (data)
{
   var mappedData = $.map(data, function (item) { return new ResultRow(item) });
   this.projects(mappedData);
};

그러나 this.projects()여전히 400 행의 경우 약 10 초가 걸립니다. Knockout (DOM을 통해 행 추가) 없이 이것이 얼마나 빠를 지 잘 모르겠지만 10 초보다 훨씬 빠를 것 같습니다.

업데이트 2 :

아래의 다른 조언에 따라 jQuery.tmpl ( KnockOut 에서 기본적으로 지원됨)을 제공 했으며이 템플릿 엔진은 3 초 만에 약 400 개의 행을 그릴 것입니다. 스크롤 할 때 더 많은 데이터를 동적으로로드하는 솔루션이 부족한 가장 좋은 방법 인 것 같습니다.



답변

의견에서 제안한대로.

Knockout에는 바인딩 (foreach, with)과 관련된 고유 한 기본 템플릿 엔진이 있습니다. 또한 다른 템플릿 엔진, 즉 jquery.tmpl을 지원합니다. 읽기 여기에 자세한 내용은. 다른 엔진으로 벤치마킹을하지 않았으므로 도움이 될지 모르겠습니다. 이전 의견을 읽으면 IE7에서 원하는 성능을 얻는 데 어려움을 겪을 수 있습니다.

제쳐두고 KO는 누군가가 어댑터를 작성한 경우 모든 js 템플릿 엔진을 지원합니다. JQuery와 tmpl에 의해 대체 될 예정이로에서 당신은 다른 사람을 시도 할 수 있습니다 JsRender .


답변

참조하십시오 : Knockout.js 성능 Gotcha # 2-observableArrays 조작

더 나은 패턴은 기본 배열에 대한 참조를 가져 와서 푸시 한 다음 .valueHasMutated ()를 호출하는 것입니다. 이제 구독자는 배열이 변경되었음을 나타내는 알림을 하나만받습니다.


답변

$ .map을 사용하는 것 외에도 KO로 페이지 매김사용하십시오 .

녹아웃과 함께 페이징을 사용할 때까지 1400 레코드의 대규모 데이터 세트에서 동일한 문제가 발생했습니다. 사용 $.map기록을로드하는 것은 큰 차이를 만들 않았다하지만 DOM은 시간이 아직도 끔찍한했다 렌더링합니다. 그런 다음 페이지 매김을 사용하여 데이터 세트 조명을 빠르고 사용자 친화적으로 만들었습니다. 페이지 크기가 50이면 데이터 세트의 부담이 훨씬 줄어들고 DOM 요소 수가 크게 감소했습니다.

KO로 매우 쉽게 할 수 있습니다.

http://jsfiddle.net/rniemeyer/5Xr2X/


답변

KnockoutJS에는 특히 데이터로드 및 저장에 관한 훌륭한 튜토리얼이 있습니다.

그들의 경우에는 getJSON()매우 빠른 데이터를 사용 합니다. 그들의 예에서 :

function TaskListViewModel() {
    // ... leave the existing code unchanged ...

    // Load initial state from server, convert it to Task instances, then populate self.tasks
    $.getJSON("/tasks", function(allData) {
        var mappedTasks = $.map(allData, function(item) { return new Task(item) });
        self.tasks(mappedTasks);
    });
}


답변

KoGrid 제공 봐. 행 렌더링을 지능적으로 관리하여 성능을 향상시킵니다.

다음을 사용하여 400 행을 테이블에 바인딩하려는 경우 foreach바인딩을 KO를 통해 DOM으로 많은 것을 푸시하는 데 어려움이 있습니다.

KO는 foreach바인딩을 사용하여 매우 흥미로운 작업을 수행하며 대부분은 매우 좋은 작업이지만 배열의 크기가 커짐에 따라 성능이 저하되기 시작합니다.

나는 큰 데이터 세트를 테이블 / 그리드에 바인딩하려는 길고 어두운 길을 걸어 왔고 결국 데이터를 로컬에서 분리 / 페이지 화해야합니다.

KoGrid 가이 모든 작업을 수행합니다. 뷰어가 페이지에서 볼 수있는 행만 렌더링 한 다음 필요할 때까지 다른 행을 가상화하도록 구축되었습니다. 나는 당신이 경험하는 것보다 훨씬 더 나은 400 항목에 대한 성능을 발견 할 것이라고 생각합니다.


답변

매우 큰 배열을 렌더링 할 때 브라우저가 잠기는 것을 방지하는 해결책은 배열을 ‘조절’하여 한 번에 몇 개의 요소 만 추가되고 그 사이에 휴면 상태를 유지하는 것입니다. 이를 수행하는 함수는 다음과 같습니다.

function throttledArray(getData) {
    var showingDataO = ko.observableArray(),
        showingData = [],
        sourceData = [];
    ko.computed(function () {
        var data = getData();
        if ( Math.abs(sourceData.length - data.length) / sourceData.length > 0.5 ) {
            showingData = [];
            sourceData = data;
            (function load() {
                if ( data == sourceData && showingData.length != data.length ) {
                    showingData = showingData.concat( data.slice(showingData.length, showingData.length + 20) );
                    showingDataO(showingData);
                    setTimeout(load, 500);
                }
            })();
        } else {
            showingDataO(showingData = sourceData = data);
        }
    });
    return showingDataO;
}

사용 사례에 따라 사용자가 스크롤하기 전에 첫 번째 행 배치 만 볼 수 있으므로 UX가 크게 향상 될 수 있습니다.


답변

가변 인수를 받아들이는 push ()를 활용하면 제 경우에는 최상의 성능을 얻을 수있었습니다. 5973ms (~ 6 초) 동안 1300 개의 행이로드되었습니다. 이 최적화를 통해로드 시간은 914ms (<1 초)로 줄었습니다.
이는 84.7 % 향상되었습니다!

observableArray에 항목 푸시에 대한 추가 정보

this.projects = ko.observableArray( [] ); //Bind to empty array at startup

this.loadData = function (data) //Called when AJAX method returns
{
   var arrMappedData = ko.utils.arrayMap(data, function (item) {
       return new ResultRow(item);
   });
   //take advantage of push accepting variable arguments
   this.projects.push.apply(this.projects, arrMappedData);
};