Introduction
Angular app build后会生成如下文件结构,其中main/polyfills/runtime文件中的xxx是Angular随机生成的hash值。
1 | dist/ |
今天我们来分析一下runtime.js
文件的内容。(注意,由于Angular编译后的代码是压缩过的,所以以下源码是使用ng server命令本地启动app后在浏览器中查看的。)
1 | /******/ (() => { // webpackBootstrap |
Angular app build后会生成如下文件结构,其中main/polyfills/runtime文件中的xxx是Angular随机生成的hash值。
1 | dist/ |
今天我们来分析一下runtime.js
文件的内容。(注意,由于Angular编译后的代码是压缩过的,所以以下源码是使用ng server命令本地启动app后在浏览器中查看的。)
1 | /******/ (() => { // webpackBootstrap |
字符串操作是JS中非常常见的操作,本文将介绍JS中搜索字符串的几种方法。
在ES5中,我们通常使用indexOf
方法来查找字符串中的子串。indexOf
方法返回给定子串的第一个索引,如果不存在,则返回 -1
。indexOf
方法的语法如下:
1 | indexOf(searchValue) |
includes
方法是ES6引入的,相比indexOf
方法,includes
方法更加直观。includes
方法用于判断一个字符串是否包含另一个字符串。includes
方法的语法如下:
startsWith
也是ES6引入的方法,用于判断一个字符串是否以另一个字符串开头。startsWith
方法的语法如下:
endsWith
也是ES6引入的方法,用于判断一个字符串是否以另一个字符串结尾。endsWith
方法的语法如下:
includes
, startsWith
, endsWith
方法都可以传递第二个参数,表示从指定位置开始搜索。个人感觉没有必要。
今天我们一起研究一下Angular App是如何运行的,今天主要讨论的是在生产环境下,Angular App是如何运行的。既然限定了生产环境,那么就不讨论开发环境相关的东西了,我们直接看编译后的代码如何运行。
首先,我们需要创建一个Angular项目,这里我们使用Angular CLI
来创建一个项目,具体的步骤可以参考这篇文章。
然后,我们使用ng build --prod
命令来构建我们的Angular项目,这个命令会将我们的项目编译成生产环境下的代码。编译完成后,我们会得到一个dist
目录,里面包含了我们的项目的所有文件,如下图所示:
众所周知,index.html
是前端项目的入口文件,我们看看这个文件的内容:
1 | <!DOCTYPE html> |
该文件主要执行以下操作:
styles.ef46db3751d8e999.css
样式文件 - 这是项目打包后的样式文件。runtime.17ade4109dbace19.js
文件 - 这个文件的作用是初始化webpack相关的设置,比如加载模块的方法,为后续的代码运行做准备。polyfills.7488d7e9e1657922.js
文件 - 这个文件主要是加载一些polyfills,用来兼容一些老的浏览器,当然也做一些额外的操作,比如zone.js的monkey patch。main.301f0813b18ba60a.js
文件 - 这个文件是我们的项目的主要代码,我们书写的所有组件,服务等代码都会被编译到这个文件。由于runtime/polyfills/main这三个文件打包后都是混淆的,我们无法查看其代码,所以我们还是借助开发环境,使用ng serve
命令来查看这三个文件的内容。
以下是runtime.js
的内容:
该文件主要是初始化一些Webpack的相关函数,用来为后续加载polyfills
和main
文件做准备。
1 | /******/ (() => { // webpackBootstrap |
注意代码末尾的self["webpackChunkangular_15"]
, 这里angular_15
是我们的项目名称,webpackChunk
是前缀。self
是浏览器Window对象的一个属性。它就指向Window对象本身。详情参考这里:https://developer.mozilla.org/en-US/docs/Web/API/Window/self
其余代码我就不细说了,大伙有空自己看吧,都比较简单。
JS中Object类型转换为Primitive类型的过程,称为ToPrimitive操作。这篇文章主要介绍一下这个过程。
[Symbol.toPrimitive]
方法,如果返回的是Primitive类型,则返回。valueOf
方法,如果返回的是Primitive类型,则返回。toString
方法,如果返回的是Primitive类型,则返回。[]
与{}
注意:[]
和{}
在转换为Primitive类型时,会有很大的不同。
[]
会转换为0
,{}
会转换为NaN
。[]
会转换为''
,{}
会转换为'[object Object]'
。[]
会转换为false
,{}
会转换为true
。之所以造成这么大的区别就是因为[]
和{}
的和toString()
方法的返回值不同。
[]
的toString()
方法返回的是''
,而valueOf()
方法返回的是[]
。{}
的toString()
方法返回的是'[object Object]'
,而valueOf()
方法返回的是{}
。因为[]
能转换为空字符串
,而空字符串
可以转换为0
或false
,所以[]
能转换为0
或false
。而{}
则不能。
Angular更新检测是自动执行的,但是有些情况下我们需要手动触发更新检测,Angular提供以下方法来手动触发更新检测:
detectChanges
The follow cases are when you should use detectChanges
:
detached
from current component.1 | ngDoCheck(): void { |
1 | myFunction(){ |
Note that we can also fix this by wrapping the third party code in setTimeout
or NgZone.run
:
1 | myFunction(){ |
1 | myFunction(){ |
Angular has monkey patched setTimeout
, and will do the change detection after the setTimeout
is finished.
"Expression has changed after it was checked";
markForCheck
The most common case to use markForCheck
is when your component use OnPush
change detection strategy and you want to trigger change detection for the component and its ancestors.
1 | import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core'; |
detectChanges
and markForCheck
detectChanges
triggers change detection for the component and its children
.markForCheck
marks the component and its ancestors
for change detection, but it doesn’t trigger change detection immediately.detectChanges
still work even when change detector is detached
, but markForCheck
doesn’t.在Angular中,我们可以通过useClass
、useValue
、useFactory
、useExisting
等属性来指定依赖注入的提供者,但是这些属性只能指定一个提供者,如果我们需要指定多个提供者,该怎么办呢?这时候就需要使用multi
属性了。
比如下面这个例子:
1 | import { InjectionToken, NgModule } from '@angular/core'; |
对于MY_MULTI_TOKEN
,我们多次使用useValue
属性来指定提供者,然后通过multi: true
来指定这是一个多提供者。Angular最终会将这些值放到一个数组中返回。所以MY_MULTI_TOKEN
最终的值是['Value1', 'Value2']
Angular内置的ROUTES
token也是multi的,从它的定义就可以看出来,它返回的是Route[]
的数组。
1 | export declare const ROUTES: InjectionToken<Route[][]>; |
可以在package.json
中指定前端项目的Node版本,这样其他人在安装依赖时就会自动安装指定版本的Node。
以下配置要求Node版本大于等于14.0.0。
1 | { |
也可以指定更加复杂的版本:
1 | "engines": { |
如果Node版本不符合要求,那么在运行npm install
时会报错:
1 | error angular-realworld@0.0.0: The engine "node" is incompatible with this module. Expected version "^18.13.0 || ^20.9.0". Got "22.12.0" |
关于版本好,这里多说几句,Node.js采用的是SemVer规范,即Semantic Versioning,版本号由三部分组成:major.minor.patch
,例如20.0.0
。
^
- 表示锁定主版本号,例如^20.0.0
表示只能更新到20.x.x
版本。~
- 表示锁定主/次版本号,例如~20.9.0
表示只能更新到20.9.x
版本。搜索数组中的元素是我们在日常开发中经常遇到的问题,本文将介绍 JavaScript 中搜索数组元素的几种方法。
在ES6之前,我们通常使用indexOf
方法来查找数组中的元素的下标。此函数多用于判断数组中是否存在某元素。Array.prototype.indexOf()
方法返回给定元素的第一个索引,如果不存在,则返回 -1
。indexOf
方法的语法如下:
1 | indexOf(searchElement) |
searchElement
:要查找的元素。fromIndex
:从该索引处开始查找。如果该值大于或等于数组的长度,则 indexOf
返回 -1
,表示未找到。1 | const arr = [1, 2, 3, 4, 5]; |
indexOf
的弊端是只能查找有确定值的元素,无法按条件查找,比如查找大于3的元素下标。
ES6引入了find
方法,相比indexOf
方法,find
方法使用一个条件函数来查找数组中的元素。Array.prototype.find()
方法返回数组中满足条件的第一个元素的值。如果找不到责则返回 undefined
。find
一旦找到元素,立即停止查找并返回。find
方法的语法如下:
1 | find(callbackFn) |
callbackFn
函数接收三个参数:
element
:数组中当前正在处理的元素。index
:数组中当前正在处理的元素的索引。array
:调用 find
方法的数组。以下代码查找数组中等于3的元素。
1 | const arr = [1, 2, 3, 4, 5]; |
以下代码查找数组中大于3的元素。
1 | const arr = [1, 2, 3, 4, 5]; |
由此可见find
相比indexOf
更加灵活:
indexOf
只能查找元素的下标,而find
可以查找元素本身。indexOf
只能查找有确定值的元素下标,而find
可以按条件查找。如果想按条件查找元素的下标该怎么办呢?这时候就需要用到findIndex
方法。
Array.prototype.findIndex()
该方法与find
方法类似,只不过它不是返回元素,而是返回元素的下标。找不到则返回 -1
。findIndex
一旦找到一个匹配,立即停止查找并返回。findIndex
方法的语法如下:
1 | findIndex(callbackFn) |
callbackFn
函数接收三个参数:
element
:数组中当前正在处理的元素。index
:数组中当前正在处理的元素的索引。array
:调用 findIndex
方法的数组。1 | const arr = [1, 2, 3, 4, 5]; |
Array.prototype.includes()
方法用来判断一个数组是否包含一个指定的值,如果是返回 true
,否则返回 false
。includes
方法的语法如下:
1 | includes(searchElement) |
searchElement
:要查找的元素。fromIndex
:从该索引处开始查找。如果该值大于或等于数组的长度,则 includes
返回 false
,表示未找到。1 | const arr = [1, 2, 3, 4, 5]; |
注意:includes
方法只能判断特定值,而不能按条件判断,比如判断数组中是否有大于3的元素,include
做不到。
find
和 findIndex
方法是通过回调函数来判断元素是否满足条件。indexOf
使用strict equal ===
来判断元素是否相等。includes
方法是通过 SameValueZero
算法来判断元素是否相等。indexOf
会跳过稀疏数组中的空位。findIndex
和 includes
方法不会
跳过稀疏数组中的空位。undefined
和NaN
的处理方式:indexOf
方法无法正确处理undefined
和 NaN
。1 | [NaN].indexOf(NaN); // output: -1 |
includes
方法可以正确处理undefined
及NaN
。1 | [NaN].includes(NaN); // true |
find
/findIndex
方法可以正确处理undefined
及NaN
吗?这取决于回调函数的具体实现。1 | [NaN].find(x => Number.isNaN(x)); // OK |
includes
方法。find
方法。indexOf
方法。findIndex
方法。ES6以后有了class关键字,可以方便地实现类的继承。但是JavaScript是一门单继承的语言,即一个类只能继承一个类。但是有时候我们需要多继承,这时候我们可以使用混入(mixin)来实现多继承。
Mixin是一种实现多继承的方式,即将多个类的方法混入到一个类中。下面是一个简单的Mixin实现:
1 | function mix(...mixins) { |
1 | class FlyBehavior { |