AngularJS HTML DOM 操作学习笔记详细版 v1.x核心原则AngularJS 是数据驱动的框架。永远不要在 Controller 中直接操作 DOM。DOM 操作应全部收敛在指令Directive中通过数据绑定与$digest循环自动同步视图。 一、AngularJS 操作 DOM 的正确哲学传统 jQuery 思维AngularJS 思维手动查找 DOM → 修改样式/内容 → 绑定事件修改 Model → Angular 自动更新 DOM命令式编程怎么做声明式编程要什么DOM 是数据源Model 是数据源DOM 只是投影事件回调中直接改 DOM事件回调中改$scope/vm视图自动响应✅铁律Controller只负责业务逻辑与数据状态Directive是操作 DOM 的唯一合法场所优先使用内置指令其次自定义指令最后才考虑原生 DOM API 二、指令Directive中的 DOM 操作核心1. 指令生命周期与 DOM 操作时机app.directive(myDom,function(){return{restrict:EA,// compile: 模板编译阶段DOM 还未绑定 scope适合做静态 DOM 转换compile:function(tElement,tAttrs){// tElement 是模板 DOMreturn{pre:function(scope,iElement,iAttrs){/* preLink */},post:function(scope,iElement,iAttrs){/* postLink link */}};},// link: 实际开发 90% 的 DOM 操作写在这里DOM 已就绪scope 已绑定link:function(scope,element,attrs,ctrl,transclude){// element 是 jqLite/jQuery 包装对象非原生 DOM}};});2.element对象常用 APIjqLiteAngularJS 内置轻量级 jQuery 子集。若页面先引入完整 jQuery则自动升级为 jQuery。方法说明element.find(selector)查找子元素仅支持标签名复杂选择器需 jQueryelement.addClass()/removeClass()/toggleClass()类名操作element.css(prop, value)样式操作element.attr()/removeAttr()属性操作element.on()/off()/one()事件绑定/解绑element.append()/prepend()/remove()DOM 增删element[0]获取原生 DOM 节点 三、内置 DOM 相关指令详解1. 条件渲染ng-ifvsng-show/ng-hide指令DOM 行为Scope 行为适用场景ng-ifexpr销毁/重建DOM 节点创建/销毁独立子作用域频繁切换且内容重、需释放内存ng-showexpr仅切换display: none作用域始终存在轻量切换、需保留内部状态如表单输入2. 样式与属性绑定!-- 动态类名对象/数组/字符串 --divng-class{active: vm.isActive, disabled: vm.isDisabled}/divdivng-class[vm.themeClass, vm.sizeClass]/div!-- 动态样式 --divng-style{color: vm.textColor, font-size: vm.fontSize px}/div!-- 安全属性绑定避免插值表达式导致的闪烁或无效 URL --imgng-src{{vm.imageUrl}}ang-href#/user/{{vm.userId}}详情/adivng-attr-title{{vm.tooltip}}/div3. 动态 HTML 渲染!-- 需引入 ngSanitize 模块防 XSS --divng-bind-htmlvm.htmlContent/divangular.module(app,[ngSanitize]);// 若信任后端 HTML可用 $sce.trustAsHtml()⚡ 四、事件处理与$digest同步机制1. 模板事件绑定buttonng-clickvm.handleClick($event, item)点击/buttoninputng-keyupvm.onKeyup($event)ng-blurvm.onBlur()vm.handleClickfunction($event,item){$event.preventDefault();$event.stopPropagation();// 修改数据即可无需操作 DOMitem.selected!item.selected;};2. 何时需要手动触发$digestAngular 只能检测自身上下文内的数据变化。以下场景需手动同步场景推荐方案setTimeout/setInterval改用$timeout/$interval自动$apply第三方库回调如 ECharts, Swiper回调内使用$scope.$applyAsync()或$timeout原生 DOM 事件非ng-*绑定element.on(click, () scope.$applyAsync(() { ... }))WebSocket / Promise 外部库包装进$q或使用$rootScope.$applyAsync()⚠️避免$digest already in progress报错// ❌ 危险if(!scope.$$phase)scope.$apply();// ✅ 安全$timeout(fn);// 或scope.$evalAsync(fn);// 或scope.$applyAsync(fn);️ 五、高级 DOM 操作技巧1. 动态编译 HTML$compile用于将动态生成的 HTML 字符串绑定到 Angular 作用域。app.directive(dynamicHtml,function($compile){return{link:function(scope,element,attrs){scope.$watch(attrs.dynamicHtml,function(html){if(!html)return;element.empty();varcompiled$compile(html)(scope);// 编译并绑定 scopeelement.append(compiled);});}};});注意$compile性能开销大避免在ng-repeat或高频事件中使用。2. 内容投影transcludeapp.directive(card,function(){return{transclude:true,template:div classcard h3卡片标题/h3 ng-transclude/ng-transclude /div};});cardp这段 DOM 会被投影到 ng-transclude 位置且保留原作用域/p/card3. 与 jQuery 共存加载顺序jQuery 必须在angular.js之前引入否则 Angular 使用 jqLite。获取 jQuery 对象angular.element(dom)或$(dom)在指令中安全使用 jQuery 插件link:function(scope,element){$(element).datepicker({onSelect:function(date){scope.$applyAsync(()scope.vm.selectedDatedate);}});scope.$on($destroy,function(){$(element).datepicker(destroy);// 防内存泄漏});} 六、常见坑与避坑指南现象原因解决方案Controller 里document.getElementById找不到元素DOM 尚未渲染或ng-if未生效移到 Directive 的link中或用$timeout延迟绑定事件后页面越来越卡未解绑事件导致重复绑定/内存泄漏scope.$on($destroy, () element.off())动态插入的{{}}不解析直接innerHTML或append()未经过编译使用$compile(html)(scope)ng-click不触发元素被其他层遮挡或未在 Angular 上下文检查 CSSz-index/pointer-events确保在ng-app内表单输入卡顿ng-model默认input事件触发频繁$digest使用ng-model-options{ updateOn: blur }或debounce: 300 七、DOM 性能优化最佳实践优化点做法减少 Watcher 数量静态数据用一次性绑定{{::vm.title}}列表渲染优化ng-repeatitem in list track by item.id条件渲染选择隐藏大量 DOM 用ng-if轻量切换用ng-show避免 DOM 抖动批量修改样式/类名或使用requestAnimationFrame减少$watch深度监听避免$watch(obj, fn, true)改用$watchCollection或明确路径虚拟滚动大数据使用angular-vs-repeat或ui-grid替代全量ng-repeat模板缓存使用$templateCache或构建工具预编译.html 八、完整示例自定义click-outside指令场景点击元素外部时关闭下拉菜单/弹窗。展示 DOM 事件、$document、$destroy、$applyAsync的最佳实践。angular.module(app).directive(clickOutside,function($document){return{restrict:A,scope:{clickOutside:// 父级回调},link:function(scope,element){functionhandler(event){// 判断点击目标是否在当前元素及其子元素之外varisInsideelement[0].contains(event.target);if(!isInside){scope.$applyAsync(function(){scope.clickOutside();});}}// 绑定到 document$document.on(click,handler);// 指令销毁时清理事件防止内存泄漏scope.$on($destroy,function(){$document.off(click,handler);});}};});!-- 使用 --divclassdropdownclick-outsidevm.closeDropdown()buttonng-clickvm.toggle()菜单/buttonulng-ifvm.isOpenli选项1/lili选项2/li/ul/div 九、延伸学习与迁移提示 官方文档Directives Guide | [c o m p i l e ] ( h t t p s : / / d o c s . a n g u l a r j s . o r g / a p i / n g / s e r v i c e / compile](https://docs.angularjs.org/api/ng/service/compile](https://docs.angularjs.org/api/ng/service/compile) | jqLite 调试技巧Chrome 安装AngularJS Batarang查看 Scope 树与 Watcher 数量Angular (2) 迁移提示link→ngAfterViewInitElementRefRenderer2$compile→ViewContainerRef.createComponent()严禁直接操作nativeElement必须通过Renderer2保证 SSR 兼容