Jack N @ GitHub

Full stack engineer, focus on: Angular/React, node.js/.Net

0%

CSS实现水平垂直居中

  1. margin: auto;实现绝对定位元素的居中
    .center-vertical{
        width: 100px;
        height: 100px;
        background: orange;
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        margin: auto;
    }
  1. CSS3.0弹性布局
       html,
       body {
         width: 100%;
         height: 100%;
       }
       body {
         display: flex;
         align-items: center;
         justify-content: center; /* 定义body的元素水平居中 */
       }
       .content {
         width: 300px;
         height: 300px;
         background: orange;
       }
    

行内元素有哪些?块级元素有哪些?

  1. 行内元素有:a b span img input select strong
  2. 块级元素有:div ul ol li dl dt dd h1 h2 h3 h4…p
  3. 常见的空元素: <br> <hr> <img> <input> <link> <meta>

CSS3有哪些新特性?

  1. 新增各种CSS选择器 (: not(.input):)
  2. 圆角 (border-radius:8px)
  3. 多列布局 (multi-column layout)
  4. 阴影和反射 (Shadow\Reflect)
  5. 文字特效 (text-shadow)
  6. 文字渲染 (Text-decoration)
  7. 线性渐变 (gradient)
  8. 旋转 (transform)
  9. 缩放,定位,倾斜,动画,多背景



—————- END —————-






======================

前端(Fontend)面试题汇总

1. http 1.1/2.0 新增加的功能

HTTP1.1

  1. 缓存处理,在 HTTP1.0 中主要使用 header 来控制缓存策略。
  2. 带宽优化及网络连接的使用,HTTP1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分, 并且支持断点续传。
  3. 错误通知的管理,在 HTTP1.1 中新增了 24 个错误状态响应码,如 409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
  4. Host 头处理,在 HTTP1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个 IP 地址。HTTP1.1 的请求消息和响应消息都应支持 Host 头域,且请求消息中如果没有 Host 头域会报告一个错误(400 Bad Request)。
  5. 长连接,HTTP 1.1 支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应. (Connection: keep-alive)

HTTP2.0 大幅度的提高了 web 性能,进一步减少了网络的延迟。实现低延迟高吞吐量。

  1. 二进制分帧 (二进制格式编码)
  2. 首部压缩 (header)
  3. 多路复用 (HTTP2.0 可以在共享 TCP 连接的基础上同时发送请求和响应,避免 HTTP 旧版本的队头阻塞问题)
  4. 请求优先级
  5. 服务器推送 (服务端根据客户端的请求,提前返回多个响应,推送额外的资源给客户端。)

2. 用户输入 URL 到浏览器显现给用户页面经过了什么过程

  1. 用户输入 URL,浏览器获取到 URL
  2. 检查缓存,如果有有效缓存,直接加载
  3. 浏览器(应用层)进行 DNS 解析(直接输入 IP 地址既跳过该步骤)
  4. 根据解析出的 IP 地址+端口,浏览器(应用层)发起 HTTP 请求
  5. 请求到达传输层,tcp 协议为传输报文提供可靠的字节流传输服务,它通过三次握手等手段来保证传输过程中的安全可靠。通过对大块数据的分割成一个个报文段的方式提供给大量数据的便携传输。
  6. 服务器端收到发送方的 HTTP 请求之后,进行请求文件资源(如 HTML 页面)的寻找并响应报文
  7. 客户端收到返回内容后,处理 html
  8. 解析 html,生成 dom 树
  9. CSS 解析,为 DOM 中元素加入样式
  10. javascript 解析处理,更改 dom 树
  11. 进行页面渲染
  12. 重排、重绘

3. HTML5 的新特性

  1. 新的内容标签:header nav content footer article 1. aside
  2. 更好的单元格体系:
  3. 音频、视频 API:video radio
  4. 画布(Canvas) API
  5. 地理(Geolocation) API
  6. 网页存储(Web storage) 1. API:localStorage,sessionStorage
  7. 拖拽释放(Drag and drop) API

4. 对浏览器内核的理解

主要分成两部分:渲染引擎(layout engine 或 Rendering Engine)和 JS 引擎。

  1. 渲染引擎:负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入 CSS 等),以及计算网页的显示方式,然后会输出至显示器或打印机。浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核。
  2. JS 引擎则:解析和执行 javascript 来实现网页的动态效果。最开始渲染引擎和 JS 引擎并没有区分的很明确,后来 JS 引擎越来越独立,内核就倾向于只指渲染引擎。

5. cookies,sessionStorage 和 localStorage

cookie 是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。
cookie 数据始终在同源的 http 请求中携带(即使不需要),记会在浏览器和服务器间来回传递。
sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存。

存储大小:
cookie 数据大小不能超过 4k。
sessionStorage 和 localStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或更大。

有期时间:
localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;
sessionStorage 数据在当前浏览器窗口关闭后自动删除。
cookie 设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭

6. 如何实现浏览器内多个标签页之间的通

WebSocket、SharedWorker;也可以调用 localstorge、cookies 等本地存储方式;

7. script标签中的async、defer属性的作用

  1. Async: 可选属性。表示应该立即下载脚本,但不应妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本。
  2. Defer: 可选属性。标识脚本可以延迟到文档完全被解析和显示之后再执行。

2021 javascript 面试题汇总

1. this 都可以是哪些内容

对 C#、Java 等语言,this 就是当前对象,但是 javascript 不是,简单来说:

  • 全局 this 是 window;
  • 函数 this 是调用者;
  • 构造函数的 this 是 new 之后的新对象,
  • call 和 apply bind 的 this 第一个参数

2. 原型链, prototype

  • 函数对象都包含 prototype 属性(函数的原型对象),其作用就是让该函数所实例化的对象们都可以找到公用的属性和方法;
  • constructor 属性的含义就是指向该对象的构造函数
  • __proto__和 constructor 属性是对象所独有的;prototype 属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__和 constructor 属性。
  • JavaScript 的每个对象都继承另一个父级对象,父级对象称为原型 (prototype) 对象。
  • 每一个实例对象都有一个私有属性proto指向其构造函数的原型对象 prototype;该原型对象也会作为实例对象有一个私有属性proto,层层向上直到一个对象的原型对象值为 null。
  • 当访问一个对象的属性或方法时,js 引擎会先查找该对象本身是否包含,如果没有,会去该对象的proto属性所指向的原型对象上找,如果没有,会继续向上一层找,直到某个对象的proto值为 null,这就是原型链。
  • 每个构造函数都有一个 prototype 属性,指向另外一个对象,说明整个对象所有的属性和方法都会被构造函数所拥有。

3. new 操作符都做了什么

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一。

new Object()举例:

  1. 创建一个新对象
  2. 把新对象的原型(__proto__)指向构造函数的 prototype
  3. 把构造函数里的 this 指向新对象
  4. 返回这个新对象

4. Javascript 的内存回收机制

Javascript 内嵌了垃圾收集器,用来跟踪内存的分配和使用,以便当分配的内存不再使用时,自动释放它。垃圾收集器会按照固定的时间间隔,或代码执行中预定的收集时间,周期性地执行这一操作。
垃圾收集器必须跟踪哪个变量有用哪个变量无用,对于不再有用的变量打上标记,以备将来收回其所占用的内存。用于标识无用变量的策略通常有标记清除和引用计数两种。不同浏览器采用的策略不完全一致。

5. 内存泄漏的主要原因

  • 缓存
  • 队列消费不及时
  • 作用域未释放

6. Javascript 性能优化

  1. 针对 js 方面的前端性能优化,可以 1)使用 ansyc/defer 加载;2)加载时,放到</body>之前;3) 合并 JS 文件,并进行最小化处理;4)缓存;5)使用 CDN 网络;6)js 的 HTTP 压缩
  2. 删除未使用的功能性代码以及与之相关的代码, 多余的依赖库,被滥用的 npm 包。
  3. 减少作用域链上的查找次数(减少循环中的活动)。比如 for 循环,把 Array.length 赋值给一个变量,而不是每次比较都直接使用 length 属性;对页面元素进行操作时,取出复制给一个变量,每次操作都对这个变量进行操作,而不是每次操作都再重复取一下这个元素;
  4. 闭包导致的内存泄露。闭包可以保证函数内的变量安全,可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会被自动清除。我们需要手动销毁内存中的变量。(赋值为 null);
  5. 尽量少用全局变量,尽量使用局部变量。
  6. 减少不必要的变量
  7. 类型转换:把数字转换成字符串使用 number + “” 。 性能对比: (“” + ) > String() > .toString() > new String()
  8. 对字符串进行循环操作,譬如替换、查找,应使用正则表达式。因为本身 JavaScript 的循环速度就比较慢,而正则表达式的操作是用 C 写成的语言的 API,性能很好。
  9. 浮点数转换成整型使用 Math.floor()或者 Math.round()。parseInt()是用于将字符串转换成数字,Math 是内部对象,所以 Math.floor()其实并没有多少查询方法和调用的时间,速度是最快的。
  10. 使用 classname 代替大量的内联样式修改。
  11. 循环遍历,尽量少用 for in, 虽然代码易读,但性能很差。 尤其是遍历属性数量未知的情况下,少用。

7. ES6/ES7/ES8/… 新增的功能

7.1. ES6 (2015)

  • 模块化
  • 箭头函数
  • 函数参数默认值
  • 模板字符串
  • 解构赋值
  • 延展操作符
  • 对象属性简写
  • Promise
  • Let与Const

7.2. ES7(2016)

  • 数组includes()方法,用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回true,否则返回false。
  • **指数运算符: a ** b指数运算符,它与 Math.pow(a, b)相同。

    7.3. ES8(2017)

  • async/await
  • Object.values()
  • Object.entries()
  • String padding: padStart()和padEnd(),填充字符串达到当前长度
  • 函数参数列表结尾允许逗号
  • Object.getOwnPropertyDescriptors()
  • ShareArrayBuffer和Atomics对象,用于从共享内存位置读取和写入

    7.4. ES9(2018)

  • 异步迭代
  • Promise.finally()
  • Rest/Spread 属性: ES2015引入了Rest参数和扩展运算符。三个点(…)仅用于数组。Rest参数语法允许我们将一个不定数量的参数表示为一个数组。
  • 正则表达式命名捕获组
  • 正则表达式反向断言

7.5. ES10新特性(2019)

  • 行分隔符(U + 2028)和段分隔符(U + 2029)符号现在允许在字符串文字中,与JSON匹配
  • 更加友好的 JSON.stringify
  • 新增了Array的flat()方法和flatMap()方法
  • 新增了String的trimStart()方法和trimEnd()方法
  • Object.fromEntries()
  • Symbol.prototype.description
  • String.prototype.matchAll
  • Function.prototype.toString()现在返回精确字符,包括空格和注释
  • 简化try {} catch {},修改 catch 绑定
  • 新的基本数据类型BigInt
  • globalThis
  • import()
  • Legacy RegEx
  • 私有的实例方法和访问器

7.6. ES11(2020)

  • Nullish coalescing Operator(空值处理)
  • import() 按需导入

7.7. ES12(2021)

  • string增加replaceAll
  • Promise.any, 收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise
  • WeakRefs, 使用WeakRefs的Class类创建对对象的弱引用(对对象的弱引用是指当该对象应该被GC回收时不会阻止GC的回收行为)
  • 数字分隔符
  • 逻辑运算符和赋值表达式

8. === VS ==

1
2
3
4
5
100 == "100"; // true
0 == ""; // true
0 == false; // true
false == ""; // true
null == undefined; // true

很有意思吧, 看看这两个,是等效的:

1
2
obj == null;
obj === null || obj === undefined;

9. 闭包

在 JavaScript 中,实现外部作用域访问内部作用域中变量的方法叫做闭包(closure)。这得益于高阶函数的特性:函数可以作为参数或者者返回值;

当一个内部函数被调用,就会形成闭包,闭包就是能够读取其他函数内部变量的函数,就是一个函数去访问了另外一个函数的中的变量的函数。

闭包作用:局部变量无法共享和长久的保存,而全局变量可能造成变量污染,所以我们希望有一种机制既可以长久的保存变量又不会造成全局污染。延伸变量的作用范围。

闭包特点:占用更多内存;不容易被释放

闭包用法:变量既想反复使用,又想避免全局污染如何使用?

  1. 定义外层函数,封装被保护的局部变量。
  2. 定义内层函数,执行对外部函数变量的操作。
  3. 外层函数返回内层函数的对象,并且外层函数被调用,结果保存在一个全局的变量中。

e.g.

1
2
3
4
5
6
7
function outerMethod() {
var a = "some value";
var innerMethod = function () {
console.log(a + "!");
};
innerMethod();
}

10. js 里的作用域是什么样子的?

大多数语言里边都是块作作用域,以{}进行限定,js 里边不是.js 里边叫函数作用域,就是一个变量在全函数里有效.比如有个变量 p1 在函数最后一行定义,第一行也有效,但是值是 undefined.

11. let 和 var 的区别

  • var 声明变量可以重复声明,而 let 不可以重复声明,属于 TDZ 暂时性死区问题
  • 作用域不同,var 是函数作用域,而 let 是块级作用域;
  • var 可以在声明的上面访问变量,而 let 不存在变量提升;

12. 对 async 和 await 的理解

  1. async…await 是基于 promise 的 generator 语法糖,是用来解决异步的,它用来等待 promise 的执行结果,常规函数使用 await 没有效果;
  2. async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数
  3. 当函数执行的时候,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句;

13. promised 的三种状态

promise 有三种状态:pending/reslove/reject 。pending 就是未决,resolve 可以理解为成功,reject 可以理解为拒绝。

14. async/await 的优点

1)方便级联调用: 即调用依次发生的场景;

2)同步代码编写方式: Promise 使用 then 函数进行链式调用,一直点点点,是一种从左向右的横向写法;async/await 从上到下,顺序执行,就像写同步代码一样,更符合代码编写习惯;

3)多个参数传递: Promise 的 then 函数只能传递一个参数,虽然可以通过包装成对象来传递多个参数,但是会导致传递冗余信息,频繁的解析又重新组合参数,比较麻烦;async/await 没有这个限制,可以当做普通的局部变量来处理,用 let 或者 const 定义的块级变量想怎么用就怎么用,想定义几个就定义几个,完全没有限制,也没有冗余工作;

4)同步代码和异步代码可以一起编写: 使用 Promise 的时候最好将同步代码和异步代码放在不同的 then 节点中,这样结构更加清晰;async/await 整个书写习惯都是同步的,不需要纠结同步和异步的区别,当然,异步过程需要包装成一个 Promise 对象放在 await 关键字后面;

5)基于协程: Promise 是根据函数式编程的范式,对异步过程进行了一层封装,async/await 基于协程的机制,是真正的“保存上下文,控制权切换……控制权恢复,取回上下文”这种机制,是对异步过程更精确的一种描述;

6)async/await 是对 Promise 的优化: async/await 是基于 Promise 的,是进一步的一种优化,不过在写代码时,Promise 本身的 API 出现得很少,很接近同步代码的写法;

15. async/await 的写法

1
2
3
4
5
6
7
8
9
async function testAsync() {
return "hello async";
}

// 调用时
async function test() {
var value = await testAsync();
console.log(value);
}

16. apply、call 和 bind 的区别

这三者都是用来改变函数的 this 对象的指向的(也就是说要改变函数运行时的 context 即上下文),第一个参数都是 this 要指向的对象。

不同之处:

  1. call 接受连续参数,apply 接受数组参数。
  2. bind 功能和 call apply 差不多,区别在于 bind 返回的是一个改变 this 指向的新函数,这个函数不是立即执行函数(call apply 两个立即执行!)

17. 深拷贝的原理

操作的是值,取出对象的值,放到一个新的{},修改时不会影响到原数据;要完成对象的深拷贝需要使用递归遍历所有对象的属性进行赋值,也可以使用 JSON.stringfy 和 JSON.parse 操作。

18. set 和 Map 的区别

  1. Set 是一种类似数组的集合类型,它与数组不同的是,不允许存在重复数据;常用操作方法有:add,delete,has,clear 等;遍历使用 forEach;
  2. Map 是一种类似对象的集合类型,它与对象不同的是,key 可以接受对象类型,常用的操作方法有:set,get,has,delete 等;遍历使用 forEach

19. JS 实现双向绑定 (vue/angular 双向绑定的原理)

vue.js 采用数据劫持(Proxy 模式)结合发布者-订阅者模式(PubSub 模式)的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
Object.defineProperty( )内还包含一对儿 getter 和 setter 函数,它们被称作这个对象的访问器属性,这两个函数都不是必须的,只是在读取访问对象属性时,会调用 getter 函数,这个函数负责返回有效值,在写入对象属性时,会调用 setter 函数并传入新值,这个函数负责决定如何处理数据。

Angular, 采用脏检查的方式。基于 zone.js,重写 setTimeout, httpRequest 等方法,触发脏检查, 更新 dom 节点。

20. var 和 let 的区别

  1. 作用域不同, var 是函数作用域,let 是块作用域。
  • 在函数中声明了 var,整个函数内都是有效的,比如说在 for 循环内定义的一个 var 变量,实际上其在 for 循环以外也是可以访问的
  • 而 let 由于是块作用域,所以如果在块作用域内定义的变量,比如说在 for 循环内,在其外面是不可被访问的,所以 for 循环推荐用 let
  1. let 不能在定义之前访问该变量,但是 var 可以。 (var 可以提升变量作用域)
  2. let不能被重新定义,但是var是可以的。(重新定义var,执行时会忽略)

    21. Javascript 如何实现继承?

1、构造继承
2、原型继承: Child.prototype = new Parent();
3、实例继承
4、拷贝继承

22. 变量提升

js 引擎首先在读取 js 代码时默认执行 2 个步骤:

  1. 解释(通篇扫描所有 js 代码,然后把所有声明(变量申明、函数声明)提升到对应作用域顶端)
  2. 执行(执行逻辑操作)
1
2
3
4
5
6
7
8
// 例1.
console.log(b); // 输出 ReferenceError: b is not defined
b = 2;

// 例2.
console.log(b); // 输出 undefined
var b = 2;
console.log(b); // 输出 2

23. 函数提升

首先要知道函数定义有两种方式的,一种是函数定义表达式,一种是函数声明语句。

1
2
3
4
5
6
7
// 函数定义表达式
var f = function (){
};

// 函数声明语句
function f(){
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 提升测试
// 例1.
f();
function f() {
console.log("test");
}
//输出 test

// 例2.
f();
var f = function () {
console.log("test");
};

//输出 Uncaught TypeError: f is not a function

// 例3
console.log(f);
var f = 10;
console.log(f);
function f() {
console.log("fff");
}
// 输出
// [Function: f]
// 10
// 10

// 例4
var a = 1;
function foo() {
a = 10;
console.log(a); // 输出:10
return;
function a() {} // 看似无用, 实际上在foo的作用域内重新声明了'a',和外面的a不一样了
}
foo();
console.log(a); // 输出:1
//输出:
// 10
// 1

重点:

  1. 函数提升针对是函数声明语句而言,其次变量提升只提升变量名而函数提升会提升整个函数体
  2. 优先级,函数提升会在变量提升的上面

24. 作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 例1 块级作用域, 不适合变量提升
// es6允许块级作用域的任意嵌套。外层作用域无法读取内层作用域的变量。
// 内层作用域可以定义外层作用域的同名变量。

var v = "hello";
if (true) {
console.log(v);
var v = "world";
}
//输出 hello

var v = "hello";
if (true) {
console.log(v);
var v = "world";
var z = "!";
}
console.log(v, z);
//输出 :
// hello
// world !

// 例2 闭包,变量提升
var v = "hello";
(function () {
console.log(v);
var v = "world";
})();
//输出 undefined

// 例 3, 块级作用域内let和const命令所声明的变量,只在命令所在的代码块内有效。
if (true) {
let a = 10;
var b = 1;
}
console.log(a); //ReferenceError: a is not defined.
console.log(b); // 1

// 例4 let, var与作用域、变量提升
if (true) {
console.log(a); //ReferenceError: a is not defined.
console.log(b); // 1

let a = 10;
var b = 1;
}
console.log(a); //ReferenceError: a is not defined.
console.log(b); // 1

25. 实现一个随机排序的程序

1
2
3
4
5
6
7
8
9
10
11
function randomSort() {
return Math.random() - 0.5;
}
let list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
list.sort(randomSort);
console.log("after random sort: ", list.join(","));

// ES6 箭头函数写法
let list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
list.sort(() => Math.random() - 0.5);
console.log("after random sort: ", list.join(","));

26. 实现一个函数,传入一个字符串,得到使用次数最多的字符,以及数量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function getMostUsedChar(sourceString) {
let length = sourceString.length;
let counter = {};
for (var i = 0; i < length; i++) {
let currentChar = sourceString[i];
if (counter[currentChar]) {
counter[currentChar]++;
} else {
counter[currentChar] = 1;
}
}

let maxCount = 0;
let maxChar = "";
for (const key in counter) {
if (counter[key] >= maxCount) {
maxChar = key;
maxCount = counter[key];
}
}

console.log(`most used char: ${maxChar}, count: ${maxCount}`);
}

getMostUsedChar("fjiwejfal33232jkj3fjfie");

参考:

1. nodejs 有哪些特点?

是单线程的,但是有很高的可扩展性,使用 JavaScript 作为主流编程语言。使用的是非阻塞IO,异步处理机制和事件驱动。处理高效。

擅长 IO 密集型业务业务处理,不擅长 CPU 密集型业务;

2. 什么是事件循环 Event Loop

在进程启动动时,Node 会创建一个类似于 while(true)的循环,每执行一次循环的过程我们称之为 Tick。每个 Tick 的过程就是查看是否有事件等待处理,如果有,就取出时间相关的回调函数。如果存在相关联的回调函数,就执行它们。然后进入下一个循环;如果不再有事件待处理,就退出进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   ┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
  1. timers: 这个阶段执行 setTimeout()和 setInterval()设定的回调。
  2. I/O callbacks: 执行几乎所有的回调,除了 close 回调,timer 的回调,和 setImmediate()的回调。
  3. idle, prepare: 仅内部使用。
  4. poll: 获取新的 I/O 事件;node 会在适当条件下阻塞在这里。
  5. check: 执行 setImmediate()设定的回调。
  6. close callbacks: 执行比如 socket.on(‘close’, …)的回调。

在底层, Node 是通过 libuv 来实现多线程的。Libuv库负责 Node API 的执行。它将不同的任务分配给不同的线程,形成一个事件循环, 以异步的方式将任务的执行结果返回给 V8 引擎。

另:浏览器下的 Event Loop 如下:

  1. 所有任务都在js执行线程上执行,形成一个执行栈(Execution Context Stack)
  2. 在js执行线程之外还存在一个任务队列(Task Queen),系统把异步任务放到任务队列中,然后主线程继续执行后续的任务
  3. 一旦执行栈中所有的任务执行完毕,系统就会读取任务队列。如果这时异步任务已结束等待状态,就会从任务队列进入执行栈,恢复执行
  4. js执行线程不断重复上面的第三步

3. 在每个 tick 的过程中,如何判断是否有事件需要处理呢?

  1. 每个事件循环中有一个或者多个观察者,而判断是否有事件需要处理的过程就是向这些观察者询问是否有要处理的事件。
  2. 在 Node 中,事件主要来源于网络请求、文件的 I/O 等,这些事件对应的观察者有文件 I/O 观察者,网络 I/O 的观察者。
  3. 事件循环是一个典型的生产者/消费者模型。异步 I/O,网络请求等则是事件的生产者,源源不断为 Node 提供不同类型的事件,这些事件被传递到对应的观察者那里,事件循环则从观察者那里取出事件并处理。

4. 什么是回调函数?

回调函数是指用一个函数作为参数传入另一个函数,这个函数会被在某个时机调用。

5. node.js 计时器 promise.then, process.nextTick, setTimeout,setImmediate 执行顺序

首先了解 2 个概念

  • macro-task(宏任务): script ,setTimeout, setInterval, setImmediate, I/O, UI rendering.Event Loop 在每个阶段执行的任务;
  • micro-task (微任务): process.nextTick, Promise,Object. fvEvent Loop 在每个阶段之间执行的任务 observe,MutationObserver

对于 node.js, micro-task 的任务优先级高于 macro-task 的任务优先级,所以微任务普遍优先于宏任务的执行。

另一种优先级顺序,是“观察者优先级”, 在每次轮训检查中,各观察者的优先级分别是:idle观察者 > I/O观察者 > check观察者。

  • idle 观察者:process.nextTick
  • I/O 观察者:一般性的 I/O 回调,如网络,文件,数据库 I/O 等
  • check 观察者:setTimeout > setImmediate (setTimeout(()=>{},0)优先于 setImmediate 执行);
  • 总结
  1. 同步代码执行顺序优先级高于异步代码执行顺序优先级;
  2. new Promise(fn)中的 fn 是同步执行;
  3. process.nextTick()>Promise.then()>setTimeout>setImmediate。

6. next tick 和 setImmediate 的区别是什么?

  • NextTick 会等待当前的 event 执行完成或者下一轮儿事件循环到达再执行。

  • SetImmediate, 会在下一轮的事件循环中,执行回调并且返回当前的循环来做读写操作.

7. node.js 模块加载机制

Nodejs 遵循 commonjs 规范的模块加载机制,使用 require 加载文件,使用 exports 或 module.exports 导出文件

8. 什么是 globals?

有三个 global 的关键字。

  1. Global 代表的是最上层的命名空间,用来管理所有其他的全局对象。
  2. Process 是一个全局对象,可以把异步函数转化成异步回调, 它可以在任何地方被访问,它主要是用来返回系统的应用信息和环境信息.
  3. Buffer, 是用来处理二进制数据的类.

9. 什么是 EventEmitter?

EventEmitter 是 node 中一个实现观察者模式的类,主要功能是监听和发射消息,用于处理多模块交互问题.

主要用于: 1) 模块间传递消息 2) 回调函数内外传递消息 3) 处理流数据,因为流是在 EventEmitter 基础上实现的. 4) 观察者模式发射触发机制相关应用

10. process 有哪些常用方法?

process.stdin, process.stdout, process.stderr, process.on, process.env, process.argv, process.arch, process.platform, process.exit

11. 解释一下什么是 reactor pattern。

reactor 设计模式是 event-driven architecture 的一种实现方式,处理多个客户端并发的向服务端请求服务的场景。每种服务在服务端可能由多个方法组成。reactor 会解耦并发请求的服务并分发给对应的事件处理器来处理。

Reactor pattern 主要是非阻滞的 i/o 操作。提供一个回调函数来关联 io 操作。io 请求完成以后会不会提交给 demultiplexer, 这是一个通知接口用来处理并发性的非阻滞的 io 操作,这个功能是通过查询一个 event loop 来实现的.

12. 什么是错误优先的回调函数

node.js 广泛使用异步编程,而异步中的异常很难在主程序中捕获。为此,我们将异步中的异常、错误,通过回调函数传递给主程序。

错误优先的回调函数用于传递错误和数据。第一个参数始终应该是一个错误对象, 用于检查程序是否发生了错误。其余的参数用于传递数据。

13. 如何避免回调地狱

  1. 模块化:将回调函数分割为独立的函数
  2. 使用 Promise/ansyc (ES6)
  3. 使用 yield 来计算生成器或 Promise

14. 什么是 Promise?

Promise 可以帮助我们更好地处理异步操作。采用类似同步结构的代码,实现异步处理,同时避免回调地狱。

15. 使用 NPM 有哪些好处?

通过 NPM,你可以安装和管理项目的依赖,并且能够指明依赖项的具体版本号。 对于 Node 应用开发而言,你可以通过 package.json 文件来管理项目信息,配置脚本, 以及指明项目依赖的具体版本。

16. 什么是 Stub?

Stub 是用于模拟一个组件或模块的函数或程序,在测试用例中很常用。简单的说,你可以用 Stub 去模拟一个方法,从而避免调用真实的方法, 使用 Stub 你还可以返回虚构的结果。你可以配合断言使用 Stub。

17. 什么是测试金字塔?

测试金字塔指的是: 测试用例从下到上,包括单元测试、集成测试、端到端测试;当我们在编写测试用例时,底层的单元测试应该远比上层的端到端测试要多。

18. Node 模块机制

Node 中,每个文件模块都是一个对象,所有的模块都是 Module 的实例。

19. Node.js require 的模块加载机制

1、先计算模块路径
2、如果模块在缓存里面,取出缓存;如果不存在生成模块实例,存入缓存
3、加载模块
4、输出模块的 exports 属性

20. V8 的垃圾回收机制

在 V8 中,主要将内存分为新生代和老生代两代。新生代中的对象存活时间较短的对象,老生代中的对象存活时间较长,或常驻内存的对象。
新生代中的对象主要通过 Scavenge 算法进行垃圾回收。这是一种采用复制的方式实现的垃圾回收算法。它将堆内存一份为二,分为 2 个 semispace。在这两个 semispace 空间中,只有一个处于使用中,另一个处于闲置状态。处于使用状态的 semispace 空间称为 From 空间,处于闲置状态的空间称为 To 空间。

  1. 当开始垃圾回收的时候,会检查 From 空间中的存活对象,这些存活对象将被复制到 To 空间中,而非存活对象占用的空间将会被释放。完成复制后,From 空间和 To 空间发生角色对换。
  2. 应为新生代中对象的生命周期比较短,就比较适合这个算法。
  3. 当一个对象经过多次复制依然存活,它将会被认为是生命周期较长的对象。这种新生代中生命周期较长的对象随后会被移到老生代中。
    老生代主要采取的是标记清除的垃圾回收算法。与 Scavenge 复制活着的对象不同,标记清除算法在标记阶段遍历堆中的所有对象,并标记活着的对象,只清理死亡对象。活对象在新生代中只占叫小部分,死对象在老生代中只占较小部分,这是为什么采用标记清除算法的原因。

标记清楚算法的问题
主要问题是每一次进行标记清除回收后,内存空间会出现不连续的状态

  1. 这种内存碎片会对后续内存分配造成问题,很可能出现需要分配一个大对象的情况,这时所有的碎片空间都无法完成此次分配,就会提前触发垃圾回收,而这次回收是不必要的。
  2. 为了解决碎片问题,标记整理被提出来。就是在对象被标记死亡后,在整理的过程中,将活着的对象往一端移动,移动完成后,直接清理掉边界外的内存。

21. child-process

node 是异步非阻塞的,这对高并发非常有效.可是我们还有其它一些常用需求,比如和操作系统 shell 命令交互,调用可执行文件,创建子进程进行阻塞式访问或高 CPU 计算等,child-process 就是为满足这些需求而生的.child-process 顾名思义,就是把 node 阻塞的工作交给子进程去做.

22. 创建子进程的方法有哪些,简单说一下它们的区别

创建子进程的方法大致有:

  1. spawn(): 启动一个子进程来执行命令
  2. exec(): 启动一个子进程来执行命令,与 spawn()不同的是其接口不同,它有一个回调函数获知子进程的状况
  3. execFlie(): 启动一个子进程来执行可执行文件
  4. fork(): 与 spawn()类似,不同电在于它创建 Node 子进程需要执行 js 文件
  5. spawn()与 exec()、execFile()不同的是,后两者创建时可以指定 timeout 属性设置超时时间,一旦创建的进程超过设定的时间就会被杀死
  6. exec()与 execFile()不同的是,exec()适合执行已有命令,execFile()适合执行文件。

(exec 可以用操作系统原生的方式执行各种命令,如管道 cat ab.txt | grep hello; execFile 是执行一个文件; spawn 是流式和操作系统进行交互; fork 是两个 node 程序(javascript)之间时行交互.)

23. 怎样充分利用多个 CPU?

一个 CPU 运行一个 node 实例。 (Child_Process)

24. 有哪些方法可以让 node 程序遇到错误后自动重启?

Linxu: 1) runit 2) forever 3) nohup npm start &
Winwow: Windows service(auto restart)

25. 关于 Express 框架

25.1. express 生成器的作用是什么?

通过应用生成器工具 express-generator 可以快速创建一个应用的框架, 包含一整套配置好的服务器配置, 文件和文件夹等, 包括静态资源的暴露等, 包括路由的配置, 和模板引擎配置, 以及 404 的处理,异常处理等;

25.2. express 优点是什么?

Express 的优点是线性逻辑:路由和中间件完美融合,通过中间件形式把业务逻辑细分,简化,一个请求进来经过一系列中间件处理后再响应给用户,再复杂的业务也是线性了,清晰明了。

25.3. 什么是中间件

  • 中间件是可以访问请求对象,响应对象以及 next 应用程序请求-响应周期中的函数,使用 app.use()来使用/定义中间件;
  • Express 是一个自身功能极简,完全是路由和中间件构成一个 web 开发框架;
  • 从本质上来说,一个 Express 应用就是在调用各种中间件。封装了一些或许复杂但肯定是通用的功能, 非内置的中间件需要通过安装后,require 到文件就可以运行。

25.4. express 缺点是什么?

Express 是基于 callback 来组合业务逻辑。Callback 有两大硬伤,一是不可组合,二是异常不可捕获

25.5. ejs 作用是什么?

EJS 是一个 Javascript 模板库, 用来从 JSON 数据中生成 HTML 文件。其他模板库(模板引擎如 jade/pug);

25.6. 什么是后端渲染项目?

浏览器请求静态网页资源, 服务器端会在后端把数据渲染到 HTML 页面上, 再把 html 文件内的字符串一起返回给浏览器进行展示的一种手段, Nodejs 中体现为 ejs 和 pug 模板引擎;

session 是区别于数据库存在的一种服务器临时存储技术, 它主要存储一些无需持久化的数据, 比如临时的登录状态信息等

cookie 是存在于浏览器上的一种浏览器本地存储的方式, 同域名下的 cookie 不同标签页可以共享, 默认过期时间是浏览器关闭时, 而且在进行 http 请求时, 会自动带上浏览器全部的 cookie 发给后台, 后台也可以获取 cookie, 设置可以在响应时, 想浏览器中设置 cookie。

25.8. 跨域(CORS)是什么, 如何解决跨域?

当 ajax 请求所在域名或接口和请求目标 url 的域名或接口, 有一个不同, 即发生了跨域请求, 浏览器会阻止这次 ajax 请求.

解决跨域请求的方法:

  1. 让后台(API)开启跨域支持;
  2. 通过iframe
  3. 使用 jsonp 方式处理跨域(需要服务器端支持);
  4. 反向代理(API Gateway),UI/API 配置到同一个API Gateway,浏览器端看到的结果就是在同一个域了;
  5. UI有编程能力(node.js/ asp.net)的话,可以代理其他站点的API

25.9. express4 中 app 和 router 的区别

app 可以认为是全局的,方便注入中间件;router 主要针对具体的 routing,除了注入中间件,具体处理 routing 对应的核心业务逻辑;
app 级路由不方便模块化封装,而且 app.js 文件没有路由代码,路由代码在 router 文件中,而且 router 方便模块化封装

25.10. Cookies 如何防范 XSS 攻击?

XSS(Cross-Site Scripting,跨站脚本攻击)是指攻击者在返回的 HTML 中插入 JavaScript 脚本。为了减轻这些攻击,需要在 HTTP 头部配置 set-cookie:

  • HttpOnly - 这个属性可以防止 cross-site scripting,因为它会禁止 Javascript 脚本访问 cookie。

  • secure - 这个属性告诉浏览器仅在请求为 HTTPS 时发送 cookie。

结果应该是这样的: Set-Cookie: sid=; HttpOnly. 使用 Express 的话,cookie-session 默认配置好了。

25.11. Koa 和 Express 的对比

  1. Express 是一个完整的 nodejs 应用框架。Koa 是由 Express 团队开发的,但是它有不同的关注点。Koa 致力于核心中间件功能。nodejs 中间件是访问请求对象(req)和响应对象(res)的例程。 Express 包含一个完整的应用程序框架,具有路由和模板等功能。
  2. Koa 对 Express 进行了扩展,并充分利用了 ES7 新的语法。Koa 的 Context 对象是对 Express 核心请求和应答对象的扩展,另外利用 async/await 来消除回调(callback)陷阱。
  3. Koa 的中间件的设计是洋葱模型。
  4. Koa 不是立即响应,是整个中间件处理完成在最外层进行了响应,而 Express 则是立即响应

26. KOA

26.1. KOA 洋葱模型

中间件执行就像洋葱一样,最早 use 的中间件,就放在最外层。处理顺序从左到右,左边接收一个 request,右边输出返回 response。
一般的中间件都会执行两次,调用 next 之前为第一次,调用 next 时把控制传递给下游的下一个中间件。当下游不再有中间件或者没有执行 next 函数时,就将依次恢复上游中间件的行为,让上游中间件执行 next 之后的代码。



—————- END —————-

1. 概要

大多数 Angular 短语都是日常用语或计算机术语,但是在 Angular 体系中,它们有特别的含义。

1. 概要

预编译 (ahead-of-time, AOT)
即时编译 (just-in-time, JIT)
Angular元素(element, Angular Element)
注解(Annotation): 注解也叫装饰器;
装饰器(decorator | decoration): 注解也叫装饰器;
应用外壳(app-shell)
架构(Architect)
指令 directives
属性型指令(attribute directives)
绑定 (binding)
启动/引导 (bootstrap)
构建器(Builder)
大小写类型(case types)
小驼峰形式(camelCase):小驼峰(也叫标准驼峰)形式的第一个字母要使用小写形式。
大驼峰形式(UpperCamelCase)或叫帕斯卡形式(PascalCase)
中线形式(dash-case)或叫烤串形式(kebab-case)
下划线形式(underscore_case)或叫蛇形形式(snake_case)
大写下划线形式(UPPER_UNDERSCORE_CASE)或叫大写蛇形形式(UPPER_SNAKE_CASE)
变更检测(change detection)
类装饰器(class decorator)
类字段装饰器(class field decorator)
集合(collection)
命令行工具,命令行客户端(CLI, Angular CLI)
组件 (component)
配置(configuration)
内容投影 (content projection)
自定义元素(Custom element):也叫 Web Component
数据绑定 (data binding)
可声明对象(declarable)
装饰器(decorator | decoration)
依赖注入(dependency injection)
DI 令牌(Token)
差异化加载 (differential loading)
指令 (directive)
领域特定语言(domain-specific language, DSL)
动态组件加载(dynamic component loading)
急性加载(Eager Loading)
惰性加载(Lazy loading)
元素(Element, Angular Element)
入口点(Entry Point)
表单控件(form control)
表单模型(form model)
表单验证(form validation)
不可变性(immutability)
可注入对象(injectable)
注入器 (injector)
输入属性 (input)
输出属性 (output)
插值 (interpolation)
常春藤引擎(Ivy)
惰性加载(Lazy loading)
库(Library)
生命周期钩子(Lifecycle hook)
模块 (module)
ngcc (Angular 兼容性编译器)
npm 包 (npm package)
ngc (ngc 是一个 TypeScript 到 JavaScript 的转译器)
可观察对象(Observable)
观察者(Observer)
管道(pipe)
平台(platform)
腻子脚本(polyfill)
项目(project)
提供者 (provider)
响应式表单 (reactive forms)
解析器(resolver)
路由守卫 (route guard)
路由器 (router)
路由出口(router outlet)
路由组件 (routing component)
规则(rule)
原理图(schematic)
Schematics CLI (Schematics 自带了一个命令行工具)
范围化包 (scoped package)
服务端渲染 (server-side rendering)
服务 (service)
结构型指令(Structural directives)
订阅者(Subscriber)
目标 (target)
模板 (template)
模板驱动表单(template-driven forms)
模板表达式(template expression)
模板引用变量 (template reference variable)
令牌(Token)
转译(transpile)
目录树(tree)
TypeScript 配置文件
单向数据流 (unidirectional data flow)
视图 (view)
视图引擎(View Engine)
视图树(View hierarchy)
Web 组件 (web component)
工作空间(Workspace)
工作空间配置(Workspace configuration)
区域 (zone)



—————- END —————-






======================

1. 概要

众所周期,javascript是一个很老的语言,起起伏伏,逐渐成为流行语言(主流语言),说白了,也是因为早期各个浏览器(ie/firefox/sarfari/chrome)互不妥协,谁也说服不了对方接收自己的标准,不过却都同时支持javascript。对于广大程序员来说,随着web流行度越来越高,那么为了同时支持大多数浏览器,那么使用标准的javascript成为趋势。而第三方(coffeescript, typescript, dart等)也都是选择将代码编译为JavaScript,然后在浏览器端执行。

作为从C#、java等语言转到javascript的同学来说,会发现不少C#、java的“我觉得应该是这样”的东西,JavaScript中却是不一样的。下面总结一下

2. this

对C#、Java等语言,this就是当前对象,但是javascript不是,简单来说:

  • 全局this 是window;
  • 函数this 是调用者;
  • 构造函数的this 是new 之后的新对象,
  • call 和 apply bind的this第一个参数

3. 原型链, prototype

  • 函数对象都包含prototype属性(函数的原型对象),其作用就是让该函数所实例化的对象们都可以找到公用的属性和方法;
  • constructor属性的含义就是指向该对象的构造函数
  • ①__proto__和constructor属性是对象所独有的;② prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__和constructor属性。
  • JavaScript 的每个对象都继承另一个父级对象,父级对象称为原型 (prototype) 对象。
  • 每一个实例对象都有一个私有属性__proto__指向其构造函数的原型对象prototype;该原型对象也会作为实例对象有一个私有属性__proto__,层层向上直到一个对象的原型对象值为null。
  • 当访问一个对象的属性或方法时,js引擎会先查找该对象本身是否包含,如果没有,会去该对象的__proto__属性所指向的原型对象上找,如果没有,会继续向上一层找,直到某个对象的__proto__值为null,这就是原型链。
  • 每个构造函数都有一个prototype属性,指向另外一个对象,说明整个对象所有的属性和方法都会被构造函数所拥有。

4. new操作符都做了什么

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一。

new Object()举例:

  1. 创建一个新对象
  2. 把新对象的原型指向构造函数的prototype
  3. 把构造函数里的this指向新对象
  4. 返回这个新对象

5. === VS ==

1
2
3
4
5
100 == '100' // true
0 == '' // true
0 == false // true
false == '' // true
null == undefined // true

很有意思吧, 看看这两个,是等效的:

1
2
obj == null
obj === null || obj === undefined

6. 闭包

在JavaScript中的一大特点就是闭包,很多高级应用(早期的jquery,现在的angular、vue)都要依靠闭包来实现。由于闭包会使得函数中的变量都被保存在内存中,会消耗很大的内存,导致页面的性能问题,甚至导致内存泄漏。建议不要或者少用闭包。

1
2
3
4
5
6
function outerMethod() {

var a = "some value";
var innerMethod = function() { console.log( a + "!") ;}
innerMethod();
}

7. 总结

  1. Javascipt 中有些内容和其他编程语言不一致,需要牢记
  2. JavaScript中拿不准的东西,可以简单测试一下,免得代码写好后出错。
  3. 简单测试,可以打开浏览器,直接F12(进入Dev Tools),再选择Console(控制台), 直接输入要测试的内容,查看返回结果



—————- END —————-






======================

环境:

  • Angular CLI: 11.0.6
  • Angular: 11.0.7
  • Node: 12.18.3
  • npm : 6.14.6
  • IDE: Visual Studio Code

1. 概要

实际的Angular项目,肯定不是一个简单的Hello World程序,会包含很多的功能,很多的文件。那么如何更好的组织这些文件呢?官方给了一个原则,可以供参考:
https://angular.io/guide/styleguide#application-structure-and-ngmodules。
下面,我们来通过一个例子具体解释一下。

2. 目录结构(工程结构)推荐

2.1. 总的原则

  1. 基于Angular CLI创建模块(module),组件(component)等等的内容;
  2. 源代码都放到src 文件夹下;
  3. 应用的根目录创建一个 NgModule, 并命名为app.module.ts(例如 /src/app,这个Angular CLI 会自动帮我们做)
  4. 组件具有多个伴生文件 (.ts、.html、.css 和 .spec),建议为它创建一个文件夹;(Angular CLI 会自动帮我们做)
  5. 为每一组功能(特性区)创建一个模块(NgModule);(这个也方便我们应用惰性加载/延迟加载,预加载)
  6. 在 src/app/shared 目录中创建名叫 SharedModule 的共享模块,方便其他功能调用;

2.2. 实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
src/ 目录下:
+---app
| | app-routing.module.ts
| | app.component.css
| | app.component.html
| | app.component.spec.ts
| | app.component.ts
| | app.module.ts
| |
| +---feature1
| | | ......
| |
| +---feature1
| | | ......
| |
| +---core
| | | core.module.ts
| | | ....
| |
| \---shared
| | shared.module.ts
| |
| +---components
| +---pipes
| +---services
|
+---assets
| .gitkeep
|
\---environments
environment.prod.ts
environment.ts

说明

  1. 根目录下的NgModule 默认是 app.module.ts, 不要改名,方便阅读
  2. app 下,每个目录,同时也都是一个模块-NgModule
  3. 一个项目,一般包含多个功能(feature)模块
  4. 推荐使用共享模块-SharedModule,将通用的、功能的功能(service/component/pipe等)放到改模块中。 (下文详细介绍)
  5. 推荐使用核心模块-CoreModule,可以将项目的一些全局的设置、UI等放到该模块。如header、footer组件,安全组件(服务),上下文存储服务等。

2.3. 共享模块-SharedModule

上文提到,推荐使用共享模块-SharedModule,将通用的、功能的功能(service/component/pipe等)放到改模块中。具体存放内容包括:

  1. 共享模块中声明那些可能被特性模块引用的可复用组件(Component)、指令(Directive)和管道(Pipe)。
  2. 如果放置服务(Service),由于服务的单例特性,共享模块中只建议放置无状态的服务(Service),对于有状态、或者和业务紧密相关的服务,建议放到CoreModule中。
  3. SharedModule 中声明(declarations)和导出(exports)所有组件、指令和管道,方便其他模块调用
  4. 共享模块(SharedModule)在项目中处于底层,从逻辑上,只能由其他业务逻辑模块调用,不能调用其他模块;
  5. 共享模块(SharedModule)不建议使用延迟加载(惰性加载),因为这样会破坏服务的单例特性;

3. 高级应用 - angular库、工作空间(workspace )

想一想你是如何安装angular相关的类库的?我们使用的是npm install xxx。那么这些第三方angular库是如何开发的呢?我们是否可以把通用的内容写成Angular类库,然后方便在多个angular项目间共享了?又或者直接发布到官方npm站点?

Angular 从6.0开始,引入了工作区的概念。使用Angular CLI,默认创建的就是一个工作空间(workspace)。一个工作空间(workspace)可以有一个主项目,同时可以有多个子项目。当然这些子项目可以是angular的application, 也可以是Library。Angular的子项目,都在app下的projects 之下,和src目录平级。

这样,一般来说,我们的项目中,除了主程序,还可以包含多个子类库。这样在开发时方便主程序、类库同时开发调试;也可以项目结束后,单独发布类库,实现项目之间的代码共享。

创建子项目也很简单,子项目有2种类型,application(可以启动的)和Library(Angular类库),默认都在projects文件夹下:

  1. 创建子Applicaton
    1
    ng generate application <name> [options]
  2. 创建子类库(Angular Library)
    1
    ng generate library <name> [options]

3.1. 目录结构示意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+---projects
| \---ng-lib-a
| \---src
| \---lib
| \---ng-lib-b
| \---src
| \---lib
\---src
+---app
| +---feature1
| +---feature2
| +---core
| | +---footer
| | \---header
| \---shared
| +---components
| +---pipes
| \---services
+---assets
\---environments

4. 总结

  1. Angular CLI默认创建的Angular项目,实际是一个工作空间(workspace ),在其内部还可以创建多个子项目
  2. 子项目可以是Application,也可以是类库(Library);
  3. 对于主项目(一般来说是一个Application),以功能模块的方式进行组织;
  4. 公共内容,建议放到共享模块-SharedModule中。
  5. 对于功能模块,可以通过惰性加载(延迟)加载,提高首页加载速度;同时通过预加载技术,可以在空闲时间加载这部分模块,使用户体验更好。



—————- END —————-






======================

1. 概要

Flexible Box翻译过来就是弹性盒子、弹性布局,是css3中新增的一种布局方式,是当页面需要适应不同的屏幕大小以及设备类型时确保元素拥有恰当的行为的布局方式。引入弹性布局模型的目的是提供一种更加有效的方式来对一个容器中的子元素进行排列、对齐和分配空白空间。

弹性盒子由弹性容器(Flex container)和弹性子元素(Flex item)组成。

  • 弹性容器通过设置 display 属性的值为 flex 或 inline-flex将其定义为弹性容器。
  • 弹性容器内包含了一个或多个弹性子元素。
属性 描述
display 指定 HTML 元素盒子类型。
flex-direction 指定了弹性容器中子元素的排列方式
justify-content 设置弹性盒子元素在主轴(横轴)方向上的对齐方式。
align-items 设置弹性盒子元素在侧轴(纵轴)方向上的对齐方式。
flex-wrap 设置弹性盒子的子元素超出父容器时是否换行。
align-content 修改 flex-wrap 属性的行为,类似align-items, 但不是设置子元素对齐,而是设置行对齐
flex-flow flex-direction 和 flex-wrap 的简写
order 设置弹性盒子的子元素排列顺序。
align-self 在弹性子元素上使用。覆盖容器的 align-items 属性。
flex 设置弹性盒子的子元素如何分配空间。

2. justify-content 属性

内容对齐(justify-content)属性应用在弹性容器上,把弹性项沿着弹性容器的横轴(主轴线,main axis)对齐。

·justify-content 语法:

1
justify-content: flex-start | flex-end | center | space-between | space-around

可选项:

  • flex-start:弹性项目向行头紧挨着填充。这个是默认值。第一个弹性项的main-start外边距边线被放置在该行的main-start边线,而后续弹性项依次平齐摆放。
  • flex-end:弹性项目向行尾紧挨着填充。第一个弹性项的main-end外边距边线被放置在该行的main-end边线,而后续弹性项依次平齐摆放。
  • center:弹性项目居中紧挨着填充。(如果剩余的自由空间是负的,则弹性项目将在两个方向上同时溢出)。
  • space-between:弹性项目平均分布在该行上。如果剩余空间为负或者只有一个弹性项,则该值等同于flex-start。否则,第1个弹性项的外边距和行的main-start边线对齐,而最后1个弹性项的外边距和行的main-end边线对齐,然后剩余的弹性项分布在该行上,相邻项目的间隔相等。
  • space-around:弹性项目平均分布在该行上,两边留有一半的间隔空间。如果剩余空间为负或者只有一个弹性项,则该值等同于center。否则,弹性项目沿该行分布,且彼此间隔相等(比如是20px),同时首尾两边和弹性容器之间留有一半的间隔(1/2*20px=10px)。

3. align-items 属性

align-items 设置或检索弹性盒子元素在侧轴(纵轴)方向上的对齐方式。

align-items语法

1
align-items: flex-start | flex-end | center | baseline | stretch

可选项:

  • flex-start:弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴起始边界。
  • flex-end:弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴结束边界。
  • center:弹性盒子元素在该行的侧轴(纵轴)上居中放置。(如果该行的尺寸小于弹性盒子元素的尺寸,则会向两个方向溢出相同的长度)。
  • baseline:如弹性盒子元素的行内轴与侧轴为同一条,则该值与’flex-start’等效。其它情况下,该值将参与基线对齐。
  • stretch:如果指定侧轴大小的属性值为’auto’,则其值会使项目的边距盒的尺寸尽可能接近所在行的尺寸,但同时会遵照’min/max-width/height’属性的限制。

4. flex-wrap 属性

flex-wrap 属性用于指定弹性盒子的子元素换行方式。

语法

1
flex-wrap: nowrap|wrap|wrap-reverse|initial|inherit; 

可选项:

  • nowrap - 默认,弹性容器为单行。该情况下弹性子项可能会溢出容器。
  • wrap - 弹性容器为多行。该情况下弹性子项溢出的部分会被放置到新行,子项内部会发生断行
  • wrap-reverse -反转 wrap 排列。

5. align-content 属性

align-content 属性用于修改 flex-wrap 属性的行为。类似于 align-items, 但它不是设置弹性子元素的对齐,而是设置各个行的对齐。

语法

1
align-content: flex-start | flex-end | center | space-between | space-around | stretch

各个值解析:

  • stretch - 默认。各行将会伸展以占用剩余的空间。
  • flex-start - 各行向弹性盒容器的起始位置堆叠。
  • flex-end - 各行向弹性盒容器的结束位置堆叠。
  • center -各行向弹性盒容器的中间位置堆叠。
  • space-between -各行在弹性盒容器中平均分布。
  • space-around - 各行在弹性盒容器中平均分布,两端保留子元素与子元素之间间距大小的一半。

6. 居中

使用弹性盒子,居中变的很简单,只想要设置 margin: auto; 可以使得弹性子元素在两上轴方向上完全居中:

1
2
3
4
5
.flex-item {
width: 100px;
height: 100px;
margin: auto;
}

7. align-self

align-self 属性用于设置弹性元素自身在侧轴(纵轴)方向上的对齐方式。

选项:

  • auto:如果’align-self’的值为’auto’,则其计算值为元素的父元素的’align-items’值,如果其没有父元素,则计算值为’stretch’。
  • flex-start:弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴起始边界。
  • flex-end:弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴结束边界。
  • center:弹性盒子元素在该行的侧轴(纵轴)上居中放置。(如果该行的尺寸小于弹性盒子元素的尺寸,则会向两个方向溢出相同的长度)。
  • baseline:如弹性盒子元素的行内轴与侧轴为同一条,则该值与’flex-start’等效。其它情况下,该值将参与基线对齐。
  • stretch:如果指定侧轴大小的属性值为’auto’,则其值会使项目的边距盒的尺寸尽可能接近所在行的尺寸,但同时会遵照’min/max-width/height’属性的限制。

8. 总结



—————- END —————-






======================

环境:

  • Angular CLI: 11.0.6
  • Angular: 11.0.7
  • Node: 12.18.3
  • npm : 6.14.6
  • IDE: Visual Studio Code

1. 概要

当我们完成angular的开发后,如何部署到服务器呢?

2. 编译打包

2.1. 基本打包命令

基于Angular CLI生成的Angular项目,默认会有2个环境配置文件

1
2
3
└──myProject/src/environments/
└──environment.ts
└──environment.prod.ts
  1. environment.ts: 针对开发环境使用的环境文件
  2. environment.prod.ts: 生产环境编译时,将替换原有的environment.ts,然后再打包。 (根目录下的angular.json定义了这个默认行为,有需要,可以进行修改)

AngularCLI刚刚生成2个文件后,如果打开比较2个文件的区别,可以看到开发环境使用的environment.ts文件中,有这么一句production: false。因为,针对生产环境,angular在编译时需要核心考虑效率等问题,而开发环境,要考虑方便开发者进行调试,侧重点不同。

那么针对生产环境如何编译呢?Angular CLI同样提供了命令,

1
ng build --prod

其中,参数--prod 即告诉编译环境,编译为生产环境包。同样,angular.json中定义了默认的编译参数,如果需要,可以进行修改。主要配置参数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false,
"budgets": [
{
"type": "initial",
"maximumWarning": "5mb",
"maximumError": "10mb"
}
]
}
}

Angular默认打包到根目录下的dist目录下,生成的文件为纯静态文件(html, css, js),以及图片文件。

2.2. 打包部署到二级目录

有不少情况,我们的angular web站点不能直接部署到网站的根目录下,需要部署到二级目录下。 比如,不能部署到 http://abc.com下,要求部署到 http://abc.com/demo 这个二级目录下。针对这种情况,就需要修改一下我们的编译参数,修改为:

1
ng build --prod --deploy-url /demo/ --base-href /demo/

增加 --deploy-url--base-href

使用场景:比如我们有多个站点,希望使用同一个反向代理, http://site1, http://site2, 分别映射到 http://abc.com/site1, http://abc.com/site2/。 那么为了方便配置,需要把site1, site2都部署到二级目录,如http://site1/site1, http://site2/site2。 然后 http://site1/site1代理到http://abc.com/site1, http://site2/site2代理到http://abc.com/site2/, 免得css、js因为目录级别问题找不到。

3. Angular站点的发布

Angular站点编译打包后,可以方便的发布到已有web服务器,或者打成docker image, 然后发布。

3.1. web服务器发布

因为我们打包后,生成的文件为纯静态文件(html, css, js, 图片等), 所以打包后的问题,可以直接copy到iis, nginx , apache tomcat等web服务器,或者node.js, java等可以显示静态文件的程序的目录下即可。

3.2. 使用docker发布

如果部署到docker,我们可以基于一个基础的nginx docker, 然后把编译好的angular项目,copy到docker 内的nginx目录下即可。

基本步骤:

  1. 准备Dockerfile 文件, docker可以基于nginx:alpine, 将编译好的angular 站点文件复制到 docker 的nginx默认目录 /usr/share/nginx/html
1
2
FROM nginx:alpine
COPY . /usr/share/nginx/html

说明: 1) 假设angular打包后的文件,与Dockerfile文件在同一个目录
2) COPY . /usr/share/nginx/html, 两个参数 . 代表当前路径, /usr/share/nginx/html是docker中的目标目录

  1. 编译docker。 在Dockerfile目录下,执行
    1
    2
    3
    docker build -t your-docker-name .
    docker save your-docker-name > your-docker-name.tar
    gzip your-docker-name.tar

三条命令分别为:

  • 生成docker image, 名字(name)为your-docker-name
  • 导出docker image为本地文件, 文件名为 your-docker-name.tar
  • 压缩docker image

可以看到,因为angular编译后为纯静态文件,所以使用docker发布非常简单。部署时,只需要复制docker文件到目标机器,解压缩,然后执行 docker load < your-docker-name.tar 即可加载docker image到目标机器。

4. 总结

  1. 为生产环境编译,一定要加参数--prod
  2. 如果要部署到二级目录,编译时加参数。如部署到/demo二级目录下,加参数: --deploy-url /demo/ --base-href /demo/
  3. 使用docker发布,可以选择基本的nginx docker, 然后将编译好的angular文件copy到nginx目录下即可。



—————- END —————-






======================