$scope ใน AngularJS คืออะไร? บทช่วยสอนพร้อมตัวอย่าง

⚡ สรุปอย่างชาญฉลาด

$Scope ใน AngularJS เป็นอ็อบเจ็กต์ในตัวที่ใช้เชื่อมโยงคอนโทรลเลอร์กับวิว หน้านี้จะอธิบายเกี่ยวกับการสร้างสโคป ฟังก์ชันการทำงาน ลำดับชั้นของพาเรนต์และลูก ความแตกต่างจาก $rootScope และวิธีการที่วงจรไดเจสต์รีเฟรชการเชื่อมโยง

  • 🔗 บทบาทการผูกมัด: `$scope` จะส่งค่าตัวแปรสมาชิกจากคอนโทรลเลอร์ไปยังวิว ทำให้เทมเพลตแสดงผลข้อมูลโดยไม่ต้องแก้ไข DOM โดยตรง
  • ⚙️ ฟังก์ชันพฤติกรรม: แนบฟังก์ชันเข้ากับ $scope เมื่อวิวจำเป็นต้องตอบสนองต่อการคลิก การเปลี่ยนแปลงข้อมูล หรือเหตุการณ์อื่นๆ ของผู้ใช้
  • 🌳 ลำดับชั้นของขอบเขต: คำสั่งต่างๆ เช่น ng-controller และ ng-repeat สร้างขอบเขตย่อยที่สะท้อนโครงสร้าง DOM ของแอปพลิเคชัน
  • 🌍 $rootScope เข้าถึง: แต่ละแอปพลิเคชันจะมี $rootScope เพียงหนึ่งเดียว และทำหน้าที่เหมือนอ็อบเจ็กต์ส่วนกลางที่ขอบเขตย่อยทุกอันสามารถอ่านได้
  • 🔍 วงจรการย่อยอาหาร: $watch ใช้สำหรับลงทะเบียนตัวรับฟังเหตุการณ์, $apply ใช้สำหรับเข้าสู่บริบทการทำงานของ AngularJS และ $digest ใช้สำหรับเปรียบเทียบค่าต่างๆ จนกว่าโมเดลจะเสถียร
  • 🚀 นิสัยการทำงาน: ควรจำกัดการใช้คำสั่ง watch expression ให้น้อยที่สุด หลีกเลี่ยงการเข้าถึง DOM ภายในคำสั่งเหล่านั้น และเรียกใช้ $destroy กับ scope ที่ไม่จำเป็นอีกต่อไป

$Scope ใน AngularJS คืออะไร

$scope ใน AngularJS คืออะไร?

$ขอบเขตใน AngularJS เป็นวัตถุในตัวซึ่งโดยทั่วไปจะผูก "ตัวควบคุม" และ "มุมมอง" เราสามารถกำหนดตัวแปรสมาชิกในขอบเขตภายในตัวควบคุมซึ่งสามารถเข้าถึงได้โดยมุมมอง

ตัวควบคุมทุกตัวใน AngularJS แอปพลิเคชันจะได้รับอินสแตนซ์ `$scope` ของตัวเองผ่านการฉีดการพึ่งพา (dependency injection) สิ่งใดก็ตามที่คุณแนบเข้ากับอ็อบเจ็กต์นั้นจะสามารถมองเห็นได้ในเทมเพลตที่คอนโทรลเลอร์ควบคุม ดังนั้น `$scope` จึงทำหน้าที่เป็นโมเดลข้อมูลที่ใช้ร่วมกันซึ่งอยู่ระหว่างแอปพลิเคชันและคอนโทรลเลอร์ของคุณ Javaตรรกะของสคริปต์และโค้ด HTML ของคุณ

พิจารณาตัวอย่างด้านล่าง:

$ขอบเขตใน AngularJS

angular.module('app',[]).controller('HelloWorldCtrl',
	function($scope)
	{
		$scope.message = "Hello World"
	});

Code คำอธิบาย:

  1. ชื่อของโมดูลคือ "แอป"
  2. ชื่อของตัวควบคุมคือ “HelloWorldCtrl”
  3. วัตถุขอบเขตเป็นวัตถุหลักที่ใช้ในการส่งข้อมูลจากตัวควบคุมไปยังมุมมอง
  4. เพิ่มตัวแปรสมาชิกให้กับวัตถุขอบเขต

⚠️ คำเตือนเกี่ยวกับไวยากรณ์: การขอ .controller() เมธอดนี้รับอาร์กิวเมนต์สองตัว คือ ชื่อคอนโทรลเลอร์และฟังก์ชันโรงงาน ดังนั้นจึงต้องคั่นด้วยเครื่องหมายจุลภาค 'HelloWorldCtrl' และ function($scope)การไม่ใส่เครื่องหมายจุลภาคทำให้เกิดข้อผิดพลาด Uncaught SyntaxError ก่อนที่ AngularJS จะเริ่มทำงาน

ตัวควบคุมเพียงตัวเดียวและมุมมองเดียวเป็นเพียงครึ่งหนึ่งของภาพรวมทั้งหมด แอปพลิเคชันในความเป็นจริงมักมีการซ้อนตัวควบคุม ทำซ้ำรายการ และนำวิดเจ็ตกลับมาใช้ใหม่ ซึ่งหมายความว่ามีขอบเขตหลายอย่างเกิดขึ้นพร้อมกัน

ลำดับชั้นของขอบเขตและการสืบทอดทำงานอย่างไรใน AngularJS

แอปพลิเคชัน AngularJS ทุกแอปพลิเคชันจะมีขอบเขตหลัก (root scope) เพียงหนึ่งเดียว และอาจมีขอบเขตย่อย (child scope) ได้หลายขอบเขต ไดเร็กทีฟ (Directives) จะสร้างขอบเขตย่อยเหล่านั้นและแนบเข้ากับองค์ประกอบ DOM ที่มันตกแต่ง ดังนั้นโครงสร้างของขอบเขตจึงสะท้อนโครงสร้างของหน้าเว็บที่แสดงผล

เมื่อ AngularJS ประเมินนิพจน์ เช่น {{siteName}}โดยจะค้นหาคุณสมบัตินั้นในขอบเขตที่แนบมากับองค์ประกอบก่อน หากไม่พบคุณสมบัตินั้น การค้นหาจะย้ายไปยังขอบเขตแม่ และจะค้นหาต่อไปเรื่อยๆ จนกว่าจะถึงขอบเขตราก Javaนักพัฒนาสคริปต์รู้จักพฤติกรรมนี้ในชื่อการสืบทอดแบบโปรโตไทป์ และขอบเขตย่อยจะสืบทอดจากขอบเขตแม่ในลักษณะนั้นอย่างแน่นอน

ตัวอย่างด้านล่างนี้แสดงการซ้อนคอนโทรลเลอร์หนึ่งไว้ภายในอีกคอนโทรลเลอร์หนึ่ง เพื่อให้คุณสามารถสังเกตการสืบทอดคุณสมบัติได้

<div ng-app="scopeDemo">
    <div ng-controller="ParentCtrl">
        Parent scope value: {{siteName}}
        <div ng-controller="ChildCtrl">
            Child reads parent value: {{siteName}}
            Child own value: {{lesson}}
        </div>
    </div>
</div>

<script type="text/javascript">
    var app = angular.module("scopeDemo", []);

    app.controller("ParentCtrl", function($scope) {
        $scope.siteName = "Guru99";
    });

    app.controller("ChildCtrl", function($scope) {
        $scope.lesson = "AngularJS Scope";
    });
</script>

Code คำอธิบาย:

  1. ParentCtrl กำหนด siteName ในขอบเขตของมันเอง
  2. ChildCtrl ไม่เคยกำหนด siteNameแต่ div ด้านในยังคงแสดงผลอยู่ Guru99 เนื่องจากการค้นหาจะไปยังขอบเขตหลัก
  3. lesson ตัวแปรนี้มีอยู่เฉพาะในขอบเขตของ div ย่อยเท่านั้น ดังนั้น div แม่จึงไม่สามารถอ่านได้

คำสั่งใดบ้างที่สร้างขอบเขตใหม่?

  • ng-controller: สร้างขอบเขตย่อยสำหรับองค์ประกอบที่วางไว้
  • ng-repeat: สร้างขอบเขตย่อยหนึ่งรายการต่อการวนซ้ำ ซึ่งเป็นเหตุผลว่าทำไมแต่ละแถวจึงสามารถเก็บค่าที่แตกต่างกันสำหรับนิพจน์เดียวกันได้
  • คำสั่งที่กำหนดเอง: อาจขอตรวจดูเด็ก หรือ แยก ขอบเขตที่ไม่สืบทอดคุณสมบัติจากขอบเขตแม่โดยเจตนา
  • คำสั่งของส่วนประกอบ: สิ่งใดก็ตามที่ลงทะเบียนไว้กับ .component() ตัวช่วยจะได้รับขอบเขตแบบแยกเดี่ยวเสมอ

เนื่องจากขอบเขตของลูกจะอ่านค่าจากล่างขึ้นบน นักพัฒนาจึงมักสงสัยว่าจุดสูงสุดของลำดับนั้นอยู่ที่ใด วัตถุระดับบนสุดนั้นเรียกว่า $rootScope

$scope กับ $rootScope ต่างกันอย่างไร?

แองกูลาร์เจเอส $rootScope $rootScope คือขอบเขตสำหรับแอปพลิเคชันทั้งหมด แอปพลิเคชันหนึ่งๆ จะมี $rootScope ได้เพียงหนึ่งเดียวเท่านั้น และมันถูกใช้งานเหมือนตัวแปรส่วนกลาง $scope ทุกตัวเป็นขอบเขตย่อย และ $rootScope เป็นขอบเขตหลักที่อยู่บนสุดของโครงสร้าง ตารางด้านล่างเปรียบเทียบวัตถุทั้งสองนี้

แง่มุม $ขอบเขต $rootScope
อินสแตนซ์ต่อแอปพลิเคชัน หลายตัว โดยแต่ละตัวจะอยู่บนตัวควบคุมหรือคำสั่งสร้างขอบเขตหนึ่งตัว หนึ่งอันพอดี
จัดทำโดย คำสั่งเช่น ng-controller และ ng-repeat ตัวฉีดระหว่างการบูตแอปพลิเคชัน
แพ็กเกจ องค์ประกอบของมันเองและลูกหลานทั้งหมด ทุกขอบเขตในแอปพลิเคชัน
การใช้งานทั่วไป ข้อมูลและพฤติกรรมสำหรับมุมมองเดียวหรือแถวรายการเดียว ค่าต่างๆ ที่แสดงผลทั่วทั้งแอปพลิเคชัน เช่น ผู้ใช้ที่เข้าสู่ระบบ
ความเสี่ยงหลัก คุณค่าต่างๆ จะไม่สามารถเข้าถึงได้หากอยู่นอกสาขานี้ การชนกันของชื่อและการเชื่อมโยงที่ซ่อนเร้น เช่น ตัวแปรส่วนกลาง

ควรใช้ $rootScope อย่างระมัดระวัง สิ่งใดก็ตามที่จัดเก็บไว้ในนั้นจะคงอยู่ตลอดทั้งแอปพลิเคชันและสามารถอ่านได้ทุกที่ ดังนั้นโดยทั่วไปแล้วบริการที่ใช้ร่วมกันจึงเป็นสถานที่ที่ปลอดภัยกว่าสำหรับสถานะส่วนกลาง (อย่างเป็นทางการ) การอ้างอิง $rootScope แสดงรายการ API ทั้งหมด

การรู้ว่าข้อมูลอยู่ที่ไหนเป็นเพียงครึ่งหนึ่งของงาน อีกครึ่งหนึ่งคือการสอนให้ระบบตอบสนองอย่างไรเมื่อผู้ใช้ทำบางสิ่งบางอย่าง

การตั้งค่าหรือเพิ่มพฤติกรรมใน AngularJS

เพื่อที่จะตอบสนองต่อเหตุการณ์หรือดำเนินการคำนวณ/ประมวลผลบางประเภทในมุมมอง เราต้องจัดให้มีพฤติกรรมในขอบเขต

พฤติกรรมจะถูกเพิ่มให้กับวัตถุขอบเขตเพื่อตอบสนองต่อเหตุการณ์เฉพาะที่อาจถูกกระตุ้นโดยมุมมอง เมื่อกำหนดพฤติกรรมในคอนโทรลเลอร์แล้ว มุมมองจะสามารถเข้าถึงได้

ลองดูตัวอย่างว่าเราจะบรรลุเป้าหมายนี้ได้อย่างไร

การตั้งค่าหรือเพิ่มพฤติกรรม

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta chrset="UTF 8">
    <title>Guru99</title>     
</head>  
<body ng-app="DemoApp">
<h1> Guru99 Global Event</h1>
<script src="https://code.angularjs.org/1.6.9/angular.js"></script>
<div ng-controller="DemoController">
	{{fullName("Guru","99")}}
</div>
<script type="text/javascript">  
	var app = angular.module("DemoApp", []);
	app.controller("DemoController", function($scope) {
    
    $scope.fullName=function(firstName,lastname){
		return firstName + lastname;
		}
	} );
</script> 

</body>  
</html>

Code คำอธิบาย:

  1. เรากำลังสร้างพฤติกรรมที่เรียกว่า "ชื่อเต็ม" ลักษณะการทำงานนี้เป็นฟังก์ชันที่ยอมรับ 2 พารามิเตอร์ (ชื่อ, นามสกุล)
  2. ลักษณะการทำงานจะส่งกลับการต่อกันของพารามิเตอร์ 2 ตัวนี้
  3. ในมุมมองนี้ เรากำลังเรียกใช้พฤติกรรมและส่งค่า 2 ค่าของ “Guru“และ “99” ซึ่งจะถูกส่งผ่านเป็นพารามิเตอร์ไปยังพฤติกรรม

หากดำเนินการคำสั่งสำเร็จ ผลลัพธ์ต่อไปนี้จะปรากฏขึ้นเมื่อคุณรันโค้ดในเบราว์เซอร์

Output:

การตั้งค่าหรือเพิ่มพฤติกรรม

ในเบราว์เซอร์ คุณจะเห็นการรวมกันของค่าทั้งสอง Guru และ 99 ซึ่งถูกส่งต่อไปยังพฤติกรรมใน ตัวควบคุม.

การเชื่อมโยงข้อมูลแบบที่แสดงด้านบนจะรีเฟรชตัวเองโดยอัตโนมัติ และกลไกที่ทำให้เกิดสิ่งนั้นเป็นสิ่งสำคัญที่ควรทำความเข้าใจก่อนที่แอปพลิเคชันจะเติบโตจนมีขนาดใหญ่

เหตุใด Scope Watchers และ Digest Cycle จึงมีความสำคัญ

AngularJS รักษาความสอดคล้องของมุมมองกับขอบเขตโดยใช้การตรวจสอบการเปลี่ยนแปลง (dirty checking) แทนที่จะใช้เหตุการณ์การเปลี่ยนแปลง (change events) โดยมี API สามตัวที่ขับเคลื่อนกระบวนการนี้:

  • $watch: ลงทะเบียนผู้ฟังสำหรับนิพจน์ การแทรกค่า เช่น {{fullName}} ลงทะเบียนผู้ติดตามให้คุณระหว่างการเชื่อมโยงเทมเพลต
  • $apply: ฟังก์ชันนี้จะเข้าสู่บริบทการทำงานของ AngularJS ประเมินโค้ดของคุณ แล้วจึงเรียกใช้ฟังก์ชัน digest จำเป็นต้องใช้เฉพาะภายใน callback เหตุการณ์แบบกำหนดเองหรือ callback ของไลบรารีภายนอกเท่านั้น เนื่องจาก directive ที่มีอยู่แล้วได้เรียกใช้ฟังก์ชันนี้อยู่แล้ว
  • $digest: วนลูปผ่านตัวเฝ้าดูทุกตัวบน $rootScope และลูกๆ ของมัน เปรียบเทียบค่าปัจจุบันกับค่าก่อนหน้า และทำซ้ำจนกว่าโมเดลจะหยุดเปลี่ยนแปลง

เนื่องจากกระบวนการไดเจสต์สามารถทำงานได้หลายรอบ ดังนั้นนิพจน์การตรวจสอบจึงต้องมีต้นทุนต่ำ หลีกเลี่ยงการเข้าถึง DOM ภายในนิพจน์เหล่านั้น เนื่องจาก1การอ่าน DOM นั้นช้ากว่าการอ่านนิพจน์การตรวจสอบหลายเท่า Javaคุณสมบัติของสคริปต์

ความลึกของการเฝ้าสังเกตก็ส่งผลต่อต้นทุนเช่นกัน การเฝ้าสังเกตโดยใช้ข้อมูลอ้างอิงเป็นวิธีที่เร็วที่สุด และจะสังเกตเห็นได้ก็ต่อเมื่อค่าทั้งหมดถูกแทนที่แล้วเท่านั้น $watchCollection ฟังก์ชันนี้จะแจ้งเตือนเมื่อมีการเพิ่ม ลบ หรือจัดเรียงลำดับรายการใหม่ภายในอาร์เรย์หรืออ็อบเจ็กต์ การตรวจสอบด้วยค่าจะวนลูปผ่านโครงสร้างแบบซ้อนกันในทุกๆ การตรวจสอบ และเป็นตัวเลือกที่แพงที่สุด ดังนั้นควรเลือกใช้เป็นตัวเลือกสุดท้าย

คำถามที่พบบ่อย

ขอบเขตแยก (isolate scope) จะไม่สืบทอดคุณสมบัติแบบโปรโตไทป์จากผู้ปกครอง คำสั่งคอมโพเนนต์ที่สร้างด้วยตัวช่วย .component() จะได้รับขอบเขตแยกนี้เสมอ ซึ่งจะทำให้วิดเจ็ตที่นำกลับมาใช้ใหม่ได้เป็นอิสระจากคอนโทรลเลอร์โดยรอบ และป้องกันการอ่านคุณสมบัติของผู้ปกครองโดยไม่ตั้งใจ

$emit ส่งเหตุการณ์ขอบเขตขึ้นไปยังขอบเขตแม่จนถึง $rootScope ในขณะที่ $broadcast ส่งเหตุการณ์ลงไปยังขอบเขตลูกทุกขอบเขต ทั้งสองอย่างรับได้ด้วย $on ควรใช้ $broadcast อย่างระมัดระวัง เพราะมันจะไปตรวจสอบขอบเขตลูกหลานทุกขอบเขต

เรียก scope.$destroy() เมื่อขอบเขตย่อยไม่จำเป็นอีกต่อไป การทำเช่นนี้จะหยุดการแพร่กระจายของไดเจสต์ไปยังขอบเขตและปล่อยให้ตัวเก็บขยะเรียกคืนโมเดลของมัน นอกจากนี้ยังสามารถยกเลิกการลงทะเบียนตัวเฝ้าดูแบบกำหนดเองได้ด้วยการเรียกฟังก์ชันที่ $watch ส่งคืน

ผู้ช่วย เช่น นักบิน GitHub สร้างโค้ดตัวอย่างได้รวดเร็ว แต่ก็ยังคงมีข้อผิดพลาดอยู่บ้าง เช่น การขาดเครื่องหมายจุลภาคในการลงทะเบียนคอนโทรลเลอร์ ตรวจสอบโค้ดตัวอย่างที่สร้างขึ้นทุกส่วนกับมาตรฐานอย่างเป็นทางการ เอกสารขอบเขตของ AngularJS.

ผู้ช่วย AI จะแมปคุณสมบัติ $scope ไปยังฟิลด์คลาสคอมโพเนนต์ และเขียนตรรกะ $watch ใหม่เป็นสัญญาณหรือสตรีม RxJS จับคู่กับไลบรารีอย่างเป็นทางการ เชิงมุม จัดทำเอกสารและเรียกใช้การทดสอบหน่วย เนื่องจากการแปลงที่สร้างขึ้นมักจะพลาดการค้นหาขอบเขตที่สืบทอดมา

สรุปโพสต์นี้ด้วย: