Introduction
arguments
是一个类数组对象,包含了函数调用时传入的所有参数。arguments
对象只有在函数内部才可以使用。
访问arguments对象
1 | function add(a, b) { |
通过arguments对象可以改变函数参数的值
- 改变arguments时,函数参数的值也会改变。
1 | function add(a, b) { |
- 改变函数参数时,arguments对象的值也会改变。
1 | function add(a, b) { |
arguments
是一个类数组对象,包含了函数调用时传入的所有参数。arguments
对象只有在函数内部才可以使用。
1 | function add(a, b) { |
1 | function add(a, b) { |
1 | function add(a, b) { |
JavaScript中的catch不能按类型捕获异常,只能捕获所有异常。如果需要按类型捕获异常,可以使用if
语句判断异常类型。
1 | try { |
与Java
等其他类型的语言不同,JS不能像下面这样捕获异常。JS中一个try
只能对应一个catch
。
1 | try { |
return
和finally
同时存在时,finally
会在return
之前执行。
1 | function test() { |
JavaScript模板字符串是ES6新增的一种字符串形式,它可以让我们在字符串中插入变量,而不需要使用+
号连接字符串,这样使得字符串的拼接更加简洁。以下是模板字符串的特点。
模板字符串中可以插入变量,使用${}
包裹变量名。
1 | const name = 'zdd'; |
模板字符串可以跨行,这样我们就可以直接在字符串中换行,而不需要使用\n
。
1 | const str = `Hello |
模板字符串还支持标签模板,即在模板字符串前面加上一个函数名,这个函数会处理模板字符串的内容。所以,这个模板字符串的应用场景是什么?在项目中好像没见过这么用的。
1 | function tag(strings, ...values) { |
There are some questions about RxJS that are often asked in interviews. Let’s take a look at them.
Promise
and Observable
?Promise
是一对一的,而Observable
可以是一对多的。(一个Observer可以有多个订阅者)Promise
不能取消(配合某些Library也许可以),而Observable
可以。Promise
定义后立即执行,而Observable
是惰性的。订阅后才执行。Promise
可以配合async/await
使用,而Observable
不行。Promise
没有操作符,而Observable
有很多操作符,比如map,retry等,在处理复杂请求时更加方便。ngDoCheck
runs before every time Angular checks a component’s template for changes.
很多人误以为,只要这个函数调用了,那么就证明Angular对当前组件进行了变更检测,这是一个常见的误解。注意看这个函数的定义:它是在每次Angular检查组件的模板变化之前运行的。所以不能以这个函数的调用作为Angular进行了变更检测的依据。
我们来看一个实际的例子:
ParentComponent
, 采用ChangeDetectionStrategyDefault
模式ChildComponent
,采用ChangeDetectionStrategy.OnPush
模式ngDoCheck
函数会被调用吗?GrandChildComponent
,采用ChangeDetectionStrategy.OnPush
模式,当父组件处理点击事件时,孙子组件的ngDoCheck
函数会被调用吗?ParentComponent
采用默认的ChangeDetectionStrategyDefault
模式,并且添加了一个按钮,当我们点击这个按钮时,Angular会触发变更检测。
1 | // parent.component.ts |
ChildComponent
采用ChangeDetectionStrategy.OnPush
模式,只有当Input属性发生变化时,或者响应自身事件,或者手动触发了变更检测时,ngDoCheck
函数才会被调用。
1 | // child.component.ts |
GrandChildComponent
同样采用了ChangeDetectionStrategy.OnPush
模式。
1 | // grand-child.component.ts |
当我们点击Parent按钮时,控制台输出包含如下一行,说明ChildComponent的ngDoCheck
函数被调用了。
1 | ngDoCheck in ChildComponent |
可是我们明明指定了ChangeDetectionStrategy.OnPush
模式,为什么会调用ngDoCheck
函数呢?难道OnPush
模式失效了吗?
其实不然,正如前面所说的,ngDoCheck
函数是在每次Angular检查组件的模板变化之前运行的。所以,即使ChildComponent
采用了ChangeDetectionStrategy.OnPush
模式,ngDoCheck
也被调用了,但是这并不意味着Angular对ChildComponent
进行了变更检测。
这种情况只发生在OnPush
根组件上,上面的GrandChildComponent
并没有被调用,因为它是ChildComponent
的子组件,所以它的ngDoCheck
不会调用。
有此类行为的生命周期函数还有ngAfterViewChecked
,无论Angular是否进行了变更检测,这个函数都会被调用。
对于一个组件来说,我如何确定Angular是否对它进行了变更检测呢?这个问题,其实困扰了我很久,以前我一直以为ngDoCheck
函数的调用就是Angular进行了变更检测的标志,由上面的结论可知,这是不准确的。而其他生命周期函数也无法准确的告诉我们Angular是否进行了变更检测。
真的没办法了吗?
有的!其实之所以有这个困惑,还是对Angular变更检测理解不够深入,Angular的变更检测到底做了什么?其中必然有一个步骤是对template进行检查,如果template中绑定的值发生了变化,那么Angular就会更新视图。所以,我们可以通过template是否发生变化来判断Angular是否进行了变更检测。
代码很简单,只要在ChildComponent的模板中插入一个随机值即可,如果Angular进行了变更检测,那么每次这个值都会变化。如果这个值没有变,那么Angular就没有进行变更检测。
1 | @Component({ |
这时我们再点击Parent按钮,发现ChildComponent的模板中的随机值是不变的,这证明Angular没有进行变更检测。
ngDoCheck
到底怎么用?其实ngDoCheck
是在Angular进行变更检测之前给用户一个机会,执行一些自定义逻辑。注意看官网的这句话:
1 | You can use this lifecycle hook to manually check for state changes outside of Angular's normal change detection, manually updating the component's state. |
所以到底要怎么使用它?恕我经验不够,我至今还未在项目中实际使用过ngDoCheck
函数。但是我在网上找到了一些资料,可以参考一下:
ngDoCheck
函数来手动检测状态变化。ngDoCheck
函数中实现。ngOnChanges
函数不会被调用的,这时可以使用ngDoCheck
函数来检测对象的属性变化。组件中使用第三方库改变了DOM状态,但是Angular没有检测到,这时可以使用ngDoCheck
函数来手动检测状态变化。
1 | ngDoCheck() { |
假设有一个父组件ParentComponent,一个子组件ChildComponent,父组件给子组件传递一个User对象,我们将User的年龄显示到子组件页面上,我们希望达到一个效果,如果年龄小于50岁时,子组件不进行变更检测,这种情况就需要使用ngDoCheck
函数。
父组件定义如下,注意在onParentButtonClick
函数中要重新赋值一个新的User对象,而不是改变User对象的属性。
1 | @Component({ |
子组件定义如下,我们需要在ngDoCheck
中自定义变更检测的逻辑。(注意,ngDoCheck
在每次ngOnChanges
之后调用。), 如果年龄小于50岁时,我们是用ChangeDetectorRef.detach()
函数来停止变更检测,大于50岁时,我们使用ChangeDetectorRef.reattach()
函数来恢复变更检测。
1 | @Component({ |
注意:这个例子十分牵强,现实中不会有这种情况的。
User类型定义:
1 | export interface User { |
父组件定义如下,初始化时,我们传递一个user对象给ChildComponent
, 点击按钮时,改变user对象的age属性。
1 | @Component({ |
子组件定义如下,当父组件点击按钮时,子组件的页面上,age值并没有变化,还是40.
子组件的ngDoCheck
函数会被调用,我们可以在ngDoCheck
函数中检测user对象的属性变化,并打印出变化的值。
但是子组件的ngOnChanges
函数不会被调用,因为父组件只改变了user对象的属性,而没有改变user对象本身。而ngOnChanges
比较的是对象的引用,而不是对象内部的值。
1 | @Component({ |
如果我们想让子组件页面上的age值变化,那么只需要手动触发一次变更检测即可。此时再点击父组件中的按钮,子组件的age值会变为18。
1 | constructor(private cdf: ChangeDetectorRef) {} |
ngOnChanges
是Angular中非常重要的一个生命周期函数,今天我们来详细讨论一下它的用法。
ngOnChanges
?ngOnChanges
是一个生命周期钩子,当Angular检测到组件的输入属性发生变化时,就会调用ngOnChanges
函数。这意味着,只有当组件的输入属性发生变化时,ngOnChanges
才会被调用。
ngOnChanges
?通常情况下,我们不需要重写ngOnChanges
函数,因为Angular会自动调用它。但是,以下几种情况都需要重写ngOnChanges
函数:
OnPush
时,且需要根据Input变化做相应操作时,那么就要响应ngOnChanges
。ngOnChanges
函数。假设有一个父组件:ParentComponent,一个子组件ChildComponent,父组件中调用一个API,返回值作为Input传递给子组件,子组件要根据这个Input继续调用另外一个API,这时候就需要在子组件中重写ngOnChanges
函数。
1 | @Component({ |
Directives are classes that add additional behavior to elements in your Angular applications.
Angular has many kinds of directives, including components, attribute directives, and structural directives.
This is the most common one we used every day, a component is a directive with a template. Components are the most basic building block of an Angular application.
https://angular.dev/guide/components
Structural directives are responsible for HTML layout. They shape or reshape the DOM’s structure, typically by adding, removing, or manipulating elements.
NgIf
is used to conditionally creates or disposes of subviews in a template. see this post for more details.
Angular17引入了新的控制流语句:@if
, @for
, @switch
,这些语句可以替代NgIf, NgFor, NgSwitch,更加简洁,高效,我们建议使用新的控制流语句。
Attribute directives are used to change the appearance or behavior of an element, component, or another directive. They are usually applied to elements as attributes.
Attribute directives doesn’t change the layout of the template, just change the appearance of it, for example, change the color, font size, visibility etc.
The most common attribute directives are as follows:
如果内置的Directive不能满足我们的需求,我们可以自定义Directive,下面我们自定义一个Directive来实现一个简单Hover变色功能,当鼠标移动到元素上时,元素背景色变为红色,鼠标移出时恢复原背景色。
使用以下命令创建一个新的指令:HoverDirective
,该命令会创建文件src/app/hover.directive.ts
。
1 | ng g d hover |
以下是该指令的实现代码:
1 | import {Directive, ElementRef, HostListener} from '@angular/core'; |
简单解释一下上述代码:
appHover
,这个是Directive的名字,我们在HTML中使用这个名字来引用这个Directive。ElementRef
,这个是Angular提供的一个服务,用来获取当前元素的引用。这里的当前元素就是使用了该Directive的元素。mouseenter
和mouseleave
事件,当鼠标移入时,设置背景色为红色,移出时恢复原色。首先,在需要使用这个指令的组件或者模块中的imports
数组中导入这个Directive。
1 | imports: [HoverDirective], |
然后,在需要使用这个Directive的元素上加上appHover
属性即可(注意:appHover
就是定义Directive时指定的selector
)。此时,当鼠标移入时,该段落的背景色会变为红色,移出时恢复原背景色。
1 | <p appHover>Hover me</p> |
这个自定义指令非常简单,虽然现在可以工作,但是有一个缺点,就是颜色的变化是固定的,如果我想让用户自定义Hover时的颜色,该怎么办呢?这就涉及到如何给自定义指令传递参数了。
我们修改一下实现,将颜色作为参数传递给Directive,这里我们定义了一个@Input属性,用来接收参数。@Input属性的名字是appHover
,所以这里appHover
同时作为Directive的selector和Input属性的名字。
1 | // Add an input property to the directive |
另外,为了防止用户忘记传递参数,我们给hoverColor设置了一个默认值'red'
,这样即使用户不传递参数,也不会报错。
现在,我们可以在HTML中传递颜色参数了。
1 | <p appHover="green">Hover me</p> |
上面的例子只有一个参数,所以我们在定义@Input
的时候,直接把参数定义到了Directive的selector上,如果有多个参数,这种方法就不好使了,为了说明问题,我们再加一个参数,字体颜色,就是当鼠标Hover时改变背景色,同时也改变字体颜色。
1 | import {Directive, ElementRef, HostListener, Input} from '@angular/core'; |
使用时:
1 | <p appHover hoverFontColor="red" hoverBackgroundColor="yellow">This is a paragraph</p> |
总结一些Angular的面试题,希望对大家有所帮助。
:host
和::ng-deep
的作用是什么?详情useFactory
。- 唯有此法能传参数。detectChanges
和markForCheck
有什么区别? [详情](https://zdd.github.io/2024/12/16/angular-change-detection-markforcheck/)runOutsideAngular
的作用是什么?详情Angular is a web framework which help developers to build fast and scalable web applications. It is developed and maintained by Google.
It’s a MVC(or MVVM) framework to build SPA (Single Page Application) using HTML, CSS and JavaScript/TypeScript.
Angular has a lot of features like:
AngularJS | Angular | |
---|---|---|
Version | 1.x | 2+ |
Language | JavaScript | TypeScript |
Architecture | Controller | Components |
Mobile Support | No | Yes(Ionic) |
CLI | No | Yes |
Lazy loading | No | Yes |
SSR | No | Yes |
Directives are classes that add additional behavior to elements in your Angular applications. Angular has many kinds of directives, including:
Component is where you write you binding code, and Module groups components. An app contains many modules, and a module contains many components.
1 | app |
Before standalone component, Two components in different module can’t communicate with each other directly, they need to communicate by their enclosing module. See here for more details.
Angular is a MVVM framework, it’s a bit different from MVC.
In MVVM pattern, Model and View doesn’t communicate directly. ViewModel is a mediator between Model and View, it’s a class that contains the business logic of the application. In Angular, ViewModel is a Component.
#工作中遇到的真实案例
之前一个同事写了一个很大的组件,就是一个左侧边栏菜单,这个边栏要加载用户账号,有的用户有上千个账号,而这个边栏还有一个收起和展开的操作。测试人员发现,当用户的账号很多时,收起和展开就特别卡。调查发现,收起和展开是通过ng-if实现的,而ng-if在显示和隐藏时是要操作DOM将对应的组件重建或删除的。这个组件很大,所以导致了卡顿。解决方案是使用ng-show/ng-hide,这样就不会导致组件的重建和删除。
In Angular, the resolution modifier is used to control how the dependency injector resolves dependencies. There are four resolution modifiers in Angular:
先来给这几个操作符分个类:
@SkipSelf
- 何时开始查找。@Self
, @Host
- 何时停止查找。@Optional
- 找不到时怎么办。这个是最简单了修饰符了,它告诉Angular依赖注入器,如果找不到依赖项,不要抛出错误,而是返回null
。
大家都见过下面这个错误吧,当Angular找不到某个依赖时,就会抛出这个错误:(注意:这种错误一般发生在某个Service没有提供{providedIn: 'root'}
,而且该Service的使用者也没有在providers
数组中提供该Service时。)
1 | ERROR NullInjectorError: R3InjectorError(Environment Injector)[_OrderService -> _OrderService]: |
如果我们在依赖注入的时候加上@Optional()
修饰符,那么Angular就不会抛出错误,而是返回null
。
1 | constructor(@Optional() private orderService: OrderService) { |
注意:上面这种写法,在使用orderService
时,需要判断是否为null
,否则仍然会抛出Cannot read properties of null (reading 'fetchData')
的错误。
我们可以针对orderService
使用?(Optional Chaining Operator),就不会抛出错误了:
1 | constructor(@Optional() private orderService: OrderService) { |
@Self
也比较简单,就是告诉Angular只在当前Component或者Directive中搜索某个依赖项,而不会去父级Component或者Directive中搜索。假设有OrderService,且是providedIn: 'root'
的。
1 | @Injectable({providedIn: "root"}) |
如果我们在注入该Service时,加上@Self()
修饰符,那么Angular只会在当前Component或者Directive中搜索该Service,但是当前Component或者Directive中没有提供该Service时,所以就会抛出错误。
1 | constructor(@Self() private orderService: OrderService) { |
注意观察这个错误和上面错误的区别,这个错误的顶端不再是NullInjector
了,因为我们只在当前Component中查找,所以永远不会到达NullInjector
这一步。而NodeInjector
是当前Component的Injector。
1 | core.mjs:7191 ERROR RuntimeError: NG0201: No provider for _OrderService found in NodeInjector. Find more at https://angular.dev/errors/NG0201 |
@Self
和@Optional
是可以配合使用的,为了修复上面的错误,我们可以这样写,这样就不会报错了。
1 | constructor(@Self() @Optional() private orderService: OrderService) { |
所以,@Self
修饰符的使用场景到底是啥?请继续往下看。
如果一个DOM元素上应用了多个Directive, 那么当第一个Directive提供了某个Service时,后面的Directive就可以通过@Self
来获取这个Service。
下面的代码,假设dir1对应Directive1,dir2对应Directive2,如果Directive1中的providers数组提供了某个Service,那么Directive2中constructor可以使用@Self
来获取Service。
1 | <p dir1 dir2>Hello, world!</p> |
Directive1中提供了OrderService
1 | // Directive1 |
Directive2中使用@Self
来获取OrderService。
1 | // Directive2 |
为什么这样可以呢?因为dir1
和dir2
位于同一个dom元素-p
标签上,Angular会为这个p标签创建一个Injector,dir1
和dir2
会share这个Injector,所以dir2
可以通过@Self
来获取dir1中提供的OrderService。
@Host
修饰符告诉Angular依赖注入器,只在当前Component或者Host元素中搜索某个依赖项。
ng-content
时,Host就是注入依赖的Component。ng-content
时,Host就是投射内容的Component(也就是ng-content
所在的Component)。see here for details.@Host的作用体现在使用ng-content
进行内容投射的时候。下面的例子中,我们有一个ProductComponent,它的模板中有ng-content
,我们将ProductDetailComponent投射到ProductComponent中。- 此时,ProductComponent就是ProductDetailComponent的Host。
1 | <!-- product.component.html --> |
然后我们在ProductComponent中提供OrderService,并在ProductDetailComponent中使用@Host
来获取OrderService。
1 | // product.component.ts |
1 | // product-detail.component.ts |
最后在AppComponent
的模板中使用ProductComponent
1 | <!--app.component.html--> |
这时,app-product
就是app-product-detail
的Host
,如果我们在ProductDetailComponent
中使用@Host
来获取OrderService
,那么Angular会首先在ProductDetailComponent
中查找,结果没找到,然后再去ProductComponent
中查找OrderService,结果找到了。
@SkipSelf
就是查找依赖时不在当前Component或者Directive中查找,而是跳过当前Component或者Directive,去父级Component或者Directive中查找。
所以,@SkipSelf
修饰符的使用场景到底是啥?