ข้อมูลที่คงอยู่ตลอดการโหลดหน้าซ้ำ: คุกกี้ IndexedDB และทุกอย่างในระหว่าง
เผยแพร่แล้ว: 2022-03-11สมมติว่าฉันกำลังเยี่ยมชมเว็บไซต์ ฉันคลิกขวาที่ลิงก์การนำทาง และเลือกเพื่อเปิดลิงก์ในหน้าต่างใหม่ จะเกิดอะไรขึ้น? หากฉันเป็นเหมือนผู้ใช้ส่วนใหญ่ ฉันคาดว่าหน้าใหม่จะมีเนื้อหาเหมือนกับว่าฉันได้คลิกลิงก์โดยตรง ข้อแตกต่างเพียงอย่างเดียวคือหน้าดังกล่าวจะปรากฏในหน้าต่างใหม่ แต่ถ้าเว็บไซต์ของคุณเป็นแอปพลิเคชันหน้าเดียว (SPA) คุณอาจเห็นผลลัพธ์ที่แปลกประหลาด เว้นแต่คุณจะวางแผนไว้อย่างรอบคอบสำหรับกรณีนี้
โปรดจำไว้ว่าใน SPA ลิงก์การนำทางทั่วไปมักจะเป็นตัวระบุส่วนย่อย โดยเริ่มด้วยเครื่องหมายแฮช (#) การคลิกลิงก์โดยตรงไม่ได้ทำให้หน้าโหลดซ้ำ ดังนั้นข้อมูลทั้งหมดที่จัดเก็บไว้ในตัวแปร JavaScript จะยังคงอยู่ แต่ถ้าฉันเปิดลิงก์ในแท็บหรือหน้าต่างใหม่ เบราว์เซอร์จะโหลดหน้าซ้ำ โดยจะเริ่มต้นตัวแปร JavaScript ทั้งหมดใหม่ ดังนั้นองค์ประกอบ HTML ใดๆ ที่ผูกไว้กับตัวแปรเหล่านั้นจะแสดงแตกต่างออกไป เว้นแต่คุณจะทำตามขั้นตอนต่างๆ เพื่อรักษาข้อมูลนั้นไว้
มีปัญหาที่คล้ายกันหากฉันโหลดหน้าซ้ำอย่างชัดแจ้ง เช่น โดยการกดปุ่ม F5 คุณอาจคิดว่าฉันไม่ควรกด F5 เพราะคุณได้ตั้งค่ากลไกเพื่อส่งการเปลี่ยนแปลงจากเซิร์ฟเวอร์โดยอัตโนมัติ แต่ถ้าฉันเป็นผู้ใช้ทั่วไป พนันได้เลยว่าฉันยังคงโหลดหน้านี้ซ้ำ บางทีดูเหมือนว่าเบราว์เซอร์ของฉันจะทาสีหน้าจอใหม่อย่างไม่ถูกต้อง หรือฉันแค่ต้องการแน่ใจว่าฉันมีราคาหุ้นล่าสุด
APIs อาจไร้สัญชาติ การโต้ตอบของมนุษย์ไม่ใช่
ไม่เหมือนกับคำขอภายในผ่าน RESTful API การโต้ตอบของผู้ใช้ที่เป็นมนุษย์กับเว็บไซต์นั้นไม่ไร้สัญชาติ ในฐานะผู้ใช้เว็บ ฉันคิดว่าการเข้าชมไซต์ของคุณเป็นเซสชัน เกือบจะเหมือนกับการโทร ฉันคาดว่าเบราว์เซอร์จะจำข้อมูลเกี่ยวกับเซสชันของฉัน ในลักษณะเดียวกับที่เมื่อฉันโทรหาฝ่ายขายหรือฝ่ายสนับสนุนของคุณ ฉันคาดหวังว่าตัวแทนจะจำสิ่งที่ได้กล่าวไว้ก่อนหน้านี้ในการโทร
ตัวอย่างที่ชัดเจนของข้อมูลเซสชันคือฉันลงชื่อเข้าใช้หรือไม่ และหากเป็นเช่นนั้น แสดงว่าเป็นผู้ใช้รายใด เมื่อฉันผ่านหน้าจอเข้าสู่ระบบ ฉันควรจะสามารถไปยังหน้าเฉพาะผู้ใช้ของไซต์ได้อย่างอิสระ หากฉันเปิดลิงก์ในแท็บหรือหน้าต่างใหม่ และพบหน้าจอการเข้าสู่ระบบอื่น แสดงว่าไม่เป็นมิตรกับผู้ใช้มากนัก
อีกตัวอย่างหนึ่งคือเนื้อหาของตะกร้าสินค้าในไซต์อีคอมเมิร์ซ หากกดปุ่ม F5 จะทำให้ตะกร้าสินค้าว่างเปล่า ผู้ใช้มักจะอารมณ์เสีย
ในแอปพลิเคชันแบบหลายหน้าที่เขียนด้วย PHP ข้อมูลเซสชันจะถูกเก็บไว้ในอาร์เรย์ superglobal $_SESSION แต่ในสปา จะต้องอยู่ที่ไหนสักแห่งในฝั่งไคลเอ็นต์ มีสี่ตัวเลือกหลักสำหรับการจัดเก็บข้อมูลเซสชันใน SPA:
- คุ้กกี้
- ตัวระบุชิ้นส่วน
- ที่เก็บข้อมูลเว็บ
- จัดทำดัชนีDB
คุกกี้สี่กิโลไบต์
คุกกี้เป็นรูปแบบที่เก่ากว่าของการจัดเก็บเว็บในเบราว์เซอร์ เดิมทีมีวัตถุประสงค์เพื่อจัดเก็บข้อมูลที่ได้รับจากเซิร์ฟเวอร์ในคำขอเดียวและส่งกลับไปยังเซิร์ฟเวอร์ในคำขอที่ตามมา แต่จาก JavaScript คุณสามารถใช้คุกกี้เพื่อจัดเก็บข้อมูลประเภทใดก็ได้ โดยจำกัดขนาดไว้ที่ 4 KB ต่อคุกกี้ AngularJS มีโมดูล ngCookies สำหรับจัดการคุกกี้ นอกจากนี้ยังมีแพ็คเกจ js-cookies ที่ให้การทำงานที่คล้ายคลึงกันในทุกเฟรมเวิร์ก
โปรดทราบว่าคุกกี้ใดๆ ที่คุณสร้างจะถูกส่งไปยังเซิร์ฟเวอร์ในทุกคำขอ ไม่ว่าจะเป็นการโหลดหน้าซ้ำหรือคำขอ Ajax แต่ถ้าข้อมูลเซสชันหลักที่คุณต้องจัดเก็บคือโทเค็นการเข้าถึงสำหรับผู้ใช้ที่เข้าสู่ระบบ คุณต้องการให้ส่งข้อมูลนี้ไปยังเซิร์ฟเวอร์ในทุกคำขอ เป็นเรื่องปกติที่จะลองใช้การส่งคุกกี้อัตโนมัตินี้เป็นวิธีการมาตรฐานในการระบุโทเค็นการเข้าถึงสำหรับคำขอ Ajax
คุณอาจโต้แย้งว่าการใช้คุกกี้ในลักษณะนี้ไม่เข้ากันกับสถาปัตยกรรม RESTful แต่ในกรณีนี้ ก็ไม่เป็นไร เพราะคำขอแต่ละรายการผ่าน API นั้นยังคงไร้สัญชาติ มีอินพุตและเอาต์พุตบางส่วน เป็นเพียงว่าหนึ่งในอินพุตถูกส่งด้วยวิธีที่ตลกผ่านคุกกี้ หากคุณสามารถจัดเตรียมคำขอการเข้าสู่ระบบ API เพื่อส่งโทเค็นการเข้าถึงกลับมาในคุกกี้ด้วย โค้ดฝั่งไคลเอ็นต์ของคุณแทบจะไม่ต้องจัดการกับคุกกี้เลย อีกครั้ง มันเป็นเพียงผลลัพธ์อื่นจากคำขอที่ส่งคืนในลักษณะที่ผิดปกติ
คุกกี้มอบข้อได้เปรียบหนึ่งข้อเหนือพื้นที่จัดเก็บเว็บ คุณสามารถระบุช่องทำเครื่องหมาย "ให้ฉันอยู่ในระบบต่อไป" ในแบบฟอร์มการเข้าสู่ระบบ ด้วยความหมาย ฉันคาดว่าหากปล่อยทิ้งไว้โดยไม่ได้เลือก ฉันจะยังคงเข้าสู่ระบบถ้าฉันโหลดหน้าเว็บซ้ำหรือเปิดลิงก์ในแท็บหรือหน้าต่างใหม่ แต่ฉันรับประกันว่าจะออกจากระบบเมื่อฉันปิดเบราว์เซอร์ นี่เป็นคุณลักษณะด้านความปลอดภัยที่สำคัญหากฉันใช้คอมพิวเตอร์ที่ใช้ร่วมกัน ตามที่เราจะเห็นในภายหลัง พื้นที่เก็บข้อมูลบนเว็บไม่รองรับพฤติกรรมนี้
ดังนั้นแนวทางนี้จะได้ผลในทางปฏิบัติอย่างไร? สมมติว่าคุณกำลังใช้ LoopBack ทางฝั่งเซิร์ฟเวอร์ คุณได้กำหนดโมเดลบุคคล ขยายโมเดลผู้ใช้ที่มีอยู่แล้ว เพิ่มคุณสมบัติที่คุณต้องการรักษาสำหรับผู้ใช้แต่ละราย คุณได้กำหนดค่าโมเดล บุคคล ให้เปิดเผยผ่าน REST ตอนนี้ คุณต้องปรับแต่ง server/server.js เพื่อให้ได้พฤติกรรมคุกกี้ที่ต้องการ ด้านล่างนี้คือ server/server.js เริ่มต้นจากสิ่งที่สร้างโดย slc loopback โดยมีการเปลี่ยนแปลงที่ทำเครื่องหมายไว้:
var loopback = require('loopback'); var boot = require('loopback-boot'); var app = module.exports = loopback(); app.start = function() { // start the web server return app.listen(function() { app.emit('started'); var baseUrl = app.get('url').replace(/\/$/, ''); console.log('Web server listening at: %s', baseUrl); if (app.get('loopback-component-explorer')) { var explorerPath = app.get('loopback-component-explorer').mountPath; console.log('Browse your REST API at %s%s', baseUrl, explorerPath); } }); }; // start of first change app.use(loopback.cookieParser('secret')); // end of first change // Bootstrap the application, configure models, datasources and middleware. // Sub-apps like REST API are mounted via boot scripts. boot(app, __dirname, function(err) { if (err) throw err; // start of second change app.remotes().after('Person.login', function (ctx, next) { if (ctx.result.id) { var opts = {signed: true}; if (ctx.req.body.rememberme !== false) { opts.maxAge = 1209600000; } ctx.res.cookie('authorization', ctx.result.id, opts); } next(); }); app.remotes().after('Person.logout', function (ctx, next) { ctx.res.cookie('authorization', ''); next(); }); // end of second change // start the server if `$ node server.js` if (require.main === module) app.start(); });การเปลี่ยนแปลงครั้งแรกกำหนดค่าตัวแยกวิเคราะห์คุกกี้ให้ใช้ 'ความลับ' เป็นความลับในการเซ็นชื่อคุกกี้ ซึ่งจะทำให้เปิดใช้งานคุกกี้ที่ลงชื่อแล้ว คุณต้องทำเช่นนี้เพราะแม้ว่า LoopBack จะค้นหาโทเค็นการเข้าถึงใน 'การอนุญาต' หรือ 'access_token' ของคุกกี้ แต่ก็ต้องการให้มีการลงชื่อคุกกี้ดังกล่าว อันที่จริงข้อกำหนดนี้ไม่มีจุดหมาย การลงนามในคุกกี้มีวัตถุประสงค์เพื่อให้แน่ใจว่าคุกกี้จะไม่ได้รับการแก้ไข แต่การแก้ไขโทเค็นการเข้าถึงจะไม่เป็นอันตราย ท้ายที่สุด คุณสามารถส่งโทเค็นการเข้าถึงในรูปแบบที่ไม่ได้ลงนาม เป็นพารามิเตอร์ธรรมดา ดังนั้น คุณจึงไม่ต้องกังวลว่าความลับในการเซ็นชื่อคุกกี้จะเดายาก เว้นแต่ว่าคุณกำลังใช้คุกกี้ที่ลงชื่อสำหรับอย่างอื่น
การเปลี่ยนแปลงครั้งที่สองตั้งค่าการประมวลผลภายหลังสำหรับเมธอด Person.login และ Person.logout สำหรับ Person.login คุณต้องการนำโทเค็นการเข้าถึงที่เป็นผลลัพธ์ และส่งไปยังไคลเอนต์เป็น 'การอนุญาต' คุกกี้ที่ลงนามด้วย ลูกค้าอาจเพิ่มพร็อพเพอร์ตี้อีกหนึ่งรายการในพารามิเตอร์ข้อมูลรับรอง (rememberme) ซึ่งระบุว่าต้องทำให้คุกกี้คงอยู่เป็นเวลา 2 สัปดาห์หรือไม่ ค่าเริ่มต้นเป็นจริง วิธีการเข้าสู่ระบบเองจะละเว้นคุณสมบัตินี้ แต่ตัวประมวลผลภายหลังจะตรวจสอบ
สำหรับ Person.logout คุณต้องการล้างคุกกี้นี้
คุณสามารถดูผลลัพธ์ของการเปลี่ยนแปลงเหล่านี้ได้ทันทีใน StrongLoop API Explorer โดยปกติหลังจากการร้องขอ Person.login คุณจะต้องคัดลอกโทเค็นการเข้าถึง วางลงในแบบฟอร์มที่ด้านบนขวา แล้วคลิก ตั้งค่าโทเค็นการเข้าถึง แต่ด้วยการเปลี่ยนแปลงเหล่านี้ คุณไม่จำเป็นต้องดำเนินการใดๆ โทเค็นการเข้าถึงจะถูกบันทึกโดยอัตโนมัติเป็น 'การอนุญาต' ของคุกกี้ และส่งกลับในแต่ละคำขอที่ตามมา เมื่อ Explorer แสดงส่วนหัวการตอบสนองจาก Person.login จะไม่มีคุกกี้ เนื่องจาก JavaScript ไม่ได้รับอนุญาตให้ดูส่วนหัว Set-Cookie แต่มั่นใจได้เลยว่าคุกกี้อยู่ที่นั่น
ในฝั่งไคลเอ็นต์ ในหน้าที่โหลดซ้ำ คุณจะเห็นว่ามี 'การอนุญาต' คุกกี้อยู่หรือไม่ หากเป็นเช่นนั้น คุณจะต้องอัปเดตบันทึกของรหัสผู้ใช้ปัจจุบัน วิธีที่ง่ายที่สุดในการทำเช่นนี้คือการจัดเก็บ userId ไว้ในคุกกี้แยกต่างหากเมื่อเข้าสู่ระบบสำเร็จ ดังนั้นคุณจึงสามารถดึงข้อมูลดังกล่าวได้จากการโหลดหน้าซ้ำ
ตัวระบุชิ้นส่วน
ขณะที่ฉันกำลังเยี่ยมชมเว็บไซต์ที่มีการใช้งานเป็น SPA URL ในแถบที่อยู่ของเบราว์เซอร์ของฉันอาจดูเหมือน “https://example.com/#/my-photos/37” ส่วนระบุส่วนย่อยของสิ่งนี้ “#/my-photos/37” เป็นชุดของข้อมูลสถานะที่สามารถดูได้เป็นข้อมูลเซสชันอยู่แล้ว ในกรณีนี้ ฉันอาจกำลังดูรูปถ่ายของฉันอยู่ ซึ่งมี ID 37
คุณอาจตัดสินใจฝังข้อมูลเซสชันอื่นๆ ภายในตัวระบุส่วนย่อย โปรดจำไว้ว่าในส่วนก่อนหน้านี้ ด้วยโทเค็นการเข้าถึงที่เก็บไว้ใน 'การอนุญาต' คุกกี้ คุณยังคงต้องติดตาม ID ผู้ใช้อย่างใด ทางเลือกหนึ่งคือเก็บไว้ในคุกกี้แยกต่างหาก แต่อีกวิธีหนึ่งคือการฝังไว้ในตัวระบุส่วนย่อย คุณสามารถตัดสินใจได้ว่าในขณะที่ฉันเข้าสู่ระบบ หน้าทั้งหมดที่ฉันเข้าชมจะมีตัวระบุส่วนย่อยที่ขึ้นต้นด้วย “#/u/XXX” โดยที่ XXX คือรหัสผู้ใช้ ในตัวอย่างก่อนหน้านี้ ตัวระบุส่วนย่อยอาจเป็น “#/u/59/my-photos/37” หาก ID ผู้ใช้ของฉันคือ 59
ในทางทฤษฎี คุณสามารถฝังโทเค็นเพื่อการเข้าถึงในตัวระบุแฟรกเมนต์ โดยไม่ต้องใช้คุกกี้หรือที่จัดเก็บเว็บ แต่นั่นจะเป็นความคิดที่ไม่ดี โทเค็นการเข้าถึงของฉันจะปรากฏในแถบที่อยู่ ใครก็ตามที่มองผ่านไหล่ของฉันด้วยกล้องสามารถถ่ายภาพหน้าจอได้ ซึ่งจะทำให้เข้าถึงบัญชีของฉันได้

หมายเหตุสุดท้าย: เป็นไปได้ที่จะตั้งค่า SPA เพื่อไม่ให้ใช้ตัวระบุส่วนย่อยเลย แต่จะใช้ URL ธรรมดาเช่น “http://example.com/app/dashboard” และ “http://example.com/app/my-photos/37” โดยที่เซิร์ฟเวอร์กำหนดค่าให้ส่งคืน HTML ระดับบนสุดสำหรับคุณ SPA เพื่อตอบสนองต่อคำขอสำหรับ URL เหล่านี้ จากนั้น SPA ของคุณจะกำหนดเส้นทางตามเส้นทาง (เช่น “/app/dashboard” หรือ “/app/my-photos/37”) แทนตัวระบุส่วนย่อย มันสกัดกั้นการคลิกลิงก์การนำทาง และใช้ History.pushState() เพื่อพุช URL ใหม่ จากนั้นดำเนินการตามเส้นทางตามปกติ นอกจากนี้ยังรับฟังเหตุการณ์ popstate เพื่อตรวจจับผู้ใช้ที่คลิกปุ่มย้อนกลับ และดำเนินการกำหนดเส้นทางบน URL ที่กู้คืนอีกครั้ง รายละเอียดทั้งหมดเกี่ยวกับวิธีการดำเนินการนี้อยู่นอกเหนือขอบเขตของบทความนี้ แต่ถ้าคุณใช้เทคนิคนี้ แน่นอนว่าคุณสามารถจัดเก็บข้อมูลเซสชันในเส้นทางแทนตัวระบุส่วนย่อยได้
ที่เก็บข้อมูลเว็บ
ที่เก็บข้อมูลเว็บเป็นกลไกสำหรับ JavaScript ในการจัดเก็บข้อมูลภายในเบราว์เซอร์ เช่นเดียวกับคุกกี้ พื้นที่จัดเก็บเว็บจะแยกจากกันสำหรับแต่ละต้นทาง รายการที่จัดเก็บแต่ละรายการมีชื่อและค่า ซึ่งทั้งสองรายการเป็นสตริง แต่เซิร์ฟเวอร์ไม่สามารถมองเห็นที่เก็บข้อมูลเว็บได้อย่างสมบูรณ์ และมีความจุมากกว่าคุกกี้ ที่จัดเก็บเว็บมีสองประเภท: ที่จัดเก็บในตัวเครื่องและที่จัดเก็บเซสชัน
รายการของที่จัดเก็บในตัวเครื่องสามารถมองเห็นได้ในทุกแท็บของหน้าต่างทั้งหมด และยังคงอยู่แม้หลังจากที่ปิดเบราว์เซอร์แล้ว ในแง่นี้ มันมีลักษณะเหมือนคุกกี้ที่มีวันหมดอายุในอนาคตอันใกล้ ดังนั้นจึงเหมาะสำหรับการจัดเก็บโทเค็นการเข้าถึงในกรณีที่ผู้ใช้ทำเครื่องหมาย "ให้ฉันอยู่ในระบบต่อไป" ในแบบฟอร์มการเข้าสู่ระบบ
รายการของพื้นที่จัดเก็บเซสชันจะมองเห็นได้เฉพาะในแท็บที่สร้างรายการนั้น และรายการนั้นจะหายไปเมื่อปิดแท็บนั้น ทำให้อายุการใช้งานแตกต่างจากคุกกี้มาก โปรดจำไว้ว่าคุกกี้เซสชันยังคงมองเห็นได้ในทุกแท็บของหน้าต่างทั้งหมด
หากคุณใช้ AngularJS SDK สำหรับ LoopBack ฝั่งไคลเอ็นต์จะใช้ที่เก็บข้อมูลเว็บโดยอัตโนมัติเพื่อบันทึกทั้งโทเค็นการเข้าถึงและ ID ผู้ใช้ สิ่งนี้เกิดขึ้นในบริการ LoopBackAuth ใน js/services/lb-services.js จะใช้ที่เก็บข้อมูลในเครื่อง เว้นแต่ว่าพารามิเตอร์ RememberMe จะเป็นเท็จ (ปกติหมายถึงไม่ได้เลือกช่องกาเครื่องหมาย "ให้ฉันอยู่ในระบบ") ซึ่งในกรณีนี้ จะใช้ที่เก็บข้อมูลเซสชัน
ผลลัพธ์คือ ถ้าฉันเข้าสู่ระบบโดยไม่ได้เลือก "ให้ฉันอยู่ในระบบ" และจากนั้นฉันเปิดลิงก์ในแท็บหรือหน้าต่างใหม่ ฉันจะไม่เข้าสู่ระบบที่นั่น เป็นไปได้มากว่าฉันจะเห็นหน้าจอเข้าสู่ระบบ คุณสามารถตัดสินใจได้ด้วยตัวเองว่านี่เป็นพฤติกรรมที่ยอมรับได้หรือไม่ บางคนอาจมองว่าเป็นคุณลักษณะที่ดี ซึ่งคุณสามารถมีแท็บได้หลายแท็บ โดยแต่ละแท็บจะเข้าสู่ระบบในฐานะผู้ใช้ที่แตกต่างกัน หรือคุณอาจตัดสินใจว่าแทบจะไม่มีใครใช้คอมพิวเตอร์ร่วมกันอีกต่อไป ดังนั้นคุณจึงไม่ต้องเลือกช่องทำเครื่องหมาย "ให้ฉันอยู่ในระบบต่อไป" ไปเลยก็ได้
ดังนั้นการจัดการข้อมูลเซสชันจะมีลักษณะอย่างไรหากคุณตัดสินใจใช้ AngularJS SDK สำหรับ LoopBack สมมติว่าคุณมีสถานการณ์เหมือนเดิมในฝั่งเซิร์ฟเวอร์: คุณได้กำหนดโมเดลบุคคล ขยายโมเดลผู้ใช้ และคุณได้เปิดเผยโมเดลบุคคลผ่าน REST คุณจะไม่ได้ใช้คุกกี้ ดังนั้นคุณไม่จำเป็นต้องเปลี่ยนแปลงใดๆ ที่อธิบายไว้ก่อนหน้านี้
ในฝั่งไคลเอ็นต์ ที่ใดที่หนึ่งในตัวควบคุมที่อยู่นอกสุดของคุณ คุณอาจมีตัวแปรเช่น $scope.currentUserId ซึ่งเก็บ ID ผู้ใช้ของผู้ใช้ที่เข้าสู่ระบบอยู่ในปัจจุบัน หรือเป็นค่าว่างหากผู้ใช้ไม่ได้ลงชื่อเข้าใช้ จากนั้นเพื่อจัดการการโหลดหน้าใหม่อย่างถูกต้อง คุณ เพียงรวมคำสั่งนี้ในฟังก์ชันคอนสตรัคเตอร์สำหรับคอนโทรลเลอร์นั้น:
$scope.currentUserId = Person.getCurrentId();มันง่ายมาก เพิ่ม 'บุคคล' เป็นการพึ่งพาคอนโทรลเลอร์ของคุณ หากยังไม่ได้ดำเนินการ
จัดทำดัชนีDB
IndexedDB เป็นสิ่งอำนวยความสะดวกใหม่สำหรับการจัดเก็บข้อมูลจำนวนมากในเบราว์เซอร์ คุณสามารถใช้เพื่อเก็บข้อมูลของ JavaScript ประเภทใดก็ได้ เช่น อ็อบเจ็กต์หรืออาร์เรย์ โดยไม่ต้องทำให้เป็นอนุกรม คำขอทั้งหมดที่เทียบกับฐานข้อมูลเป็นแบบอะซิงโครนัส ดังนั้นคุณจะได้รับการติดต่อกลับเมื่อคำขอเสร็จสิ้น
คุณอาจใช้ IndexedDB เพื่อจัดเก็บข้อมูลที่มีโครงสร้างซึ่งไม่เกี่ยวข้องกับข้อมูลใดๆ บนเซิร์ฟเวอร์ ตัวอย่างอาจเป็นปฏิทิน รายการสิ่งที่ต้องทำ หรือเกมที่บันทึกไว้ซึ่งเล่นในเครื่อง ในกรณีนี้ แอปพลิเคชันเป็นแอปพลิเคชันท้องถิ่นจริงๆ และเว็บไซต์ของคุณเป็นเพียงเครื่องมือในการส่งมอบ
ปัจจุบัน Internet Explorer และ Safari รองรับ IndexedDB เพียงบางส่วนเท่านั้น เบราว์เซอร์หลักอื่นๆ รองรับอย่างเต็มที่ ข้อจำกัดที่ร้ายแรงประการหนึ่งในขณะนี้คือ Firefox ปิดใช้งาน IndexedDB ทั้งหมดในโหมดการเรียกดูแบบส่วนตัว
เป็นตัวอย่างที่เป็นรูปธรรมของการใช้ IndexedDB ลองใช้แอปพลิเคชันตัวต่อแบบเลื่อนโดย Pavol Daniš และปรับแต่งเพื่อบันทึกสถานะของตัวต่อตัวแรก ซึ่งเป็นตัวต่อแบบเลื่อนพื้นฐาน 3x3 ตามโลโก้ AngularJS หลังจากการเคลื่อนไหวแต่ละครั้ง การโหลดหน้าซ้ำจะเป็นการคืนค่าสถานะของปริศนาแรกนี้
ฉันได้ตั้งค่าที่เก็บถาวรด้วยการเปลี่ยนแปลงเหล่านี้ ซึ่งทั้งหมดอยู่ใน app/js/puzzle/slidingPuzzle.js อย่างที่คุณเห็น แม้แต่การใช้ IndexedDB เบื้องต้นก็ยังมีส่วนเกี่ยวข้องอยู่ ฉันจะแสดงไฮไลท์ด้านล่าง ขั้นแรก ฟังก์ชันเรียกคืนจะถูกเรียกในระหว่างการโหลดเพจ เพื่อเปิดฐานข้อมูล IndexedDB:
/* * Tries to restore game */ this.restore = function(scope, storekey) { this.storekey = storekey; if (this.db) { this.restore2(scope); } else if (!window.indexedDB) { console.log('SlidingPuzzle: browser does not support indexedDB'); this.shuffle(); } else { var self = this; var request = window.indexedDB.open('SlidingPuzzleDatabase'); request.onerror = function(event) { console.log('SlidingPuzzle: error opening database, ' + request.error.name); scope.$apply(function() { self.shuffle(); }); }; request.onupgradeneeded = function(event) { event.target.result.createObjectStore('SlidingPuzzleStore'); }; request.onsuccess = function(event) { self.db = event.target.result; self.restore2(scope); }; } };เหตุการณ์ request.onupgradeneeded จัดการกรณีที่ฐานข้อมูลยังไม่มีอยู่ ในกรณีนี้ เราสร้างที่เก็บอ็อบเจ็กต์
เมื่อฐานข้อมูลเปิดขึ้น ฟังก์ชัน restore2 จะถูกเรียกใช้ ซึ่งจะค้นหาเร็กคอร์ดด้วยคีย์ที่กำหนด (ซึ่งจริง ๆ แล้วจะเป็นค่าคงที่ 'Basic' ในกรณีนี้):
/* * Tries to restore game, once database has been opened */ this.restore2 = function(scope) { var transaction = this.db.transaction('SlidingPuzzleStore'); var objectStore = transaction.objectStore('SlidingPuzzleStore'); var self = this; var request = objectStore.get(this.storekey); request.onerror = function(event) { console.log('SlidingPuzzle: error reading from database, ' + request.error.name); scope.$apply(function() { self.shuffle(); }); }; request.onsuccess = function(event) { if (!request.result) { console.log('SlidingPuzzle: no saved game for ' + self.storekey); scope.$apply(function() { self.shuffle(); }); } else { scope.$apply(function() { self.grid = request.result; }); } }; }หากมีบันทึกดังกล่าว ค่าของระเบียนนั้นจะแทนที่อาร์เรย์กริดของปริศนา หากมีข้อผิดพลาดในการกู้คืนเกม เราก็แค่สับไพ่เหมือนเมื่อก่อน โปรดทราบว่ากริดเป็นอาร์เรย์ 3x3 ของอ็อบเจ็กต์ไทล์ ซึ่งแต่ละอ็อบเจ็กต์ค่อนข้างซับซ้อน ข้อได้เปรียบที่ยอดเยี่ยมของ IndexedDB คือคุณสามารถจัดเก็บและดึงค่าดังกล่าวได้โดยไม่ต้องทำให้เป็นอนุกรม
เราใช้ $apply เพื่อแจ้ง AngularJS ว่าโมเดลมีการเปลี่ยนแปลง ดังนั้นมุมมองจะได้รับการอัปเดตอย่างเหมาะสม เนื่องจากการอัปเดตเกิดขึ้นภายในตัวจัดการเหตุการณ์ DOM ดังนั้น AngularJS จะไม่สามารถตรวจพบการเปลี่ยนแปลงได้ แอปพลิเคชัน AngularJS ใดๆ ที่ใช้ IndexedDB อาจต้องใช้ $apply ด้วยเหตุผลนี้
หลังจากการกระทำใดๆ ที่จะเปลี่ยนกริดอาเรย์ เช่น การย้ายโดยผู้ใช้ บันทึกฟังก์ชันจะถูกเรียกซึ่งเพิ่มหรืออัพเดตเรกคอร์ดด้วยคีย์ที่เหมาะสม ตามค่ากริดที่อัพเดต:
/* * Tries to save game */ this.save = function() { if (!this.db) { return; } var transaction = this.db.transaction('SlidingPuzzleStore', 'readwrite'); var objectStore = transaction.objectStore('SlidingPuzzleStore'); var request = objectStore.put(this.grid, this.storekey); request.onerror = function(event) { console.log('SlidingPuzzle: error writing to database, ' + request.error.name); }; request.onsuccess = function(event) { // successful, no further action needed }; }การเปลี่ยนแปลงที่เหลือคือการเรียกใช้ฟังก์ชันข้างต้นในเวลาที่เหมาะสม คุณสามารถตรวจทานคอมมิตที่แสดงการเปลี่ยนแปลงทั้งหมดได้ โปรดทราบว่าเรากำลังเรียกการคืนค่าสำหรับตัวต่อพื้นฐานเท่านั้น ไม่ใช่สำหรับตัวต่อขั้นสูงสามตัว เราใช้ประโยชน์จากข้อเท็จจริงที่ว่าปริศนาขั้นสูงทั้งสามมีแอตทริบิวต์ api ดังนั้นสำหรับปริศนาเหล่านั้น เราก็แค่สับไพ่ตามปกติ
จะเป็นอย่างไรถ้าเราต้องการบันทึกและกู้คืนปริศนาขั้นสูงด้วย นั่นจะต้องมีการปรับโครงสร้างใหม่ ในแต่ละปริศนาขั้นสูง ผู้ใช้สามารถปรับไฟล์ที่มาของภาพและขนาดของปริศนาได้ ดังนั้นเราจึงต้องปรับปรุงค่าที่เก็บไว้ใน IndexedDB เพื่อรวมข้อมูลนี้ ที่สำคัญกว่านั้น เราต้องการวิธีอัปเดตจากการคืนค่า นั่นค่อนข้างมากสำหรับตัวอย่างที่มีความยาวอยู่แล้ว
บทสรุป
ในกรณีส่วนใหญ่ พื้นที่เก็บข้อมูลเว็บเป็นทางเลือกที่ดีที่สุดสำหรับการจัดเก็บข้อมูลเซสชัน ได้รับการสนับสนุนอย่างเต็มที่จากเบราว์เซอร์หลักทั้งหมด และมีความจุมากกว่าคุกกี้
คุณจะใช้คุกกี้หากเซิร์ฟเวอร์ของคุณได้รับการตั้งค่าให้ใช้งานแล้ว หรือหากคุณต้องการให้เข้าถึงข้อมูลได้ในทุกแท็บของหน้าต่างทุกบาน แต่คุณต้องการให้แน่ใจว่าจะถูกลบออกเมื่อปิดเบราว์เซอร์
คุณใช้ตัวระบุส่วนย่อยเพื่อจัดเก็บข้อมูลเซสชันเฉพาะสำหรับหน้านั้นแล้ว เช่น ID ของรูปภาพที่ผู้ใช้กำลังดูอยู่ แม้ว่าคุณจะสามารถฝังข้อมูลเซสชันอื่นๆ ในตัวระบุส่วนย่อยได้ แต่สิ่งนี้ไม่ได้ให้ประโยชน์ใดๆ กับที่เก็บข้อมูลเว็บหรือคุกกี้เลย
การใช้ IndexedDB อาจต้องใช้การเข้ารหัสมากกว่าเทคนิคอื่นๆ แต่ถ้าค่าที่คุณจัดเก็บเป็นออบเจ็กต์ JavaScript ที่ซับซ้อนซึ่งยากต่อการทำให้เป็นอนุกรม หรือหากคุณต้องการโมเดลทรานแซคชัน ก็ถือว่าคุ้มค่า
