0%

capture full size screenshot

  1. F12
  2. Ctrl + Shift + P
  3. type in capture full size screenshot

dom process tips

FreeCodeCamp:你知道 Chrome 自带的开发者工具有这些功能吗

  • $$(‘.className’) chrome自带元素选择器
  • 将页面作为文本进行编辑
    1
    document.body.contentEditable = true;
  • 获取事件以及监听事件
  • console.time()
    1
    2
    3
    4
    console.time('heavy process')
    console.timeLog('heavy process', 'step1 finished')
    console.timeLog('heavy process', 'step2 finished')
    console.timeEnd('heavy process')
  • console.table(array)

    使用chrome 模拟加载较慢网速

  1. F12
  2. Network Tab
  3. Change “Online” to “Slow 3G”

    页面元素断点

    第三方UI控件会动态添加元素事件,使用css的hover,focus无法触发,把光标放上去触发又无法查看code,设置element的breakpoint可以在指定元素被修改时break,无论查看js逻辑还是元素的样式变化都很方便

    headless chrome

    知乎:Headless Chrome入门 即不显示浏览器界面而在命令行运行Chrome功能,该模式主要用于自动化测试工具#### 响应式
    使用 Chrome DevTools 中的 Device Mode 模拟移动设备

performance

current version: 6.5.2

关于响应式编程

知乎:响应式编程

Observable 的发布和订阅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Create simple observable that emits three values
const myObservable = of(1, 2, 3); // or from([1,2,3])

// Create observer object
const myObserver = {
next: x => console.log('Observer got a next value: ' + x),
error: err => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
};

// Execute with the observer object
myObservable.subscribe(myObserver);
// Logs:
// Observer got a next value: 1
// Observer got a next value: 2
// Observer got a next value: 3
// Observer got a complete notification

of(…items) from(iterable)是自带常用产生Observable对象的工具

Observable 可观察对象

在http请求中,可观察对象不是new构造的,而是由httpclient的get或post方法返回一个Observable\. 私以为是成功调用接口后就发布一个结果,这个结果可以用管道加工

参考 Observable 的操作符

1
2
3
4
5
6
7
8
9
10
11
getDict(dictname): Observable<DictItem[]> {
return this.http.get(`dict/${dictname}`).pipe(
map((res: HttpResponse) => {
if (res.status === 'ok') {
return res.data;
} else {
return [];
}
}),
);
}

获取这个结果就需要订阅(subscribe)这个Observable,这个Observable是匿名的,每次获取它需要调用函数来返回

QQs:为什么httpclient方法不需要取消订阅

据说这些方法被实现为只next一次

Subject

A Subject is a special type of Observable which shares a single execution path among observers.

被比喻成广播者

1
2
3
4
5
6
7
const subject = new Subject();

subject.subscribe(log('s1 subject'));
subject.subscribe(log('s2 subject'));

subject.next('r');
subject.next('x');

同步数据转Observable

场景:组件粒度小,在视图中多次实例化,或者重复初始化,为防止频繁调用后台接口应加入数据缓存,
基础数据缓存实现为,第一次调用,从httpclient获取接口数据的Observable对象,并用管道处理加入缓存,之后将缓存数据取出转为Observable对象
basicdata.service.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Observable, of, from } from 'rxjs';
import { map } from 'rxjs/operators';

users: User[];
getUserList(): Observable<User[]> {
if (this.users) {
return of(this.users);
} else {
return this.http.post(`user/search`, { isvalid: 1 }).pipe(
map((res) => {
this.users = res as User[];
return this.users;
})
);
}
}

app.component.ts
1
2
3
4
this.basicData.getUserList().subscribe(list => {
this.userlist = list;
this.getUserName();
});

Promise转Observable

起初为了用async await编码以及利用链式调用,很多异步操作封装成了Promise,Promise转Observable用from方法转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Observable, of, from } from 'rxjs';
import { map } from 'rxjs/operators';

getCurrentUserToken(): Observable<any> {
if (this.currentUser) {
return of(this.currentUser);
} else {
return from(this.ipcService.call('currentuser')).pipe(
map(arg => {
if (arg) {
return typeof arg === 'string' ? JSON.parse(arg) : arg;
} else {
return null;
}
})
);
}
}

Observable的链式调用

操作符

操作符实在太多了

实现一个乘10的operator

1
2
3
4
5
6
7
8
9
function multiplyByTen(input: Observable<any>): Observable<any> {
return Rx.Observable.create(observer => {
input.subscribe({
next: (v) => observer.next(10 * v),
error: (err) => observer.error(err),
complete: () => observer.complete()
});
});
}

创建操作符 of from interval等:

from

转化Promise对象、类数组对象、迭代器对象转化为 Observables
将数组转化为 Observable

1
2
3
4
5
6
var array = [10, 20, 30];
var result = Rx.Observable.from(array);
result.subscribe(x => console.log(x));

// 结果如下:
// 10 20 30

将一个无限的迭代器(来自于 generator)转化为 Observable。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* generateDoubles(seed) {
var i = seed;
while (true) {
yield i;
i = 2 * i; // double it
}
}

var iterator = generateDoubles(3);
var result = Rx.Observable.from(iterator).take(10);
result.subscribe(x => console.log(x));

// Results in the following:
// 3 6 12 24 48 96 192 384 768 1536

转化操作符 map mapTo merge mergeMap等:

map类似于Array.prototype.map投射函数应用于每个值;mapTo相当于忽略实际订阅接受结果,替换为指定值;

merge将多个订阅捋直成一个订阅;mergeMap将投射函数应用于每个值,并将多个订阅捋直(啥?不懂)

take
filter
tap

Perform a side effect for every emission on the source Observable, but return an Observable that is identical to the source.对源可观察对象的每个‘发射’应用一个副作用,但仍然返回与源相同的可观察对象

1
2
3
4
tap<T>(nextOrObserver?: NextObserver<T> | ErrorObserver<T> | CompletionObserver<T> | (
(x: T) => void),
error?: (e: any) => void,
complete?: () => void): MonoTypeOperatorFunction<T>

参数可以是可观察对象或回调方法
常见于附上一个log操作
1
2
3
4
5
6
7
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(heroes => this.log(`fetched heroes`)),
catchError(this.handleError('getHeroes'))
) as Observable<Hero[]>;
}

BehaviorSubject

Subject 的作用是实现 Observable 的多播。由于其 Observable execution 是在多个订阅者之间共享的,所以它可以确保每个订阅者接收到的数据绝对相等。不仅使用 Subject 可以实现多播,RxJS 还提供了一些 Subject 的变体以应对不同场景,那就是:BehaviorSubject、ReplaySubject 以及 AsyncSubject。

BehaviorSubject 的特性就是它会存储“当前”的值。这意味着你始终可以直接拿到 BehaviorSubject 最后一次发出的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const subject = new Rx.BehaviorSubject(Math.random());

// 订阅者 A
subject.subscribe((data) => {
console.log('Subscriber A:', data);
});

subject.next(Math.random());

// 订阅者 B
subject.subscribe((data) => {
console.log('Subscriber B:', data);
});

subject.next(Math.random());

toPromise deprecated

Rxjs v8版本后toPromise方法弃用, 原因基于Observable与Promise前后返回值不一致的issue
RxJS heads up: toPromise is being deprecated

曾常使用的

1
2
3
4
5
public async loadCategories() {
this.categories = await this.inventoryService
.getCategories()
.toPromise()
}

变更为
1
2
3
4
5
6
import { lastValueFrom } from 'rxjs';
...
public async loadCategories() {
const categories$ = this.inventoryService.getCategories();
this.categories = await lastValueFrom(categories$);
}

rxjs 6中被废弃的toPromise

The lastValueFrom is almost exactly the same as toPromise() meaning that it will resolve with the last value that has arrived when the Observable completes, but with the difference in behavior when Observable completes without emitting a single value. When Observable completes without emitting, toPromise() will successfully resolve with undefined (thus the return type change), while the lastValueFrom will reject with the EmptyError. Thus, the return type of the lastValueFrom is Promise, just like toPromise() had in RxJS 6.lastValueFrom几乎与toPromise() 一个意思,在Observable complete的时候,lastValueFrom会带着Observable最后一个产生的值resolve,但是不同之处在于Observable不带值complete的情况下。
当Observable不产生值并且complete时,toPromise方法会成功resolve为undefined(也就是返回类型改变了,不再是T),但是lastValueFrom会带着EmptyErrorreject。因此,就如同在RxJS 6里一样,它的返回类型是Promise

However, you might want to take the first value as it arrives without waiting an Observable to complete, thus you can use firstValueFrom. The firstValueFrom will resolve a Promise with the first value that was emitted from the Observable and will immediately unsubscribe to retain resources. The firstValueFrom will also reject with an EmptyError if the Observable completes with no values emitted.然而,你可能不等Observable complete,想要使用其第一个生产的值,因此可以用firstValueFrom。firstValueFrom会将Observable第一个生产的值resolve并且立刻unsubscribe保存该值。firstValueFrom也会在Observable没有值产生而complete的情况下带着 EmptyErrorreject。

风险一 用户数据库泄露导致用户密码泄露

风险二 网站管理人员、数据库管理员获取用户密码

如何安全的存储密码
要点如下

  • 加密(对称加密)存储无法防止风险二
  • 通用的以hash(单向散列算法如md5)获取的 digest(摘要)无法还原完整的原数据,但是存在rainbow table的隐患

rainbow table: 多数人所使用的密码为常见的组合,攻击者可以将所有密码的常见组合进行单向哈希,得到一个摘要组合, 然后与数据库中的摘要进行比对即可获得对应的密码(QQs理解的是,这里的密码或许与用户密码并不完全一致,但是可以通过网站的摘要核对)。这个摘要组合也被称为rainbow table。在当今计算机处理能力下,这种破译方式是可行而且可取的。

在线彩虹表

明文 摘要(32bit MD5)
111111 96e79218965eb72c92a549dd5a330112
123456 E10ADC3949BA59ABBE56E057F20F883E
000000 670b14728ad9902aecba32e22fa4f6bd
  • Salted Hash 改进的单项hash算法,明文密码混入“随机因素“,然后进行单向哈希后存储,其结果是rainbow table因salt不同而不同

    QQs按:做到这个份上,应该足够安全了,然而据说随着显卡并行计算能力的发展,存在破解出rainbow table的可能,我想可能是暴力破解salt。因此有了Salted Hash的各种改进方案

  • 慢哈希 如bcrypt,迭代执行多次hash运算,现实是增加了计算时间,使得暴力破解实际不可行

如设置 bcrypt 计算一次需要 0.5 秒,遍历 6 位的简单密码,需要的时间为:((26 * 2 + 10)^6) / 2 秒,约 900 年。

bcrypt

文章略老,基于以上原理,单向hash算法有了更多的发展

docker build

Dockerfile是构建docker的脚本,docker Hub很多应用容器以Dockerfile方式分享。

1
docker build -t tomcat8.5.51 .

命令格式 docker build [option] [url]

  • 选项-t tomcat8.5.51将build完成的镜像命名为tomcat8.5.51
  • . 是当前目录,即执行当前目录下的Dockerfile

执行完成后可在 docker images中查到tomcat8.5.51

docker compose

Compose 定位是 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)」

我们知道使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如,要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。

Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

安装docker-compose

1
2
3
4
$ sudo curl -L "https://github.com/docker/compose/releases/download/v2.17.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

$ sudo chmod +x /usr/local/bin/docker-compose
$ sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

菜鸟教程

使用docker为某项开发封装开发环境(存目)

Node.js 官方文档 C++ Addon

基于Node.js c++ Addons机制对接动态链接库,以实现现有Node.js接口外的底层访问

“Chromium和Node.js大部分的代码都是用c++实现的,所以理所当然地也可以用C++为它们开发插件。” ——— <给electron做c开发的那些坑>

node-gyp

GYP is short for ‘Generate Your Projects’,顾名思义,GYP工具用于生成在相应平台上的项目,如在windows平台上生成Visual Studio解决方案(.sln), Mac下则是XCode项目配置以及Scons工具。

node-gyp is a cross-platform command-line tool written in Node.js for compiling native addon modules for Node.js. It contains a fork of the gyp project that was previously used by the Chromium team, extended to support the development of Node.js native addons.

node-gyp 用于编译nodejs原生addon模块的,跨平台的命令行工具,fork了Chromium team使用的gyp项目,该项目用户开发Node.js C++插件

1
2
npm install -g node-gyp
npm install --global --production windows-build-tools

Caution!安装过程中出现 issue#147 Hangs on Python is already installed, not installing again. 原因是VisualStudio有进程占用了相关工具链,结束VS进程并重新安装windows-build-tools即可

当前版本node-gyp使用 vs2017 build tools而不支持2019版本,下文中记述了workaround

python依赖

支持v2.7, v3.5, v3.6, or v3.7 v3.8 (QQs已实践2.7,3.8)

如果安装了多个版本 应使用下述命令注明python路径

1
node-gyp <command> --python /path/to/executable/python

抑或修改npm调用设置
1
npm config set python /path/to/executable/python

否则会调用默认版本(环境变量Path中指向的版本)

binding.gyp

1
2
3
4
5
6
7
8
{
"targets": [
{
"target_name": "hello",
"sources": [ "src/hello.cc" ]
}
]
}

关于gyp配置,但凡要比hello world走的远,都需要阅读下列文档

node-pre-gyp

node-pre-gyp makes it easy to publish and install Node.js C++ addons from binaries.

node-pre-gyp stands between npm and node-gyp and offers a cross-platform method of binary deployment.

Features :

  • 使用node-pre-gyp命令行工具安装依赖二进制C++模块(install your package’s C++ module from a binary)
  • 使用node-pre-gyp模块动态引入js模块 require(‘node-pre-gyp’).find
  • 其他开发命令如 test, publish (详见—help)

配置package.json

  • 依赖 + node-pre-gyp
  • 开发依赖 + aws-sdk
  • install命令脚本 node-pre-gyp install —fallback-to-build
  • 声明需要的二进制模块
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    "dependencies"  : {
    "node-pre-gyp": "0.6.x"
    },
    "devDependencies": {
    "aws-sdk": "2.x"
    }
    "scripts": {
    "install": "node-pre-gyp install --fallback-to-build"
    },
    "binary": {
    "module_name": "your_module",
    "module_path": "./lib/binding/",
    "host": "https://your_module.s3-us-west-1.amazonaws.com"
    }
    这就是为什么英国人的项目使用npm install —build-from-source来打包addon的原理

导学列表:

  • 编译一个hello QQs C++ Addon的实践
  • 可以交互的manager插件
  • 为什么CSActivation可以用 npm install —build-from-source 打包插件

参考 Electron-利用DLL实现不可能

编译方法

官方example

  • nan: C++-based abstraction between Node and direct V8 APIs.
  • napi: C-based API guaranteeing ABI stability across different node versions as well as JavaScript engines.
  • node-addon-api: header-only C++ wrapper classes which simplify the use of the C-based N-API.

文件结构

hello.cc

binding.gyp

package.json

编译后的二进制插件的文件扩展名是 .node

所有的 Node.js 插件必须导出一个如下模式的初始化函数:

1
2
void Initialize(Local<Object> exports);
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

使用 node-gyp 构建插件时,使用宏 NODE_GYP_MODULE_NAME 作为 NODE_MODULE() 的第一个参数将确保会将最终二进制文件的名称传给 NODE_MODULE()。

1
node-gyp configure

为当前平台生成相应的项目构建文件。 这会在 build/ 目录下生成一个 Makefile 文件(Unix 平台)或 vcxproj 文件(Windows平台)。

1
node-gyp build

生成编译后的 *.node 的文件。 它会被放进 build/Release/ 目录。

关于VS2019 找不到C++ build tool的问题

私以为比较好的workaround是创建一个用2017替代2019的shim,详见node-gyp issue#1663

经实践可行

关于rebuild ffi error的问题

electron-rebuild issue#308

需求:使用Crypt32加密(编码/解码)

1
npm i ffi-napi ref ref-struct

后面两个包是映射C语言类型的接口

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
const fs = require("fs");
const ref = require("ref");
const ffi = require("ffi-napi");
const Struct = require("ref-struct");

const DATA_BLOB = Struct({
cbData: ref.types.uint32,
pbData: ref.refType(ref.types.byte)
});
const PDATA_BLOB = new ref.refType(DATA_BLOB);
const Crypto = new ffi.Library('Crypt32', {
"CryptUnprotectData": ['bool', [PDATA_BLOB, 'string', 'string', 'void *', 'string', 'int', PDATA_BLOB]],
"CryptProtectData" : ['bool', [PDATA_BLOB, 'string', 'string', 'void *', 'string', 'int', PDATA_BLOB]]
});

function encrypt(plaintext) {
let buf = Buffer.from(plaintext, 'utf16le');
let dataBlobInput = new DATA_BLOB();
dataBlobInput.pbData = buf;
dataBlobInput.cbData = buf.length;
let dataBlobOutput = ref.alloc(DATA_BLOB);
let result = Crypto.CryptProtectData(dataBlobInput.ref(), null, null, null, null, 0, dataBlobOutput);
let outputDeref = dataBlobOutput.deref();
let ciphertext = ref.reinterpret(outputDeref.pbData, outputDeref.cbData, 0);
return ciphertext.toString('base64');
};

function decrypt(ciphertext) {
let buf = Buffer.from(ciphertext, 'base64');
let dataBlobInput = new DATA_BLOB();
dataBlobInput.pbData = buf;
dataBlobInput.cbData = buf.length;
let dataBlobOutput = ref.alloc(DATA_BLOB);
let result = Crypto.CryptUnprotectData(dataBlobInput.ref(), null, null, null, null, 0, dataBlobOutput);
let outputDeref = dataBlobOutput.deref();
let plaintext = ref.reinterpret(outputDeref.pbData, outputDeref.cbData, 0);
return plaintext.toString('utf16le');
};

let text = "有死之荣,无生之耻";
let ciphertext = encrypt(text);
let plaintext = decrypt(ciphertext);

console.log("text:", text);
console.log("ciphertext:", ciphertext);
console.log("plaintext:", plaintext);

以上代码白嫖自Github node-ffi Issue#355 comments from Wackerberg 侵删

ElementUI, a Vue 2.0 based component library

Caution! 限定的引入顺序:

UI.css —> template —> vue.js —> UI.js —> vue controller

其中 template 和 vue.js 交换没有影响

响应式栅格布局

基础栅格,基于24划分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<el-row>
<el-col :span="6">
<div class="grid-content bg-purple"></div>
</el-col>
<el-col :span="6">
<div class="grid-content bg-purple-light"></div>
</el-col>
<el-col :span="6">
<div class="grid-content bg-purple"></div>
</el-col>
<el-col :span="6">
<div class="grid-content bg-purple-light"></div>
</el-col>
</el-row>

间隔( :gutter )

栅格之间的水平间距
1
2
3
<el-row :gutter="20">
...
</el-col>

偏移( :offset )
1
<el-col :span="6" :offset="6"><div class="grid-content"></div></el-col>

弹性盒子( flex )
1
2
3
<el-row type="flex" justify="center" align="center">
...
</el-row>

水平值:start, center, end, space-between, space-around

垂直值:top, middle, bottom

响应式( Responsive )

1
2
3
<el-col :xs="4" :sm="6" :md="8" :lg="9" :xl="11">
<div class="grid-content"></div>
</el-col>

隐藏

  • hidden-xs-only - 当视口在 xs 尺寸时隐藏
  • hidden-sm-only - 当视口在 sm 尺寸时隐藏
  • hidden-sm-and-down - 当视口在 sm 及以下尺寸时隐藏
  • hidden-sm-and-up - 当视口在 sm 及以上尺寸时隐藏

    Form表单

    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
    <div id="app">
    <el-form ref="form" :model="formData" :rules="formValidators" label-width="80px">
    <el-form-item label="title" prop="title">
    <el-input v-model="formData.title"></el-input>
    </el-form-item>
    <el-form-item label="Date">
    <el-col :span="11">
    <el-date-picker type="date" placeholder="Pick a date" v-model="formData.date1" style="width: 100%;">
    </el-date-picker>
    </el-col>
    <el-col class="line" :span="2">-</el-col>
    <el-col :span="11">
    <el-time-picker placeholder="Pick a time" v-model="formData.date2" style="width: 100%;">
    </el-time-picker>
    </el-col>
    </el-form-item>
    <el-form-item label="Subscribe">
    <el-switch v-model="formData.isSubscribe"></el-switch>
    </el-form-item>
    <el-form-item label="TAG">
    <el-checkbox-group v-model="formData.tags">
    <el-checkbox label="Activity" name="type"></el-checkbox>
    <el-checkbox label="Bonus" name="type"></el-checkbox>
    <el-checkbox label="Online" name="type"></el-checkbox>
    </el-checkbox-group>
    </el-form-item>
    <el-form-item label="Comments">
    <el-input type="textarea" v-model="formData.Comments"></el-input>
    </el-form-item>
    <el-form-item>
    <el-button type="primary" @click="onSubmit">Create</el-button>
    <el-button>Cancel</el-button>
    </el-form-item>
    </el-form>
    </div>
    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
    var Main = {
    data() {
    return {
    formData: {
    title: '',
    date1: '',
    date2: '',
    isSubscribe: false,
    tags: [],
    Comments: ''
    },
    formValidators: {
    title: [{
    required: true,
    message: '请输入标题',
    trigger: 'blur'
    },
    {
    min: 3,
    max: 5,
    message: '长度在 3 到 5 个字符',
    trigger: 'blur'
    }
    ]
    }
    }
    },
    methods: {
    onSubmit(event) {
    this.$refs[formName].validate((valid) => {
    if (valid) {
    console.log(event);
    console.log(this.formData)
    } else {
    console.log('validate fail');
    return false;
    }
    });

    }
    }
    }
    var Ctor = Vue.extend(Main)
    new Ctor().$mount('#app')
    以上几乎就是官网的example,官方文档未给出submit事件触发form action的表单提交方式,api文档中未提及action属性。有资料表示存在“ this.$refs.formName.submit ”(QQs未验证)

    这种方式也造成了做表单校验时,要在提交方法中调用form.validate,form.validate又是个异步操作,后续动作(一般是http post)要放在其回调函数中

    而在我大Angular的响应式表单中,ngSubmit监听FormGroup当前值,在校验不通过时将禁用type=”submit”提交按钮

如上例所示,表单验证需添加表单的:rules属性,表单项的prop属性(注意在label,值同name),提交方法中调用form.validate

Table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<el-table :data="tableData" border style="width: 100%">
<el-table-column fixed prop="date" label="日期" width="150">
</el-table-column>
<el-table-column prop="name" label="姓名" width="120">
</el-table-column>
<el-table-column prop="address" label="地址" width="300">
</el-table-column>
<el-table-column prop="zip" label="邮编" width="120">
</el-table-column>
<el-table-column fixed="right" label="操作" width="100">
<template slot-scope="scope">
<el-button @click="handleClick(scope.row)" type="text" size="small">查看</el-button>
<el-button type="text" size="small">编辑</el-button>
</template>
</el-table-column>
</el-table>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var Main = {
data() {
return {
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-04',
name: '王大虎',
address: '上海市普陀区金沙江路 1517 弄',
zip: 200333
}]
}
},
methods: {
handleClick(row) {
console.log(row);
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')

比较有意思的是这个el-table-column label可以设置 多级表头

模板与绑定

1
2
3
4
5
6
7
<span>Message: {{ msg }}</span>
<span v-html="rawHtml"></span>
<div v-bind:id="dynamicId"></div>
<img v-bind:src="url" />
<p v-if="seen">现在你看到我了</p>
<a v-on:click="doSomething">...</a>
<li v-for="item in items">...</li>

@click is short for v-on:click

方法的括号()不是必须的

支持动态属性名和事件名称

1
2
<a v-bind:[attributeName]="url"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>

Vue实例

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<template>
<div id="container" >
<span>{{ title }}</span>
<span v-once>{{ title }} 没变吧</span>
<span v-if="flag">now shown</span>
<p>
<a v-on:click="handleClick">
<img v-bind:src="url" :class="[flag? 'imgSize1':'imgSize2']" />
</a>
</p>
<p>

</p>
</div>
</template>
<script>
import * as THREE from 'three'
export default {
name:'viewPort',
props:{
//target
},
data(){
return{
title:"titleXXX",
flag:true,
items:["item1","item2","item3"],
url: require('@/assets/images/threelogo.png')
}
},
//
created(){

},
mounted(){
this.initScene()
},
methods:{
initScene(){
// 创建3D场景对象Scene
const scene = new THREE.Scene();
console.log(scene);

},
handleClick() {
this.flag = !this.flag;
}
}
}
</script>

<style scoped>
#container {
margin: 0;
width: 100%;
height: 100vh;
background: #fff;
}

.imgSize1 {
width: 100px;
height: 100px;
}

.imgSize2 {
width: 50px;
height: 50px;
}
</style>

当一个 Vue 实例被创建时,它将 data 对象中的所有的属性加入到 Vue 的响应式系统中。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。

常用实例方法:

1
2
3
4
5
vm.$data === data // => true
vm.$el === document.getElementById('example') // => true
vm.$watch('a', function (newValue, oldValue) {
// 这个回调将在 `vm.a` 改变后调用
})

生命周期钩子
生命周期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
new Vue({
data: {
a: 1
},
created: function () {
// `this` 指向 vm 实例
console.log('a is: ' + this.a)
}
mounted: function () {
console.log('a is: ' + this.a)
}
beforeDestroy:function(){

}
})

内联处理器方法
1
2
3
4
5
6
7
8
new Vue({
data: {

},
methods:{
// TODO functions
}
})

官网

爬行起步方式

Caution! 限定的引入顺序:
template —> vue.js —> vue controller
其中 template 和 vue.js 交换没有影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<html>
<head>
<meta charset="UTF-8">
<title>Hi vue</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{ message }}
</div>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
message: 'here here'
}
})
</script>
</body>
</html>

常规起步方式

1
2
3
npm install -g @vue/cli

vue create vueproj

阅读 vue-cli指引

原力起飞方式

webpack 打包 vue 基础

vue渐进式

  • 完整版
  • 编译器 用以将模板字符串编译成js渲染函数
  • 运行时 不含编译器 用以创建vue实例 处理虚拟dom
  • UMD 通过\引入html的完整版本

vue对比其他框架

特性 Vue.js React Angular 2+
核心特点 渐进式框架,易学易用 虚拟DOM,组件化 完整的MVC框架
学习曲线 平缓,只需HTML/CSS/JS基础 中等,需掌握JSX和ES2015 陡峭,需掌握TypeScript和大量概念
性能优化 自动依赖追踪,精确更新 需手动使用PureComponent或shouldComponentUpdate 自动优化,但体积较大
模板语法 支持HTML模板,也支持JSX 强制使用JSX 使用类似HTML的模板
CSS处理 单文件组件中的scoped样式 CSS-in-JS方案(如styled-components) 支持组件样式,但不如Vue灵活
生态系统 官方维护路由和状态管理 丰富的第三方库,生态系统庞大 官方完整解决方案
向下扩展 简单,可通过CDN直接使用 需要构建系统,不适合简单项目 不适合简单项目
向上扩展 良好,官方提供CLI脚手架 强大,但配置较复杂 非常强大,适合大型企业应用
TypeScript支持 良好,官方提供类型声明 优秀,TS是React的一部分 必须使用TypeScript开发
项目体积 小(gzip后约30KB) 中等 较大(gzip后约65KB)
灵活性 高,不强制代码组织方式 中等,组件灵活但生态约束 低,有严格的代码组织规范
浏览器兼容性 IE9+ 现代浏览器 现代浏览器
响应式系统 基于依赖追踪的观察系统 基于状态管理和虚拟DOM 基于Zone.js和变更检测
原生渲染 通过Weex或NativeScript-Vue React Native Ionic或NativeScript
数据流 单向数据流 单向数据流(通过状态管理) 单向数据流
  • 易用性:Vue > React > Angular
    • Vue学习曲线最平缓,React需要掌握JSX,Angular最复杂
  • 灵活性:Vue > React > Angular
    • Vue不强制代码组织方式,React组件灵活但生态约束,Angular有严格规范
  • 性能:Vue ≈ React > Angular
    • Vue和React性能接近且优秀,Angular体积较大但性能也不错
  • 生态系统:React > Angular > Vue
    • React生态系统最庞大,Angular提供完整官方解决方案,Vue官方维护核心库
  • 适用场景
    • Vue:中小型项目,需要快速开发,团队技术栈多样
    • React:大型项目,需要丰富生态系统,团队熟悉JS
    • Angular:大型企业应用,需要完整解决方案,团队熟悉TypeScript

RazorEngine: Open source templating engine based on Microsoft’s Razor parsing engine.

Razor文件类型

Razor可以在vb.net和C#中使用。分别对应了两种文件类型,.vbhtml和.cshtml

Razor的标识符

@字符被定义为Razor服务器代码块的标识符,表示是服务器代码。web form中使用<%%>中写服务器代码一个道理。

1
2
3
4
5
@{
string userName= "邓星林";
} <!-- 块级作用域 -->
<span>@userName</span>
<span>@DateTime.Now.ToString("yyyy-MM-hh")</span>

HTML

@Href(“~/“)//表示网站的根目录

@Html.Raw(Module.Content) 输出HTML

Razor标识混合HTML

1
2
3
4
5
6
7
8
9
@{
// html标签
<div></div>
var str = "abc";
// 下面会输出:this is a mail:dxl0321@qq.com, this is var: abc,this is mail@str,this is @;
@: this is a mail:dxl0321@qq.com, this is var: @str,this is mail@str,this is @@;
// 下面输出abc
@str
}

a.在作用域内如果是以html标签开始则视为文本输出

b.如果要输出符号@,则使用@@

c.如果要输出非html标签和非Razor语句的代码,则用@:,他的作用是相当于在处于html下面编写一样了,如在@:后面可以加上@就是表示Razor语句的变量

注释

1
2
3
4
5
6
7
@{
@*
多行注释
多行注释
*@
var i = 10; @* asdfasf *@
}

Layout 视图片段

插入指定路径定义的布局视图模板

1
2
3
4
5
6
7
@{
ViewData["Title"] = "分享优惠券管理";
Layout = "~/Views/Shared/_Layoutv2.cshtml";
}
<div class="main">
...
</div>

插入Page

1
2
3
<div class="area">
@RenderPage("/b.cshtml")
</div>

Section

Helper

helper就是可以定义可重复使用的帮助器方法,不仅可以在同一个页面不同地方使用,还可以在不同的页面使用。

1
2
3
4
5
6
7
8
9
10
@helper sum(int a,int b)
{
var result=a+b;
  @result

}
<div >
<p>2+3=@sum(2,3)</p>
<p>5+9=@sum(5,9)</p>
</div>

自带类型转换

  • AsInt()
  • IsInt()
  • AsBool()
  • IsBool()
  • AsFloat()
  • IsFloat()
  • AsDecimal()
  • IsDecimal()
  • AsDateTime()
  • IsDateTime()
  • ToString()

inventing js

关于0.1+0.2==0.3 false

0.1 + 0.2不等于0.3?为什么JavaScript有这种“骚”操作?
关于浮点数的二进制表示,js浮点数精度(存目)

保留2位小数

Number.prototype.toFixed 返回指定小数位数的字符串 必要时四舍五入 且必要时以0补足位数
返回number的方法不如用类似 Math.round(num * 100) / 100 保留两位小数

动态语言

Dynamic Programming Language 动态语言或动态编程语言,程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。动态语言如Javascript Python Ruby等 C/C++ Jave则不是

另外 图灵完备

JS Tips

Caps lock sensitive

1
2
var input = document.getElementsByTag('input')[0];
input.addEventListener('keyup',function(event){console.log(event.getModifierState('CapsLock'))});

getModifierState方法挂在KeyboardEvent或者MouseEvent上,可获取的键盘状态见MDN:KeyboardEvent.getModifierState()

运算符

  • 幂运算 2**10=1024

this

this的设计目的是在函数体内部,指代函数当前的运行环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var f = function () {
console.log(this.x);
}

var x = 1;
var obj = {
f: f,
x: 2,
};

// 全局环境执行
f() // 1

// obj 环境执行
obj.f() // 2

另外,严格模式(‘use strict’)下,全局环境下运行时,this不会自动绑定到‘全局对象’上,将变量绑定到全局对象需要显式调用形如global.name = ‘Global’

常用处理

应用场景标题 描述 补充1 补充2
数组去重 Array.from(new Set(array)) 通过内置数据解构特性进行去重 [] => set => [] 通过遍历并判断是否存在进行去重[many items].forEach(item => (item <不存在于> uniqueArr) && uniqueArr.push(item))
数组的最后一个元素 获取数组中位置最后的一个元素 使用array.at(-1)
数组对象的相关转换 对象到数组:Object.entries() 数组到对象:Obecjt.fromEntries()
短路操作 通过短路操作避免后续表达式的执行 a或b:a真b不执行 a且b:a假b不执行
基于默认值的对象赋值 通过对象解构合并进行带有默认值的对象赋值操作 {...defaultData, ...data}
多重条件判断优化 单个值与多个值进行对比判断时,使用includes进行优化 [404,400,403].includes
交换两个值 通过对象解构操作进行简洁的双值交换 [a, b] = [b, a]
位运算 通过位运算提高性能和简洁程度 按位与(&)或(\ )按位取反(~) 取整~~、<<、>> >>>移位
replace()的回调 通过传入回调进行更加细粒度的操作
sort()的回调 通过传入回调进行更加细粒度的操作 根据字母顺序排序 根据真假值进行排序

call 和 apply

1
2
3
4
5
6
7
8
9
10
11
12
// 已有构造函数
function Product(name, price) {
this.name = name;
this.price = price;
}
// 在新函数中复用已有逻辑
function Food(name, price) {
Product.call(this, name, price); // 实际上相当于将构造函数Product的两行拿过来
this.category = 'food';
}
// test
const bread = new Food('bread', 0.8) // Food {name: "bread", price: 0.8, category: "food"}

FunctionX.call(thisArg,…args)中的第一个参数也可以是对象,函数相当于将剩余参数以FunctionX的实现方式应用到该对象内返回一个结果
apply与call的区别仅在:剩余参数是数组,即FunctionX.call(thisArg,[…args])

bind

bind是基于call或apply实现的函数原型方法 FunctionX.bind(thisArg,…args)返回一个新的函数,该函数套用FunctionX的样子,但以thisArg代替FunctionX的this,剩余参数填补FunctionX所需参数(即结果函数所需参数为FunctionX所需参数减去…args)
模拟实现

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
Function.prototype.bind = function (context) {
// 调用 bind 的不是函数,需要抛出异常
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}

// this 指向调用者
var self = this;
// 实现第2点,因为第1个参数是指定的this,所以只截取第1个之后的参数
var args = Array.prototype.slice.call(arguments, 1);

// 创建一个空对象
var fNOP = function () {};

// 实现第3点,返回一个函数
var fBound = function () {
// 实现第4点,获取 bind 返回函数的参数
var bindArgs = Array.prototype.slice.call(arguments);
// 然后同传入参数合并成一个参数数组,并作为 self.apply() 的第二个参数
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
// 注释1
}

// 注释2
// 空对象的原型指向绑定函数的原型
fNOP.prototype = this.prototype;
// 空对象的实例赋值给 fBound.prototype
fBound.prototype = new fNOP();
return fBound;
}

关于替换this的操作可用于回调函数调用类函数, 如下类型A中定义回调函数,该回调函数会作为目标对象B的函数形参,process是类A的函数,但如果在目标对象B中调用callback函数,函数中的this会指向到B
1
2
3
4
5
callback = (response)=>{
this.process(response) // 这里的this指向当前运行环境
}

B.getDataAsync(this.callback)

应使用bind
1
B.getDataAsync(this.callback.bind(this))

柯里化Currying

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
style=function(){this.x="";this.y="";} // 构造函数style
style.prototype.addA=function(){
this.A='A'
return this
}
style.prototype.addB=function(){
this.B='B'
return this
}
style.prototype.addC=function(){
this.C='C'
return this
}
// test
xxx=new style()
xxx.addA()
xxx.addB().addC()

Garbage Collection

  • 游离dom

    1
    2
    3
    4
    5
    6
    let root=document.getElementById("root")
    for(let i=0;i<2000;i++){
    let div=document.createElement("div")
    root.appendChild(div)
    }
    document.body.removeChild(root)

    此时document中移除了dom,但内存中root还在,而且上千个子元素仍然被root对象引用,应设root=null解除引用

  • clearInterval和clearTimeOut

关于严格模式

strict mode

通知浏览器以严格模式运行脚本,严格模式下会将一些在非严格模式下被忽略的错位识别为异常,一些静默行为(无效但非异常操作)会被禁止

  • 防止无意添加全局变量,声明全局变量必须用global关键字
  • 静默行为被禁止 抛出TypeError
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 如无效的赋值 
    NaN=0
    // 写入不可写属性
    var obj={}
    Object.defineProperty(obj, 'x', {value:0, writable:false})
    // 写入只读
    var obj={
    get x(){
    return 0;
    }
    }
    obj.x = 2
    // 扩展不可扩展对象
    var fixed = {}
    Object.preventExtensions(fixed)
    fixed.newProp = 0

    vanilla.js

    it’s a joke. 这是一个空的js,用以提醒其他开发者使用plain JavaScript实现,而不要引入任何library

javascript 手写面试题

task 和 microtask

Tasks, microtasks, queues and schedules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
console.log('script start'); // task

setTimeout(function () {
console.log('setTimeout'); // scheduled task
}, 0);

Promise.resolve()
.then(function () {
console.log('promise1'); // microtask
})
.then(function () {
console.log('promise2');
});

// 结果:
// script start
// promise1
// promise2
// setTimeout

至少Chrome中, Promise.resolve().then()中的回调作为microtask会立即排队等待当前任务执行完, 而setTimeout的回调则在给定延迟之后,排队任务队列中。

节流和防抖

防抖,全时间内后面的覆盖前面的

1
2
3
4
5
6
7
8
9
function debounce(fn, wait){
let timeout
return function(...args){
clearTimeout(timeout)
timeout = setTimeout(()=> {
fn.apply(this, args)
}, wait)
}
}

事实上带有 this 的函数在使用生命周期钩子的vue/react组件中可能并不好用,应在组件范围声明timer, 在响应函数中clear并重新赋值

注意组件unmount时清理timer避免泄露

节流

1
2
3
4
5
6
7
8
9
10
11
var throttle = function(delay, action){
// 这里是个全局变量
var last = 0
return function(){
var curr = new Date()
if (curr - last > delay){
action.apply(this, arguments)
last = curr
}
}
}