0%

RAIL模型

使用 RAIL 模型衡量性能 即对于应用的 相应 — 动画 — 空闲 — 加载 四种不同场景,用户会对性能有不一样的期望

以用户为中心的性能指标

  • First Paint 首次绘制(FP) 记录从空白页到任意像素的呈现所需时间
  • First contentful paint 首次内容绘制 (FCP) 首次页面加载完成
  • Largest contentful paint 最大内容绘制 (LCP) 任意访问和交互过程中 呈现内容所需的最长时间
  • First input delay 首次输入延迟 (FID) 从第一次交互到第一次响应
  • Time to Interactive 可交互时间 (TTI) 衡量页面从内容渲染完成到可以相应用户交互所需时间
  • Total blocking time 总阻塞时间 (TBT) 即FCP与TTI之间的时差
  • Cumulative layout shift 累积布局偏移 (CLS) 这是一个评分 衡量页面出现意外的加载内容偏移的程度

页面渲染的过程

优化

http2

在你的应用中安装sdk向Azure Application Insight服务发送遥测数据,支持web客户端,web服务和后台服务等
对应用性能影响小。非阻塞,单独线程

The instrumentation monitors your app and directs the telemetry data to an Azure Application Insights Resource using a unique GUID that we refer to as an Instrumentation Key. You can instrument not only the web service application, but also any background components, and the JavaScript in the web pages themselves. The application and its components can run anywhere - it doesn’t have to be hosted in Azure. 使用Guid作为Instrumentation Key(ikey)标识作为监视器的Azure Insights资源。 监测web服务应用或后台组件亦或页面js 这些应用或组件、命令不必托管在Azure中

创建Azure App Insights资源

Azure Portal -> Application Insights -> Create

添加Javascript SDK

1
npm i --save @microsoft/applicationinsights-web

使用ikey或连接字符串创建客户端instance

1
2
3
4
5
6
7
8
9
import { ApplicationInsights } from '@microsoft/applicationinsights-web'

const appInsights = new ApplicationInsights({ config: {
instrumentationKey: 'YOUR_INSTRUMENTATION_KEY_GOES_HERE' /* 使用ikey */
/* 或使用 connectionString: 'YOUR_CONNECTION_STRING_GOES_HERE' */
/* ...Other Configuration Options... */
} });
appInsights.loadAppInsights();
appInsights.trackPageView(); // Manually call trackPageView to establish the current user/session/pageview

React Plugin
1
2
npm install @microsoft/applicationinsights-react-js
npm install @microsoft/applicationinsights-web

创建实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// AppInsights.js
import { ApplicationInsights } from '@microsoft/applicationinsights-web';
import { ReactPlugin } from '@microsoft/applicationinsights-react-js';
import { createBrowserHistory } from 'history';

const browserHistory = createBrowserHistory({ basename: '' });
const reactPlugin = new ReactPlugin();
const appInsights = new ApplicationInsights({
config: {
instrumentationKey: 'YOUR_INSTRUMENTATION_KEY_GOES_HERE',
extensions: [reactPlugin],
extensionConfig: {
[reactPlugin.identifier]: { history: browserHistory }
}
}
});
appInsights.loadAppInsights();
export { reactPlugin, appInsights };

在AppComponent使用Context.Provider 使所有子组件内可使用 useContext hook调用AppInsights实例
1
2
3
4
5
6
7
8
9
10
11
import React from "react";
import { AppInsightsContext } from "@microsoft/applicationinsights-react-js";
import { reactPlugin } from "./AppInsights";

const App = () => {
return (
<AppInsightsContext.Provider value={reactPlugin}>
/* your application here */
</AppInsightsContext.Provider>
);
};

子组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from "react";
import { useAppInsightsContext } from "@microsoft/applicationinsights-react-js";

const MyComponent = () => {
const appInsights = useAppInsightsContext();

appInsights.trackMetric("Component 'MyComponent' is in use");
appInsights.trackEvent({ name: 'Component Init', properties: { 'mydata' } });

return (
<h1>My Component</h1>
);
}
export default MyComponent;

useTrackMetric

1
2
3
4
5
6
7
8
const MyComponent = () => {
const appInsights = useAppInsightsContext();
const trackComponent = useTrackMetric(appInsights, "MyComponent");

return (
<h1 onHover={trackComponent} onClick={trackComponent}>My Component</h1>
);
}

useTrackEvent

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
import React, { useState, useEffect } from "react";
import { useAppInsightsContext, useTrackEvent } from "@microsoft/applicationinsights-react-js";

const MyComponent = () => {
const appInsights = useAppInsightsContext();
const [cart, setCart] = useState([]);
const trackCheckout = useTrackEvent(appInsights, "Checkout", cart);
const trackCartUpdate = useTrackEvent(appInsights, "Cart Updated", cart);
useEffect(() => {
trackCartUpdate({ cartCount: cart.length });
}, [cart]);

const performCheckout = () => {
trackCheckout();
// submit data
};

return (
<div>
<ul>
<li>Product 1 <button onClick={() => setCart([...cart, "Product 1"])}>Add to Cart</button></li>
<li>Product 2 <button onClick={() => setCart([...cart, "Product 2"])}>Add to Cart</button></li>
<li>Product 3 <button onClick={() => setCart([...cart, "Product 3"])}>Add to Cart</button></li>
<li>Product 4 <button onClick={() => setCart([...cart, "Product 4"])}>Add to Cart</button></li>
</ul>
<button onClick={performCheckout}>Checkout</button>
</div>
);
}

export default MyComponent;

Click Analytics Auto-collection plugin

自动跟踪网页上的单击事件,并使用 HTML 元素上的 data-* 属性来填充事件遥测数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { ApplicationInsights } from '@microsoft/applicationinsights-web';
import { ClickAnalyticsPlugin } from '@microsoft/applicationinsights-clickanalytics-js';

const clickPluginInstance = new ClickAnalyticsPlugin();
// Click Analytics configuration
const clickPluginConfig = {
autoCapture: true
};
// Application Insights Configuration
const configObj = {
instrumentationKey: "YOUR INSTRUMENTATION KEY",
extensions: [clickPluginInstance],
extensionConfig: {
[clickPluginInstance.identifier]: clickPluginConfig
},
};

const appInsights = new ApplicationInsights({ config: configObj });
appInsights.loadAppInsights();

监视对象

  • User 用浏览器cookie中存储的匿名id区分用户
    JavaScript SDK 自动生成匿名用户和会话 ID,然后在从应用发送这些 ID 后使用这些 ID 填充遥测事件。
  • Session 不活动半小时重新记Session 活动24h后重新记Session
  • Event 每次执行trackEvent逻辑 Event参数已加入一组标准属性,包括匿名用户id(anonymous user ID)QQs存疑!

    其他

    cookie处理 visit time

简写为SOLID

单一职责(Sigle Responsibility Principle)

避免设计对象(类,方法)承担多项职责,在对职责1操作或修改时可能会造成职责2的异常。

  • 在需要if else时考虑划分为两个类或方法
  • 在需求变更导致职责细分时考虑将类或方法划分

开放关闭(Open Closed Principle)

设计对象应该根据需求被扩展,而不应该因需求而做修改,即对类进行抽象和继承
违反开放关闭原则:

1
2
3
4
5
6
7
8
9
10
11
class Factory {
public Computer produceComputer(String type) {
Computer c = null;
if(type.equals("macbook")){
c = new Macbook();
}else if(type.equals("surface")){
c = new Surface();
}
return c;
}
}

抽象出produce接口
1
2
3
4
5
6
7
8
9
10
11
12
13
interface Factory {
public Computer produceComputer();
}
class AppleFactory implements Factory {
public Computer produceComputer() {
return new Macbook();
}
}
class MSFactory implements Factory {
public Computer produceComputer() {
return new Surface();
}
}

里氏替换(Liskov Substitution Principle)

所有引用基类的地方必须能透明地使用其子类的对象。

A的子类B继承父类时,不应改变原功能,只做扩展

接口隔离(Interface Segregation Principle)

合理设计接口的粒度,避免类型依赖它不需要的接口

依赖倒置(Dependency Injection Principle)

高层模块不应依赖低层模块,以免因为低层修改而牵扯高层,两者应该共同依赖接口,将改动限制在接口的实现上
即最少知道,类型对其直接引用的对象A保持最少的了解,不会通过该对象与第三者对象B建立间接的调用关系,要求A将必要的方法封装为public提供外部调用,而不暴露其引用了B的事实

23种设计模式

  • 触发器是一类存储过程
  • 由数据表的事件(如insert update delete)触发,而不是手动调用

登录触发器

官网例子 场景:如果登录名login_test 已经创建了三个用户会话,触发器将拒绝该用户的登录尝试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
USE master;  
GO
CREATE LOGIN login_test WITH PASSWORD = N'3KHJ6dhx(0xVYsdf' MUST_CHANGE,
CHECK_EXPIRATION = ON;
GO
GRANT VIEW SERVER STATE TO login_test;
GO
CREATE TRIGGER connection_limit_trigger
ON ALL SERVER WITH EXECUTE AS N'login_test'
FOR LOGON
AS
BEGIN
IF ORIGINAL_LOGIN()= N'login_test' AND
(SELECT COUNT(*) FROM sys.dm_exec_sessions
WHERE is_user_process = 1 AND
original_login_name = N'login_test') > 3
ROLLBACK;
END;

可知登录触发器在身份认证之后,建立会话之前触发
多个触发器的顺序,即支持指定the first和the last 见Microsoft Docs

DDL

data define language 数据定义语言
即在使用会改变数据库数据结构的语句时触发,如CREATE、ALTER、DROP、GRANT、DENY、REVOKE 或 UPDATE STATISTICS 开头的 Transact-SQL 语句
场景

  • 防止对数据库架构进行某些更改。
  • 希望数据库中发生某种情况以响应数据库架构的更改。
  • 记录数据库架构的更改或事件。

    DML

    data manipulation language (DML) 数据操作语言 INSERT、UPDATE 或 DELETE 语句
    after 触发器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    CREATE TRIGGER schemaA.SyncData 
    ON schemaA.TableA
    AFTER INSERT
    AS
    BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.

    -- Insert statements for trigger here
    insert into schemaB.TableB(Name,TrustedId,EmailAddress,Logo,Type,Enable,RecordStatus,RecordCreated,RecordLastUpdated)
    select name,record_pk,email,logo,'1',0,0,SYSDATETIME(),SYSDATETIME() from schemaA.TableA
    SET NOCOUNT ON;
    END
    GO
    instead of 触发器

  1. DOCTYPE 有什么作用?怎么写?
    1
    <!DOCTYPE html>
    html5规范的文档声明,示意浏览器以相应的标准解析文档,使支持html5规范如新的标签等
  2. 列出常见的标签,并简单介绍这些标签用在什么场景?
    canvas
  3. 页面出现了乱码,是怎么回事?如何解决?
    1
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
    可能是即字符编码不匹配造成
  4. title 属性和 alt 属性分别有什么作用?
  5. HTML 的注释怎样写?
  6. data- 属性的作用?
    自定义数据属性data-*
    1
    2
    var box = document.getElementById("box"); // <div id="box" data-user-name="QQs"></div>
    var username = box.dataset.userName;
  7. \ 的 title 和 alt 有什么区别?
  8. Web 标准以及 W3C 标准是什么? web标准
  9. HTML 全局属性(Global Attribute)有哪些?
    id class style title name data-* contenteditable translate
  10. meta 有哪些常见的值?charset
  11. meta viewport 是做什么用的,怎么写?\
  12. 列出常见的标签,并简单介绍这些标签用在什么场景?
  13. 如何在 HTML 页面上展示
    这几个字符?
  14. 你是如何理解 HTML 语义化的?
  15. 前端需要注意哪些 SEO?

VBoxManage

ubuntu可ssh远程用此命令行工具管理虚拟机

  • VBoxManage list vms/runningvms
  • VBoxManage startvm MyUbuntu
    1
    vboxmanage startvm MyUbuntu --type headless #在宿主机端隐藏图形界面 
  • VBoxManage controlvm MyUbuntu poweroff

OData和GraphQL

用了微软家的OData,就不得不再看一遍GraphQL,两者都让前端调用api获得了很大的自由度
相比OData的底层渗透性,GraphQL只在Http接口层做文章,实际上将底层模型封装成schema,开放给接口,这个过程更大程度上做到了安全性和前端业务的可控。

Quick Start

见官方GraphQL Doc:各种语言实现

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
var express = require('express');
var {graphqlHTTP} = require('express-graphql');
var { buildSchema } = require('graphql');

var schema = buildSchema(`
type Query {
heros: [Hero]
battle(name:String): String
random: Float!
}
type Hero{
name:String
abilities: [String]
}
`);

var root = { heros: () => ([
{
name: 'Luke skywalker',
abilities: ['light sward', 'force']
},
{
name: 'Anakin skywalker',
abilities: ['light sward', 'force', 'dark']
}
]),
battle: (name) => {
return 'I am your father';
},
random: () => {
return Math.random();
}
};

var app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
app.listen(3000, () => console.log('Now browse to localhost:3000/graphql'));

关于graphqlHTTP(也就是GraphiQL客户端)的Options:
schema是查询涉及的类型声明, rootValue api的查询方法的集,详见下文章节

schema 类型声明

1
2
3
4
5
6
7
var schema = buildSchema(`
type Hero {
name: String!
abilities: [String!]!
length(unit: LengthUnit = METER): Float
}
`);

标量类型

  • ID
  • String
  • Boolean
  • Int Float
    注意 !表示非空
    枚举
    1
    2
    3
    4
    5
    enum Episode {
    NEWHOPE
    EMPIRE
    JEDI
    }

接口和实现接口的类型

1
2
3
4
5
6
7
8
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}

联合类型

1
union SearchResult = Human | Droid | Starship

操作类型

  • query 查询:获取数据、查找
  • mutation 变更:对数据进行变更,比如增加、删除、修改
  • substription 订阅:当数据发生更改,进行消息推送

GraphQL client

前面的express-graphQL启动后打开GraphiQL页面,GraphiQL就是一个客户端,使用GraphQL client向 GraphQL 服务器上的入口端点发送一个 HTTP POST 请求,其中将 GraphQL 查询作为 JSON 载荷的 query 字段,就能调用 GraphQL 服务器。
其js实现大致是

1
2
3
4
5
6
7
8
9
10
fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({query: "{ hello }"})
})
.then(r => r.json())
.then(data => console.log('data returned:', data));

查询参数

schema 中的 Query声明了若干查询方法,查询方法的具体实现在root中定义,其参数类型的指定格式与typescript相同!
调用格式如下

1
2
3
{
battle(name:"dark lord")
}

template

vue和angular都鼓励开发者编写尽可能与常规Html相似的template,react却并不是,react则崇尚all in js. 不过从编译角度看,两种方式都是语法糖而已,无论编码风格,编译后都是一个可执行函数

render principle

了解Angular Ivy: Incremental DOM 和 Virtual DOM

ecosystem

react生态庞杂,官方定义了基架,各种具体场景的实现技术在社区百花齐放,各路大神乐于造自己的轮子,库选型劝退选择恐惧症。。。
angular:桶里啥都有。。。

https://www.canva.com/
这个网站的404是一个拼图游戏,纯html img + css以及拖动实现