บทนำ
การปฏิวัติกำลังจะเกิดขึ้น JavaScript มีการเพิ่มเติมใหม่ที่จะเปลี่ยนแปลงทุกสิ่งที่คุณคิดว่าคุณรู้เกี่ยวกับการเชื่อมโยงข้อมูล และยังเปลี่ยนจำนวนไลบรารี MVC ที่ใช้สังเกตโมเดลสำหรับการแก้ไขและการอัปเดตอีกด้วย คุณพร้อมที่จะเพิ่มประสิทธิภาพอันหอมหวานให้กับแอปที่ให้ความสำคัญกับการสังเกตพร็อพเพอร์ตี้แล้วหรือยัง
ได้เลย ได้เลย เรายินดีที่จะประกาศว่า Object.observe()
พร้อมให้ใช้งานใน Chrome 36 เวอร์ชันเสถียรแล้ว [ไชโย THE CROWD GOES WILD]
Object.observe()
ซึ่งเป็นส่วนหนึ่งของมาตรฐาน ECMAScript ในอนาคต เป็นวิธีการสังเกตการเปลี่ยนแปลงออบเจ็กต์ 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) ช่วยให้คุณใช้การเชื่อมโยงข้อมูลแบบ 2 ทางได้โดยไม่ต้องใช้เฟรมเวิร์ก
แต่นั่นไม่ได้หมายความว่าคุณไม่ควรใช้ สําหรับโปรเจ็กต์ขนาดใหญ่ที่มีตรรกะทางธุรกิจที่ซับซ้อน เฟรมเวิร์กที่แสดงความคิดเห็นนั้นมีประโยชน์อย่างยิ่งและควรใช้ต่อไป ช่วยลดความซับซ้อนในการวางแนวทางของนักพัฒนาซอฟต์แวร์รายใหม่ ลดภาระการบำรุงรักษาโค้ด และกำหนดรูปแบบเกี่ยวกับวิธีดำเนินงานทั่วไปให้เสร็จสิ้น เมื่อไม่จำเป็นต้องใช้ คุณสามารถใช้ไลบรารีขนาดเล็กและเจาะจงมากขึ้น เช่น Polymer (ซึ่งใช้ประโยชน์จาก O.o() อยู่แล้ว)
แม้คุณจะใช้งานเฟรมเวิร์กหรือไลบรารี MV* เป็นอย่างมาก แต่ O.o() ก็มีศักยภาพในการปรับปรุงประสิทธิภาพการทำงานที่มีคุณภาพสำหรับการใช้งานที่รวดเร็วยิ่งขึ้นและง่ายขึ้น ในขณะที่ยังคงใช้ API เดิม ตัวอย่างเช่น เมื่อปีที่แล้ว Angular พบว่า ในการเปรียบเทียบที่เกิดขึ้นกับโมเดล การตรวจสอบความสกปรกใช้เวลา 40 มิลลิวินาทีต่อการอัปเดต และ O.o() ใช้เวลา 1-2 มิลลิวินาทีต่อการอัปเดต (มีการปรับปรุงเร็วขึ้น 20-40 เท่า)
การผูกข้อมูลโดยไม่ต้องใช้โค้ดที่ซับซ้อนมากมายก็หมายความว่าคุณไม่ต้องตรวจสอบการเปลี่ยนแปลงต่างๆ อีกต่อไปและทำให้อายุการใช้งานแบตเตอรี่ยาวนานขึ้นอีกด้วย
หากขายใน O.o() อยู่แล้ว ให้ข้ามไปที่การแนะนำฟีเจอร์ หรืออ่านข้อมูลเพิ่มเติมเกี่ยวกับปัญหาที่โซลูชันนี้แก้ไขได้
เราต้องการสังเกตอะไร
เมื่อพูดถึงการสังเกตข้อมูล โดยทั่วไปเราหมายถึงการคอยสังเกตการเปลี่ยนแปลงบางประเภทต่อไปนี้
- การเปลี่ยนแปลงออบเจ็กต์ JavaScript ดิบ
- เมื่อมีการเพิ่ม เปลี่ยนแปลง หรือลบพร็อพเพอร์ตี้
- เมื่ออาร์เรย์มีการต่อองค์ประกอบเข้าและออกจากอาร์เรย์
- การเปลี่ยนแปลงต้นแบบของออบเจ็กต์
ความสำคัญของการเชื่อมโยงข้อมูล
การเชื่อมโยงข้อมูลจะเริ่มมีความสำคัญเมื่อคุณให้ความสำคัญกับการแยกการควบคุมมุมมองโมเดล HTML เป็นกลไกการประกาศที่ยอดเยี่ยม แต่เป็นแบบคงที่โดยสมบูรณ์ โดยหลักการแล้ว คุณเพียงต้องประกาศความสัมพันธ์ระหว่างข้อมูลกับ DOM และอัปเดต DOM ให้เป็นข้อมูลล่าสุดอยู่เสมอ ซึ่งจะสร้างประโยชน์และช่วยประหยัดเวลาในการเขียนโค้ดซ้ำๆ มากจริงๆ ซึ่งส่งข้อมูลจาก DOM ไปยังสถานะภายในของแอปพลิเคชันหรือเซิร์ฟเวอร์
การเชื่อมโยงข้อมูลจะมีประโยชน์อย่างยิ่งเมื่อคุณมีอินเทอร์เฟซผู้ใช้ที่ซับซ้อนซึ่งคุณต้องเชื่อมต่อความสัมพันธ์ระหว่างพร็อพเพอร์ตี้หลายรายการในโมเดลข้อมูลกับองค์ประกอบหลายรายการในมุมมอง ปัญหานี้พบได้ทั่วไปในแอปพลิเคชันหน้าเว็บเดียวที่เรากำลังสร้างในปัจจุบัน
การอบวิธีสังเกตข้อมูลในเบราว์เซอร์โดยธรรมชาติ ทำให้เราได้ให้เฟรมเวิร์ก JavaScript (และไลบรารียูทิลิตีขนาดเล็กที่คุณเขียน) สำหรับสังเกตการเปลี่ยนแปลงของโมเดลข้อมูลโดยไม่ต้องอาศัยการแฮ็กแบบช้าที่คนทั่วโลกใช้กันในปัจจุบัน
สภาพโลกในปัจจุบัน
การตรวจสอบข้อมูล
คุณเคยเห็นการเชื่อมโยงข้อมูลที่ใดมาก่อน หากคุณใช้ไลบรารี MV* สมัยใหม่ในการสร้างเว็บแอป (เช่น Angular, Knockout) คุณอาจคุ้นเคยกับการเชื่อมโยงข้อมูลโมเดลกับ DOM เพื่อเป็นการทบทวน มาดูตัวอย่างแอปรายการโทรศัพท์ที่เราเชื่อมโยงมูลค่าของโทรศัพท์แต่ละเครื่องในอาร์เรย์ phones
(ที่กำหนดไว้ใน JavaScript) กับรายการเพื่อให้ข้อมูลและ UI ซิงค์กันเสมอ
<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, กระดูกสันหลัง)
ออบเจ็กต์คอนเทนเนอร์คือส่วนที่เฟรมเวิร์กสร้างออบเจ็กต์ที่ด้านในจะเก็บข้อมูลไว้ ตัวแปรเหล่านี้มีตัวเข้าถึงข้อมูลและสามารถบันทึกสิ่งที่คุณตั้งค่าหรือรับ และออกอากาศภายในได้ วิธีนี้ได้ผลดี เครื่องมือนี้ค่อนข้างมีประสิทธิภาพและมีลักษณะการทำงานตามอัลกอริทึมที่ดี ตัวอย่างออบเจ็กต์คอนเทนเนอร์ที่ใช้ 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()
ตามหลักการแล้ว สิ่งที่เราต้องการคือสิ่งที่ดีที่สุดจากทั้ง 2 ทาง ซึ่งก็คือวิธีสังเกตข้อมูลด้วยการสนับสนุนวัตถุข้อมูลดิบ (ออบเจ็กต์ JavaScript ทั่วไป) หากเราเลือก AND โดยไม่ต้องตรวจสอบทุกอย่างให้ไม่เป็นระเบียบตลอดเวลา เนื้อหาที่มีลักษณะการทํางานของอัลกอริทึมที่ดี เนื้อหาที่เขียนได้ดีและฝังอยู่ในแพลตฟอร์ม นี่คือข้อดีของ Object.observe()
วิธีนี้ช่วยให้เราสังเกตออบเจ็กต์ เปลี่ยนแปลงพร็อพเพอร์ตี้ และดูรายงานการเปลี่ยนแปลงของสิ่งที่เปลี่ยนแปลงไปได้ แต่พอกันทีเรื่องทฤษฎี มาลองดูโค้ดกัน
Object.observe() และ Object.unobserve()
สมมติว่าเรามีออบเจ็กต์ JavaScript ธรรมดาๆ ที่แสดงโมเดล
// A model can be a simple vanilla object
var todoModel = {
label: 'Default',
completed: false
};
จากนั้นเราจะระบุการเรียกกลับทุกครั้งที่มีการเปลี่ยนแปลง (Mutation) กับออบเจ็กต์ได้ ดังนี้
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() โดยส่งผ่านวัตถุเป็นอาร์กิวเมนต์แรกและ Callback เป็นอาร์กิวเมนต์ที่สองของเรา
Object.observe(todoModel, observer);
มาเริ่มทำการเปลี่ยนแปลงบางอย่างกับออบเจ็กต์โมเดล Todos กัน
todoModel.label = 'Buy some more milk';
เมื่อดูคอนโซลแล้ว เราได้ข้อมูลที่เป็นประโยชน์บางอย่างกลับคืนมา เราทราบดีว่าคุณสมบัติที่เปลี่ยนแปลง การเปลี่ยนแปลง และค่าใหม่คืออะไร
เย่! ลาก่อน การตรวจสอบข้อมูลใหม่ หลุมฝังศพของคุณควรสลักอยู่ใน Comic Sans มาเปลี่ยนพร็อพเพอร์ตี้อื่นกัน เวลานี้ completeBy
:
todoModel.completeBy = '01/01/2014';
เราได้รับรายงานการเปลี่ยนแปลงอีกครั้งเรียบร้อยแล้วดังที่เห็นด้านล่าง
เยี่ยมเลย จะเกิดอะไรขึ้นหากตอนนี้เราตัดสินใจลบพร็อพเพอร์ตี้ "เสร็จสมบูรณ์" ออกจากออบเจ็กต์
delete todoModel.completed;
ดังที่เห็น รายงานการเปลี่ยนแปลงที่แสดงผลมีข้อมูลเกี่ยวกับการลบ ค่าใหม่ของพร็อพเพอร์ตี้เป็น "ไม่ระบุ" ตามที่เราคาดไว้ ตอนนี้เราก็ทราบแล้วว่าคุณจะทราบได้เมื่อมีการเพิ่มพร็อพเพอร์ตี้เข้ามา เมื่อลบแล้ว โดยพื้นฐานแล้ว ชุดพร็อพเพอร์ตี้บนออบเจ็กต์ ("ใหม่" "ถูกลบ" "ได้รับการกำหนดค่าใหม่") และการเปลี่ยนแปลงโปรโตไทป์ (proto)
วิธีการนี้ยังมีไว้เพื่อหยุดการฟังการเปลี่ยนแปลงเช่นเดียวกับในระบบสังเกตอื่นๆ ในกรณีนี้ Object.unobserve()
ซึ่งมีลายเซ็นเดียวกับ O.o() แต่เรียกได้ดังนี้
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 Observer ซึ่งจะเกิดขึ้นเมื่อทำไมโครแทสก์เสร็จแล้ว ในบริบทของเบราว์เซอร์ การดำเนินการนี้จะอยู่ที่ส่วนท้ายของตัวแฮนเดิลเหตุการณ์ปัจจุบันเกือบทุกครั้ง
ช่วงเวลานี้เหมาะมากเพราะโดยทั่วไปแล้ว 1 หน่วยของงานจะเสร็จสิ้นแล้ว และตอนนี้ผู้สังเกตการณ์ก็เริ่มทำงานได้ ซึ่งเป็นรูปแบบการประมวลผลแบบผลัดกันทีละฝ่ายที่ยอดเยี่ยม
เวิร์กโฟลว์ในการใช้เครื่องมือแจ้งเตือนมีลักษณะดังนี้
มาดูตัวอย่างว่าในทางปฏิบัติอาจใช้ตัวแจ้งเพื่อกำหนดการแจ้งเตือนที่กำหนดเองเมื่อมีการรับหรือตั้งค่าพร็อพเพอร์ตี้ในออบเจ็กต์ ติดตามดูความคิดเห็นได้ที่นี่:
// 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()
)
ประสบการณ์หลายปีในแพลตฟอร์มเว็บทำให้เราทราบว่าแนวทางแบบซิงค์เป็นสิ่งแรกที่คุณควรลองใช้เนื่องจากเข้าใจได้ง่ายที่สุด ปัญหาคือการสร้างรูปแบบการประมวลผลที่เป็นอันตรายโดยพื้นฐาน หากคุณเขียนโค้ดและบอกว่าอัปเดตพร็อพเพอร์ตี้ของออบเจ็กต์ คุณไม่ต้องการให้เกิดสถานการณ์ที่การอัปเดตพร็อพเพอร์ตี้ของออบเจ็กต์นั้นอาจเชิญชวนให้โค้ดที่กําหนดเองทําตามที่ต้องการ ไม่ควรทำให้สมมติฐานของคุณเป็นโมฆะขณะที่ดำเนินการอยู่กลางคัน
ถ้าคุณเป็นผู้สังเกตการณ์ คุณคงไม่อยากถูกเรียกตัวถ้าใครกำลังเผชิญหน้ากับเรื่องบางอย่าง เราไม่ต้องการให้คุณทำงานในตำแหน่งที่ไม่สอดคล้องกัน จบลงด้วยการทำการตรวจสอบข้อผิดพลาดอีกมากมาย การพยายามรับมือกับสถานการณ์แย่ๆ ให้มากขึ้นและโดยทั่วไปแล้วก็เป็นโมเดลที่ยุ่งยาก การทำงานแบบแอซิงค์นั้นจัดการได้ยากกว่า แต่ท้ายที่สุดแล้วจะเป็นรูปแบบที่ดีกว่า
วิธีแก้ปัญหานี้คือระเบียนการเปลี่ยนแปลงสังเคราะห์
ระเบียนการเปลี่ยนแปลงสังเคราะห์
โดยพื้นฐานแล้ว หากคุณต้องการมีตัวเข้าถึงหรือพร็อพเพอร์ตี้ที่คำนวณแล้ว ก็มีหน้าที่แจ้งเตือนเมื่อค่าเหล่านี้มีการเปลี่ยนแปลง แม้จะเป็นการดำเนินการเพิ่มเติมเล็กน้อย แต่ก็ได้รับการออกแบบมาให้เป็นฟีเจอร์ชั้นหนึ่งของกลไกนี้ และการแจ้งเตือนเหล่านี้จะแสดงพร้อมการแจ้งเตือนส่วนที่เหลือจากออบเจ็กต์ข้อมูลที่เกี่ยวข้อง จากพร็อพเพอร์ตี้ข้อมูล
การสังเกตตัวเข้าถึงและคุณสมบัติที่คำนวณแล้วสามารถแก้ไขได้ด้วย notifier.notify ซึ่งเป็นอีกส่วนหนึ่งของ O.o() ระบบสังเกตการณ์ส่วนใหญ่ต้องการค่าที่สังเกตได้บางรูปแบบ ซึ่งทำได้หลายวิธี O.o ไม่ได้ตัดสินทางที่ "ถูก" พร็อพเพอร์ตี้ที่คำนวณควรเป็นตัวเข้าถึงที่แจ้งเมื่อสถานะภายใน (ส่วนตัว) มีการเปลี่ยนแปลง
ขอย้ำอีกครั้งว่าผู้ดูแลเว็บควรคาดหวังว่าไลบรารีจะช่วยให้การแจ้งเตือนและวิธีที่หลากหลายในการคำนวณพร็อพเพอร์ตี้ง่ายขึ้น (และลดการทำซ้ำ)
มาดูตัวอย่างกันต่อ ซึ่งเป็นคลาสของวงกลม แนวคิดคือเรามีวงกลมนี้และมีพร็อพเพอร์ตี้รัศมี ในกรณีนี้ รัศมีคือตัวเข้าถึง และเมื่อค่าเปลี่ยนแปลง ระบบจะแจ้งเองว่าค่ามีการเปลี่ยนแปลง ระบบจะส่งรายการนี้พร้อมกับการเปลี่ยนแปลงอื่นๆ ทั้งหมดสำหรับออบเจ็กต์นี้หรือออบเจ็กต์อื่นใดก็ตาม โดยพื้นฐานแล้ว หากคุณกำลังติดตั้งใช้งานออบเจ็กต์ที่ต้องการให้มีพร็อพเพอร์ตี้สังเคราะห์หรือพร็อพเพอร์ตี้ที่คำนวณแล้ว หรือคุณต้องเลือกกลยุทธ์สำหรับวิธีการทำงานของออบเจ็กต์ เมื่อดำเนินการแล้ว การเปลี่ยนแปลงดังกล่าวจะใช้กับระบบของคุณโดยรวมได้
ข้ามโค้ดเพื่อดูข้อมูลนี้ในเครื่องมือสำหรับนักพัฒนาเว็บ
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() คือแนวคิดของผู้สังเกตการณ์ Callback เดียว ซึ่งช่วยให้สามารถใช้ Callback เดียวเป็น "ผู้สังเกตการณ์" สำหรับออบเจ็กต์ต่างๆ จำนวนมาก ฟังก์ชันการเรียกกลับจะได้รับชุดการเปลี่ยนแปลงทั้งหมดของออบเจ็กต์ทั้งหมดที่สังเกตเห็นเมื่อ "สิ้นสุดไมโครแทสก์" (โปรดสังเกตความคล้ายคลึงกับ Mutation Observer)
การเปลี่ยนแปลงขนาดใหญ่
อาจเป็นเพราะคุณกําลังทํางานกับแอปขนาดใหญ่มากและต้องทําการเปลี่ยนแปลงขนาดใหญ่เป็นประจำ ออบเจ็กต์อาจต้องการอธิบายการเปลี่ยนแปลงเชิงความหมายขนาดใหญ่ซึ่งจะส่งผลต่อพร็อพเพอร์ตี้จํานวนมากในลักษณะที่กะทัดรัดยิ่งขึ้น (แทนที่จะเผยแพร่การเปลี่ยนแปลงพร็อพเพอร์ตี้จํานวนมาก)
O.o() ช่วยในเรื่องนี้ในรูปแบบของยูทิลิตีเฉพาะ 2 อย่าง ได้แก่ notifier.performChange()
และ notifier.notify()
ซึ่งเราได้แนะนำไปแล้ว
ลองดูตัวอย่างนี้ในตัวอย่างของการเปลี่ยนแปลงขนาดใหญ่ที่สามารถอธิบายตำแหน่งที่เรากำหนดออบเจ็กต์ Thingy ด้วยยูทิลิตีทางคณิตศาสตร์บางอย่าง (คูณ เพิ่มขึ้น เพิ่มขึ้น เพิ่มAndMultiply) ทุกครั้งที่มีการใช้ยูทิลิตีจะบอกระบบว่าการรวบรวมผลงานประกอบด้วยการเปลี่ยนแปลงบางประเภท
เช่น 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
});
}
}
จากนั้นเราจะกำหนดผู้สังเกตการณ์ 2 กลุ่มสำหรับออบเจ็กต์ของเรา รายการหนึ่งคือตรวจจับการเปลี่ยนแปลงทั้งหมด และอีกรายการหนึ่งจะรายงานเฉพาะประเภทการยอมรับที่เรากำหนดไว้เท่านั้น (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 }
ทุกอย่างภายใน "ฟังก์ชันการดําเนินการ" จะถือว่าเป็นงานของ "big-change" ผู้สังเกตการณ์ที่ยอมรับ "big-change" จะได้รับเฉพาะระเบียน "big-change" เท่านั้น ผู้สังเกตการณ์ที่ไม่ได้ดำเนินการดังกล่าวจะได้รับการเปลี่ยนแปลงพื้นฐานที่เกิดจากงานที่ "ดำเนินการฟังก์ชัน" ดำเนินการ
การสังเกตอาร์เรย์
เราคุยกันมานานแล้วเกี่ยวกับการสังเกตการเปลี่ยนแปลงของออบเจ็กต์ แต่แล้วอาร์เรย์ล่ะ เป็นคำถามที่ดีนะ เมื่อมีคนบอกฉันว่า "คำถามดีมาก" ฉันไม่เคยได้ยินคำตอบเลยเพราะกำลังยุ่งกับการแสดงความยินดีกับตัวเองที่ถามคำถามที่ยอดเยี่ยม แต่ฉันก็พูดออกไป เรามีวิธีการใหม่ๆ ในการทำงานกับอาร์เรย์เช่นกัน!
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() คือให้คิดว่าสิ่งนี้เหมือนกับแคชการอ่าน โดยทั่วไป แคชเป็นตัวเลือกที่ดีในกรณีต่อไปนี้ (ตามลำดับความสำคัญ)
- ความถี่ในการอ่านจะครอบคลุมความถี่ในการเขียน
- คุณสามารถสร้างแคชที่จะแลกเปลี่ยนงานจำนวนคงที่ที่เกี่ยวข้องระหว่างการเขียนเพื่อประสิทธิภาพที่ดีขึ้นตามอัลกอริทึมระหว่างการอ่าน
- การเขียนที่ช้าลงอย่างต่อเนื่องนั้นยอมรับได้
O.o() ออกแบบมาสำหรับกรณีการใช้งานอย่างเช่น 1)
การตรวจสอบข้อมูลใหม่ต้องเก็บสําเนาของข้อมูลทั้งหมดที่คุณกําลังสังเกต ซึ่งหมายความว่าคุณต้องจ่ายค่าหน่วยความจำแบบโครงสร้างไปจนถึงการตรวจสอบที่สกปรกแต่ไม่ได้รับด้วย O.o() การตรวจสอบความสกปรกในขณะที่เป็นโซลูชันที่ช่วยปิดช่องว่างที่ดีก็ยังเป็นนามธรรมที่รั่วไหลโดยพื้นฐาน ซึ่งทำให้แอปพลิเคชันมีความซับซ้อนโดยไม่จำเป็น
เหตุผล การตรวจสอบข้อมูลที่มีการเปลี่ยนแปลงต้องทำงานทุกครั้งที่ข้อมูลอาจมีการเปลี่ยนแปลง ไม่มีวิธีที่มีประสิทธิภาพมากนักในการทำเช่นนี้ และวิธีการใดๆ ที่มีข้อเสียอย่างมาก (เช่น การตรวจสอบช่วงเวลาในการทำโพลมีความเสี่ยงต่อสิ่งแปลกปลอมที่เป็นภาพและสภาวะการแข่งขันระหว่างข้อกังวลเกี่ยวกับโค้ด) นอกจากนี้ การตรวจสอบความสกปรกยังต้องอาศัยผู้สังเกตการณ์ทั่วโลก ซึ่งก่อให้เกิดอันตรายจากการรั่วไหลของหน่วยความจำและความเสียหายที่เกิดจากการฉีกขาด O.o() สามารถหลีกเลี่ยงได้
มาดูตัวเลขกัน
การทดสอบเปรียบเทียบด้านล่าง (มีใน GitHub) ช่วยให้เราสามารถเปรียบเทียบการตรวจสอบแบบสกปรกกับ O.o() ได้ การทดสอบนี้มีโครงสร้างเป็นกราฟของ Obsave-Object-Set-Size กับ Number-Of-Mutations ผลลัพธ์ทั่วไปคือประสิทธิภาพการตรวจสอบแบบสกปรกจะมีสัดส่วนตามอัลกอริทึมกับจำนวนออบเจ็กต์ที่สังเกตได้ ขณะที่ประสิทธิภาพ O.o() จะเป็นไปตามสัดส่วนของจำนวนการกลายพันธุ์ที่เกิดขึ้น
การตรวจสอบสิ่งสกปรก
Chrome ที่เปิด Object.observe()
การใช้ Object.observe() แบบ Polyfill
เยี่ยม - คุณสามารถใช้ O.o() ใน Chrome 36 ได้ แต่ต้องใช้ในเบราว์เซอร์อื่นด้วยไหม เรารวบรวมมาให้แล้ว Observe-JS ของพอลิเมอร์เป็น Polyfill สำหรับ O.o() ซึ่งจะใช้การติดตั้งใช้งานแบบเนทีฟหากมีอยู่ แต่จะใช้ Polyfill ในรูปแบบอื่นและมีน้ำตาลที่มีประโยชน์อยู่ด้านบน ซึ่งจะแสดงมุมมองรวมของโลกที่สรุปการเปลี่ยนแปลงและแสดงรายงานเกี่ยวกับสิ่งที่เปลี่ยนแปลง มีอยู่ 2 สิ่งที่มีประสิทธิภาพจริงๆ ที่จะแสดงให้เห็นคือ
- คุณสามารถสังเกตเส้นทาง ซึ่งหมายความว่าคุณสามารถพูดได้ว่า ฉันต้องการสังเกต "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.
});
- ซึ่งจะบอกเกี่ยวกับการเชื่อมต่ออาร์เรย์ พูดง่ายๆ ก็คือการเชื่อมต่ออาร์เรย์เป็นชุดการดำเนินการเชื่อมต่อขั้นต่ำที่คุณต้องทำในอาร์เรย์เพื่อเปลี่ยนอาร์เรย์เวอร์ชันเก่าให้เป็นอาร์เรย์เวอร์ชันใหม่ นี่เป็นประเภทการเปลี่ยนรูปแบบหรือมุมมองอื่นของอาร์เรย์ หมายถึงปริมาณงานขั้นต่ำที่คุณต้องทําเพื่อย้ายจากสถานะเก่าไปยังสถานะใหม่
ตัวอย่างการรายงานการเปลี่ยนแปลงอาร์เรย์เป็นชุดการต่อขั้นต่ำ
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() จะให้โอกาสเฟรมเวิร์กและไลบรารีในการปรับปรุงประสิทธิภาพการเชื่อมโยงข้อมูลในเบราว์เซอร์ที่รองรับฟีเจอร์นี้
Yehuda Katz และ Erik Bryn จาก Ember ยืนยันว่าการเพิ่มการรองรับ O.o() อยู่ในแผนงานระยะสั้นของ Ember Misko Hervy ของ Angular เขียนเอกสารการออกแบบเกี่ยวกับการตรวจจับการเปลี่ยนแปลงที่ปรับปรุงใหม่ของ Angular 2.0 แนวทางในระยะยาวของพวกเขาคือการใช้ประโยชน์จาก Object.observe() เมื่อมาถึง Chrome เวอร์ชันเสถียรแล้ว และเลือกใช้ Watchtower.js ซึ่งเป็นวิธีตรวจจับการเปลี่ยนแปลงของตัวเองไปก่อน Suuuuper น่าตื่นเต้นนะ
บทสรุป
O.o() เป็นส่วนเติมเต็มที่ดีให้แก่แพลตฟอร์มเว็บ ซึ่งคุณสามารถออกไปใช้ภายนอกได้เลยวันนี้
เราหวังว่าในอนาคตฟีเจอร์นี้จะพร้อมให้บริการในเบราว์เซอร์อื่นๆ มากขึ้น ซึ่งจะช่วยให้เฟรมเวิร์ก JavaScript มีประสิทธิภาพมากขึ้นจากการเข้าถึงความสามารถในการสังเกตวัตถุแบบเนทีฟ ผู้ใช้ที่กำหนดเป้าหมายที่ Chrome ควรจะใช้ O.o() ใน Chrome 36 (ขึ้นไป) ได้ และฟีเจอร์นี้ควรพร้อมใช้งานใน Opera รุ่นต่อๆ ไป
ดังนั้น โปรดไปพูดคุยกับผู้เขียนเฟรมเวิร์ก JavaScript เกี่ยวกับ Object.observe()
และการวางแผนจะใช้เฟรมเวิร์กนี้เพื่อปรับปรุงประสิทธิภาพการเชื่อมโยงข้อมูลในแอป นี่เป็นช่วงเวลาที่น่าตื่นเต้นอย่างแน่นอน
แหล่งข้อมูล
- Object.observe() ในวิกิ Harmony>
- การเชื่อมโยงข้อมูลด้วย Object.observe() โดย Rick Waldron
- ทุกสิ่งที่คุณอยากรู้เกี่ยวกับ Object.observe() - JSConf
- เหตุใด Object.observe() จึงถือเป็นฟีเจอร์ที่ดีที่สุดของ ES7
ขอขอบคุณ Rafael Weinstein, Jake Archibald, Eric Bidelman, Paul Kinlan และ Vivian Cromwell ที่ให้ข้อมูลและความคิดเห็น