0%

题目链接:
https://www.nowcoder.com/practice/04a5560e43e24e9db4595865dc9c63a3
解法分析:纯的层序遍历,使用 BFS

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
/*
* function TreeNode(x) {
* this.val = x;
* this.left = null;
* this.right = null;
* }
*/

/**
*
* @param root TreeNode类
* @return int整型二维数组
*/
function levelOrder(root) {
// write code here
if (!root) {
return [];
}
const res = [], queue = [];
queue.push(root);

// 从上到下遍历每一层
while (queue.length) {
const currentLevelSize = queue.length;
res.push([]);
// 从左到右遍历该层的所有结点
for (let i = 0; i < currentLevelSize; i++) {
const node = queue.shift();
res[res.length - 1].push(node.val);

// 将下一层的结点放入队列
node.left && queue.push(node.left);
node.right && queue.push(node.right);
}
}

return res;
}

module.exports = {
levelOrder : levelOrder
};

本文摘自字节青训营社区技术问答板块

#1 在什么情况下 a === a - 1 ?(上)

  • 正负 Infinity
  • 给 a 赋一个很大的值(准确地说,是超过 Number.MAX_SAFE_INTEGER 的数)

    这是因为在 JavaScript 里,整数可以被精确表示的范围是从 -2 ** 53 + 12 ** 53 - 1,即 -9007199254740991 到 9007199254740991。超过这个数值的整数,都不能被精确表示。
  • 注意 NaN 跟任何值都不相等,包括 NaN 本身

#2 在什么情况下 a === a - 1 ?(下)

使用 defineProperty 函数

1
2
3
4
5
6
7
let count = 0;
Object.defineProperty(window, 'a', {
get() {
return ++count;
}
});
console.log(a === a - 1); // true

基础问

1
2
3
4
5
6
7
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}

console.log(i);

这段代码的执行结果是什么?
答案:立刻输出一个 5,1s 后再一次性输入 5 个 5。
解释:

  • 因为 setTimeout 是异步函数,每一次 for 循环的时候,setTimeout 都执行一次,但是里面的函数没有被执行,而是被放到了任务队列里,等待执行。只有主线程上的任务执行完,才会执行任务队列里的任务。也就是说它会等到 for 循环全部运行完毕后,才会执行 setTimeout 里面的函数,但是当 for 循环结束后此时 i 的值已经变成了 5,因此控制台上的内容会输出 5 个 5。
  • 关于这 5 个 5 为什么是一次性输出的:这里涉及到 JS 中的定时器机制,循环执行过程中,几乎同时设置了 5 个定时器。一般情况下,这些定时器都会在大约 1 秒之后触发,而循环完的输出是立即执行的,因此会立即输出 5 个 5。

改造的思路

如果期望代码的输出变成 1s 后输出 0,1,2,3,4,该怎么改造代码?
首先思考为什么会输出 5 个 5 呢?究其缘由,var 声明的变量不存在块级作用域,所以最终的 i 都是同一个 i。也就是说,i 是声明在全局作用域中的。所以如果我们可以使 i 声明在私有作用域中,就可以解决这个 bug。

闭包

因此,我们可以利用闭包,让 i 在每次迭代的时候,都产生一个私有的作用域,在这个私有的作用域中保存当前 i 的值。

1
2
3
4
5
6
7
8
9
for (var i = 0; i < 5; i++) {
(function(j) { // 相当于 j = i
setTimeout(function() {
console.log(j);
}, 1000);
})(i);
}

console.log(i);

let

接着我们很自然地会想到使用 let,let 本身当然是可以的,但是此处代码的最后一行十分恶心,要求输出 i,所以会报错。

拆分结构

闭包解决方法的最大问题就是可读性不好。
我们可以利用一个特性:JS 中基本类型的参数传递是按值传递的。

1
2
3
4
5
6
7
8
9
10
function output(i) {
setTimeout(function () {
console.log(i);
}, 1000);
}
for (var i = 0; i < 5; i++) {
output(i); // 此处传过去的 i 被复制了
}

console.log(i);

setTimeout 的第三个参数

1
2
3
4
5
for (var i = 0; i < 5; i++) {
setTimeout(function (j) {
console.log(j);
}, 1000, i);
}

如果想要每隔 1s 输出一次怎么办

1
2
3
4
5
6
7
8
9
10
11
for (var i = 0; i < 5; i++) {
(function (j) {
setTimeout(function () {
console.log(j);
}, 1000 * j);
})(i);
}

setTimeout(() => {
console.log(i);
}, 1000 * i);

这算是一种解决方法,但是太粗暴了(x

使用 ES6 优化(Promise)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const tasks = []; // 这里存放异步操作的 Promise
const output = (i) => new Promise((resolve) => {
setTimeout(() => {
console.log(i);
resolve();
}, 1000 * i);
});

// 生成全部的异步操作
for (var i = 0; i < 5; i++) {
tasks.push(output(i));
}

// 进行异步操作;当异步操作完成之后,输出最后的 i = 5
Promise.all(tasks).then(() => {
setTimeout(() => {
console.log(i);
}, 1000);
});

使用 ES7 优化(await)

使用 await 优化其实是很简单的,实际上就相当于写同步,只要实现一个 sleep 函数就行了。(bks 异步的最终解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 模拟其他语言中的 sleep 函数
const sleep = (timeout) => new Promise((resolve) => {
setTimeout(resolve, timeout);
});

(async () => { // 声明立即执行的 async 函数表达式
for (var i = 0; i < 5; i++) {
if (i > 0) {
await sleep(1000);
}
console.log(i);
}

// 输出最后的 5
await sleep(1000);
console.log(i);
})();

参考资料:
https://juejin.cn/post/6844903474212143117
https://juejin.cn/post/6844903612879994887
https://juejin.cn/post/6844903841888993287

什么是闭包

闭包是一个可以访问外部函数作用域的函数,即便外部函数已经运行完成。这意味着闭包可以记住并访问其外部函数的变量和参数,即使在函数完成之后也是如此。
在深入闭包之前,我们首先需要理解词法作用域。

什么是词法作用域

Javascript 词法作用域是指我们可以获取源代码中变量、函数、对象的物理位置。

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let a = 'global';
function outer() {
let b = 'outer';
function inner() {
let c = 'inner'
console.log(c); // prints 'inner'
console.log(b); // prints 'outer'
console.log(a); // prints 'global'
}
console.log(a); // prints 'global'
console.log(b); // prints 'outer'
inner();
}
outer();
console.log(a);

此处函数 inner 可以获取它自己作用域、outer 函数作用域、全局作用域中的变量;outer 函数可以获取它自己作用域、全局作用域中的变量。

闭包的具体实例

Example 1

1
2
3
4
5
6
7
8
function person() {
let name = 'Peter';
return function displayName() {
console.log(name);
};
}
let peter = person();
peter(); // prints 'Peter'

在这段代码中,我们调用了 person 函数,它会返回内部的函数 displayName 并把这个内部函数存储在变量 peter 中。当我们调用函数 peter 时,实际上也就是在调用函数 displayName,因此 console 结果为 Peter。

但是在函数 displayName 里面并不存在一个叫 name 的变量,也就意味着这个函数可以以某种方式获取它外部的函数 person 中的值,甚至在 person 函数返回之后。所以 displayName 函数实际上就是一个闭包。

Example 2

1
2
3
4
5
6
7
8
9
10
function getCounter() {
let counter = 0;
return function () {
return counter++;
}
}
let count = getCounter();
console.log(count()); // 0
console.log(count()); // 1
console.log(count()); // 2

这次我们把一个通过 getCounter 返回的匿名函数存储在一个叫做 count 的变量中。因为 count 函数是一个闭包,它可以通过函数 getCounter 获取变量 counter 的值,即便是在 getCounter 函数已经返回之后。

这里我们需要注意的是,变量 counter 在每次调用时,并没有重置为 0。这似乎有悖我们之前对函数的认知。
这是因为每次调用函数 count 的时候,虽然都会创建一个 count 的新的函数作用域,但是对于函数 getCounter 始终只存在一个作用域,而 counter 变量是在 getCounter 的作用域中定义的,所以每次我们调用函数 count 的时候,计数会递增,而不是重置为 0。

闭包的工作原理

要想清楚地理解闭包的工作原理,我们需要首先理解 Javascript 中的两个重要概念:

  • 执行上下文
  • 词法环境

执行上下文

简单来说,执行上下文是关于 Javascript 代码解析和执行的环境的抽象概念。JavaScript 中运行任何的代码都是在执行上下文中运行的。

因为 Javascript 是单线程语言,所以一次只能有一个正在运行的执行上下文,它由一个被称为执行栈或调用栈的数据结构管理。

当前运行的执行上下文将始终位于栈顶,并且当当前函数运行完成时,对应的执行上下文会从栈顶弹出,并将控制权移交给下一个执行上下文。

请看例子:

当这段代码运行时,Javascript 引擎会创建一个全局执行上下文来运行全局代码,而当遇到函数 first 时,它会为 first 函数创建一个新的执行上下文,并将其压入栈内。

执行栈的图示就像这样:

first 函数执行完成时,它的执行上下文会从执行栈中弹出,然后控制权移交给它下面的执行上下文,也就是全局上下文。

词法环境

每当 Javascript 引擎为全局代码或者某个函数创建一个新的执行上下文的时候,它同时也会创建一个新的词法环境,用于存储函数执行过程中出现的变量。

词法环境是一个类似 (标识符, 变量) 的映射数据结构,这里的标识符具体指变量名或者函数名,而变量指的是对象的引用,包括它的函数类型或者初始值。

每一个词法环境有三个组件:

  • 环境记录器:环境记录器用于存储变量或函数声明的实际位置
  • 一个对外部环境的引用:外部引用意味着它可以访问它外部(父级)的词法环境
  • this 的绑定

词法环境可以被抽象地表示为:

1
2
3
4
5
6
7
lexicalEnvironment = {
environmentRecord: {
<identifier>: <value>,
<identifier>: <value>
}
outer: <Reference to the parent lexical environment>
}

以下面这段代码为例:

1
2
3
4
5
6
7
let a = 'Hello World!';
function first() {
let b = 25;
console.log('Inside first function');
}
first();
console.log('Inside global execution context');

全局作用域的词法环境如下所示:

1
2
3
4
5
6
7
globalLexicalEnvironment = {
environmentRecord: {
a: 'Hello World!',
first: <reference to function object>
},
outer: null
}

first 函数的词法环境如下所示:

1
2
3
4
5
6
functionLexicalEnvironment = {
environmentRecord: {
b: 25,
},
outer: <globalLexicalEnvironment>
}

函数的外部词法环境被设置为全局词法环境,因为该函数被源代码中的全局作用域包围。

注意:当函数执行完成时,它的执行上下文将从执行栈中弹出,但是它的词法环境不一定从内存中删除,这取决于该词法环境是否被它外部词法环境属性中的任意其它的词法环境所引用。

结合实例来看闭包的工作原理

1
2
3
4
5
6
7
8
function person() {
let name = 'Peter';
return function displayName() {
console.log(name);
};
}
let peter = person();
peter(); // prints 'Peter'

person 函数执行的时候,Javascript 引擎会为这个函数创建一个新的执行上下文词法环境。在这个函数执行完成后,它会返回 displayName 函数然后将它赋值给变量 peter
词法环境如下所示:

1
2
3
4
5
6
7
personLexicalEnvironment = {
environmentRecord: {
name: 'Peter',
displayName: <displayName function reference>
},
outer: <globalLexicalEnvironment>
}

当函数 person 执行完成之后,执行上下文会从执行栈中弹出。但是它的词法环境依然在内存中,因为它的词法环境被它内部的 displayName 函数的词法环境引用了。所以它的变量在内存中依然可以获取。

简单来说就是,执行上下文删除了,但是词法环境还在。(译者注)

peter 函数(实际上是对 displayName 函数的引用)执行时,JavaScript 引擎为该函数创建一个新的执行上下文和词法环境。对应的词法环境如下所示:

1
2
3
4
5
6
displayNameLexicalEnvironment = {
environmentRecord: {

}
outer: <personLexicalEnvironment>
}

由于 displayName 函数中没有变量,所以它的环境记录器将为空。在执行此函数期间,JavaScript 引擎将尝试在函数的词法环境中查找变量 name

而在 displayName 函数的词法环境中没有变量 name,所以它会去外部词法环境中找,也就是还在内存中的 person 函数的词法环境。JavaScript 引擎查找到了变量 name 后,将其打印到控制台。

原文:
https://blog.bitsrc.io/a-beginners-guide-to-closures-in-javascript-97d372284dda

什么是执行上下文

简单来说,执行上下文是关于 Javascript 代码解析和执行的环境的抽象概念。JavaScript 中运行任何的代码都是在执行上下文中运行的。

执行上下文的类型

  • 全局执行上下文:任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
  • 函数执行上下文:每当一个函数被调用时, 都会为该函数创建一个新的上下文。每一个函数都有它自己的执行上下文,但它是在函数被调用的时候创建的。函数执行上下文可以有任意多个。
  • eval 函数执行上下文 — 执行在 eval 函数内部的代码也会有它属于自己的执行上下文,但 JavaScript 开发者并不经常使用 eval,所以此处不再赘述。

执行栈

执行栈,也就是调用栈,它用来存储代码运行时创建的所有执行上下文。本质上它就是数据结构中所说的栈,满足先进后出。
当 JavaScript 引擎第一次读取你的脚本时,它会创建一个全局执行上下文并将其压入执行栈;每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈。
JavaScript 引擎会优先运行执行上下文位于栈顶的函数。当该函数运行结束时,其对应的执行上下文会从栈中弹出,上下文的控制权将被移到当前执行栈的下一个执行上下文。

示例:

1
2
3
4
5
6
7
8
9
10
11
let a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');

  • 当上述代码在浏览器加载时,JavaScript 引擎创建了一个全局执行上下文并把它压入当前执行栈。当遇到 first() 函数调用时,JavaScript 引擎为该函数创建一个新的执行上下文并把它压入当前执行栈
  • 当从 first() 函数内部调用 second() 函数时,JavaScript 引擎为 second() 函数创建了一个新的执行上下文并把它压入当前执行栈的顶部。当 second() 函数执行完毕,它的执行上下文会从当前栈弹出,并且控制权移交给下一个执行上下文,即 first() 函数的执行上下文
  • first() 执行完毕,它的执行上下文从栈弹出,上下文控制权会被移交给全局执行上下文。一旦所有代码执行完毕,JavaScript 引擎从当前栈中移除全局执行上下文

执行上下文是如何创建的

创建执行上下文有两个阶段:

  • 创建阶段
  • 执行阶段

创建阶段

在创建阶段创建执行上下文。在创建阶段会发生以下情况:

  1. 创建词法环境组件
  2. 创建变量环境组件

我们可以将其抽象表示为:

1
2
3
4
ExecutionContext = {
LexicalEnvironment = <ref. to LexicalEnvironment in memory>,
VariableEnvironment = <ref. to VariableEnvironment in memory>,
}

词法环境

词法环境是用于保存 标识符-变量 的映射的结构。这里标识符是指变量/函数的名称,变量是对实际对象(包括函数对象和数组对象)或基本类型的引用。

简而言之,词法环境是存储变量和对象引用的地方

根据官方的 ES6 文档,一个词法环境由环境记录器和一个可能为空的对于外部环境的引用组成。

示例:

1
2
3
4
5
var a = 20;
var b = 40;
function foo() {
console.log('bar');
}

对应的词法环境为:

1
2
3
4
5
lexicalEnvironment = {
a: 20,
b: 40,
foo: <ref. to foo function>
}

每一个词法环境有三个组件:

  • 环境记录器
  • 一个对外部环境的引用
  • this 的绑定

环境记录器

环境记录用于在词法环境中存储变量和函数声明的位置。
环境记录器有两种类型:

  • 声明式环境记录器:顾名思义,它存储变量和函数声明。函数代码的词法环境包含声明性环境记录
  • 对象环境记录器:全局代码的词法环境包含一个对象环境记录器。除了变量和函数声明外,对象环境记录器还存储全局对象 window。因此,对于每个绑定对象的属性(对于浏览器,它包含浏览器提供给 window 的属性和方法),将会创建一条新的记录

注意:对于函数代码,环境记录器还包含一个 arguments 对象。

示例:

1
2
3
4
5
6
function foo(a, b) {
var c = a + b;
}
foo(2, 3);
// argument object
Arguments: {0: 2, 1: 3, length: 2}

对于外部环境的引用

对外部环境的引用意味着它可以访问外部的词法环境。这意味着,如果是当前词法环境中找不到的变量,JavaScript 引擎可以在外部环境中查找这些变量。

this 的绑定

在全局执行上下文中,this 的值引用全局对象。(在浏览器中,这指的是窗口对象)
在函数执行上下文中,其值取决于函数的调用方式。如果它由对象的引用调用,则此值将设置为该对象,否则,此值将设置为全局对象或 undefined(严格模式)。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
const person = {
name: 'peter',
birthYear: 1994,
calcAge: function() {
console.log(2018 - this.birthYear);
}
}
person.calcAge();
// 'this' refers to 'person', because 'calcAge' was called with //'person' object reference
const calculateAge = person.calcAge;
calculateAge();
// 'this' refers to the global window object, because no object reference was given

词法环境大概如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
}
outer: <null>,
this: <global object>
}
}
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
}
outer: <Global or outer function environment reference>,
this: <depends on how function is called>
}
}

变量环境

它也是一个词法环境,其环境记录器保存在此执行上下文中由 VariableStatements 创建的绑定。
因为变量环境也是一个词法环境,所以它具有上面定义的词法环境的所有属性和组件。
在 ES6 中,词法环境组件和变量环境组件之间的一个区别是前者用于存储函数声明和变量(let 和 const)绑定,而后者仅用于存储变量(var)绑定。

执行阶段

在这个阶段,所有的变量赋值都完成了,代码最终会被执行。

示例:

1
2
3
4
5
6
7
8
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);

当上面的代码被执行时,Javascript 引擎会创建一个全局执行上下文来执行全局代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
outer: <null>,
ThisBinding: <Global Object>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
c: undefined,
}
outer: <null>,
ThisBinding: <Global Object>
}
}

当函数 multiply(20, 30) 被调用时,一个新的函数执行上下文会被创建来执行函数代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>,
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
g: undefined
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>
}
}

此后,执行上下文将经历执行阶段,也就是要完成对函数内变量的赋值。(此处指对变量 g 的赋值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>,
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
g: 20
},
outer: <GlobalLexicalEnvironment>,
ThisBinding: <Global Object or undefined>
}
}

函数执行完成后,返回值会被存储在变量 c 之中。所以全局的词法环境会被更新。最终,全局的代码被执行完成,程序也运行完成。

注意:你也许注意到了,let 和 const 定义的变量在创建阶段没有任何赋值操作,但是 var 定义的变量被赋值为了 undefined。这也就解释了为什么当你想获取已声明但未定义的变量时,var 声明的变量会得到 undefined,而 let 声明的变量会显示 undeclared。这也就是我们所谓的变量提升。

总结

在本文中,我们讨论了 JavaScript 程序内部的执行机制。虽然学习这些概念并不是成为一名出色的 JavaScript 开发人员的必要条件,但对上述概念有一个良好的理解将有助于您更轻松、更深入地理解其他概念,如变量提升、作用域和闭包。

原文:
https://blog.bitsrc.io/understanding-execution-context-and-execution-stack-in-javascript-1c9ea8642dd0

数据类型

js 数据类型

执行上下文

相关知识:词法环境、调用栈
js 执行上下文详解(译)

闭包

相关知识:词法作用域、执行上下文、词法环境
js 闭包详解(译)

作用域和作用域链

相关知识:词法环境
js 作用域和作用域链详解(译)

遍历方法

js 遍历对象

var & const & let

var、let、const 辨析

bind & apply & call

call、apply、bind辨析(译) & 手写实现 call、apply、bind

Promise

Promise 详解(译) & 手写 Promise

instanceof

instanceof 详解

new

new 详解

继承

js 继承

事件机制

js 事件机制

异步工作原理

相关知识:执行上下文、词法环境、事件循环
js 异步原理详解(译)

什么是软件架构

软件架构是一组用于推理系统的结构,包含了软件元素、它们之间的关系以及他们的属性

软件架构的重要性

  • 架构影响系统的驱动质量属性
  • 可以帮助开发者随着系统发展而推理和管理变更
  • 可以实现对系统质量的早期预测
  • 架构促进利益相关者间的沟通
  • 架构是最早产生的设计决策的载体,所以它也是最基础的、最难以改变的设计决策的载体
  • 架构为后续的实现定义了一系列的约束
  • 架构决定了组织结构,反之亦然
  • 架构提供了进化原型的基础
  • 架构可以帮助架构师和项目经理预估成本和时间
  • 基于架构的开发关注组件之间的组合,而不是简单地关注它们的创建
  • 架构可以被设计成一个可转换、可复用的模型,构成产品线的心脏
  • 通过限制设计的自由度,架构引导开发者的创造方向,以减少设计和系统的复杂度
  • 架构可以作为培训团队新成员的基础

视图、架构和结构的区别与联系

  • 视图是一组耦合的架构元素的展示,它由一组元素和元素间的关系组成
  • 结构是元素集合本身
  • 架构设计结构,架构将结构的视图文档化

三类结构

三类结构回答的问题

Module structures

  • What is the primary responsibilities assigned to each module?
  • What other software elements is a module allowed to use?
  • What other software does it actually use and depend on?
  • What modules are related to other modules by generalization or specialization relationships?

Component-and-connector structures 回答的问题

  • What are the major executing components and how do they interact at runtime?
  • What are the major shared data stores?
  • Which parts of the system are replicated?
  • How does data progress through the system?
  • What parts of the system can run in parallel?
  • Can the system’s structure change as it executes and, if so, how?

Allocation structures 回答的问题

  • What processor does each software element execute on?
  • In what directories or files is each element stored during development, testing, and system building?
  • What is the assignment of each software element to development teams?

Module structures 模块结构

  • Decomposition structure 分解结构:决定了系统的可修改性,以确保可能的变化是局部的
  • Uses structure 使用结构:是一种专门的依赖关系,用来拓展或缩小系统,创建子系统
  • Layered structure 层结构:通过接口提供一个有凝聚力的服务集合
  • Class structure 类结构:允许重载功能
  • Data model 数据模型:描述了数据实体及其关系的信息结构

Component-connector structures 组件连接件结构

  • Service structure 服务结构:单位服务与其他服务的协调机制
  • Concurrency structure 并发结构:确定机会的并行性和资源争夺可能发生的位置

Allocation structures 分配结构

  • Deployment structure 部署结构:软件如何被分配给硬件处理和通信,如分布式和并行系统
  • Implementation structure 实现结构:显示了软件元素如何在系统的开发、集成和配置控制环境中映射到文件结构中
  • Work assignment structure 工作分配结构:分配责任,实施和整合模块给将要执行它的团队

架构模式

架构模式的作用

  • 架构模式描述了软件系统里的基本的结构
  • 架构模式提供一些预先定义好的子系统,指定它们的责任,并给出把它们组织在一起的方法
  • 架构模式建立了背景(context)、问题(problem)和解决方案(solution)间的关系

常见的架构模式

  • 分层模式 layered pattern
  • 数据共享模式 shared-data pattern
  • C/S 模式 client-server pattern
  • 多层模式 multi-tier pattern
  • 能力中心和平台 competence center and platform

架构的四大背景

  • 技术 technical
  • 项目生命周期 project life cycle
  • 业务 business
  • 专业性 professional

什么会影响架构

  • 需求
  • 架构的四大背景
  • 架构本身会影响未来的架构

战术 Tactics 与模式 Patterns 的区别

  • 模式建立在战术之上,一个模式包含很多战术
  • 模式用于解决一个特定问题,但是同时会给其他质量属性带来负面影响

模块框架 Module Pattern

Layered pattern 分层模式

  • context:关系的分离
  • problem:软件在某些情况下需要分离,模块可以单独开发,提高可移植性、可修改性、重用性
  • solution:分层模式定义了层(每个层是一组模块),和层与层之间单向调用的关系,下一层只能使用上一层提供的服务

组件-连接件框架 Component-Connector Pattern

Broker pattern 代理模式

  • context:分布式服务之间的交互操作
  • problem:如何构造分布式软件,使用户不需要知道供应商的性质和位置,就可以容易地动态改变用户和供应商之间的绑定
  • solution:代理模式插入一个称为代理的中介,将服务提供者(服务器)和用户(客户端)分开

Model-View-Controller Pattern MVC 模式

  • context:从模型分离视图
  • problem:如何将用户界面的功能与应用程序的功能分开,但是仍然可以响应用户的输入,或者改变应用程序数据。当应用数据改变时,如何创建、维护和协调用户界面的多个视图
  • solution:MVC 模式把应用功能分成三种类型的组件

Pipe and Filter Pattern 管道-过滤器模式

  • context:处理数据流
  • problem:一些系统可能被分解为可重用的、松散耦合的组件,这些组件具有简单通用的交互机制
  • solution:管道和过滤器模式特征就是数据流连续变换

Client-Server Pattern C/S 模式

  • context:大量的分布式客户希望访问共享的资源和服务
  • problem:我们希望通过集中控制资源和服务来提高可扩展性和可用性,同时在多台物理服务器上分配资源
  • solution:客户端通过请求服务器的服务进行交互,服务器提供一系列的服务

Peer-to-Peer Pattern P2P 模式

  • context:分布式计算实体被认为是平等的
  • problem:如何将一组平等的分布式计算实体通过公共协议相互连接,使得他们能够以高可用性和可扩展性的组织和共享服务
  • solution:在 P2P 模式中,组件直接作为对等点进行交互,所有的对等点都是平等的,没有对等点对系统的健康来说是至关重要的

Service Oriented Architecture Pattern 面向服务的架构模式(SOA)

  • context:许多服务需要进行互操作,但是开发者对它们的内部实现没有任何了解
  • problem:如何保证在不同的平台上、由不同的语言实现、由不同的组织提供的分布式系统的互操作性
  • solution:SOA 模式描述了一个提供或使用服务的分布式组件的集合

Publish-Subscribe Pattern 发布-订阅模式

  • context:数据生产者和消费者的确切数量和性质不是预定的或固定的,他们也不共享数据
  • problem:我们如何创建集成机制,支持在生产者和消费者之间传递信息的能力,使得他们不知道彼此的身份甚至不知道彼此的存在
  • solution:在发布订阅模式中,组件通过已发布的消息进行交互

Shared-Data Pattern 共享数据模式

  • context:各种组件之间需要共享和操作大量的数据
  • problem:系统如何存储和操作数据,并保证可以被多个独立的组件访问
  • solution:数据共享模式中,交互主要通过在多个数据存取器和至少一个共享数据存储之间持续的数据交换进行

分配框架 Allocation Pattern

Map-Reduce Pattern 映射-规约模式

  • context:需要快速分析大量数据
  • problem:有效地执行大型数据集的分布式并行排序,为程序员指定的分析提供一种简单的方法
  • problem:map-reduce 模式需要三个部分:一个负责根据需要分配数据的基础设施、一个用于过滤数据以检索项目的 map、一个结合映射结果的 reduce

Multi-Tier Pattern 多层模式

  • context:将一个系统的基础设施分配到不同的子集
  • problem:如何将系统划分为多个独立计算的执行结构
  • solution:许多系统的执行结构被组织为一系列组件的逻辑分组,每个组被称为一级

七个质量属性场景

质量属性场景的组成

  • Source:刺激源,产生刺激的实体,如人、系统等
  • Stimulus:刺激,系统做出响应的条件
  • Response:响应,系统在被刺激时采取的行动
  • Response measure:响应度量,当响应发生时,以某种标准量化该响应
  • Environment:环境,刺激发生的特定条件
  • Artifact:制品,可以理解为研究的对象

Availability 可达性

含义

系统掩盖或修复错误的能力,使得一定时间内系统的不可用时间小于特定值

general scenario

  • Source:系统内部、系统外部
  • Stimulus:错误,如疏忽、崩溃、响应错误
  • Artifact:处理器、进程
  • Environment:正常操作、降级操作(使用更少的特性)
  • Response:记录故障;通知用户或系统;根据规则禁止导致错误的源头
  • Response measure:系统修复时间、系统可用的时间间隔、系统在降级模式下可用的时间间隔

concrete scenario

  • Source:Heartbeat monitor 心跳监控器
  • Stimulus:Server unresponsive 服务器无响应
  • Artifact:Process 进程
  • Environment:Normal operation 正常操作
  • Response:Inform operator continue to operate 通知操作者继续操作
  • Response Measure:No downtime 没有停机时间

示例

The heartbeat monitor determines that the server is nonresponsive during normal operations. The system informs the operator and continues to operate with no downtime
心跳监视器确定服务器在正常操作期间无响应。系统通知操作员继续操作,并在无停机的情况下继续运行

战术

  • 检测错误:ping/echo,monitor,timestamp,sanity checking,condition monitoring,voting,self-test,heartbeat
  • 修复错误:主动冗余、被动冗余、备份、回滚、阴影、状态重新同步
  • 防止错误:事务、预测模型、增加能力集

Interoperability 互操作性

含义

两个或以上的系统,在一定的背景下,可以通过接口有用地交换有意义的信息

general scenario

  • Source:向另一个系统提出互操作请求的系统
  • Stimulus:系统之间交换信息的请求
  • Artifact:互操作的系统
  • Environment:互操作的系统在运行前就知道对方的存在
  • Response:互操作引发信息交换
  • Response measure:被正确处理的信息交换百分比

concrete scenario

  • Source:Vehicle information system 交通信息系统
  • Stimulus:Current location sent 发送当前位置
  • Artifact:Traffic monitoring system 交通监控系统
  • Environment:Systems known prior to run-time 交通信息系统在运行之前就已经知道交通监控系统的存在
  • Response:Traffic monitor combines current location with other information, overlays on google maps, and broadcasts 交通监控器将当前位置与其他信息、谷歌地图上的覆盖和广播结合起来
  • Response Measure:Our information included correctly 99.9% of the time 在规定时间内信息被正确处理的百分比达到 99.9%

示例

Our vehicle information system sends our current location to the traffic monitoring system. The traffic monitoring system combines our location with other information, overlays this information on a Google Map, and broadcasts it. Our location information is correctly included with a probability of 99.9%
交通信息系统发送当前的地址信息给交通监控系统,交通监控系统需要将我们的位置信息和其他信息组合起来,映射到谷歌地图上,并发布结果。我们的位置信息被正确包含的概率将为 99.9%

战术

  • 定位:发现服务
  • 管理接口:协调接口、定制接口

Modifiability 可修改性

含义

在一定时限内,软件能被无副作用修改的难易程度

general scenario

  • Source:开发人员
  • Stimulus:希望增加、删除、修改功能
  • Artifact:用户界面、系统
  • Environment:在设计时、构建时、编译时、运行时
  • Response:查找需要修改的位置;进行修改而不影响其他功能
  • Response measure:该修改需要的成本与时间、该修改对其它功能的影响

concrete scenario

  • Source:Developer 开发者
  • Stimulus:Wishes to change the UI 想要改变 UI
  • Artifact:Code 代码
  • Environment:Design time 设计的时候
  • Response:Change made and unit tested 所做的更改和单元测试
  • Response Measure:In three hours with no side effects 三小时内完成,且没有副作用

示例

The developer wishes to change the user interface by modifying the code at design time. The modifications are made with no side effects within three hours
如果开发人员在设计时希望修改代码来更改用户接口,修改可以确保在三小时内完成更改以及单元测试,而且没有副作用

战术

  • 修改模块大小:拆分模块
  • 增加内聚:增强语义的一致性
  • 降低耦合:封装、使用中间件
  • 延迟绑定

Performance 性能

含义

软件系统满足时间需求的能力

general scenario

  • Source:系统内部或外部
  • Stimulus:定期事件到达;随机事件到达;偶然事件到达
  • Artifact:系统
  • Environment:正常模式、超载模式
  • Response:处理事件;改变服务的级别(如从正常模式切换到超载模式)
  • Response measure:等待时间、吞吐率

concrete scenario

  • Source:Users 用户
  • Stimulus:Initiate transactions 启动交易
  • Artifact:System 系统
  • Environment:Normal operation 正常模式
  • Response:Transactions are processed 交易被处理
  • Response Measure:Average latency of two seconds 平均等待时间为 2 秒

示例

Users initiate transactions under normal operations. The system processes the transactions with an average latency of two seconds
用户在正常操作下启动会话。系统处理事件的平均延迟为 2 秒

战术

  • 控制资源需求:管理采样率、限制事件响应、为事件划分优先级
  • 管理资源:引入并发、维护多个数据副本、维护多个计算副本、限制任务队列的大小

Security 安全性

含义

系统保护数据免受未授权访问和能够被授权访问的能力

general scenario

  • Source:授权或非授权用户、访问了有限资源或大量资源
  • Stimulus:尝试修改数据、尝试访问系统服务
  • Artifact:系统服务、系统数据
  • Environment:在线或离线、联网或断网
  • Response:对用户进行身份验证,允许或阻止用户访问数据或服务
  • Response measure:防范成功的概率

concrete scenario

  • Source:Disgruntled employee from remote location 来自远程位置的不满员工
  • Stimulus:Attempts to modify pay rate 试图修改工资比率
  • Artifact:Data within the system 系统内的数据
  • Environment:Normal operations 正常操作
  • Response:System maintains audit trails 系统维护审计跟踪
  • Response Measure:Correct data is restored within a day and source of tampering identified 在一天内恢复正确的数据,并确定篡改源

示例

A disgruntled employee from a remote location attempts to modify the pay rate table during normal operations. The system maintains an audit trail and the correct data is restored within a day
一个对工资不满的员工试图在正常操作期间远程修改工资率表。系统将保留审核跟踪,并在一天内恢复正确的数据,确定想做坏事的人是谁

战术

  • 检测攻击:确认消息的完整性、检测消息的延迟
  • 抵抗攻击:识别参与者、验证参与者、授权参与者、数据加密、限制访问
  • 对攻击做出反应:撤销访问、锁定计算机、通知参与者
  • 从攻击中恢复:维护审计追踪、重新存储

Testability 可测试性

含义

软件可以被证明有错误的容易程度

general scenario

  • Source:单元测试人员、集成测试人员
  • Stimulus:完成了一段功能完整的代码,如类、层
  • Artifact:代码片段、完整应用
  • Environment:设计时、开发时、编译时、部署时
  • Response:准备测试的环境、执行测试并捕获结果
  • Response measure:准备测试的时间、执行测试的时间、测试的覆盖率

concrete scenario

  • Source:Unit tester 单元测试人员
  • Stimulus:Code unit completed 一个代码单元被完成
  • Artifact:Code unit 一个代码单元
  • Environment:Development 开发时
  • Response:Results Captured 捕获测试结果
  • Response Measure:85% Path Converge in 3 hours 三小时内达到 85% 的路径覆盖率

示例

The unit tester completes a code unit during development and performs a test sequence whose results are captured and that gives 85% path coverage within 3 hours of testing
单元测试人员在开发过程中完成一个代码单元,并执行一个测试序列,该序列的结果被捕获,并在测试的 3 小时内提供 85% 的路径覆盖率

战术

  • 控制和观察系统状态:接口专用化、本地化状态存储、沙盒
  • 限制复杂度:控制结构的复杂度、限制非确定性因素

Usability 易用性

含义

指用户完成一项任务的容易程度和系统所提供用户支持的种类

general scenario

  • Source:最终用户
  • Stimulus:学习系统特性、学会有效使用系统
  • Artifact:系统
  • Environment:运行时、配置时
  • Response:上下文相关的帮助系统;撤销操作;取消操作
  • Response measure:用户学习时间、用户满意度、用户的操作成功率

concrete scenario

  • Source:User 用户
  • Stimulus:Downloads a new application 下载一个新的 App
  • Artifact:System 系统
  • Environment:Runtime 运行时
  • Response:User uses application productively 用户高效地使用应用程序
  • Response Measure:Within two miniutes of experimentation 在两分钟的试用时间内

示例

The user downloads a new application and is using it productively after two minutes of experimentation
用户下载一个新的应用程序,并在两分钟的试用后就能有效地使用它

战术

  • 支持用户的方案:取消、撤销、暂停/恢复
  • 支持系统的方案:维护任务模型、用户模型、系统模型

X-ability X 质量属性

含义

指未列出的的质量属性,如 variability 变异性、 portability 可移植性、scalability 可扩展性、elasticity 弹性、deployability 可部署性、mobility 可移动性、monitorability 可监测性等

通用质量属性表格

avalibilitytestabilityusabilitysecurityperformancemodifiability
source系统内部或外部单元测试人员、集成测试人员用户授权或非授权用户、访问了有限资源或大量资源系统内部或外部开发人员
stimulus错误,如疏忽、崩溃、响应错误完成了一段功能完整的代码,如类、层;完成了整个应用学习系统特性、学会有效使用系统尝试修改数据、尝试访问系统服务定期事件到达、随机事件到达、偶然事件到达增加、删除、修改功能
artifact处理器、进程代码片段、完整应用系统系统服务、系统数据系统用户界面、系统
environment正常操作、降级操作设计时、开发时、编译时、部署时运行时在线或离线、联网或断网正常模式 、超载模式设计时、构建时、编译时、运行时
response记录故障;通知用户或系统;禁止错误的数据源准备测试环境、执行测试并捕获结果上下文帮助系统、撤销操作、取消操作对用户进行身份验证、允许或拒绝用户访问数据或服务处理事件、改变服务的级别查找需要修改的位置、对内容进行修改
response measure系统修复时间、系统可用时间间隔、系统在降级模式下的可用时间间隔准备测试的时间、执行测试的时间、测试的覆盖率用户学习时间、用户满意度、用户的操作成功率防范成功的比例等待时间、吞吐率修改需要的成本、修改对其它功能的影响

质量属性的建模与分析

Performance Modeling 性能建模

  • 成本:取决于建模参数,如
    • The frequency of arrivals from outside the system
    • The queuing discipline used at the view queue
    • The time to process a message within the view
    • The number and size of messages that the view sends to the controller
    • The bandwidth of the network that connects the view and the controller
  • 作用:估计延迟时间
  • 对参数的估计越准确,对延迟的预测就越准确
  • 当延迟很重要和有问题时,这是值得的;当显然有足够的能力来满足需求时,这是不值得的

Availability Modeling 可达性建模

  • 可达性建模是为了确定组件的故障率和恢复时间
  • Steady-State Availability(实则为平均故障时间占比) 计算公式:
    • MTBF is the mean time between failure 平均故障时间
    • MTTR refers to the mean time to repair 平均修复时间
  • 三种提高可达性的主流战术
    • 主动冗余(热备份)
    • 被动冗余(暖备份)
    • 备份(冷备份)

Architecture in agile project 敏捷项目的架构

什么时候需要敏捷开发

  • 对涉众更加敏感
  • 想要更快地开发用户关注的功能
  • 想在项目生命周期中更多更早地显示项目进展

敏捷项目架构关注的问题

  • 应该做多少架构
  • 应该记录多少架构

Sweet Point 甜蜜点

  • 对于 10KSLOC(代码行数)项目,甜蜜点在最左边。 花费大量时间进行前期工作对于一个小型项目来说是一种浪费
  • 对于 100KSLOC 项目,甜蜜点约为项目进度的20%
  • 对于 10,000KSLOC 项目,甜蜜点约为项目进度的40%
  • 结论:
    • 如果是相对稳定的、易于理解需求的、分布式开发的大型复杂系统,需要大量架构工作
    • 对于需求不稳定的大型项目,从快速设计候选架构开始,即使它省略了许多细节
    • 对于不确定需求的小型项目,至少在采用的主要模式上需要达成一致意见,不要花费太多时间在架构设计、文档或分析

ASR 架构关键需求

定义

  • 对架构有深远影响
  • 具有很高的业务价值

收集 ASR 的方法

  • 需求文档收集
  • 采访利益相关者
  • 举行 QAW(Quality attribute workshop 质量属性研讨会)
  • 通过理解业务目标获取(PALM)
  • 效用树获取

Utility Tree 效用树

效用树是一种用于记录 ASR 的方法

  • 首先需要确定每个 ASR 的优先级
  • 根节点是名为 Utility 的占位符节点
  • 第二层节点包含广泛的质量检查类别
  • 第三层节点用于细化这些类别

(业务价值,架构冲击) H=high, M=medium, L=low

Design Strategies 设计策略

  • 分解:整个系统被分解成多个部分,每个部分承担一定的质量属性要求
  • 设计 ASR:当设计无法满足 ASR 时,可以
    • 调整设计
    • 降低要求
    • 更改优先级
  • 生成和测试

ADD 属性驱动设计 attribute driven design

ADD 步骤

  1. 选择系统的一个元素来设计
  2. 识别选取元素的 ASR
  3. 为所选的元素生成设计方案
  4. 清点未满足的需求,并从中选取下次迭代的输入
  5. 重复 1- 4 直至所有 ASR 被满足

ADD 输入输出

输入

  • 功能
  • 质量
  • 约束

输出

  • 信息流
  • 交互
  • 责任

架构评估

三种评估形式

  • 设计过程中由设计者评估
  • 设计过程中由同行评估
  • 设计完成后由外部评估

ATAM

Architecture Tradeoff Analysis Method 架构权衡评估方法

参与人

  • 评估小组
  • 项目决策人
  • 利益相关者

输出

  • 简明的架构介绍
  • 明确的业务目标
  • 以质量属性场景表示的、已经划分了优先级的质量属性需求
  • 一组风险点和非风险点
  • 一组风险主题
  • 一组敏感点和权衡点

步骤

  1. 展示与介绍 ATAM 相关事项,如步骤、过程、结果等
  2. 说明业务,如系统重要功能、约束、干系人、目标与背景、ASR
  3. 展示架构,如解释说明重要的质量属性问题
  4. 标识架构方法,如架构模式和战术
  5. 生成效用树。评估小组与项目决策者共同识别、排序和完善系统的重要质量属性目标
  6. 分析架构方法。识别风险点、非风险点、敏感点、权衡点
  7. 头脑风暴 & 场景优先级排序。通过头脑风暴收集场景,通过投票确定优先级。将结果与效用树进行比较,若不一致则说明系统要实现的目标存在分歧
  8. 分析架构方法。与第 6 步类似,但是使用的是最新的方案
  9. 展示结果

示例

轻量级架构评估 lightweight architecture evaluation

一个 ATAM 过程通常需要 20-30 天,只适用于大型昂贵的项目。所以发展出了轻量级架构评估,一般只需要一天甚至半天,只涉及组织内部的人员。这个评估方法不产生最终报告,而是由抄写员负责收集结果。只有组织内部成员来评估可能得出不客观的结果,缺乏创新和讨论。但是这个评估方法快速廉价,所以可以被快速部署,无论项目是否需要关于架构质量保证的合理性检查(sanity check)

特点

  • 按需供给
  • 资源池。资源池可以提高性能
  • 网络访问便捷、范围广
  • 性能具备弹性,可以自己伸缩
  • 让开发者更专注于业务本身,资源消费者不需要关注资源所在的位置
  • 节省成本,一是运维成本,另一方面是因为弹性原因,消费者只需要支付自己使用部分的金额
  • 便于监视、查看资源使用情况

服务模型

Software as a Service (SaaS).

  • 消费者是一个终端用户
  • 消费者使用恰好在云上运行的应用程序
  • 示例:e-mail

Platform as a Service (PaaS)

  • 为用户提供在云上开发和部署应用程序的编程语言和工具
  • 消费者是一个开发人员
  • 示例:谷歌 App 引擎,微软 Azure

Infrastructure as a Service (IaaS)

  • 为了提供处理、存储、网络和其他基本计算资源,消费者能够部署和运行任意软件,其中可以包括操作系统和应用程序
  • 在这种情况下,消费者是开发人员或系统管理员
  • 示例:亚马逊 EC2

部署模型

  • 公有云:云基础设施向公众开放,并由销售云服务的组织拥有
  • 私有云:云基础设施仅由单一组织拥有,并仅为该组织拥有的应用程序运行
  • 社区云:云基础设施由几个组织共享,并支持一个有共同关注点的特定社区
  • 混合云:云基础设施由两个或更多种类的云组成

云环境下的架构

质量属性

性能

  • 负载平衡。负载平衡是为了跨多个计算资源分配工作负载,以避免单个资源的过载
  • 弹性缩放是一种方法,其中计算资源的数量,通常根据负载自动缩放

可获得性

故障是云计算中常见的情况。云提供商确保云本身将保持可用,但有一些例外

  • 利用冗余来多次部署所有的服务:我们可以用两个服务器,两个负载均衡器,两个交换机,两个防火墙
  • HDFS(分布式文件系统)

安全性

多租户带来了对非云环境的额外关注

  • 无意的信息共享
  • 虚拟机转义(脱离虚拟机并与主机操作系统交互)
  • 拒绝服务攻击(一个用户可以通过消耗主机服务器的资源,以拒绝其他用户的使用)

去年对我而言是非常特殊的一年,也是从去年的暑假开始,我正式决定要走 web 前端的路。

去年主要的学习时间都放在了前端的知识点,包括 html、css、js、webpack5、node.js、react hooks 等,但是感觉好像目前还没有哪个方向研究得比较深入。前端的知识体系庞大复杂,而且涉及到的计算机基础知识也很多,让人很容易迷失学习方向。但是通过这半年来的逐渐摸索,我也大概知道了一些主流的技术栈以及学习的方向,这算是一件让人高兴的事情。不过去年最高兴的事情还是恢复了单身(

今年要开始找实习了,寒假也马上要到来了,在这画画饼做个寒假计划:

  • 复习前端三件套,熟悉 ES6+ 语法
  • 复习计网知识
  • 复习浏览器工作原理
  • 争取看完《代码随想录》
  • 复习 web 性能优化方法
  • 复习 react hooks + redux 的使用
  • 复习 node.js 基础
  • 复习数据库基础
  • 复习 os 基础
  • 学习重点的 react hooks 源码
  • 学习 redux 和 react-router 实现原理
  • 学习 webpack 及其重要原理
  • 熟悉 koa.js 使用并学习部分源码
  • 用 react hooks + koa.js 做一个有一定难度的小程序或者网页

备选:

  • 学习 three.js 基础
  • 学习 nest.js 使用

最后,新的一年,最大的愿望就是身边的人和我都能快快乐乐的!

前端需要注意的 SEO 优化

  • 合理使用 title、description、keywords:title 值强调重点即可,重要关键词出现不要超过2次,而且要靠前,不同页面 title 要有所不同;description 长度合适,不可过分堆砌关键词,不同页面 description 有所不同;keywords 列举出重要关键词即可
  • 使用语义化的 HTML 代码,让搜索引擎更容易理解网页
  • 重要内容 HTML 代码放在最前:搜索引擎抓取 HTML 顺序是从上到下,有的搜索引擎对抓取长度有限制,保证重要内容一定会被抓取
  • 重要内容不要用 js 输出,因为爬虫不会获取 js 添加的内容
  • 少用 iframe,因为搜索引擎不会抓取 iframe 中的内容
  • 非装饰性图片必须加 alt
  • 提高网站速度,因为网站速度是搜索引擎排序的一个重要指标

行内元素、块级元素、空元素有哪些

行内:span, a, label 不独占一行,不能设置宽高
块级:div, footer, header, section, p, h1-h6 独占一行,可以设置宽高
空元素:br, hr 不独占一行,可以设置宽高

标签语义化的理解

  • HTML 语义化就是让页面的内容结构化,便于对浏览器解析,便于搜索引擎捕获,可以优化 SEO
  • 提高代码可读性

HTML5 的新特性

  • 新增选择器 document.querySelector、document.querySelectorAll
  • 拖拽释放(Drag and drop) API
  • 媒体播放的 video 和 audio
  • 本地存储 localStorage 和 sessionStorage
  • 离线应用 manifest
  • 语义化标签 article、footer、header、nav、section
  • 跨域资源共享(CORS) Access-Control-Allow-Origin
  • canvas

浏览器是怎么对 HTML5 的离线储存资源进行管理和加载的

  • 在线的情况下,浏览器发现 html 头部有 manifest 属性,它会请求 manifest 文件,如果是第一次访问 app,那么浏览器就会根据 manifest 文件的内容下载相应的资源并且进行离线存储。如果已经访问过 app 并且资源已经离线存储了,那么浏览器就会使用离线的资源加载页面,然后浏览器会对比新的 manifest 文件与旧的 manifest 文件,如果文件没有发生改变,就不做任何操作,如果文件改变了,那么就会重新下载文件中的资源并进行离线存储
  • 离线的情况下,浏览器就直接使用离线存储的资源

cookie,sessionStorage 和 localStorage 的区别

  • cookie 数据始终在同源的 http 请求中携带(即使不需要),也就是说它会在浏览器和服务器之间来回传递;sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存
  • cookie 数据大小不能超过 4k;sessionStorage 和 localStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 甚至更大
  • localStorage 存储持久数据,浏览器关闭后数据不丢失除非人为主动删除数据;sessionStorage 数据在当前浏览器窗口关闭后自动删除;cookie 设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭

iframe 的缺点

  • iframe 会阻塞主页面的 onload 事件
  • 搜索引擎的检索程序无法解读这种页面,不利于 SEO

src 与 href的区别

src 用于替换当前元素,href 用于在当前文档和引用资源之间确立联系

  • src 是 source 的缩写,指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置;在请求 src 资源时会将其指向的资源下载并应用到文档内,例如 js 脚本,img 图片和 iframe 等元素
  • href 是 Hypertext Reference 的缩写,指向网络资源所在位置。如果我们在文档中添加 <link href="common.css" rel="stylesheet"/> 那么浏览器会识别该文档为 CSS 文件,然后并行下载资源并且不会停止对当前文档的处理。这也是为什么建议使用 link 方式来加载 CSS,而不是 @import 方式