انقلاب های اتصال داده با Object.observe()

آدی عثمانی
Addy Osmani

معرفی

یک انقلاب در راه است. افزودنی جدیدی به جاوا اسکریپت وجود دارد که همه چیزهایی را که فکر می کنید در مورد اتصال داده می دانید تغییر می دهد. همچنین تعداد کتابخانه‌های MVC شما به مشاهده مدل‌ها برای ویرایش‌ها و به‌روزرسانی‌ها تغییر می‌کند. آیا برای افزایش عملکرد شیرین برنامه هایی که به مشاهده اموال اهمیت می دهند، آماده هستید؟

باشه. باشه. بدون تأخیر بیشتر، خوشحالم که اعلام کنم Object.observe() در Chrome 36 stable قرار گرفت. [وووو. جمعیت وحشی می شود] .

Object.observe() ، بخشی از استاندارد ECMAScript آینده، روشی برای مشاهده ناهمزمان تغییرات در اشیاء جاوا اسکریپت بدون نیاز به کتابخانه جداگانه است. این به یک ناظر اجازه می دهد تا یک توالی مرتب شده از رکوردهای تغییر را دریافت کند که مجموعه تغییراتی را که در مجموعه ای از اشیاء مشاهده شده رخ داده است را توصیف می کند.

// 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() (من دوست دارم آن را Oo() یا Ooooooooo بنامم)، می توانید اتصال داده دو طرفه را بدون نیاز به چارچوب پیاده سازی کنید.

این بدان معنا نیست که شما نباید از یکی استفاده کنید. برای پروژه های بزرگ با منطق تجاری پیچیده، چارچوب های نظری بسیار ارزشمند هستند و شما باید به استفاده از آنها ادامه دهید. آنها جهت گیری توسعه دهندگان جدید را ساده می کنند، به نگهداری کد کمتری نیاز دارند و الگوهایی را در مورد چگونگی دستیابی به وظایف مشترک تحمیل می کنند. وقتی به یکی نیاز ندارید، می‌توانید از کتابخانه‌های کوچک‌تر و متمرکزتر مانند Polymer (که قبلاً از Oo() بهره می‌برد، استفاده کنید.

حتی اگر به شدت از یک چارچوب یا کتابخانه MV* استفاده می‌کنید، Oo() این پتانسیل را دارد که برخی بهبودهای عملکرد سالم را با اجرای سریع‌تر و ساده‌تر و در عین حال حفظ همان API، به آنها ارائه دهد. به عنوان مثال، سال گذشته Angular دریافت که در معیاری که در آن تغییراتی در یک مدل انجام می‌شد، بررسی کثیف 40 میلی‌ثانیه در هر به‌روزرسانی و Oo() 1-2 میلی‌ثانیه در هر به‌روزرسانی طول می‌کشید (بهبودی 20 تا 40 برابر سریع‌تر).

اتصال داده ها بدون نیاز به کدهای پیچیده به این معنی است که دیگر نیازی به نظرسنجی برای تغییرات ندارید، بنابراین عمر باتری بیشتر است!

اگر قبلاً در Oo() فروخته شده‌اید، به سراغ معرفی ویژگی بروید یا برای اطلاعات بیشتر در مورد مشکلاتی که حل می‌کند، ادامه مطلب را مطالعه کنید.

چه چیزی را می خواهیم مشاهده کنیم؟

هنگامی که ما در مورد مشاهده داده ها صحبت می کنیم، معمولاً به مراقبت از برخی از انواع خاص تغییرات اشاره می کنیم:

  • تغییرات در اشیاء جاوا اسکریپت خام
  • هنگامی که ویژگی ها اضافه می شوند، تغییر می کنند، حذف می شوند
  • زمانی که آرایه ها دارای عناصری هستند که در داخل و خارج از آنها به هم متصل شده اند
  • تغییرات در نمونه اولیه شی

اهمیت اتصال داده ها

زمانی که به جداسازی کنترل مدل-نما اهمیت می دهید، اتصال داده ها اهمیت پیدا می کند. HTML یک مکانیسم اعلامی عالی است، اما کاملاً ثابت است. در حالت ایده آل، شما فقط می خواهید رابطه بین داده های خود و DOM را اعلام کنید و DOM را به روز نگه دارید. این باعث ایجاد اهرم می شود و در زمان زیادی برای نوشتن کدهای تکراری صرفه جویی می کند که فقط داده ها را به DOM بین وضعیت داخلی برنامه یا سرور ارسال می کند.

اتصال داده ها به ویژه زمانی مفید است که یک رابط کاربری پیچیده دارید که در آن باید روابط بین چندین ویژگی را در مدل های داده خود با چندین عنصر در نماهای خود تنظیم کنید. این در برنامه های تک صفحه ای که امروز می سازیم بسیار رایج است.

با ایجاد روشی برای مشاهده بومی داده ها در مرورگر، ما به چارچوب های جاوا اسکریپت (و کتابخانه های ابزار کوچکی که می نویسید) راهی برای مشاهده تغییرات در داده های مدل بدون تکیه بر برخی از هک های کندی که جهان امروز استفاده می کند، می دهیم.

آنچه جهان امروز به نظر می رسد

کثیف چک کردن

قبلاً پیوند داده‌ها را کجا دیده‌اید؟ خوب، اگر از یک کتابخانه مدرن MV* برای ساخت برنامه های وب خود استفاده می کنید (مانند Angular، Knockout) احتمالاً به اتصال داده های مدل به DOM عادت کرده اید. به عنوان یک تجدید، در اینجا مثالی از یک برنامه لیست تلفن آورده شده است که در آن مقدار هر تلفن در یک آرایه phones (تعریف شده در جاوا اسکریپت) را به یک آیتم لیست متصل می کنیم تا داده ها و رابط کاربری ما همیشه همگام باشند:

<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>

و جاوا اسکریپت برای کنترلر:

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، یک چرخه خلاصه تمام عبارات ثبت شده برای مشاهده را شناسایی می کند تا ببیند آیا تغییری وجود دارد یا خیر. از مقادیر قبلی یک مدل اطلاع دارد و اگر تغییر کرده باشند، یک رویداد تغییر فعال می شود. برای یک توسعه‌دهنده، مزیت اصلی در اینجا این است که می‌توانید از داده‌های شی جاوا اسکریپت خام استفاده کنید که استفاده از آن دلپذیر است و نسبتاً خوب ترکیب می‌شود. نکته منفی این است که رفتار الگوریتمی بدی دارد و به طور بالقوه بسیار گران است.

چک کردن کثیف

هزینه این عملیات متناسب با تعداد کل اشیاء مشاهده شده است. من ممکن است نیاز به بررسی کثیف زیادی داشته باشم. همچنین ممکن است به راهی برای شروع بررسی کثیف در زمانی که داده ها ممکن است تغییر کرده باشند نیاز داشته باشد. فریمورک‌های ترفندهای هوشمندانه زیادی برای این کار استفاده می‌کنند. مشخص نیست که آیا این هرگز عالی خواهد بود یا خیر.

اکوسیستم وب باید توانایی بیشتری برای نوآوری و تکامل مکانیسم های اعلامی خود داشته باشد، به عنوان مثال

  • سیستم های مدل مبتنی بر محدودیت
  • سیستم‌های پایداری خودکار (مانند تغییرات مداوم در 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 را انتخاب کنیم بدون اینکه نیازی به بررسی دائمی همه چیز باشد. چیزی با رفتار الگوریتمی خوب. چیزی که به خوبی ترکیب می شود و در سکو پخته می شود. این زیبایی چیزی است که Object.observe() به جدول می آورد.

این به ما امکان می‌دهد یک شی را مشاهده کنیم، ویژگی‌ها را جهش دهیم و گزارش تغییر را ببینیم که چه چیزی تغییر کرده است. اما در مورد تئوری کافی است، اجازه دهید به کدهایی نگاه کنیم!

Object.observe()

Object.observe() و Object.unobserve()

بیایید تصور کنیم که یک شی جاوا اسکریپت ساده وانیلی داریم که یک مدل را نشان می دهد:

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

سپس می‌توانیم برای هر زمان که جهش (تغییر) در شی ایجاد می‌شود، یک callback تعیین کنیم:

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
  });
}

سپس می‌توانیم با استفاده از Oo()، این تغییرات را مشاهده کنیم، که در شیء به‌عنوان آرگومان اول و callback را به‌عنوان آرگومان دوم ارسال می‌کنیم:

Object.observe(todoModel, observer);

بیایید شروع به ایجاد برخی تغییرات در شی مدل Todos خود کنیم:

todoModel.label = 'Buy some more milk';

با نگاهی به کنسول، اطلاعات مفیدی دریافت می کنیم! ما می دانیم که چه ویژگی تغییر کرده است، چگونه تغییر کرده است و ارزش جدید چقدر است.

گزارش کنسول

وو خداحافظ، کثیف چک! سنگ قبر شما باید در Comic Sans حک شود. بیایید یک ملک دیگر را تغییر دهیم. این بار completeBy :

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

همانطور که می بینیم، یک بار دیگر با موفقیت گزارش تغییر را دریافت می کنیم:

تغییر گزارش

عالی. چه می شود اگر اکنون تصمیم بگیریم که ویژگی "کامل" را از شیء خود حذف کنیم:

delete todoModel.completed;
تکمیل شد

همانطور که می بینیم، گزارش تغییرات بازگشتی شامل اطلاعات مربوط به حذف است. همانطور که انتظار می رفت، ارزش جدید ملک در حال حاضر تعریف نشده است. بنابراین، اکنون می دانیم که می توانید متوجه شوید که چه زمانی خواص اضافه شده است. وقتی حذف شدند اساساً، مجموعه ای از ویژگی های یک شی ("جدید"، "حذف شده"، "تنظیم مجدد") و نمونه اولیه آن در حال تغییر است ( پرتو ).

مانند هر سیستم مشاهده‌ای، روشی نیز وجود دارد که از شنیدن تغییرات جلوگیری می‌کند. در این مورد، Object.unobserve() است که امضای مشابه Oo() دارد اما می توان آن را به صورت زیر فراخوانی کرد:

Object.unobserve(todoModel, observer);

همانطور که در زیر می بینیم، هر گونه جهش پس از اجرای این شیء، دیگر منجر به بازگشت لیستی از رکوردهای تغییر نمی شود.

جهش ها

مشخص کردن تغییرات مورد علاقه

بنابراین ما به اصول اولیه چگونگی بازگرداندن لیستی از تغییرات به یک شی مشاهده شده نگاه کرده ایم. اگر فقط به زیرمجموعه‌ای از تغییراتی که در یک شیء ایجاد شده‌اند و نه همه آنها علاقه دارید، چه؟ همه به یک فیلتر اسپم نیاز دارند. خوب، ناظران می‌توانند تنها آن دسته از تغییراتی را که می‌خواهند از طریق فهرست پذیرش بشنوند، مشخص کنند. این را می توان با استفاده از آرگومان سوم Oo() به صورت زیر مشخص کرد:

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;

اگر لیستی از انواع پذیرش را در Oo() مشخص نکنید، به طور پیش‌فرض روی انواع تغییر شی «ذاتی» است ( add ، update ، delete ، reconfigure ، preventExtensions (برای زمانی که یک شی غیرقابل توسعه قابل مشاهده نیست) ).

اطلاعیه

Oo() همچنین با مفهوم اعلان ها همراه است. آنها هیچ شباهتی به چیزهای آزاردهنده ای ندارند که در تلفن دریافت می کنید، بلکه مفید هستند. اعلان ها مشابه Mutation Observers هستند. آنها در پایان کار خرد اتفاق می افتند. در زمینه مرورگر، این تقریباً همیشه در انتهای کنترل کننده رویداد فعلی است.

زمان بندی خوب است زیرا به طور کلی یک واحد کار تمام شده است و اکنون ناظران کار خود را انجام می دهند. این یک مدل پردازش نوبتی خوب است.

گردش کار برای استفاده از اعلان کننده کمی شبیه به این است:

اطلاعیه

بیایید به مثالی از نحوه استفاده از اعلان‌کننده‌ها در عمل برای تعریف اعلان‌های سفارشی برای زمانی که ویژگی‌های یک شیء دریافت یا تنظیم می‌شوند، استفاده کنیم. به نظرات اینجا توجه کنید:

// 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);
کنسول اعلان ها

در اینجا ما گزارش می دهیم که ارزش ویژگی های داده تغییر کند ("به روز رسانی"). هر چیز دیگری که پیاده سازی شی برای گزارش انتخاب می کند ( notifier.notifyChange() ).

سالها تجربه در پلتفرم وب به ما آموخته است که رویکرد همزمان اولین چیزی است که شما امتحان می کنید زیرا ساده ترین روش برای پیچیدن سر است. مشکل این است که یک مدل پردازش اساسا خطرناک ایجاد می کند. اگر در حال نوشتن کد هستید و می‌گویید، ویژگی یک شی را به‌روزرسانی کنید، واقعاً نمی‌خواهید وضعیتی که ویژگی آن شیء را به‌روزرسانی کند، می‌توانست از یک کد دلخواه دعوت کند که هر کاری می‌خواهد انجام دهد. این ایده آل نیست که مفروضات شما باطل شود، زیرا در وسط یک تابع در حال اجرا هستید.

اگر شما یک ناظر هستید، در حالت ایده‌آل نمی‌خواهید که اگر کسی در وسط چیزی قرار دارد با شما تماس گرفته شود. شما نمی خواهید از شما خواسته شود که برای انجام کار در یک وضعیت ناسازگار دنیا بروید. در نهایت به بررسی خطاهای بسیار بیشتری بپردازید. تلاش برای تحمل شرایط بد بسیار بیشتر و به طور کلی، کار کردن با آن یک مدل سخت است. مقابله با Async سخت تر است اما در نهایت مدل بهتری است.

راه حل این مشکل ثبت تغییر مصنوعی است.

سوابق تغییر مصنوعی

اساساً، اگر می‌خواهید دسترسی‌ها یا ویژگی‌های محاسبه‌شده داشته باشید، مسئولیت شما این است که هنگام تغییر این مقادیر اطلاع دهید. این یک کار اضافی است اما به عنوان نوعی ویژگی درجه یک این مکانیسم طراحی شده است و این اعلان ها با بقیه اعلان ها از اشیاء داده زیرین ارائه می شود. از ویژگی های داده.

سوابق تغییر مصنوعی

مشاهده دسترسی ها و ویژگی های محاسبه شده را می توان با notifier.notify - بخشی دیگر از Oo() حل کرد. اکثر سیستم‌های مشاهده‌ای می‌خواهند نوعی از مشاهده مقادیر مشتق شده را داشته باشند. راه های زیادی برای انجام این کار وجود دارد. او در مورد راه "درست" قضاوتی نمی کند. ویژگی های محاسبه شده باید دسترسی هایی باشند که هنگام تغییر حالت داخلی (خصوصی) اطلاع دهند .

باز هم، وب‌دفورها باید از کتابخانه‌ها انتظار داشته باشند که به اطلاع‌رسانی و رویکردهای مختلف برای ویژگی‌های محاسبه‌شده (و کاهش دیگ بخار) کمک کنند.

بیایید مثال بعدی را که یک کلاس دایره است تنظیم کنیم. ایده اینجا این است که ما این دایره را داریم و یک ویژگی شعاع وجود دارد. در این مورد، شعاع یک دسترسی است و زمانی که مقدار آن تغییر می کند، در واقع به خود اطلاع می دهد که مقدار تغییر کرده است. این با تمام تغییرات دیگر در این شی یا هر شی دیگری ارائه می شود. اساساً، اگر در حال پیاده‌سازی یک شی هستید، می‌خواهید ویژگی‌های مصنوعی یا محاسباتی داشته باشد یا باید یک استراتژی برای نحوه عملکرد آن انتخاب کنید. هنگامی که این کار را انجام دادید، به طور کلی در سیستم شما قرار می گیرد.

برای دیدن کارکرد این کد در 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);
  })
}
کنسول ثبت تغییرات مصنوعی

ویژگی های دسترسی

یک یادداشت سریع در مورد ویژگی های Accessor. قبلاً ذکر کردیم که فقط تغییرات مقدار برای خصوصیات داده قابل مشاهده است. نه برای ویژگی های محاسبه شده یا لوازم جانبی. دلیل آن این است که جاوا اسکریپت واقعاً مفهوم تغییر در ارزش دسترسی ها را ندارد. یک Accessor فقط مجموعه ای از توابع است.

اگر به یک Accessor اختصاص دهید جاوا اسکریپت فقط تابع را در آنجا فراخوانی می کند و از نظر آن چیزی تغییر نکرده است. فقط به مقداری کد فرصت اجرا داد.

مشکل از لحاظ معنایی این است که ما می توانیم به انتساب بالا به مقدار - 5 به آن نگاه کنیم. ما باید بتوانیم بدانیم اینجا چه اتفاقی افتاده است. این در واقع یک مشکل غیر قابل حل است. مثال نشان می دهد که چرا. واقعاً هیچ راهی برای هیچ سیستمی وجود ندارد که بداند منظور از این چیست زیرا ممکن است کد دلخواه باشد. در این مورد هر کاری دلش می خواهد بکند. این مقدار را هر بار که به آن دسترسی پیدا می کند به روز می کند و بنابراین پرسیدن اینکه آیا تغییر کرده است چندان منطقی نیست.

مشاهده چندین شی با یک تماس

الگوی دیگری که با Oo() امکان پذیر است، مفهوم مشاهده گر پاسخ به تماس واحد است. این اجازه می دهد تا یک تماس برگشتی به عنوان یک "ناظر" برای بسیاری از اشیاء مختلف استفاده شود. تماس برگشتی مجموعه کاملی از تغییرات را به تمام اشیایی که مشاهده می‌کند در پایان ریزتسک ارسال می‌کند (به شباهت‌های مشاهده‌کنندگان جهش توجه کنید).

مشاهده چندین شی با یک تماس

تغییرات در مقیاس بزرگ

شاید شما در حال کار بر روی یک برنامه واقعاً بزرگ هستید و مرتباً مجبور هستید با تغییرات در مقیاس بزرگ کار کنید. اشیا ممکن است بخواهند تغییرات معنایی بزرگ‌تری را توصیف کنند که بر بسیاری از ویژگی‌ها به روشی فشرده‌تر تأثیر می‌گذارد (به‌جای پخش تن‌ها تغییر ویژگی).

Oo() در قالب دو ابزار خاص به این امر کمک می کند: notifier.performChange() و notifier.notify() که قبلاً معرفی کرده ایم.

تغییرات در مقیاس بزرگ

بیایید در مثالی به این موضوع نگاه کنیم که چگونه تغییرات در مقیاس بزرگ را می توان توصیف کرد، جایی که ما یک شی 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);

آن را رعایت کنید و سپس تغییراتی ایجاد کنید. OMG، خیلی سرگرم کننده است. خیلی چیزها!

// 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 }
تغییرات در مقیاس بزرگ

همه چیز در داخل "عملکرد انجام" به عنوان کار "تغییر بزرگ" در نظر گرفته می شود. ناظرانی که "تغییر بزرگ" را می پذیرند فقط رکورد "تغییر بزرگ" را دریافت می کنند. ناظرانی که این کار را نمی کنند، تغییرات اساسی ناشی از کاری را که «عملکرد را انجام می دهند» دریافت خواهند کرد.

مشاهده آرایه ها

مدتی در مورد مشاهده تغییرات اشیاء صحبت کرده ایم اما آرایه ها چطور؟! سوال عالی وقتی کسی به من می گوید: "سوال عالی." من هرگز پاسخ آنها را نمی شنوم زیرا مشغول تبریک به خودم برای پرسیدن چنین سؤال بزرگی هستم، اما من پرت می شوم. روش های جدیدی برای کار با آرایه ها هم داریم!

Array.observe() متدی است که تغییرات در مقیاس بزرگ را برای خود - به عنوان مثال - splice، unshift یا هر چیزی که به طور ضمنی طول آن را تغییر می دهد - به عنوان رکورد تغییر "splice" رفتار می کند. در داخل از 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';
مشاهده آرایه ها

کارایی

راه فکر کردن به تاثیر عملکرد محاسباتی Oo() این است که در مورد آن مانند یک حافظه پنهان خوانده شده فکر کنید. به طور کلی، یک کش یک انتخاب عالی است زمانی که (به ترتیب اهمیت):

  1. فرکانس خواندن بر فرکانس نوشتن غالب است.
  2. شما می توانید یک حافظه پنهان ایجاد کنید که مقدار ثابت کار در طول نوشتن را برای عملکرد الگوریتمی بهتر در حین خواندن معامله می کند.
  3. کند شدن زمان ثابت نوشتن قابل قبول است.

Oo() برای موارد استفاده مانند 1 طراحی شده است.

بررسی کثیف مستلزم نگهداری یک کپی از تمام داده هایی است که مشاهده می کنید. این به این معنی است که برای بررسی کثیف که با Oo () نمی توانید آن را دریافت کنید، هزینه ساختاری حافظه را متحمل می شوید. چک کردن کثیف، در حالی که یک راه حل توقف مناسب است، همچنین اساساً یک انتزاع نشتی است که می تواند پیچیدگی غیر ضروری برای برنامه ها ایجاد کند.

چرا؟ خوب، بررسی کثیف باید هر زمانی که داده ها تغییر کرده باشند اجرا شود. به سادگی یک راه قوی برای انجام این کار وجود ندارد و هر رویکردی به آن دارای جنبه های منفی قابل توجهی است (به عنوان مثال بررسی یک بازه نظرسنجی، مصنوعات بصری و شرایط مسابقه بین نگرانی های کد را به خطر می اندازد). چک کردن کثیف همچنین به یک ثبت جهانی از ناظران نیاز دارد، که خطرات نشت حافظه را ایجاد می کند و هزینه های پاره کردن Oo() از آن جلوگیری می کند.

بیایید نگاهی به برخی از اعداد بیندازیم.

تست‌های بنچمارک زیر (موجود در GitHub ) به ما اجازه می‌دهند تا بررسی کثیف را با Oo() مقایسه کنیم. آنها به صورت نمودارهایی از Object-Set-Set-Size در مقابل تعداد-Of-Mutations ساختار یافته اند. نتیجه کلی این است که عملکرد بررسی کثیف از نظر الگوریتمی با تعداد اشیاء مشاهده شده متناسب است در حالی که عملکرد Oo() متناسب با تعداد جهش هایی است که ایجاد شده است.

کثیف چک کردن

عملکرد چک کردن کثیف

کروم با () Object.observe روشن است

عملکرد را رعایت کنید

Polyfilling Object.observe()

عالی - بنابراین Oo() را می توان در Chrome 36 استفاده کرد، اما در مورد استفاده از آن در سایر مرورگرها چطور؟ ما شما را تحت پوشش قرار داده ایم. Polymer's Observe-JS یک polyfill برای Oo() است که در صورت وجود از پیاده‌سازی بومی استفاده می‌کند، اما در غیر این صورت آن را polyfill می‌کند و شامل چند قند مفید در بالا می‌شود. این یک نمای کلی از جهان ارائه می دهد که تغییرات را خلاصه می کند و گزارشی از آنچه تغییر کرده است ارائه می دهد. دو چیز واقعاً قدرتمند که آن را آشکار می کند عبارتند از:

  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.
  });
});

Frameworks و Object.observe()

همانطور که گفته شد، Oo() به چارچوب ها و کتابخانه ها فرصت بزرگی برای بهبود عملکرد اتصال داده خود در مرورگرهایی که از این ویژگی پشتیبانی می کنند، می دهد.

Yehuda Katz و Erik Bryn از Ember تأیید کردند که افزودن پشتیبانی برای Oo() در نقشه راه کوتاه مدت Ember است. Misko Hervy از Angular یک سند طراحی بر روی تشخیص تغییر بهبود یافته Angular 2.0 نوشت. رویکرد بلندمدت آنها این خواهد بود که از مزایای Object.observe() هنگامی که در Chrome stabil قرار می‌گیرد، استفاده می‌کنند و Watchtower.js را انتخاب می‌کنند، که تا آن زمان، رویکرد تشخیص تغییر خودشان است. فوق العاده هیجان انگیز

نتیجه گیری

Oo() یک افزونه قدرتمند برای پلتفرم وب است که می توانید امروز بیرون بروید و از آن استفاده کنید.

ما امیدواریم که به مرور زمان این ویژگی در مرورگرهای بیشتری قرار گیرد و به چارچوب‌های جاوا اسکریپت اجازه دهد عملکرد را از دسترسی به قابلیت‌های مشاهده شی بومی افزایش دهند. کسانی که کروم را هدف قرار می دهند باید بتوانند از Oo() در کروم 36 (و بالاتر) استفاده کنند و این ویژگی باید در نسخه بعدی Opera نیز در دسترس باشد.

بنابراین، با نویسندگان چارچوب های جاوا اسکریپت در مورد Object.observe() و نحوه برنامه ریزی آنها برای استفاده از آن برای بهبود عملکرد اتصال داده در برنامه های شما صحبت کنید. قطعاً زمان های هیجان انگیزی در پیش است!

منابع

با تشکر از رافائل واینستین، جیک آرچیبالد، اریک بیدلمن، پل کینلان و ویویان کرامول برای نظرات و نظراتشان.