判断和处理数组

判断数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1.
[] instanceof Array;

// 2. 数组重写了toString()方法需要借用
Object.prototype.toString.call([]) === '[object Array]'

// 3.
Array.prototype.isPrototypeOf([]);

// 4.
[].constructor === Array;

// 5.
Array.isArray([]);

如何处理类数组对象

JavaScript类数组对象的定义

可以同过索引访问元素, 并且拥有length属性

没有数组的方法,例如 push, forEach, indexOf等

1
2
3
4
5
6
var foo = {
0: 'js',
1: 'Node',
2: 'TS',
length: 3
};

转换方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1.
Array.prototype.slice.call(arguments);
Array.prototype.slice.apply(arguments);
[].slice.call(arguments)

// 2. 需要arguemnts必须有遍历接口
[...arguments]

// 3.
Array.from(arguemnts);

// 4.
[].concat.apply([], arguments)

// 5.
function toArray(s) {
var arr = [];
for(var i =0, len =s.length; i< len; i++>) {
arr[i] = s[i]
}
return arr;
}

注意

  1. 数组的长度由类数组的length决定
  2. 索引不连续,会自动补位undefined
  3. 仅仅考虑0和正整数索引
  4. slice会产生系数数组,内容是empty而不是undefined
  5. 类数组push注意,push操作是length所在的位置

MySQL

命令

启动mysql

  1. sudo /usr/local/mysql/support-files/mysql.server start**

停止mysql

  1. sudo /usr/local/mysql/support-files/mysql.server stop**

重启mysql

  1. sudo /usr/local/mysql/support-files/mysql.server restart**

服务管理命令

1
2
3
4

brew services start mysql
brew services run mysql
brew services stop mysql

参考资料

mysql启动关闭服务

UI测试之backstopjs

UI测试之backstopjs

用来比较实际做出来的项目与UX的设计图的差异,像素级比较

安装

1
npm install backstopjs

初始化

1
backstop init

会在根目录下生成一个backstop.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
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
{
"id": "qqmap",
"viewports": [
{
"label": "phone",
"width": 320,
"height": 480
},
{
"label": "tablet",
"width": 1024,
"height": 768
}
],
"onBeforeScript": "puppet/onBefore.js",
"onReadyScript": "puppet/onReady.js",
"scenarios": [
{
"label": "mapindex",
"cookiePath": "backstop_data/engine_scripts/cookies.json",
"url": "https://map.qq.com/m/",
"referenceUrl": "",
"readyEvent": "",
"readySelector": "",
"delay": 0,
"hideSelectors": [],
"removeSelectors": [],
"hoverSelector": "",
"clickSelector": "",
"postInteractionWait": 0,
"selectors": [],
"selectorExpansion": true,
"expect": 0,
"misMatchThreshold": 0.1,
"requireSameDimensions": true
}
],
"paths": {
"bitmaps_reference": "backstop_data/bitmaps_reference",
"bitmaps_test": "backstop_data/bitmaps_test",
"engine_scripts": "backstop_data/engine_scripts",
"html_report": "backstop_data/html_report",
"ci_report": "backstop_data/ci_report"
},
"report": ["browser"],
"engine": "puppeteer",
"engineOptions": {
"args": ["--no-sandbox"]
},
"asyncCaptureLimit": 5,
"asyncCompareLimit": 50,
"debug": false,
"debugWindow": false
}
字段 说明
viewports 环境尺寸
scenarios[n].label 配置别名
scenarios[n].url 配置比较url

运行测试

1
backstop test

打开页面

如图,提示为设计图缺失,在./backstop_data/bitmaps_reference/目录下补充设计图即可

同时会生成如下文件夹

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
./backstop_data
├── bitmaps_reference // 设计图,默认没有
│   ├── qqmap_mapindex_0_document_0_phone.png
│   └── qqmap_mapindex_0_document_1_tablet.png
├── bitmaps_test // 生成对比结果
│   └── 20200814-230618
│   ├── qqmap_mapindex_0_document_0_phone.png
│   ├── qqmap_mapindex_0_document_1_tablet.png
│   └── report.json
├── engine_scripts
│   ├── cookies.json
│   ├── imageStub.jpg
│   └── puppet
│   ├── clickAndHoverHelper.js
│   ├── ignoreCSP.js
│   ├── interceptImages.js
│   ├── loadCookies.js
│   ├── onBefore.js
│   ├── onReady.js
│   └── overrideCSS.js
└── html_report // 报告入口
├── a96f14595379b7c348d66e115ec65a93.png
├── assets
│   └── fonts
│   ├── Lato-Bold.ttf
│   ├── Lato-Regular.ttf
│   ├── lato-bold-webfont.woff
│   ├── lato-bold-webfont.woff2
│   ├── lato-regular-webfont.woff
│   └── lato-regular-webfont.woff2
├── b815e28b1e230cff6e9d7b749edcd562.png
├── config.js
├── diff.js
├── diverged.js
├── divergedWorker.js
├── index.html
└── index_bundle.js

补充完设计图再次运行

1
backstop test

vue-router原理解析

Vue-Router 默认是Hash模式, 使用url的hash来模拟一个完整的url,当url改变的时候,页面不会重新加载, 比如: 使用hash模式的话, 那么访问变成 http://localhost:8080/page/#/这样的访问

但是如果路由使用history的话,那么访问路径变成 如下: http://localhost:8080/page/

注意事项:

如果使用的是history这种模式, 在非首页的情况下刷新页面或直接访问的时候会报404,导致页面丢失

这是因为他是利用H5 History API 来实现的. 通过history.pushState 方法来实现URL的跳转而无需重新加载页面, 但是它的问题在于刷新页面的时候会走后端路由,相当于直接在浏览器里输入这个地址,要对服务器发起http请求,但是这个目标在服务器上又不存在这个路由,所以会返回404

解决方式: 需要服务端的辅助来兜底,避免URL无法匹配到资源的时候能返回页面

Nginx或Node作为服务端的解决方案

  1. Nginx配置
1
2
3
localtion / {
try_files $uri $uri/ /index.html;
}

Nodejs配置

1
2
3
4
5
6
7
8
var history = require("content-height-history");
var connect = require("connect");
var app = connect().use(history()).listen(3000);

// 或者使用express
var express = require("express");
var app = express();
app.use(history());

hash模式

默认是 hash模式, 基于浏览器 hash api,使用window.addEventListener(‘hashchange’, callback)对浏览器地址进行监听, 当调用push时, 把新路由添加到浏览器访问历史的栈顶,使用replace时,把浏览器访问历史的栈顶路由替换成新路由

History模式

history模式,基于浏览器history api, 使用window.onpopstate 对浏览器地址进行监听,对浏览器history api中pushState(), replaceState() 进行封装,当方法调用,会对浏览器历史栈进行修改, 从而实现URL的跳转而无需重新加载页面,

但是它的问题在于刷新页面的时候会走后端路由,所以需要服务端的辅助来兜底,避免URL无法匹配到资源时能返回页面

abstract

不涉及和浏览器地址相关的记录, 流程跟hash模式一样, 通过数组维护模拟浏览器的历史堆栈

服务端下使用, 使用一个不依赖于浏览器的浏览历史虚拟管理后端,

总结

hash 模式和 history 模式都是通过window.addEventListener()方法监听hashchange和 popState 进行相应路由的操作, 可以通过back、forward、go等方法访问浏览器的历史记录栈,进行各种跳转,而abstract模式是自己维护一个模拟的浏览器历史栈的数组.

响应式与created和mounted

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>{{ a.b }}</div>
</template>
<script>
export default {
data() {
return {
a: {},
};
},
created() {
this.a.b ={
c:1
};
},
mounted() {
this.a.b = 2;
},
};
</script>
<style>
</style>

答案一:1

解析

  1. Vue无法检测property的添加或移除.由于Vue会初始化实例时对property执行getter/setter转化,所以property必须在data对象上存在才能让Vue将它转换成响应式的.所以this.a.b中的b是非响应式的
  2. 又因为在created视图未渲染时直接对对象a的属性b赋值data 里面的值会改变的,但是在mounted里面更新this.a.bde值的时候,是非响应式的,所以视图不会更新

答案二:2

解析

  1. Vue3 使用Proxy代理数据,虽然监听不了孙子节点,但是,在读取子节点的时候使用懒代理的方式监听值的变化,从而可以检测到b的新增,就是b之后还有数据,同样也会监听到

cloneDeep

普通Clone

RegexClone

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function cloneReg(target, isDeep) {
var regFlag = /\w*$/;
var result = new target.constructor(target.source, regFlag.exec(target));
if (isDeep) {
result.lastIndex = 0;
} else {
result.lastIndex = target.lastIndex;
}
return result;
}

var regex = /yideng/g;

var reg2 = cloneReg(regex, true);

console.log(reg2.test("yideng"));
console.log(reg2.test("yideng"));
console.log(reg2.test("yideng"));
console.log(reg2.test("yideng"));
console.log(reg2.test("yideng"));

Buffer克隆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined;
const buf = Buffer.from("laoyuan");

function cloneBuffer(buffer, isDeep) {
if (!isDeep) {
return buffer.slice();
}
const length = buffer.length,
result = allocUnsafe
? allocUnsafe(length)
: new buffer.constructor(length);

return result;
}

const buf2 = cloneBuffer(buf, true);

buf2.write("nodejs");
buf2.write("22");

console.log("buf", buf.toString("utf-8"));
console.log("buf2", buf2.toString("utf-8"));

变量与函数提升

  1. var 变量总是提升到当前函数作用域的顶端
  2. let,const 为块级作用域,

变量声明提升

var与let作用域规则不一样

  1. var: 变量总是提升到当前函数作用域的顶端
  2. let: 当前块的作用域
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function varTest() {
var x = 1;
{
var x = 2; // 同样的变量!
console.log(x); // 2
}
console.log(x); // 2
}

function letTest() {
let x = 1;
{
let x = 2; // 不同的变量
console.log(x); // 2
}
console.log(x); // 1
}

let 可以用来创建类的私有属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var Thing;

{
let privateScope = new WeakMap();
let counter = 0;

Thing = function() {
this.someProperty = 'foo';

privateScope.set(this, {
hidden: ++counter,
});
};

Thing.prototype.showPublic = function() {
return this.someProperty;
};
}

let 不能重复声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (x) {
let foo;
let foo; // SyntaxError thrown.
}

if(x) {
var foo;
var foo;
}

let x = 1;
switch(x) {
case 0:
let foo;
break;

case 1:
let foo; // SyntaxError for redeclaration.
break;
}

let, const 存在暂存死区

  1. var: 变量提升,会被移动到函数顶端,被初始化为undefined,存在函数堆栈内
  2. let: 不会变量提升,直至显示赋值才会被加入函数执行堆栈内,在块级作用域开始时,会被放到暂存死区, 无法被引用
1
2
3
4
5
6
// prints out 'undefined'
console.log(typeof undeclaredVariable);

// results in a 'ReferenceError'
console.log(typeof i);
let i = 10;

习题解释

1
2
3
4
5
6

// code01
let x = 1;
{
var x = 2; // SyntaxError for re-declaration
}
1
2
3
4
5
6
7
// code02
var x = 1;
{
let x = 2;
x++;
}
console.log(x); // 1
  1. code01: var 会被提升到函数的顶端, 因此处于同一块级作用域,所以抛异常SyntaxError
  2. code02: ++ 修改的是块级作用域内的 x 的值,所以 外面 x 的值不变

函数声明提升

普通提升

1
2
3
4
5
hoisted(); // "foo"

function hoisted() {
console.log("foo");
}
1
2
3
4
5
6
var hoisted; 

hoisted = function() {
console.log("foo");
}
hoisted();

相同点:

  1. var, function 都会被提升

不同点:

  1. var 提升在 function 之前
  2. var 为函数级提升,function 为块级提升

有条件提升

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// code01
foo(); // Uncaught TypeError: foo is not a function
{
function foo(){ return 1; }
}

// 在Chrome里:
// 'foo' 变量名被提升,但是 typeof foo 为 undefined
//
// 在Firefox里:
// 'foo' 变量名被提升. 但是 typeof foo 为 undefined
//
// 在Edge里:
// 'foo' 变量名未被提升. 而且 typeof foo 为 undefined
//
// 在Safari里:
// 'foo' 变量名被提升. 而且 typeof foo 为 function
1
2
// code02
foo(); //Uncaught ReferenceError: foo is not defined
  1. code01: 变量提升,函数未提升,执行undefind(),所以报错:TypeError
  2. code02: foo 没有被声明,没有找到foo,所以报错: ReferenceError