$scope $watch $state 是什么?

1、AngularJS Scope(作用域)

Scope(作用域)是应用在 HTML (视图)和 JavaScript (控制器)之间的纽带。Scope是一个对象,有可用的方法和属性。Scope可应用在视图和控制器上。$scope的使用贯穿整个 Angular App应用,它与数据模型相关联,同时也是表达式执行的上下文.有了 $scope就在视图和控制器之间建立了一个通道,基于作用域视图在修改数据时会立刻更新 $scope,同样的 $scope发生改变时也会立刻重新渲染视图.

2、根作用域 rootScope

所有的应用都有一个 $rootScope,它可以作用在 ng-app指令包含的所有 HTML元素中。$rootScope可作用于整个应用中。是各个 controller中 scope的桥梁。用 rootscope定义的值,可以在各个 controller中使用

 

1、$scope

$scope是一个把view(一个DOM元素)连结到controller上的对象。在我们的MVC结构里,这个 $scope将成为model,它提供一个绑定到DOM元素(以及其子元素)上的excecution context。

$scope实际上就是一个JavaScript对象,controller和view都可以访问它,所以我们可以利用它在两者间传递信息。在这个 $scope对象里,我们既可以存储数据,又可以存储将要运行在view上的函数。每一个Angular应用都会有一个$rootScope。这个$rootScope 是最顶级的scope,它对应着含有ng-app 指令属性的那个DOM元素。如果页面上没有明确设定$scope ,Angular 就会把数据和函数都绑定到这里。

Angular应用启动并生成视图时,会将根 ng-app元素与 $rootScope进行绑定.$rootScope是所有 $scope的最上层对象,可以理解为一个 Angular应用中得全局作用域对象,所以不应该附加太多逻辑或者变量给$rootScope,和污染 Javascript全局作用域是一样的道理.

$scope的作用

$scope对象在 Angular中充当数据模型的作用,也就是一般 MVC框架中 Model得角色.但又不完全与通常意义上的数据模型一样,因为 $scope并不处理和操作数据,它只是建立了视图和 HTML之间的桥梁,让视图和 Controller之间可以友好的通讯。

它有如下作用和功能:

提供了观察者可以监听数据模型的变化

可以将数据模型的变化通知给整个 App

可以进行嵌套,隔离业务功能和数据

给表达式提供上下文执行环境

在 Javascript中创建一个新的执行上下文,实际就是用函数创建了一个新的本地上下文,

在 Angular中当为子 DOM元素创建新的作用域时,其实就是为子 DOM元素创建了一个新的执行上下文.

$scope的生命周期有4个阶段:

1.创建

控制器或者指令创建时, Angular会使用 $injector创建一个新的作用域,然后在控制器或指令运行时,将作用域传递进去.

2.链接

Angular启动后会将所有 $scope对象附加或者说链接到视图上,所有创建 $scope对象的函数也会被附加到视图上.

这些作用域将会注册当 Angular上下文发生变化时需要运行的函数.也就是 $watch函数, Angular通过这些函数或者何时开始事件循环.

3.更新

一旦事件循环开始运行,就会开始执行自己的脏值检测.一旦检测到变化,就会触发 $scope上指定的回调函数

4.销毁

通常来讲如果一个 $scope在视图中不再需要, Angular会自己清理它.

ng-controller指令给所在的DOM元素创建了一个新的$scope对象,并将这个$scope对象包含进外层DOM元素的$scope对象里。

在ng-app里,这个外层DOM元素的$scope对象,就是$rootScope对象。这个scope链是这样的:

所有scope都遵循原型继承(prototypal inheritance),这意味着它们都能访问父scope们。对任何属性和方法,如果AngularJS在当前scope上找不到,就会到父scope上去找,如果在父scope上也没找到,就会继续向上回溯,一直到$rootScope上。唯一的例外:有些指令属性可以选择性地创建一个独立的scope,让这个scope不继承它的父scope们。

3、$watch:

        angularjs核心之一是双向绑定,那么这个双向绑定是如何实现的呢?  当我们在创建出scope下的一个新属性的时候,ng就会主动为我们新属性注册$watch这个方法,$watch用来监听的数据变化,当数据变化之后,就立即把view和scope上数据同步。AngularJS就能够自动注册并监听变量的改变。AngularJS会首先将在{{ }}中声明的表达式编译成函数并调用$watch方法。

 $watch是一个scope函数,用于监听模型变化

 $watch(watchExpression, listener, objectEquality){ ... };

 watchExpression:$watch方法的第一个参数是一个函数,它通常被称为watch函数,它的返回值声明需要监听的变量;

 listener:第二个参数是listener,在变量发生改变的时候会被调用。和传统的事件注册和监听没有什么本质上的差别,差别仅在于AngularJS能够自动注册绝大多数的change事件并进行监听,只要按照AngularJS要求的语法来写HTML中的表达式代码,即{{ }}。 $watch方法为当前scope注册了一个watcher,这个watcher会被保存到一个scope内部维护的数组中,即是$$watchers。 watcher的主要目的是对scope上的某个属性进行监控

    objectEquality:是否深度监听,如果设置为true,它告诉Angular检查所监控的对象中每一个属性的变化.   当浏览器接收到可以被angular context处理的事件时,$digest循环就会触发。这个循环是由两个更小的循环组合起来的。    一个处理evalAsync队列(这个没有探究),另一个处理$watch队列。$digest将会遍历我们的$watch队列。如果有至少一个更新过, 这个循环就会再次触发,直到所有的$watch都没有变化。这样就能够保证每个model都已经不会再变化。 如果循环超过10次的话,它将会抛出一个异常,防止无限循环。每次当$digest循环结束时,DOM相应地变化。

    例如我们按下按钮触发ng-click事件:

  1、浏览器接收到一个事件,进入angular context。

  2、 $digest循环开始执行,查询每个$watch是否变化。

  3、 由于监视$scope.name的$watch报告了变化,它会强制再执行一次$digest循环。

  4、 新的$digest循环没有检测到变化。

  5、浏览器拿回控制权,更新与$scope.name新值相应部分的DOM。

  6、这里重要的是每一个进入angular context的事件都会执行一个$digest循环,也就是说每次我们输入一个字母循环都会检查整个页面的所有$watch。  Angular会为我们自动调用$apply!因此当点击带有ng-click的元素时,事件就会被封装到一个$apply调用。  比如有一个ng-model="foo"的输入框,然后敲一个f,事件就会这样调用$apply("foo = 'f';"),触发$digest循环。

4、$state

    $state是ui-rooter的一项服务负责表示状态以及它们之间的转换。它还提供了接口来询问当前状态

常用的方法有:

$state.go(to, params, options) :转换到新状态的方便方法

$state.includes(stateOrName, params, options) :确定当前活动状态是否等于或是状态状态子的方法。返回布尔值

$state.params: 返回状态参数的对象$stateParams

$stateParams是一个对象,包含 url中每个参数的键/值。$stateParams可以为控制器或者服务提供 url的各个部分。

注意:$stateParams必须与一个控制器相关,并且$stateParams中的“键/值”也必须事先在那个控制器的url属性中有定义。

 

我们都知道过多的watch会造成性能问题,那么如何移除不必要的$watch?

.controller('MainCtrl', function($scope) {

$scope.updated = 0;

$scope.stop = function() {

textWatch();

};

var textWatch = $scope.$watch('text', function(newVal, oldVal) {

if (newVal === oldVal) { return; }

$scope.updated++;

});

});

 

什么情况下用watch?

angular会为我们自动执行$watch,当指令中有独立作用域,或者在异步函数中,改变的数据不在angular的执行上下文,就需要手动调用$apply 来触发$digest去执行$watch

我个人的实践一般是用于观察数据中某个值的变化做出一些行为而使用。

// AI实时分析 app.directive('aiRealTimeAnalytics', function () { // 提报工单 return { restrict: 'E', replace: true, scope: { alarmData: "=" }, template: `<div class="ai-analytics-dashboard"> <div id="open_ai_analytics_dashboard" ng-click="firstWebSocket()"></div> <div id="close_ai_analytics_dashboard" ng-click="closeWebSocket()"></div> <div class="ai-analytics-line"></div> <real-time-analytics-ai ngsend-message="ngsendMessage" ng-show="alarmData.response == 0" alarm-data="alarmData" can-ask="canAsk"></real-time-analytics-ai> <real-time-analytics-record ng-show="alarmData.response == 1" ai-answer="alarmData.aiAnswer" alarm-data="alarmData"></real-time-analytics-record> </div> `, controller: function ($scope) { $scope.canAsk = true, $scope.intervalHeart = null; // DOM 元素引用 const messagesBox = document.getElementById('messagesBox'); const userInput = document.getElementById('userInput'); const sendBtn = document.getElementById('sendBtn'); const statusIndicator = document.getElementById('status'); // WebSocket 配置 let ws = null; let responseBuffer = ''; const token = getStorageToken(); const WS_ENDPOINT = `${_protocol === 'http:' ? 'ws' : 'wss'}://${_host + "/" + _apiPath}/knowledgeServer`; // 需替换为实际地址 // 初始化 WebSocket 连接 function initWebSocket() { ws = new WebSocket(WS_ENDPOINT, [token]); // 连接事件处理 ws.onopen = () => { updateUIStatus('connected', '连接已建立'); $scope.intervalHeart && clearInterval($scope.intervalHeart); $scope.intervalHeart = setInterval(_ => { ws.send(JSON.stringify('')); }, 1000) $scope.canAsk = true; }; ws.onmessage = handleMessage; ws.onerror = handleError; ws.onclose = handleClose; } // 消息处理 function handleMessage(event) { const data = JSON.parse(event.data); switch (data.isLast) { case false: responseBuffer += data.receivedMsg; updateLastMessage(markdownToHTML(responseBuffer)); break; case true: finalizeMessage(markdownToHTML(responseBuffer)); responseBuffer = ''; $scope.canAsk = true; break; case 'error': appendMessage(`系统错误: ${data.message}`, 'system'); $scope.canAsk = true; break; } } // 错误处理 function handleError(error) { console.error('WebSocket 错误:', error); updateUIStatus('error', '连接异常,正在重连...'); setTimeout(initWebSocket, 3000); } // 关闭处理 function handleClose(event) { if (!event.wasClean) return; updateUIStatus('disconnected', '连接已断开'); $scope.canAsk = false; } // 消息发送 function sendMessage() { if ($scope.canAsk) { $scope.canAsk = false; const text = userInput.value.trim(); if (!text || !ws || ws.readyState !== WebSocket.OPEN) return; const message = { businessType: 'alarm', requestType: 'question', question: text, "zpRequestId": "deepCtrl-a2eb3c18d1272ccb43c78db889a2de28", "curRequestIndex": "", "t": getStorageToken(), "modelSource": 1 }; ws.send(JSON.stringify(message)); appendMessage(text, 'user'); userInput.value = ''; createPendingMessage(); } } // 辅助函数 function appendMessage(text, type) { const messageDiv = document.createElement('div'); messageDiv.className = `message ${type}-message`; messageDiv.innerHTML = text; messagesBox.appendChild(messageDiv); messagesBox.scrollTop = messagesBox.scrollHeight; return messageDiv; } function createPendingMessage() { const pending = appendMessage(`<div class="dots"> <span>思考中</span> <span class="dot"></span> <span class="dot"></span> <span class="dot"></span> </div>`, 'ai'); pending.style.opacity = '0.6'; return pending; } function updateLastMessage(content) { const lastMsg = messagesBox.lastElementChild; if (lastMsg?.classList.contains('ai-message')) { lastMsg.innerHTML = content; messagesBox.scrollTop = messagesBox.scrollHeight; } } function finalizeMessage(content) { const lastMsg = messagesBox.lastElementChild; if (lastMsg?.classList.contains('ai-message')) { lastMsg.innerHTML = content; lastMsg.style.opacity = '1'; } } function updateUIStatus(state, message) { statusIndicator.textContent = message; statusIndicator.style.color = { connected: '#28a745', error: '#dc3545', disconnected: '#6c757d' }[state] || '#000'; } /** * 设置处理消息 */ function setDealMessage(aiAnswer) { if (aiAnswer) { let content = markdownToHTML(aiAnswer); const firstMsg = messagesBox.firstElementChild; if (firstMsg?.classList.contains('ai-message')) { firstMsg.innerHTML = content; messagesBox.scrollTop = messagesBox.scrollHeight; } } } // 事件监听 userInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') sendMessage(); }); /** * ng 发送 */ $scope.ngsendMessage = () => { sendMessage(); } //打开弹窗 $scope.firstWebSocket = () => { setDealMessage($scope.alarmData.aiAnswer); initWebSocket(); }; /** * 关闭连接 */ $scope.closeWebSocket = () => { $scope.intervalHeart && clearInterval($scope.intervalHeart); // 已关闭记录清空 const ai_record_scroller = document.getElementById('ai_record_scroller'); ai_record_scroller.innerHTML = ""; // 未关闭记录,ai对话 messagesBox.innerHTML = `<div class="message ai-message"></div>`; userInput.value = ''; // 主动关闭连接 1000 表示正常关闭 ws.close(1000, '用户主动断开'); } } } }); app.directive('realTimeAnalyticsAi', function () { // 提报工单 return { restrict: 'E', replace: true, scope: { alarmData: "=", canAsk: "=", ngsendMessage: "=" }, template: `<div> <div class="ai-analytics-title">AI实时分析 <span>由 DeepSeek 驱动</span></div> <div class="chat-container"> <div class="status-indicator hide" id="status">连接中...</div> <div class="messages-box" id="messagesBox"> <div class="message ai-message"></div> </div> <div class="input-area"> <div class="input-group"> <input type="text" id="userInput" placeholder="支持深度追问分析" ng-disabled="!canAsk"> <button id="sendBtn" ng-click="ngsendMessage()" ng-disabled="!canAsk"><div class="ai-analytics-send"></div>发送</button> </div> </div> </div> `, controller: function ($scope) { } } }) app.directive('realTimeAnalyticsRecord', function () { // 提报工单 return { restrict: 'E', replace: true, scope: { aiAnswer: "=", alarmData: "=" }, template: `<div> <div class="ai-analytics-title">处理详情 <span>由 deepseek 驱动</span></div> <div class="ai-analytics-record"> <div id="ai_record_scroller" class="ai-record-scroller"> </div> <div class="ai-record-deal"> <span data-language="alarm.unclosed_alarms.dialog.processing_record" data-language-format="{0}:">处理记录:</span> <span ng-bind="dealResult(alarmData)" ng-class="alarmData.alarmCloseRemark?'decoration-blue':''"></span> </div> </div> </div> `, controller: function ($scope) { const ai_record_scroller = document.getElementById('ai_record_scroller'); $scope.setAIAnswer = () => { if ($scope.aiAnswer) { ai_record_scroller.innerHTML = markdownToHTML($scope.aiAnswer); ai_record_scroller.style.opacity = '1'; } } $scope.$watch('aiAnswer', function () { $scope.setAIAnswer(); }) /** * 处理结果 */ $scope.dealResult = function (alarm) { if (alarm.status == 1) { return languageSwitch('alarm.unclosed_alarms.dialog.auto_recovery'); } else if (alarm.alarmCloseTime != null) { return alarm.alarmCloseRemark ? languageSwitch('alarm.alarm_overview.table.manual_close') + '(' + alarm.alarmCloseRemark + ')' : languageSwitch('alarm.alarm_overview.table.manual_close'); } else if (alarm.configRuleTypeEditTime != null) { return alarm.alarmCloseRemark ? languageSwitch('alarm.alarm_overview.table.alarm_type_adjustment') + '(' + alarm.alarmCloseRemark + ')' : languageSwitch('alarm.alarm_overview.table.alarm_type_adjustment'); } else { return ""; } }; } } }) 兼容chorme 74.0.3792.6版本
07-02
/** * Created by wupeng5 on 2016/5/9. */ var app = angular.module("app",['ui.router','oc.lazyLoad','tableKillModule','selectModule',"select2Module"]).config(function($stateProvider,$controllerProvider,$compileProvider,$filterProvider,$provide,$urlRouterProvider,$httpProvider){ $urlRouterProvider.otherwise('login'); //引入$stateProvider对象,为动态路由做准备 app.register = { stateProvider:$stateProvider } //$locationProvider.html5Mode(true); $httpProvider.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; $httpProvider.defaults.headers.common["Accept"] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'; $httpProvider.defaults.headers.post['X-Requested-With'] = 'XMLHttpRequest'; $httpProvider.defaults.transformRequest = [function(data) { /** * The workhorse; converts an object to x-www-form-urlencoded serialization. * @param {Object} obj * @return {String} */ var param = function(obj) { var query = ''; var name, value, fullSubName, subName, subValue, innerObj, i; for (name in obj) { value = obj[name]; if (value instanceof Array) { for (i = 0; i < value.length; ++i) { subValue = value[i]; fullSubName = name + '[' + i + ']'; innerObj = {}; innerObj[fullSubName] = subValue; query += param(innerObj) + '&'; } } else if (value instanceof Object) { for (subName in value) { subValue = value[subName]; fullSubName = name + '[' + subName + ']'; innerObj = {}; innerObj[fullSubName] = subValue; query += param(innerObj) + '&'; } } else if (value !== undefined && value !== null) { query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&'; } } return query.length ? query.substr(0, query.length - 1) : query; }; return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data; }]; //配合nginx处理本地开发, 服务器调式的问题(接口是跨域的) $httpProvider.interceptors.push(function($q){ return { 'request':function(config){ if(config.url.indexOf('html') == -1){ config.url = "" + config.url; } return config || $q.when(config); } } }); //注销RootScope上面的广播事件 //var deregister = $rootScope.$on("rootEvent", function(event,data) { // //}); // //$scope.$on('$destory', function() { // deregister(); // 退订事件 //}); //注册$onRootScope方法,并在执行完毕后直接销毁, 防止内存溢出 $provide.decorator('$rootScope',['$delegate',function($delegate){ Object.defineProperty($delegate.constructor.prototype,'$onRootScope',{ value:function(name,listener){ var unsubscribe = $delegate.$on(name,listener); this.$on('$destroy',unsubscribe); }, enumerable:false }); return $delegate; }]) //使用,不需要手动去销毁这个事件 //$scope.$onRootScope("key",function(){ // console.log("get key"); //}) //$rootScope.$broadcast("key",{name:1}); }); app.factory('timestampMarker', [function() { return { request: function(config) { config.requestTimestamp = new Date().getTime(); return config; }, response: function(response) { response.config.responseTimestamp = new Date().getTime(); return response; } }; }]); app.config(['$httpProvider', function($httpProvider) { $httpProvider.interceptors.push('timestampMarker'); }]); //$http.get('xxxx').then(function(response) { // var time = response.config.responseTimestamp - response.config.requestTimestamp; // console.log('The request took ' + (time / 1000) + ' seconds.'); //}); //当路由变化的时候触发 app.run(function($rootScope,$templateCache) { $rootScope.isLogin = true; $rootScope.loginStyle = ""; $rootScope.$on('$stateChangeStart', function (event, current, previous) { if(current.name != "login" && $rootScope.currentState != current.name){ // console.log($rootScope.currentState); $rootScope.currentState = current.name; } //清理所有template缓存 if (typeof(current) !== 'undefined'){ $templateCache.removeAll(); } $rootScope.currentName = current.name; if($rootScope.currentName == "login"){ $rootScope.isLogin = false; $rootScope.loginStyle = "login"; $rootScope.col = ""; }else{ $rootScope.isLogin = true; $rootScope.loginStyle = ""; } }); });代码审计一下
最新发布
08-23
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值