Object.observe()를 사용한 데이터 결합 회전

아디 오스마니
애디 오스마니

소개

혁명이 다가오고 있습니다. JavaScript에 새로 추가된 이 기능으로 인해 데이터 결합에 관해 알고 있는 모든 것이 바뀔 것입니다. 또한 수정 및 업데이트를 위해 모델을 관찰하는 MVC 라이브러리의 수도 변경됩니다. 속성 관찰을 중시하는 앱의 성능을 향상할 준비가 되셨나요?

알겠습니다. 이제 서두르지 않고 Chrome 36 안정화 버전에서 Object.observe()을(를) 출시했습니다. [우우. THE CROWD GOES WILD]를 사용하세요.

향후 ECMAScript 표준의 일부인 Object.observe()는 별도의 라이브러리 없이 JavaScript 객체의 변경사항을 비동기식으로 관찰하는 메서드입니다. 관찰자는 관찰된 객체 집합에 발생한 변경 세트를 설명하는 시간순으로 변경 기록 시퀀스를 수신할 수 있습니다.

// Let's say we have a model with data
var model = {};

// Which we then observe
Object.observe(model, function(changes){

    // This asynchronous callback runs
    changes.forEach(function(change) {

        // Letting us know what changed
        console.log(change.type, change.name, change.oldValue);
    });

});

변경사항이 발생할 때마다 다음과 같이 보고됩니다.

변경사항이 보고되었습니다.

Object.observe() (O.o() 또는 Oooooooo로 부르고 싶음)를 사용하면 프레임워크 없이도 양방향 데이터 결합을 구현할 수 있습니다.

그렇다고 해서 사용하지 말아야 하는 것은 아닙니다. 비즈니스 로직이 복잡한 대규모 프로젝트의 경우 독자적인 프레임워크가 매우 중요하며, 이를 계속 사용해야 합니다. 신규 개발자의 방향을 단순화하고 코드 유지보수 작업이 적으며 일반적인 작업을 수행하는 방법에 대한 패턴을 적용합니다. 필요하지 않은 경우 이미 O.o()를 활용하고 있는 Polymer와 같이 더 작고 집중력이 높은 라이브러리를 사용할 수 있습니다.

프레임워크나 MV* 라이브러리를 많이 사용하는 경우에도 O.o()는 동일한 API를 유지하면서 더 빠르고 간단한 구현으로 일부 건전한 성능 개선을 제공할 수 있습니다. 예를 들어 작년 Angular는 모델을 변경하는 벤치마크에서 더티 검사에 업데이트당 40ms가 걸렸고 O.o()는 업데이트당 1~2ms가 걸렸다는 사실을 발견했습니다 (20~40배 더 빨라짐).

또한 수많은 복잡한 코드를 사용할 필요 없이 데이터 결합이 가능하므로 더 이상 변경사항을 폴링할 필요가 없으므로 배터리 수명이 늘어납니다.

이미 O.o()를 통해 판매되고 있다면 기능 소개로 건너뛰거나 이 기능으로 해결할 수 있는 문제에 대해 미리 읽어 보세요.

관찰하고자 하는 것은 무엇인가?

데이터 관찰이란 일반적으로 다음과 같은 특정 유형의 변화를 주시하는 것을 뜻합니다.

  • 원시 자바스크립트 객체 변경사항
  • 속성 추가, 변경, 삭제 시
  • 배열에 요소가 결합되고 배열에서 분리되는 경우
  • 객체의 프로토타입 변경사항

데이터 결합의 중요성

모델 뷰 컨트롤 분리에 관심이 있을 때 데이터 결합이 중요해지기 시작합니다. HTML은 훌륭한 선언적 메커니즘이지만 완전히 정적입니다. 이상적으로는 데이터와 DOM 사이의 관계를 선언하고 DOM을 최신 상태로 유지하는 것이 좋습니다. 이렇게 하면 애플리케이션의 내부 상태 또는 서버 간에 DOM과 데이터를 주고받는 매우 반복적인 코드를 작성하는 데 많은 시간을 절약할 수 있습니다.

데이터 결합은 뷰의 여러 요소와 데이터 모델의 여러 속성 간의 관계를 연결해야 하는 복잡한 사용자 인터페이스가 있는 경우에 특히 유용합니다. 이는 오늘날 빌드 중인 단일 페이지 애플리케이션에서 매우 일반적입니다.

Google은 브라우저에서 기본적으로 데이터를 관찰하는 방법을 구축함으로써 오늘날 전 세계에서 사용하는 느린 해킹에 의존하지 않고도 모델 데이터의 변경사항을 관찰할 수 있는 방법을 JavaScript 프레임워크 (및 개발자가 작성하는 작은 유틸리티 라이브러리)에 제공합니다.

오늘날의 세계

더티 검사

이전에 데이터 결합을 어디에서 봤나요? 웹 앱 (예: Angular, Knockout)을 빌드하기 위해 최신 MV* 라이브러리를 사용한다면 모델 데이터를 DOM에 결합하는 데 익숙할 것입니다. 다시 말씀드리자면, 다음은 데이터와 UI가 항상 동기화되도록 phones 배열 (JavaScript에서 정의)에 있는 각 휴대전화의 값을 목록 항목에 결합하는 전화 목록 앱의 예입니다.

<html ng-app>
  <head>
    ...
    <script src='angular.js'></script>
    <script src='controller.js'></script>
  </head>
  <body ng-controller='PhoneListCtrl'>
    <ul>
      <li ng-repeat='phone in phones'>
        
        <p></p>
      </li>
    </ul>
  </body>
</html>

그리고 컨트롤러용 JavaScript입니다.

var phonecatApp = angular.module('phonecatApp', []);

phonecatApp.controller('PhoneListCtrl', function($scope) {
  $scope.phones = [
    {'name': 'Nexus S',
     'snippet': 'Fast just got faster with Nexus S.'},
    {'name': 'Motorola XOOM with Wi-Fi',
     'snippet': 'The Next, Next Generation tablet.'},
    {'name': 'MOTOROLA XOOM',
     'snippet': 'The Next, Next Generation tablet.'}
  ];
});

기본 모델 데이터가 변경될 때마다 DOM의 목록이 업데이트됩니다. Angular는 어떻게 이를 실현할 수 있을까요? 배후에서는 더티 체크라는 일을 하고 있습니다.

더티 체크

더티 검사의 기본 아이디어는 데이터가 변경될 수 있으면 언제든지 라이브러리를 이동하여 다이제스트 또는 변경 주기를 통해 변경되었는지 확인해야 한다는 것입니다. Angular의 경우 다이제스트 주기는 변경 여부를 확인하기 위해 감시하도록 등록된 모든 식을 식별합니다. 모델은 모델의 이전 값을 인식하고 값이 변경된 경우 변경 이벤트가 실행됩니다. 개발자에게 있어 여기서 가장 큰 이점은 사용하기 쉽고 상당히 원활하게 구성된 원시 JavaScript 객체 데이터를 사용할 수 있다는 것입니다. 단점은 잘못된 알고리즘 동작이 있고 비용이 많이 들 수 있다는 점입니다.

더티 체크.

이 작업의 비용은 관찰된 객체의 총 개수에 비례합니다. 더티 검사를 많이 해야 할지도 몰라요. 또한 데이터가 변경되었을 가능성이 있는 경우 더티 검사를 트리거하는 방법이 필요할 수 있습니다. 프레임워크가 이를 위해 사용하는 영리한 트릭이 많이 있습니다. 과연 완벽할 수 있을지는 알 수 없습니다.

웹 생태계는 다음과 같은 자체 선언적 메커니즘을 혁신하고 발전시킬 수 있는 더 많은 능력을 갖춰야 합니다.

  • 제약조건 기반 모델 시스템
  • 자동 지속성 시스템 (예: IndexedDB 또는 localStorage에 대한 변경사항 유지)
  • 컨테이너 객체 (엠버, 백본)

컨테이너 개체는 프레임워크가 개체를 생성하는 곳으로, 내부에는 데이터를 보유하고 있습니다. 그들은 데이터에 접근하고, 설정 또는 얻고, 내부에 브로드캐스트하는 것을 캡쳐할 수 있습니다. 잘 작동합니다. 비교적 성능이 좋고 알고리즘 동작이 우수합니다. Ember를 사용하는 컨테이너 객체의 예는 다음과 같습니다.

// Container objects
MyApp.president = Ember.Object.create({
  name: "Barack Obama"
});
 
MyApp.country = Ember.Object.create({
  // ending a property with "Binding" tells Ember to
  // create a binding to the presidentName property
  presidentNameBinding: "MyApp.president.name"
});
 
// Later, after Ember has resolved bindings
MyApp.country.get("presidentName");
// "Barack Obama"
 
// Data from the server needs to be converted
// Composes poorly with existing code

여기에서 변경된 것을 발견하는 데 드는 비용은 변경된 항목의 수에 비례합니다. 또 다른 문제는 이러한 종류의 객체를 사용하고 있다는 것입니다. 일반적으로 서버에서 가져온 데이터를 관측할 수 있도록 이러한 객체로 변환해야 합니다.

대부분의 코드는 원시 데이터 작업을 할 수 있다고 가정하기 때문에 기존 JS 코드로는 특히 잘 작성되지 않습니다. 이렇게 특수한 유형의 객체에는 적합하지 않습니다.

Introducing Object.observe()

가장 좋은 방법은 두 가지 방식의 장점을 모두 제공하는 것입니다. 항상 모든 것을 더티 체크할 필요 없이 AND를 선택하면 원시 데이터 객체 (일반 JavaScript 객체)를 지원하면서 데이터를 관찰할 수 있는 방법입니다. 알고리즘 동작이 우수합니다. 원활하게 구성되고 플랫폼에 구현되는 것들입니다. 이것이 Object.observe()가 제공하는 멋진 기능입니다.

이를 통해 객체를 관찰하고, 속성을 변경하고, 변경된 사항에 대한 변경 보고서를 볼 수 있습니다. 그러나 이론에 관해서는 여기까지입니다. 이제 몇 가지 코드를 살펴보겠습니다.

Object.observe()

Object.observe() 및 Object.unobserve()

모델을 나타내는 간단한 바닐라 JavaScript 객체가 있다고 가정해 보겠습니다.

// A model can be a simple vanilla object
var todoModel = {
  label: 'Default',
  completed: false
};

그런 다음 객체에 변형 (변경)이 발생할 때마다 콜백을 지정할 수 있습니다.

function observer(changes){
  changes.forEach(function(change, i){
      console.log('what property changed? ' + change.name);
      console.log('how did it change? ' + change.type);
      console.log('whats the current value? ' + change.object[change.name]);
      console.log(change); // all changes
  });
}

그런 다음 O.o()를 사용하여 객체를 첫 번째 인수로 전달하고 콜백을 두 번째 인수로 전달하여 이러한 변경사항을 관찰할 수 있습니다.

Object.observe(todoModel, observer);

이제 Todos 모델 객체를 약간 변경해 보겠습니다.

todoModel.label = 'Buy some more milk';

콘솔을 살펴보면 몇 가지 유용한 정보를 확인할 수 있습니다. 어떤 속성이 변경되었고 어떻게 변경되었는지, 새 값은 무엇인지 알고 있습니다.

콘솔 보고서

와! 더티 체크는 이제 안녕! Tombstone은 Comic Sans로 조각해야 합니다. 다른 속성을 변경해 보겠습니다. 이번에는 completeBy:

todoModel.completeBy = '01/01/2014';

변경 보고서를 다시 성공적으로 가져오는 것을 확인할 수 있습니다.

보고서를 변경합니다.

만족하며 이제 객체에서 'completed' 속성을 삭제하기로 결정했다면 어떻게 해야 할까요?

delete todoModel.completed;
완료됨

반환된 변경사항 보고서에는 삭제에 대한 정보가 포함되어 있습니다. 예상대로 속성의 새 값은 이제 정의되지 않습니다. 이제 속성이 추가된 시점을 파악할 수 있습니다. 삭제된 경우 기본적으로 객체의 속성 집합 ('신규', '삭제됨', '재구성됨')이며 프로토타입 변경 (proto)입니다.

모든 관찰 시스템과 마찬가지로 변경 사항 리슨을 중지하는 메서드도 존재합니다. 여기서는 O.o()와 동일한 서명이 있지만 다음과 같이 호출할 수 있는 Object.unobserve()입니다.

Object.unobserve(todoModel, observer);

아래에서 볼 수 있듯이 이 작업이 실행된 후 객체에 변형이 발생해도 더 이상 변경 기록 목록이 반환되지 않습니다.

변형

관심 변경사항 지정

지금까지 관찰된 객체에 대한 변경사항 목록을 다시 가져오는 방법의 기본 사항을 살펴봤습니다. 객체에 대한 변경사항의 전부가 아닌 일부 변경사항에만 관심이 있다면 어떻게 해야 할까요? 누구나 스팸 필터가 필요합니다. 관찰자는 수락 목록을 통해 듣고자 하는 변경사항 유형만 지정할 수 있습니다. 이것은 다음과 같이 O.o()의 세 번째 인수를 사용하여 지정할 수 있습니다.

Object.observe(obj, callback, optAcceptList)

예시를 통해 어떻게 사용할 수 있는지 살펴보겠습니다.

// Like earlier, a model can be a simple vanilla object

var todoModel = {
  label: 'Default',
  completed: false

};


// We then specify a callback for whenever mutations 
// are made to the object
function observer(changes){
  changes.forEach(function(change, i){
    console.log(change);
  })

};

// Which we then observe, specifying an array of change 
// types we're interested in

Object.observe(todoModel, observer, ['delete']);

// without this third option, the change types provided 
// default to intrinsic types

todoModel.label = 'Buy some milk'; 

// note that no changes were reported

하지만 이제 라벨을 삭제하면 다음과 같은 유형의 변경사항이 보고됩니다.

delete todoModel.label;

허용 유형 목록을 O.o()에 지정하지 않으면 기본적으로 '내장' 객체 변경 유형 (add, update, delete, reconfigure, preventExtensions)이 사용됩니다 (확장 불가능한 객체를 관찰할 수 없는 경우).

알림

O.o()는 알림이라는 개념과 함께 제공됩니다. 이러한 기능은 전화 사용 시 발생하는 성가신 내용과는 다르고 오히려 유용합니다. 알림은 Mutation Observers와 유사합니다. 마이크로 태스크가 끝날 때 발생합니다. 브라우저 컨텍스트에서는 거의 항상 현재 이벤트 핸들러의 끝에 있습니다.

일반적으로 한 작업 단위가 완료되고 이제 관찰자가 작업을 수행할 수 있으므로 타이밍이 좋습니다. 멋진 턴 기반 처리 모델입니다.

알리미를 사용하는 워크플로는 다음과 같습니다.

알림

객체의 속성이 get 또는 set로 설정될 때 맞춤 알림을 정의하는 데 알리미가 실제로 어떻게 사용되는지 예를 살펴보겠습니다. 여기에서 댓글을 잘 살펴보세요.

// Define a simple model
var model = {
    a: {}
};

// And a separate variable we'll be using for our model's 
// getter in just a moment
var _b = 2;

// Define a new property 'b' under 'a' with a custom
// getter and setter

Object.defineProperty(model.a, 'b', {
    get: function () {
        return _b;
    },
    set: function (b) {

        // Whenever 'b' is set on the model
        // notify the world about a specific type
        // of change being made. This gives you a huge
        // amount of control over notifications
        Object.getNotifier(this).notify({
            type: 'update',
            name: 'b',
            oldValue: _b
        });

        // Let's also log out the value anytime it gets
        // set for kicks
        console.log('set', b);

        _b = b;
    }
});

// Set up our observer
function observer(changes) {
    changes.forEach(function (change, i) {
        console.log(change);
    })
}

// Begin observing model.a for changes
Object.observe(model.a, observer);
알림 콘솔

여기서는 데이터 속성의 값이 변경 ('update')되면 보고합니다. 객체의 구현에서 보고하기로 선택한 모든 항목 (notifier.notifyChange())

웹 플랫폼에서 수년간 쌓아온 경험에서 동기식 접근방법이 가장 먼저 시도해 볼 수 있다는 것을 깨달았습니다. 문제는 이 과정에서 근본적으로 위험한 처리 모델이 생성된다는 점입니다. 코드를 작성하고 객체의 속성을 업데이트하는 경우 객체의 속성을 업데이트한다고 해서 임의의 코드가 원하는 대로 실행되도록 하는 상황이 발생하지 않도록 해야 합니다. 함수 중간을 실행하는 동안 가정을 무효화하는 것은 이상적이지 않습니다.

관찰자라면 누군가가 어떤 일 중에 호출되면 바람직하지 않습니다. 일관되지 않은 세상에서 일을 하라는 요청을 받는 것은 바람직하지 않습니다. 결국 더 많은 오류를 확인하게 됩니다. 훨씬 더 나쁜 상황을 용인하려고 하는 것은 일반적으로 일하기 어려운 모델입니다. 비동기는 다루기가 더 어렵지만 결국에는 더 나은 모델입니다.

이 문제를 해결할 수 있는 방법은 종합 변경 레코드입니다.

종합 변경 레코드

기본적으로 접근자 또는 계산된 속성을 가지려면 이러한 값이 변경될 때 이를 알려야 합니다. 이는 약간의 추가 작업이지만 이 메커니즘의 일종의 최고급 기능으로 설계되었으며 이러한 알림은 기본 데이터 객체의 나머지 알림과 함께 전달됩니다. 데이터 속성에서 가져옵니다.

종합 변경 레코드

접근자 및 계산된 속성은 O.o()의 또 다른 부분인 notifier.notify를 사용하여 해결할 수 있습니다. 대부분의 관측 시스템은 파생된 값을 관찰하는 일종의 형태를 원합니다. 여러 가지 방법이 있습니다. O.o는 "올바른" 방법에 대해 판단하지 않습니다. 계산된 속성은 내부 (비공개) 상태가 변경될 때 notify을 주는 접근자여야 합니다.

다시 말하지만, 웹 개발자들은 라이브러리가 계산된 속성에 대한 다양한 알림 및 접근 방식을 쉽게 만들어 주고 상용구를 줄일 수 있기를 기대해야 합니다.

다음 예인 원 클래스를 설정해 보겠습니다. 여기서 아이디어는 이 원이 있고 반경 속성이 있다는 것입니다. 이 경우 반경은 접근자이며 값이 변경되면 실제로 값이 변경되었음을 자체적으로 알립니다. 이 객체 또는 다른 객체의 다른 모든 변경사항과 함께 전달됩니다. 기본적으로 객체를 구현하는 경우 합성 속성이나 계산된 속성을 포함하거나 이를 어떻게 실행할지 전략을 선택해야 합니다. 이렇게 하면 시스템 전체에 적용됩니다.

코드를 건너뛰어 DevTools에서 작동하는지 확인하세요.

function Circle(r) {
  var radius = r;
 
  var notifier = Object.getNotifier(this);
  function notifyAreaAndRadius(radius) {
    notifier.notify({
      type: 'update',
      name: 'radius',
      oldValue: radius
    })
    notifier.notify({
      type: 'update',
      name: 'area',
      oldValue: Math.pow(radius * Math.PI, 2)
    });
  }
 
  Object.defineProperty(this, 'radius', {
    get: function() {
      return radius;
    },
    set: function(r) {
      if (radius === r)
        return;
      notifyAreaAndRadius(radius);
      radius = r;
    }
  });
 
  Object.defineProperty(this, 'area', {
    get: function() {
      return Math.pow(radius, 2) * Math.PI;
    },
    set: function(a) {
      r = Math.sqrt(a/Math.PI);
      notifyAreaAndRadius(radius);
      radius = r;
    }
  });
}
 
function observer(changes){
  changes.forEach(function(change, i){
    console.log(change);
  })
}
종합 변경 레코드 콘솔

접근자 속성

접근자 속성에 관한 간단한 참고사항입니다. 앞서 데이터 속성에서 값 변경사항만 관찰할 수 있다고 언급했습니다. 계산된 속성이나 접근자에는 해당되지 않습니다. 자바스크립트에는 접근자 값의 변경에 관한 개념이 실제로 없기 때문입니다. 접근자는 함수의 모음일 뿐입니다.

접근자 JavaScript에 할당하면 JavaScript에서 함수를 호출하기만 하면 되므로 아무 것도 변경되지 않습니다. 단지 일부 코드를 실행할 수 있었습니다.

문제는 의미상 위의 값, 즉 5에 할당된 값을 볼 수 있다는 것입니다. 여기서 무슨 일이 일어났는지 알 수 있어야 합니다. 이는 사실상 해결할 수 없는 문제입니다. 예시를 통해 그 이유를 알 수 있습니다. 이는 임의의 코드일 수 있기 때문에 어떤 시스템에서도 이것이 무엇을 의미하는지 알 수 없습니다. 이 경우 원하는 모든 것을 할 수 있습니다. 액세스할 때마다 값을 업데이트하므로 변경 여부를 묻는 것은 말이 되지 않습니다.

콜백 한 번으로 여러 객체 관찰

O.o()로 가능한 또 다른 패턴은 단일 콜백 관찰자 개념입니다. 따라서 단일 콜백을 여러 다양한 객체의 '관찰자'로 사용할 수 있습니다. 콜백은 '마이크로 태스크의 끝'에서 관찰되는 모든 객체에 변경사항의 전체 세트를 전달합니다(돌연변이 관찰자와의 유사성에 유의).

콜백 한 번으로 여러 객체 관찰

대규모 변경

엄청나게 큰 앱을 개발 중이고 정기적으로 대규모 변경을 처리해야 할 수도 있습니다. 객체는 대규모의 의미 체계 변경을 설명하려고 할 수 있으며, 이는 수많은 속성 변경사항을 브로드캐스트하는 대신 더 간결한 방식으로 많은 속성에 영향을 미칩니다.

O.o()는 이미 도입된 두 가지 특정 유틸리티인 notifier.performChange()notifier.notify()의 형태로 이 작업을 도와줍니다.

대규모 변경

몇 가지 수학 유틸리티 (곱하기, 증분, incrementAndMultiply)를 사용하여 Thingy 객체를 정의할 때 대규모 변경이 어떻게 설명되는지에 대한 예시를 통해 살펴보겠습니다. 유틸리티가 사용될 때마다 시스템은 저작물의 모음이 특정 유형의 변화로 구성된다고 시스템에 알립니다.

예: notifier.performChange('foo', performFooChangeFn);

function Thingy(a, b, c) {
  this.a = a;
  this.b = b;
}

Thingy.MULTIPLY = 'multiply';
Thingy.INCREMENT = 'increment';
Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply';


Thingy.prototype = {
  increment: function(amount) {
    var notifier = Object.getNotifier(this);

    // Tell the system that a collection of work comprises 
    // a given changeType. e.g
    // notifier.performChange('foo', performFooChangeFn);
    // notifier.notify('foo', 'fooChangeRecord');
    notifier.performChange(Thingy.INCREMENT, function() {
      this.a += amount;
      this.b += amount;
    }, this);

    notifier.notify({
      object: this,
      type: Thingy.INCREMENT,
      incremented: amount
    });
  },

  multiply: function(amount) {
    var notifier = Object.getNotifier(this);

    notifier.performChange(Thingy.MULTIPLY, function() {
      this.a *= amount;
      this.b *= amount;
    }, this);

    notifier.notify({
      object: this,
      type: Thingy.MULTIPLY,
      multiplied: amount
    });
  },

  incrementAndMultiply: function(incAmount, multAmount) {
    var notifier = Object.getNotifier(this);

    notifier.performChange(Thingy.INCREMENT_AND_MULTIPLY, function() {
      this.increment(incAmount);
      this.multiply(multAmount);
    }, this);

    notifier.notify({
      object: this,
      type: Thingy.INCREMENT_AND_MULTIPLY,
      incremented: incAmount,
      multiplied: multAmount
    });
  }
}

그런 다음 객체에 두 개의 관찰자를 정의합니다. 하나는 변경사항을 포괄하는 관찰자이고 다른 하나는 정의한 특정 허용 유형 (Thingy.INCREMENT, Thingy.MULTIPLY, Thingy.INCREMENT_AND_MULTIPLY)을 다시 보고하는 관찰자입니다.

var observer, observer2 = {
    records: undefined,
    callbackCount: 0,
    reset: function() {
      this.records = undefined;
      this.callbackCount = 0;
    },
};

observer.callback = function(r) {
    console.log(r);
    observer.records = r;
    observer.callbackCount++;
};

observer2.callback = function(r){
    console.log('Observer 2', r);
}


Thingy.observe = function(thingy, callback) {
  // Object.observe(obj, callback, optAcceptList)
  Object.observe(thingy, callback, [Thingy.INCREMENT,
                                    Thingy.MULTIPLY,
                                    Thingy.INCREMENT_AND_MULTIPLY,
                                    'update']);
}

Thingy.unobserve = function(thingy, callback) {
  Object.unobserve(thingy);
}

이제 이 코드로 플레이를 시작할 수 있습니다. 새로운 Thingy를 정의해 보겠습니다.

var thingy = new Thingy(2, 4);

확인 후 내용을 수정해 보세요. 와, 재미있네. 정말 멋져요!

// Observe thingy
Object.observe(thingy, observer.callback);
Thingy.observe(thingy, observer2.callback);

// Play with the methods thingy exposes
thingy.increment(3);               // { a: 5, b: 7 }
thingy.b++;                        // { a: 5, b: 8 }
thingy.multiply(2);                // { a: 10, b: 16 }
thingy.a++;                        // { a: 11, b: 16 }
thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 }
대규모 변경

'perform function'에 포함된 모든 것은 'big-change'의 작업으로 간주됩니다. 'big-change'를 허용하는 관찰자는 'big-change' 레코드만 수신합니다. 'perform function'을 사용한 작업에서 비롯된 기본 변경사항을 수신하지 않는 관찰자입니다.

배열 관찰

객체의 변경 사항을 관찰하는 것에 관해서는 오랫동안 논의했지만 배열은 어떨까요?! 좋은 질문이에요. 누군가 "좋은 질문입니다."라고 말하면 좋은 질문을 해주셔서 감사하느라 바빠서 답변을 받지 못했습니다. 배열을 다루는 새로운 메서드도 있습니다.

Array.observe()는 스플라이스, 시프트 해제, 길이가 암시적으로 변경되는 경우 등 자체의 대규모 변경을 '스플라이스' 변경 레코드로 처리하는 메서드입니다. 내부적으로 notifier.performChange("splice",...)를 사용합니다.

다음은 모델 '배열'을 관찰하고 기본 데이터에 변경사항이 있을 때 마찬가지로 변경사항 목록을 가져오는 예입니다.

var model = ['Buy some milk', 'Learn to code', 'Wear some plaid'];
var count = 0;

Array.observe(model, function(changeRecords) {
  count++;
  console.log('Array observe', changeRecords, count);
});

model[0] = 'Teach Paul Lewis to code';
model[1] = 'Channel your inner Paul Irish';
배열 관찰

성능

O.o()의 계산 성능 영향을 읽기 캐시와 같다고 생각하면 됩니다. 일반적으로 캐시는 다음과 같은 경우에 적합합니다.

  1. 읽기 빈도는 쓰기 빈도의 대부분을 차지합니다.
  2. 읽기 중 알고리즘으로 더 나은 성능을 발휘하기 위해 쓰기 중에 관련된 일정한 양의 작업을 교환하는 캐시를 만들 수 있습니다.
  3. 쓰기의 지속적인 시간 속도는 허용됩니다.

O.o()는 1과 같은 사용 사례를 위해 설계되었습니다.

더티 검사를 실행하려면 관찰 중인 모든 데이터의 사본을 보관해야 합니다. 즉, O.o()로 얻을 수 없는 더티 검사에 구조적 메모리 비용이 발생합니다. 더티 검사는 적절한 스톱 갭 솔루션이지만 근본적으로 누수되는 추상화로 인해 애플리케이션에 불필요한 복잡성을 야기할 수 있습니다.

왜냐하면 더티 검사는 데이터가 변경될 수 있는 경우 항상 실행해야 합니다. 이 작업을 할 수 있는 강력한 방법은 없으며, 접근 방식에는 상당한 단점이 있습니다 (예: 폴링 간격을 확인하면 시각적 아티팩트와 코드 문제 간의 경합 상태가 발생할 수 있음). 또한 더티 검사에는 전역적인 관찰자 레지스트리가 필요하며, 메모리 누수 위험과 해체 비용이 O.o()로 방지됩니다.

몇 가지 수치를 살펴보겠습니다.

아래의 벤치마크 테스트 (GitHub에서 사용 가능)를 사용하면 더티 검사와 O.o()를 비교할 수 있습니다. 관찰된 객체 세트 크기와 변형 수의 그래프로 구성됩니다. 결과적으로 더티 검사 성능은 관찰된 객체의 수에 알고리즘적으로 비례하는 반면, O.o() 성능은 생성된 변형의 수에 비례합니다.

더티 검사

잘못된 검사 성능

Object.observe()가 사용 설정된 Chrome

성능 관찰

Object.observe() 폴리필(Polyfilling Object.observe())

좋습니다. Chrome 36에서 O.o()를 사용할 수 있지만 다른 브라우저에서는 어떻게 해야 할까요? Google에서 지원해 드립니다. Polymer의 Observe-JS는 O.o()의 폴리필로, 있는 경우 네이티브 구현을 사용하지만 그 외에는 폴리필하고 그 위에 유용한 슈가링을 포함합니다. 변경사항을 요약하고 변경된 사항에 대한 보고서를 제공하는 전체 뷰를 제공합니다. 두 가지 강력한 특징은 다음과 같습니다.

  1. 경로를 관찰할 수 있습니다. 즉, 주어진 객체에서 'foo.bar.baz'를 관찰하면 해당 경로의 값이 변경되었을 때 이를 알 수 있습니다. 경로에 연결할 수 없는 경우 값이 정의되지 않은 것으로 간주합니다.

특정 객체의 경로에서 값을 관찰하는 예:

var obj = { foo: { bar: 'baz' } };

var observer = new PathObserver(obj, 'foo.bar');
observer.open(function(newValue, oldValue) {
  // respond to obj.foo.bar having changed value.
});
  1. 배열 스플라이스에 관해 알 수 있습니다. 배열 이음은 기본적으로 이전 버전의 배열을 새 버전의 배열로 변환하기 위해 배열에서 수행해야 하는 최소 이음 작업 집합입니다. 이는 변환의 유형 또는 배열의 다른 뷰입니다. 이는 이전 상태에서 새 상태로 이동하기 위해 해야 하는 최소한의 작업입니다.

배열의 변경사항을 최소 스플라이스 집합으로 보고하는 예는 다음과 같습니다.

var arr = [0, 1, 2, 4];

var observer = new ArrayObserver(arr);
observer.open(function(splices) {
  // respond to changes to the elements of arr.
  splices.forEach(function(splice) {
    splice.index; // index position that the change occurred.
    splice.removed; // an array of values representing the sequence of elements which were removed
    splice.addedCount; // the number of elements which were inserted.
  });
});

프레임워크 및 Object.observe()

앞서 언급했듯이 O.o()는 프레임워크와 라이브러리가 이 기능을 지원하는 브라우저에서 데이터 바인딩 성능을 개선할 수 있는 엄청난 기회를 제공합니다.

Ember의 예후다 카츠와 에릭 브린은 O.o() 지원을 추가하는 것이 Ember의 단기적인 로드맵에 있다고 확인해 주었습니다. Angular의 Misko Hervy는 Angular 2.0의 향상된 변경 감지 기능에 관한 디자인 문서를 작성했습니다. Object.observe()가 Chrome 안정화 버전으로 출시되면 그때까지 자체 변경 감지 방식인 Watchtower.js를 선택하는 것이 장기적인 접근 방식입니다. Suuuuper는 흥미진진하네요.

결론

O.o()는 지금 바로 웹 플랫폼에 추가할 수 있는 강력한 추가 기능입니다.

조만간 더 많은 브라우저에서 이 기능을 사용할 수 있게 되어 JavaScript 프레임워크가 기본 객체 관찰 기능에 액세스함으로써 성능을 향상할 수 있게 되기를 바랍니다. Chrome을 대상으로 하는 경우 Chrome 36 이상에서 O.o()를 사용할 수 있으며 향후 Opera 릴리스에서도 이 기능을 사용할 수 있게 됩니다.

JavaScript 프레임워크 작성자와 함께 Object.observe()에 관해 알아보고 이들이 앱의 데이터 결합 성능을 개선하기 위해 이 프레임워크를 어떻게 사용할 계획인지 알아보세요. 앞으로 가장 기대되는 시기가 다가오고 있습니다.

자료

의견과 리뷰를 제공해 주신 라파엘 와인스타인, 제이크 아치볼드, 에릭 비델만, 폴 킨란, 비비안 크롬웰 씨께 감사드립니다.