前端学习之路


  • 首页

  • 归档

  • 分类

  • 标签

  • 搜索
close
小朱

小朱

前端学习之路

168 日志
37 分类
37 标签
RSS
GitHub
友情链接
  • 极客学院
  • caniuse
  • codepen
  • JS Bin
  • Babel在线编译
  • Iconfinder图标
  • 在线JSON格式化
  • 智能图像压缩

CSS 基础

发表于 2021-06-11   |   分类于 FJ

BFC(块级格式化上下文)

块级格式化上下文,是一个独立的渲染区域,并且有一定的布局规则,可用于清除浮动,防止 margin 重叠等。

  • BFC 区域不会与浮动元素重叠,浮动元素也會参与高度计算
  • BFC 是页面上的一个独立容器,子元素不会影响到外面
  • 哪些元素会生成 BFC
    • 根元素
    • float 不为 none 的元素
    • position 为 fixed 和 absolute 的元素
    • display 为 inline-block、table-cell、table-caption,flex,inline-flex 的元素
    • overflow 不为 visible 的元素

overflow 的原理

要讲清楚这个解决方案的原理,首先需要了解块格式化上下文。块格式化上下文是 CSS 可视化渲染的一部分,它是一块区域,规定了内部块的渲染方式,以及浮动相互之间的影响关系,当元素设置了 overflow 样式且值不为 visible 时,该元素就构建了一个 BFC,BFC 在计算高度时,内部浮动元素的高度也要计算在内,也就是说即使 BFC 区域内只有一个浮动元素,BFC 的高度也不会发生塌缩,所以达到了清除浮动的目的。

垂直居中的方法

1
2
3
4
5
6
7
8
img {
position: absolute;
margin: auto;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
1
2
3
4
5
6
7
8
9
.inner {
width: 480px;
height: 380px;
position: absolute;
top: 50%;
left: 50%;
margin-top: -190px; /*height 的一半,也可以用 transform:translateY(-50%) */
margin-left: -240px; /*width 的一半,也可以用 transform:translateX(-50%) */
}
1
2
3
4
.parent {
display: table-cell;
vertical-align: middle;
}
1
2
3
4
5
.parent {
display: flex;
justify-content: center;
align-items: center;
}

说一下块元素和行元素

块元素:独占一行,并且有自动填满父元素,可以设置 margin 和 padding 以及高度和宽度
行元素:不会独占一行,width 和 height 会失效,并且在垂直方向的 padding 和 margin 会失效

visibility=hidden, opacity=0,display:none

  • opacity=0 该元素隐藏起来了,但不会改变页面布局,会触发该元素已经绑定的事件
  • visibility=hidden 该元素隐藏起来了,但不会改变页面布局,但是不会触发该元素已经绑定的事件
  • display=none 把元素隐藏起来,并且会改变页面布局,可以理解成在页面中把该元素删除掉一样

float 的元素 display 是什么值

float 的元素,display 是 block。

z-index 的定位方法

z-index 属性设置元素的堆叠顺序,拥有更好堆叠顺序的元素会处于较低顺序元素之前, z-index 可以为负,且 z-index 只能在定位元素上奏效,该属性设置一个定位元素沿 z 轴的位置,如果为正数,离用户越近,为负数,离用户越远,它的属性值有 auto(默认,堆叠顺序与父元素相等),number,inherit(从父元素继承 z-index 属性的值)。

了解重绘和重排吗,知道怎么去减少重绘和重排吗,让文档脱离文档流有哪些方法

DOM 的变化影响到了预算内宿的几何属性比如宽高,浏览器重新计算元素的几何属性, 其他元素的几何属性也会受到影响,浏览器需要重新构造渲染书,这个过程称之为重排, 浏览器将受到影响的部分重新绘制在屏幕上 的过程称为重绘,引起重排重绘的原因有: 添加或者删除可见的 DOM 元素, 元素尺寸位置的改变 浏览器页面初始化, 浏览器窗口大小发生改变,重排一定导致重绘,重绘不一定导致重排, 减少重绘重排的方法有: 不在布局信息改变时做 DOM 查询, 使用 csstext,className 一次性改变属性 使用 fragment 对于多次重排的元素,比如说动画。使用绝对定位脱离文档流,使其不影响其他元素

js 动画 和 css 动画的区别

js 动画

首先,js动画是逐帧动画,几乎可以完成您想要的任何动画形式。但因为内容不同会增加生产负担,并且资源占用相对较大。但是它的优点也很明显:非常适合执行非常精致的动画,例如3D效果,人物或动物的急转弯等。但是,如果帧速率太低,则从一个帧到另一个帧的过渡很可能是不自然且不一致的。

缺点:

  • JavaScript在浏览器的主线程中运行,还有其他JavaScript脚本、样式计算、布局、绘图任务需要在主线程中运行。干扰它们可能导致线程阻塞,从而导致帧丢失。
  • 代码复杂度高于CSS动画。

优点:

  • JavaScript动画控制能力很强,可以控制动画在播放过程中:开始、暂停、播放、终止、取消都可以完成。
  • 动画效果比CSS3动画更丰富,一些动画效果,如曲线运动、冲击闪烁、视差滚动等效果,只有JavaScript动画才能完成。
  • CSS3有兼容性问题,而JS大多数时候没有兼容性问题。

css 动画

动画是使元素从一种样式逐渐变化为另一种样式的效果。当实现一些简单的滑动,翻转和其他特殊效果时,Css3非常方便,但是当你想要实现一些很酷的效果时,它的操作通常比js操作具有更多的冗余。

缺点:

  • 运行过程的控制较弱,不可能附加事件绑定回调函数。 CSS动画只能暂停,无法在动画中找到特定的时间点,不能中途反转动画,无法更改时间比例,无法添加回调函数或将播放事件绑定到特定位置,并且没有进度报告
  • 代码冗长。 如果您想使用CSS来实现稍微复杂一点的动画,那么CSS代码最终将变得非常繁琐。

优点:

  • 浏览器可以优化动画。
  • 代码相对简单,并且性能调整方向是固定的。
  • 对于帧速率性能较差的低版本浏览器,CSS3可以自然降级,而JS需要编写其他代码。

Web 基础

发表于 2021-06-09   |   分类于 FJ

click 在 ios 上有 300ms 延迟,原因及如何解决

问题:双击缩放(double tap to zoom),是会有 300 毫秒延迟的主要原因。当用户一次点击屏幕之后,浏览器并不能立刻判断用户是要进行双击缩放,还是想要进行单击操作。因此,iOS Safari 就等待 300 毫秒,以判断用户是否再次点击了屏幕。

解决方案:

  • 使用 FastClick 库,FastClick 在检测到 touchend 事件的时候,会通过 DOM 自定义事件立即触发一个模拟click 事件的click事件(自定义事件),并把浏览器在 300 毫秒之后真正触发的 click 事件阻止掉。
  • 禁用缩放。<meta name="viewport" content="width=device-width,user-scalable=no">

缓存

强缓存:从缓存取,状态码为 200,不发送请求到服务器。相关字段 expires、cache-control,cache-control 优先级高

协商缓存:从缓存取,状态码为 304,发送请求到服务器。相关字段 Last-Modified/If-Modified-Since,Etag/If-None-Match

HTTP强缓存和协商缓存

前端优化

  • 减少 HTTP 请求数量
  • minify / gzip 压缩
  • lazyLoad
  • 预解析 DNS
  • 使用 CDN
  • 缓存
  • 加载顺序优化
  • 服务端渲染

输入 URL 到页面至加载显示完成,发生了什么?

  • DNS 解析:浏览器缓存 -> 系统缓存 -> 路由器缓存 -> hosts 文件 -> DNS 服务器,得到服务器的 ip 地址
  • TCP 连接
  • 发送 HTTP 请求
  • 服务器处理请求并返回 HTTP 报文
  • 浏览器解析渲染页面:构建 DOM 树,在 DOM 树的构建过程中如果遇到 JS 脚本和外部 JS 连接,则会停止构建 DOM 树来执行和下载相应的代码,这会造成阻塞,这就是为什么推荐 JS 代码应该放在 html 代码的后面;构建 CSSOM 树;合并为渲染树,渲染页面;解析 DOM 过程中如果遇到图片、视频、音频等资源,会并行下载
  • 连接结束

什么时候用 304?

如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个 304 状态码。

link 标签和 import 标签的区别

  • link 属于 html 标签,因此没有兼容性,而@import 是 css 提供的,只有 IE5 以上才能识别
  • link 方式样式的权重高于 @import 的
  • 页面被加载时,link 会同时被加载,而@import 引用的 css 会等到页面加载结束后加载

JS 基础

发表于 2021-06-04   |   分类于 FJ

自己实现一个 bind 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Function.prototype.bind = function (obj, ...arg) {
var context = this;
var bound = function (...newArg) {
arg = arg.concat(newArg);
return context.apply(obj, arg);
};
var F = function () {};
F.prototype = context.prototype;
bound.prototype = new F();
return bound;
};
function func(a, b) {
console.log(this);
console.log(a);
console.log(b);
}
const newFunc = func.bind({ new: true }, 1);
newFunc(2);

累加输出

1
2
3
4
5
6
7
8
9
var getId = (function () {
var i = 0;
return function () {
return i++;
};
})();
console.log(getId()); // 0
console.log(getId()); // 1

实现一个 add 方法

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
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add(...args1) {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = [...args1];
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var _adder = function(...args2) {
_args.push(...args2);
return _adder;
};
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
add(1)(2)(3) // 6
add(1, 2, 3)(4) // 10
add(1)(2)(3)(4)(5) // 15
add(2, 6)(1) // 9

B 继承 A

1
2
3
4
5
6
7
function A(...) {}
A.prototype...
function B(...) {}
B.prototype...
B.prototype = Object.create(A.prototype);
// 再在 A 的构造函数里 new B(props);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Parent() {
this.name = 'parent';
}
function Child() {
this.age = 40;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var c = new Child();
console.log(c.name); // parent
console.log(c.age); // 40
console.log(Child.prototype.constructor); // Child 函数

对象的深拷贝

  • 一般对象和数组对象的克隆
1
2
3
4
5
6
7
8
9
10
11
12
13
function deepClone(obj) {
if (typeof obj !== 'object') {
return;
}
var newObj = obj instanceof Array ? [] : {};
for (var item in obj) {
if (obj.hasOwnProperty(item)) {
newObj[item] =
typeof obj[item] == 'object' ? deepClone(obj[item]) : obj[item];
}
}
return newObj;
}
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
function clone(obj) {
var copy;
switch (typeof obj) {
case 'undefined':
break;
case 'number':
copy = obj - 0;
break;
case 'string':
copy = obj + '';
break;
case 'boolean':
copy = obj;
break;
case 'object':
if (obj === null) {
copy = null;
} else {
if (Object.prototype.toString.call(obj).slice(8, -1) === 'Array') {
copy = [];
for (var i = 0; i < obj.length; i++) {
copy.push(clone(obj[i]));
}
} else {
copy = {};
for (var j in obj) {
copy[j] = clone(obj[j]);
}
}
}
break;
default:
copy = obj;
break;
}
return copy;
}
  • 这个方法不能够拷贝函数
1
JSON.parse(JSON.stringify(obj));

代码的执行顺序

参考这一次,彻底弄懂 JavaScript 执行机制

任务队列中,在每一次事件循环中,macrotask 只会提取一个执行,而 microtask 会一直提取,直到 microsoft 队列为空为止。 也就是说如果某个 microtask 任务被推入到执行中,那么当主线程任务执行完成后,会循环调用该队列任务中的下一个任务来执行,直到该任务队列到最后一个任务为止。而事件循环每次只会入栈一个 macrotask,主线程执行完成该任务后又会检查 microtasks 队列并完成里面的所有任务后再执行 macrotask 的任务。
macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering。
microtasks: process.nextTick, Promise, MutationObserver。

同步执行的代码 -> promise.then -> setTimeout

js 引擎单线程执行的,它是基于事件驱动的语言。它的执行顺序是遵循一个叫做事件队列的机制。浏览器有各种各样的线程,比如事件触发器、网络请求、定时器等等,线程的联系都是基于事件的。js 引擎处理到与其他线程相关的代码,就会分发给其他线程。他们处理完之后,需要 js 引擎计算时就是在事件队列里面添加一个任务。这个过程中 js 并不会阻塞代码等待其他线程执行完毕,而且其他线程执行完毕后添加事件任务告诉 js 引擎执行相关操作,这就是 js 的异步编程模型。在指定时间内,将任务放入事件队列,等待 js 引擎空闲后被执行。

那怎么知道主线程执行栈为空啊?js 引擎存在 monitoring process 进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去 Event Queue 那里检查是否有等待被调用的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 输出 2 5 3 6 1 4
setTimeout(() => {
console.log(1);
}, 0);
var promise1 = new Promise(function (resolve, reject) {
console.log(2);
resolve(3);
});
promise1.then((val) => {
console.log(val);
});
setTimeout(() => {
console.log(4);
}, 0);
var promise2 = new Promise(function (resolve, reject) {
console.log(5);
resolve(6);
});
promise2.then((val) => {
console.log(val);
});
1
2
3
4
5
6
7
8
9
10
// 不知道是多少,就是从2开始打印的
var i = 1;
setTimeout(() => {
i = 0;
console.log(i);
}, 5000);
while (i) {
i++;
console.log(i);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 输出 2 1 4 3 5
setTimeout(() => {
console.log(1);
}, 0);
const p = new Promise(function (resole, reject) {
console.log(2);
setTimeout(() => {
resole(3);
console.log(4);
}, 0);
});
p.then((a) => {
console.log(a);
});
setTimeout(() => {
console.log(5);
}, 0);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//输出 2,6,5,3,4,1
setTimeout(function () {
console.log(1);
}, 0);
new Promise(function (resolve, reject) {
console.log(2);
resolve();
})
.then(function () {
console.log(3);
})
.then(function () {
console.log(4);
});
process.nextTick(function () {
console.log(5);
});
console.log(6);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 输出 2 10 3 5 4 1
setTimeout(function () {
console.log(1);
}, 0);
new Promise(function (resolve, reject) {
console.log(2);
for (var i = 0; i < 10000; i++) {
if (i === 10) {
console.log(10);
}
i == 9999 && resolve();
}
console.log(3);
}).then(function () {
console.log(4);
});
console.log(5);

Event Table

就是个注册站,可以理解成一张 事件=>回调函数 对应表,它就是用来存储 JavaScript 中的异步事件 (request, setTimeout, IO 等) 及其对应的回调函数的列表。当指定的事情发生时,Event Table 会将这个函数移到 Event Queue。

实现一个 once 函数,传入函数参数只执行一次

1
2
3
4
5
6
7
8
9
10
function ones(func) {
var tag = true;
return function () {
if (tag == true) {
func.apply(null, arguments);
tag = false;
}
return undefined;
};
}

将原生的 ajax 封装成 promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var myNewAjax = function (url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
var json = JSON.parse(xhr.responseText);
resolve(json);
} else if (xhr.readyState == 4 && xhr.status != 200) {
reject('error');
}
};
});
};

如何实现一个私有变量,用 getName 方法可以访问,不能直接访问

如下方式无法解决这个问题,enumerable: false 是 for-in 中不会遍历。

1
2
3
4
5
6
7
8
9
obj = {
name: 'abc',
getName: function () {
return this.name;
},
};
Object.defineProperty(obj, 'name', {
enumerable: false,
});

如下方式可行。

1
2
3
4
5
6
7
function product() {
var name = 'abc';
this.getName = function () {
return name;
};
}
var obj = new product();

==和===、以及 Object.is 的区别

  • == 等于,存在强制转换成 number,null == undefined 的问题
  • === 严格等于,不存在类型转换,存在 NaN !== NaN, +0 === -0
  • Object.is 加强版严格等于

requestAnimationFrame

浏览器(所以只能在浏览器中使用)专门为动画提供的 API,让 DOM 动画、Canvas 动画、 SVG 动画、WebGL 动画等有一个统一的刷新机制。requestAnimationFrame 不需要设置时间间隔,RAF 采用的是系统时间间隔,并请求浏览器在下一次重绘之前调用指定的函数来更新动画,一次,若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用 window.requestAnimationFrame(),一般用于动画。

  • requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
  • 在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回流,这当然就意味着更少的 CPU、GPU 和内存使用量
  • requestAnimationFrame 是由浏览器专门为动画提供的 API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了 CPU 开销

setTimeout 与 requestAnimationFrame 的区别

引擎层面:

  • setTimeout 属于 JS 引擎,存在事件轮询,存在事件队列
  • requestAnimationFrame 属于 GUI 引擎,发生在渲染过程中的重绘重排部分,与电脑分辨路保持一致

性能层面:

  • 当页面被隐藏或最小化时,定时器 setTimeout 仍在后台执行动画任务
  • 当页面处于未激活的状态下,该页面的屏幕刷新任务会被系统暂停,requestAnimationFrame 也会停止

应用层面:

  • 利用 setTimeout,这种定时机制去做动画,模拟固定时间刷新页面
  • requestAnimationFrame 由浏览器专门为动画提供的 API,在运行时浏览器会自动优化方法的调用,在特定性环境下可以有效节省了 CPU 开销

用 setTimeout 来实现 setInterval

setInterval 有两个问题:1.某些间隔会被跳过 2.多个定时器的代码执行时间 可能会比预期小。

1
2
3
4
5
function say() {
//do something
setTimeout(say, 200);
}
setTimeout(say, 200);
1
2
3
4
setTimeout(function () {
// do something
setTimeout(arguments.callee, 200);
}, 200);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function _setInterval(fn, time) {
function cb() {
fn();
setTimeout(arguments.callee, time);
}
setTimeout(cb, time);
}
function _setInterval(fn, time) {
fn();
setTimeout(() => _setInterval(fn, time), time);
}
_setInterval(() => {
console.log('a');
}, 1000);

setTimeout 的时间

首先要明白 Javascript 是单线程,单线程就意味着所有任务需要排队。然后会将所有任务分成两类:同步任务和异步任务!同步任务:在主线程上执行的任务,只有前一个任务执行完成,才会执行后一个!异步任务:不进入主线程、而进入“任务队列”的任务,当主线程上的任务执行完,主线程才会去执行“任务队列”。

对于 setTimeout(fn,200),当到 200ms 时,fn 会被放进“任务队列”,而“任务队列”必须要等到主线程已有的代码执行完才会执行 fn,所以当程序执行到 setTimeout(fn,200)这一行时,时间就开始计算,但是 fn 实际执行时并不一定是在 200ms 后,可能是在更久的时间后(取决于主线程上的同步代码的执行时间)。

JS 怎么控制一次加载一张图片,加载完后再加载下一张

1
2
3
4
5
6
7
8
9
10
<script type="text/javascript">
var obj = new Image();
obj.src = '图片地址';
obj.onload = function () {
alert('图片的宽度为:' + obj.width + ';图片的高度为:' + obj.height);
document.getElementById('mypic').innnerHTML =
"<img src='" + this.src + "' />";
};
</script>
<div id="mypic">onloading……</div>

如何实现 sleep 的效果(es5 或者 es6)

  • while 循环的方式,容易造成死循环
1
2
3
4
5
6
7
function sleep(ms) {
var start = Date.now(),
expire = start + ms;
while (Date.now() < expire);
console.log('1111');
return;
}
  • 通过 promise 来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function sleep(ms) {
var temple = new Promise((resolve) => {
console.log(111);
setTimeout(resolve, ms);
});
return temple;
}
sleep(500).then(function () {
console.log(222);
});
async function test() {
var temple = await sleep(1000);
console.log(222);
return temple;
}
test();
  • 通过 generate 来实现
1
2
3
4
5
6
7
8
9
10
11
function* sleep(ms) {
yield new Promise(function (resolve, reject) {
console.log(111);
setTimeout(resolve, ms);
});
}
sleep(500)
.next()
.value.then(function () {
console.log(222);
});

简单的实现一个 promise

实现一个完美符合 Promise/A+规范的 Promise

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
function myPromise(constructor) {
let self = this;
self.status = 'pending'; //定义状态改变前的初始状态
self.value = undefined; //定义状态为resolved的时候的状态
self.reason = undefined; //定义状态为rejected的时候的状态
function resolve(value) {
//两个==="pending",保证了状态的改变是不可逆的
if (self.status === 'pending') {
self.value = value;
self.status = 'resolved';
}
}
function reject(reason) {
//两个==="pending",保证了状态的改变是不可逆的
if (self.status === 'pending') {
self.reason = reason;
self.status = 'rejected';
}
}
//捕获构造异常
try {
constructor(resolve, reject);
} catch (e) {
reject(e);
}
}
myPromise.prototype.then = function (onFullfilled, onRejected) {
let self = this;
switch (self.status) {
case 'resolved':
onFullfilled(self.value);
break;
case 'rejected':
onRejected(self.reason);
break;
default:
}
};
var mm = new myPromise(function (resolve, reject) {
resolve('123');
});
mm.then(
function (success) {
console.log(success);
},
function () {
console.log('fail!');
}
);
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
class PromiseM {
constructor(process) {
this.status = 'pending';
this.msg = '';
process(this.resolve.bind(this), this.reject.bind(this));
return this;
}
resolve(val) {
this.status = 'fulfilled';
this.msg = val;
}
reject(err) {
this.status = 'rejected';
this.msg = err;
}
then(fufilled, reject) {
if (this.status === 'fulfilled') {
fufilled(this.msg);
}
if (this.status === 'rejected') {
reject(this.msg);
}
}
}
var mm = new PromiseM(function (resolve, reject) {
resolve('123');
});
mm.then(
function (success) {
console.log(success);
},
function () {
console.log('fail!');
}
);

Function.proto(getPrototypeOf)是什么?

获取一个对象的原型,在 chrome 中可以通过 __proto__ 的形式,或者在 ES6 中可以通过 Object.getPrototypeOf 的形式。

1
2
3
4
5
6
7
8
9
Function.__proto__ === Function.prototype; // true
Function.prototype.constructor === Function; // true
Function.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
function F() {}
var f = new F();
F.__proto__ === Function.prototype; // true
F.constructor === Function; // true
f.constructor === F; // true

JS 判断类型

typeof,instanceof,Object.prototype.toString.call() 等。

数组去重

  • indexOf 循环去重
  • ES6 Set 去重 Array.from(new Set(array))
  • Object 键值对去重,把数组的值存成 Object 的 key 值,比如 Object[value1] = true,在判断另一个值的时候,如果 Object[value2]存在的话,就说明该值是重复的

JS 实现跨域

跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对 JavaScript 实施的安全限制,那么只要协议、域名、端口有任何一个不同,都被当作是不同的域。跨域原理,即是通过各种方式,避开浏览器的安全限制。

  • JSONP
  • document.domain + iframe
  • window.name + iframe
  • location.hash + iframe
  • 服务端设置 Access-Control-Allow-Origin
  • 启一个代理服务器进行数据转发

JS 数据类型

引自 MDN JavaScript 数据类型和数据结构

最新的 ECMAScript 标准定义了 8 种数据类型:

  • 6 种原始类型,使用 typeof 运算符检查:
    • undefined:typeof instance === “undefined”
    • Boolean:typeof instance === “boolean”
    • Number:typeof instance === “number”
    • String:typeof instance === “string
    • BigInt:typeof instance === “bigint”
    • Symbol :typeof instance === “symbol”
  • null:typeof instance === “object”。
  • Object:typeof instance === “object”。任何 constructed 对象实例的特殊非数据结构类型,也用做数据结构:new Object,new Array,new Map,new Set,new WeakMap,new WeakSet,new Date,和几乎所有通过 new keyword 创建的东西。

除 Object 以外的所有类型都是不可变的(值本身无法被改变)。

记住 typeof 操作符的唯一目的就是检查数据类型,如果我们希望检查任何从 Object 派生出来的结构类型,使用 typeof 是不起作用的,因为总是会得到 “object”。检查 Object 种类的合适方式是使用 instanceof 关键字。但即使这样也存在误差。

JS 的全排列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function fullpermutate(str) {
var result = [];
if (str.length > 1) {
//遍历每一项
for (var m = 0; m < str.length; m++) {
//拿到当前的元素
var left = str[m];
//除当前元素的其他元素组合
var rest = str.slice(0, m) + str.slice(m + 1, str.length);
//上一次递归返回的全排列
var preResult = fullpermutate(rest);
//组合在一起
for (var i = 0; i < preResult.length; i++) {
var tmp = left + preResult[i];
result.push(tmp);
}
}
} else if (str.length === 1) {
result.push(str);
}
return result;
}

补充 get 和 post 请求在缓存方面的区别

  • get 请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存
  • post 做的一般是修改和删除的工作,所以必须与数据库交互,不能使用缓存

说一下闭包

闭包就是能够读取其他函数内部变量的函数,或者子函数在外调用,子函数所在的父函数的作用域不会被释放。

  • 单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var Singleton = (function () {
var instance;
var CreateSingleton = function (name) {
if (instance) {
return instance;
}
this.name = name;
this.getName();
instance = this;
return instance;
};
CreateSingleton.prototype.getName = function () {
console.log(this.name);
};
return CreateSingleton;
})();
var a = new Singleton('a');
var b = new Singleton('b');
a.getName();
b.getName();
console.log(a === b);

说说前端中的事件流

HTML 中与 javascript 交互是通过事件驱动来实现的,例如鼠标点击事件 onclick、页面的滚动事件 onscroll 等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念。

什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2 级事件流包括下面几个阶段:

  • 事件捕获阶段
  • 处于目标阶段
  • 事件冒泡阶段

addEventListener 是 DOM2 级事件新增的指定事件处理程序的操作,这个方法接收 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。IE 只支持事件冒泡。

DOM0 级和 DOM2 级有什么区别:

  • document.getElementById("btn").onclick = function(){}; 是 DOM0,只能绑定一个事件
  • document.getElementById("btn").addEventListener("click", function(){}, false); 是 DOM2,可以绑定多个事件

如何让事件先冒泡后捕获

如果要实现先冒泡后捕获的效果,对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后再执行捕获事件。

说一下事件委托或事件代理

简介:事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。

举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。 好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。

经过试验,在事件捕获阶段做事件代理也可以啊!!!

mouseover 和 mouseenter 的区别

mouseenter:当鼠标移入元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的移除事件是 mouseleave

mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程,对应的移除事件是 mouseout

异步加载 JS 的方法

defer:如果 script 标签设置了该属性,会异步的下载并且不会影响到后续 DOM 的渲染,会在文档渲染完毕后 DOMContentLoaded 事件调用前执行;如果有多个设置了 defer 的 script 标签存在,则会按照顺序执行所有的 script。

async:如果 script 标签设置了该属性,会异步的下载并在允许的情况下执行,并不会按着 script 在页面中的顺序来执行,而是谁先加载完谁执行。

一句话,defer 是“渲染完再执行”,async 是“下载完就执行”。另外,如果有多个 defer 脚本,会按照它们在页面出现的顺序加载,而多个 async 脚本是不能保证加载顺序的。

Ajax 解决浏览器缓存问题

  • 在 ajax 发送请求前加上 anyAjaxObj.setRequestHeader(“If-Modified-Since”,”0”)
  • 在 ajax 发送请求前加上 anyAjaxObj.setRequestHeader(“Cache-Control”,”no-cache”)
  • 在 URL 后面加上时间戳:”nowtime=” + new Date().getTime()
  • 如果是使用 jQuery,直接这样就可以了 $.ajaxSetup({cache:false},这样页面的所有 ajax 都会执行这条语句就是不需要保存缓存记录。

垃圾回收的方法

标记清除和计数引用,用引用计数法会存在内存泄露,如 objA 和 objB 通过各自的属性相互引用。

eval 是做什么的

将对应的字符串解析成 JS 并执行,应该避免使用,因为非常消耗性能(2 次,一次解析成 JS,一次执行)。

说一下类的创建和继承

JS 的 new 操作符做了哪些事情

new 操作符新建了一个空对象,这个对象原型指向构造函数的 prototype,执行构造函数后返回这个对象。

  • 创建一个类的实例:创建一个空对象 obj,然后把这个空对象的proto设置为构造函数的 prototype
  • 初始化实例:构造函数被传入参数并调用,关键字 this 被设定指向该实例 obj
  • 返回实例 obj

JS 的节流和防抖

http://www.cnblogs.com/coco1s/p/5499469.html

暂停死区

在代码块内,使用 let、const 命令声明变量之前,该变量都是不可用的。这在语法上, 称为“暂时性死区”。

编写代码,满足以下条件:(1)Hero(“37er”);执行结果为 Hi! This is 37er (2)Hero(“37er”).kill(1).recover(30);执行结果为 Hi! This is 37er Kill 1 bug Recover 30 bloods (3)Hero(“37er”).sleep(10).kill(2)执行结果为 Hi! This is 37er //等待 10s 后 Kill 2 bugs //注意为 bugs (双斜线后的为提示信息, 不需要打印)

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
function Hero(name) {
let o = new Object();
o.name = name;
o.time = 0;
console.log('Hi! This is ' + o.name);
o.kill = function (bugs) {
setTimeout(function () {
if (bugs == 1) {
console.log('Kill ' + bugs + ' bug');
} else {
console.log('Kill ' + bugs + ' bugs');
}
}, 1000 * this.time);
return o;
};
o.recover = function (bloods) {
console.log('Recover ' + bloods + ' bloods');
return o;
};
o.sleep = function (sleepTime) {
o.time = sleepTime;
return o;
};
return o;
}

说一下什么是 virtual dom

用 JavaScript 对象结构表示 DOM 树的结构,然后用这个树构建一个真正的 DOM 树,插到文档当中。当状态变更的时候,重新构造一棵新的对象树,用新的树和旧的树进行比较,记录两棵树差异,把所记录的差异应用到所构建的真正的 DOM 树上,视图就更新了。Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。

简单实现 Node 的 Events 模块,订阅-发布模式

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
function Events() {
this.handles = {};
this.on = function (eventName, callBack) {
if (!this.handles[eventName]) {
this.handles[eventName] = [];
}
this.handles[eventName].push(callBack);
};
this.emit = function (eventName, obj) {
if (this.handles[eventName]) {
for (var i = 0; i < this.handles[eventName].length; i++) {
this.handles[eventName][i](obj);
}
}
};
this.remove = function (eventName, callBack) {
if (!callBack) {
this.handles[eventName] = [];
} else {
for (var i = 0; i < this.handles[eventName].length; i++) {
if (callBack === this.handles[eventName][i]) {
this.handles[eventName].splice(i, 1);
}
}
}
};
return this;
}
var events = new Events();
events.on('say', function (name) {
console.log('Hello', name);
});
events.emit('say', 'Jony yu');

写个函数,可以转化下划线命名到驼峰命名

1
2
3
4
5
6
7
8
9
10
11
12
13
function UnderlineToHump(para) {
var result = '';
var arr = para.split('_');
for (var s of arr) {
if (result.length === 0) {
result = result + s.toLowerCase();
} else {
result =
result + s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
}
}
return result;
}

数组的随机排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function shuffle(arr) {
arr.sort(function () {
return Math.random() - 0.5;
});
}
function shuffle(arr) {
let i = arr.length;
while (i) {
let j = Math.floor(Math.random() * i);
i--;
[arr[j], arr[i]] = [arr[i], arr[j]];
}
}

iframe 的优缺点

优点

  • 程序调入静态页面比较方便,能够把嵌入的网页原样展现出来
  • 模块分离,便于更改,增加代码的可重用
  • 重载页面时不需要重载整个页面,只需要重载页面中的一个框架页
  • 可以解决跨域问题

缺点

  • 样式和脚本需要额外链入,增加服务器的 http 请求,占用 http 链接数
  • 会影响搜索引擎优化,不利于网站排名
  • 链接导航需要定义好,否则用户可能无法离开当前框架
  • 阻塞页面加载,影响网页加载速度,window 的 onload 事件需要在所有 iframe 加载完毕后触发
  • 多处滚动条问题

vue2 vs. vue3

  • 针对 API 的优化,createApp,生命周期函数修改,组合式 api,如 setup 回调
  • Object.defineProperty 改为 ES6 的 Proxy
  • 加强 TypeScript 支持
  • 更快,重构了虚拟 DOM
  • 更小,Tree-shaking

Vue Function-based API RFC

React 和 Vue 区别

React 和 Vue 区别

  • 监听数据变化的实现原理不同

Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,Vue 使用的是可变数据。React 默认是通过比较引用的方式(diff)进行的,React 更强调数据的不可变。

  • 数据流的不同

Vue 双向绑定。 React 单向数据流。如果使用了 Vuex 以及 Redux 等单向数据流的状态管理框架,可能感受不到这一点的区别了。Vuex 和 Redux 的区别如下。

从表面上来说,store 注入和使用方式有一些区别。在 Vuex 中,$store被直接注入到了组件实例中,因此可以比较灵活的使用:使用dispatch、commit提交更新,通过mapState或者直接通过this.$store 来读取数据。在 Redux 中,我们每一个组件都需要显示的用 connect 把需要的 props 和 dispatch 连接起来。另外,Vuex 更加灵活一些,组件中既可以 dispatch action,也可以 commit updates,而 Redux 中只能进行 dispatch,不能直接调用 reducer 进行修改。

从实现原理上来说,最大的区别是两点:Redux 使用的是不可变数据,而 Vuex 的数据是可变的,因此,Redux 每次都是用新 state 替换旧 state,而 Vuex 是直接修改。Redux 在检测数据变化的时候,是通过 diff 的方式比较差异的,而 Vuex 其实和 Vue 的原理一样,是通过 getter/setter 来比较的,这两点的区别,也是因为 React 和 Vue 的设计理念不同。React 更偏向于构建稳定大型的应用,非常的科班化。相比之下,Vue 更偏向于简单迅速的解决问题,更灵活,不那么严格遵循条条框框。因此也会给人一种大型项目用 React,小型项目用 Vue 的感觉。

  • 组件通信的区别

React 本身并不支持自定义事件,Vue 中子组件向父组件传递消息有两种方式:事件和回调函数,而且 Vue 更倾向于使用事件。但是在 React 中我们都是使用回调函数的,这可能是他们二者最大的区别。

  • 模板渲染方式的不同

React 是通过 JSX 渲染模板,而 Vue 是通过一种拓展的 HTML 语法进行渲染。在深层上,模板的原理不同:React 是在组件 JS 代码中,通过原生 JS 实现模板中的常见语法,比如插值,条件,循环等,都是通过 JS 语法实现的,更加纯粹更加原生。而 Vue 是在和组件 JS 代码分离的单独的模板中,通过指令来实现的,比如条件语句就需要 v-if 来实现对这一点,这样的做法显得有些独特,会把 HTML 弄得很乱。

举个例子,说明 React 的好处:react 中 render 函数是支持闭包特性的,所以我们 import 的组件在 render 中可以直接调用。但是在 Vue 中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以我们 import 一个组件完了之后,还需要在 components 中再声明下,这样显然是很奇怪但又不得不这样的做法。

  • 渲染过程不同

Vue 可以更快地计算出 Virtual DOM 的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。React 在应用的状态被改变时,全部子组件都会重新渲染。通过 shouldComponentUpdate 这个生命周期方法可以进行控制,但 Vue 将此视为默认的优化。

  • 框架本质不同

Vue 本质是 MVVM 框架,由 MVC 发展而来。React 是前端组件化框架,由后端组件化发展而来。

debounce 实现

1
2
3
4
5
6
7
8
9
10
11
12
function getDebounced(func, time) {
let timer;
return function (...args) {
const that = this;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func.apply(that, args);
}, time);
};
}

throttle 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getThrottle(fn, delay) {
let valid = true;
return function () {
if (!valid) {
// 休息时间 暂不接客
return false;
}
// 工作时间,执行函数并且在间隔期内把状态位设为无效
valid = false;
setTimeout(() => {
fn();
valid = true;
}, delay);
};
}

webpack 打包过程

其中包含四个核心概念

入口(entry):指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的
输出(output):告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist
loader:让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)
插件(plugins):插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

  • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
  • 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
  • 确定入口:根据配置中的 entry 找出所有的入口文件
  • 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  • 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

Promise.all 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Promise.prototype.all = function (promises) {
let results = [];
let promiseCount = 0;
let promisesLength = promises.length;
return new Promise(function (resolve, reject) {
for (let i = 0; i < promisesLength; i++) {
Promise.resolve(promises[i]).then(
function (res) {
promiseCount++;
results[i] = res;
if (promiseCount === promisesLength) {
return resolve(results);
}
},
function (err) {
return reject(err);
}
);
}
});
};

localStorage 监听

当同源页面的某个页面修改了 localStorage,其余的同源页面只要注册了 storage 事件,就会触发。

  • 同一浏览器打开了两个同源页面
  • 其中一个网页修改了 localStorage
  • 另一网页注册了 storage 事件

在同源的两个页面中,可以监听 storage 事件:

1
2
3
window.addEventListener('storage', function (e) {
alert(e.newValue);
});

在同一个页面中,对 localStorage 的 setItem 方法进行重写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var originalSetItem = localStorage.setItem;
localStorage.setItem = function (key, newValue) {
var setItemEvent = new Event('setItemEvent');
setItemEvent.newValue = newValue;
window.dispatchEvent(setItemEvent);
originalSetItem.apply(this, arguments);
};
window.addEventListener('setItemEvent', function (e) {
alert(e.newValue);
});
localStorage.setItem('name', 'wang');

router 原理

【源码拾遗】从 vue-router 看前端路由的两种实现

更新视图但不重新请求页面,是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有 2 种方式,Hash 模式和 History 模式。

hash(#)是 URL 的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页,也就是说 #是用来指导浏览器动作的,对服务器端完全无用,HTTP 请求中也不会不包括#;同时每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置。可以为 hash 的改变添加监听事件:window.addEventListener("hashchange", funcRef, false)。

HTML5 History API 提供了一种功能,能让开发人员在不刷新整个页面的情况下修改站点的 URL,就是利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

虚拟 dom 的作用

之前使用原生 js 或者 jquery 写页面的时候会发现操作 DOM 是一件非常麻烦的一件事情,往往是 DOM 标签和 js 逻辑同时写在 js 文件里,数据交互时不时还要写很多的 input 隐藏域,如果没有好的代码规范的话会显得代码非常冗余混乱,耦合性高并且难以维护。

另外一方面在浏览器里一遍又一遍的渲染 DOM 是非常非常消耗性能的,常常会出现页面卡死的情况;所以尽量减少对 DOM 的操作成为了优化前端性能的必要手段,vdom 就是将 DOM 的对比放在了 js 层,通过对比不同之处来选择新渲染 DOM 节点,从而提高渲染效率。

Virtual DOM 是用 JS 对象记录一个 dom 节点的副本,当 dom 发生更改时候,先用虚拟 dom 进行 diff,算出最小差异,然后再修改真实 dom,当用传统的方式操作 DOM 的时候,浏览器会从构建 DOM 树开始从头到尾执行一遍流程,效率很低。而虚拟 DOM 是用 javascript 对象表示的,而操作 javascript 是很简便高效的。虚拟 DOM 和真正的 DOM 有一层映射关系,很多需要操作 DOM 的地方都会去操作虚拟 DOM,最后统一一次更新 DOM,因而可以提高性能。

找出数组中唯一不同的数

1
2
3
4
5
6
7
function findOnly(arr) {
let result = 0;
arr.forEach((i) => {
result = i ^ result;
});
return result;
}
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
// 如果存在2个这样的元素
function findOnlyTwo(arr) {
let result = 0;
let post = 0;
let x = 0;
let y = 0;
// 得到所以元素异或结果
for (let i = 0; i < arr.length; i++) {
result ^= arr[i];
}
// 获取 result 二进制最低位1的位置 1&1 =1
for (let i = 0; i < 32; i++) {
if (((result >> i) & 1) == 1) {
post = i;
break;
}
}
// 遍历数组得到 x
for (let i = 0; i < arr.length; i++) {
if ((arr[i] >> post) & (1 == 1)) {
x ^= arr[i];
}
}
y = result ^ x;
console.log(x, y);
}

找出数组中不相邻元素的最大和

1
2
3
4
5
6
7
8
9
10
11
12
// 动态规划
function getMaxSum(arr) {
let result = [];
result.push(arr[0]);
result.push(Math.max(arr[0], arr[1]));
for(let i = 2; i< arr.length; i++) {
let a = result[i-2] + arr[i];
let b = result[i-1];
result.push(Math.max(a, b));
}
return result[result.length - 1];
}

Vue 大数据量性能优化

  • 减少无用字段
  • 数据扁平化
  • 利用computed
  • 数据静态化

多项目跳转

发表于 2021-06-02   |   分类于 Project Base
  • 打包后 vue-router 多次引用
  • 跳转是用 a 标签,路由可能跳不过去

注意:引用静态资源的路径可能需要处理

基于 qiankun

  • babel-polyfill 多次引用 idempotent-babel-polyfill

主应用修改

  • npm install qiankun
  • router 删除 notFoundRouter 的重定向
  • 修改 main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { registerMicroApps, start } from 'qiankun'
// import BabelPolyfill from 'babel-polyfill'
// Vue.use(BabelPolyfill)
// 添加
registerMicroApps(
[
{
name: 'project',
entry: '//localhost:8002',
container: '#app',
activeRule: '/cms'
}
]
)
start()

跳转应用修改

  • 修改 webpack 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
output: {
// 把子应用打包成 umd 库格式(必须)
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`
}
devServer: {
// ...
headers: {
'Access-Control-Allow-Origin': '*'
}
}
  • router 设置 base
1
2
3
4
5
export default new Router({
mode: 'history',
base: window.__POWERED_BY_QIANKUN__ ? '/cms' : '/',
routes: constantRouterMap
})
  • 修改 main.js
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
// 修改
import bootstrap2 from './core/bootstrap'
// 删除
// new Vue({
// router,
// store,
// i18n,
// created: bootstrap,
// render: h => h(App)
// }).$mount('#app')
let instance = null
function render (props = {}) {
const { container } = props
instance = new Vue({
router,
store: store,
i18n,
created: bootstrap2,
render: h => h(App)
}).$mount(container ? container.querySelector('#app') : '#app')
}
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
} else {
// 独立运行
render()
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap () {
console.log('app bootstraped')
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount (props) {
render(props)
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount (props) {
instance.$destroy()
instance.$el.innerHTML = ''
instance = null
router = null
}
  • 线上 nginx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
location /api/ {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,refresh_token';
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://192.168.103.219:30005/;
}
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
root /usr/local/Cellar/nginx/1.19.5/pcm/dist12;
try_files $uri $uri/ @router;
index index.html index.htm;
add_header Cache-Control max-age=no-cache;
}

基于 nginx 转发

  • 主应用没有修改
  • 跳转的应用
    • 设置 publicPath 为 ‘/cms’
    • router 设置 base 为 ‘/cms’

一个前端项目

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
location /cms/api/ {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,refresh_token';
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://192.168.103.219:30005/;
}
location /api/ {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,refresh_token';
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://192.168.103.219:30005/;
}
location /cms {
alias /usr/local/Cellar/nginx/1.19.5/pcm/dist2/;
try_files $uri $uri/ ../dist2/index.html break; # 注意这个 break
index index.html index.htm;
}
location / {
root /usr/local/Cellar/nginx/1.19.5/pcm/dist1;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

多个前端项目

主项目配置如下:

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
location /api/ {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,refresh_token';
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://192.168.103.219:30005/;
}
location /cms {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://localhost:1889;
}
location / {
root /usr/local/Cellar/nginx/1.19.5/pcm/dist1;
try_files $uri $uri/ @router;
index index.html index.htm;
add_header Cache-Control max-age=no-cache;
}

跳转项目配置如下:

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
location /cms/api/ {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,refresh_token';
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://192.168.103.219:30005/;
}
location /api/ {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,refresh_token';
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://192.168.103.219:30005/;
}
location /cms {
alias /usr/local/Cellar/nginx/1.19.5/pcm/dist2/;
try_files $uri $uri/;
index index.html index.htm;
}
location / {
root /usr/local/Cellar/nginx/1.19.5/pcm/dist2;
try_files $uri $uri/ /index.html;
index index.html index.htm;
add_header Cache-Control max-age=no-cache;
}

Vue 生命周期钩子

发表于 2021-04-16   |   分类于 Vue

参考

  • 官方文档 生命周期钩子。

  • 超详细vue生命周期解析(详解)

Vue 生命周期钩子

  • beforeCreate
    • 实例初始化后、创建之前调用
    • 没有实例化,不能访问数据
  • created
    • 实例创建后调用
    • 能访问到数据,能修改数据且不会触发 update 阶段
    • 异步数据的获取和对实例数据的初始化操作都在这里面进行
  • beforeMount
    • 编译模板已经结束,虚拟 DOM 已经存在,真实的 DOM 节点挂载到页面之前调用
    • 能访问到数据,能修改数据且不会触发 update 阶段
  • mounted
    • 真实的 DOM 节点挂载到页面之后调用,可以使用 $el、$refs 拿到节点
    • 能访问到数据,能修改数据但会触发 update 阶段
  • activated
    • 被 keep-alive 缓存的组件激活时(包括第一次)调用
  • deactivated
    • 被 keep-alive 缓存的组件停用时调用
  • beforeUpdate
    • 数据更新时调用,发生在虚拟 DOM 打补丁之前
    • 能访问到数据,能修改数据不会再次触发 update 阶段
    • 这里适合在更新之前访问现有的 DOM
  • updated
    • 组件 DOM 更新之后调用
    • 能访问到数据,包括 beforeUpdate 修改后的数据,能修改数据会再次触发 update 阶段
    • 应该避免在此期间更改状态,可能会引起死循环
  • beforeDestroy
    • 实例销毁之前调用,此时实例仍然完全可用
    • 能访问到数据,能修改数据不会再触发 update 阶段
    • 能访问到 $el
    • 可以清理非vue资源,防止内存泄露
  • destroyed
    • 实例销毁之后调用,所有子实例也都销毁了,清除vue实例与DOM的关联
    • 能访问到数据,能修改数据不会再触发 update 阶段
    • 能访问到 $el
  • errorCaptured
    • 当捕获一个来自子孙组件的错误时被调用

注意:

  • 只有在 template 中引用的变量变化时,视图才会更新,否则无论 data 还是 props 变化,都不会触发更新

  • 若只有父组件的 data 变化,只更新父组件,子组件不更新

  • 执行了销毁,是清除 Vue 实例与 DOM 的关联,页面中 DOM 元素可能仍然存在,Vue Devtools 中没有对应节点了

  • 如果有 watch 先进入 watch,再进入 beforeUpdate

  • 如果 watch 中又对监听的属性进行了修改,会再次进入 watch,然后再进入 beforeUpdate

为什么不能用箭头函数

函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。this 指向的固定化,并不是因为箭头函数内部有绑定 this 的机制,实际原因是箭头函数根本没有自己的 this,导致内部的 this 就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。

箭头函数没有自己的 this,它的 this 是继承而来;默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象,定义它的时候,可能环境是 window;箭头函数可以方便地让我们在 setTimeout 、setInterval中方便的使用 this。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const obj1 = {
name: 'ces',
method: function() {
console.log(this);
}
}
obj1.method(); // obj1
const obj2 = {
name: 'ces',
method: () => {
console.log(this);
}
}
obj2.method(); // undefined

类内部自身调用时 this 就是自己的实例,但是如果传给其它地方调用了,this 就变了,所以有了 bind this、箭头函数这种写法。

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
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString1() {
console.log('(' + this.x + ', ' + this.y + ')');
}
toString2 = () => {
console.log('(' + this.x + ', ' + this.y + ')');
}
}
const point = new Point(1, 2);
point.toString1(); // (1, 2)
point.toString2(); // (1, 2)
const obj = {
x: 3,
y: 4,
toString1: point.toString1,
toString2: point.toString2,
};
obj.toString1(); // (3, 4)
obj.toString2(); // (1, 2)

IE11 兼容调试

发表于 2021-04-13   |   分类于 前端知识

调试过程

  • 最困难的是看不见报错信息,开发者工具打开就崩溃
  • 简化项目,删除一些文件,注释一些路由,如注释了 cesium、three、example 的路由,看到报错信息了,添加了 babel-polyfill
  • 放开 example 路由,看示例程序就崩溃,把示例 html 中的内容粘贴到 index.html 中运行,看到了报错,html 中集成 babel 转码和 polyfill 兼容 IE11
  • 修改了 babel-loader 处理的范围,添加了 cesium、three 包

注意事项

  • DOM 操作的 append 方法改为 appendChild 方法
  • 示例程序的 html 中尽量不写 ES6 语法
  • 示例程序的 html 中不要写 async await,使用 then 回调
  • 示例程序的 html 中变量要有声明
  • WebGL 是实验性的,可能会有问题

《精益创业UX篇 高效用户体验设计》笔记

发表于 2021-04-04   |   分类于 Reading Notes
  • 在开发产品之前,弄清楚用户是否会购买你的产品。
  • 在整个产品生命周期中,倾听用户的心声。
  • 理解为何在设计一款产品之前要做测试。
  • 获得对产品设计至关重要的9个工具。
  • 区分必须的功能和有则更好的功能。
  • 学习最小化可行产品是如何影响用户体验设计决策的。
  • 协同使用A/B测试与优秀的用户体验设计实践。
  • 加速产品开发过程而不牺牲产品质量。

全书共3篇,分别从验证、设计及产品3个方面介绍了精益用户体验设计。

你应该了解如何设计简单产品,A/B测试,然后周期迭代改进。对了,别忘了还有持续部署、敏捷开发和最小化可行产品。

和精益创业非常相似,精益用户体验设计时围绕着验证假设展开的。

精益用户体验设计认为产品是由一组有待验证的假设所组成的。换句话说,不要以为我们知道用户想要什么。我们做用户访谈和用户调研就是为了拟定关于用户可能需要什么的假设,然后以各种方式验证这一假设,来看看我们是否正确。每一次验证都帮助我们对产品进行改进。

精益用户体验设计不是简单地把功能添加到产品,它是要找出关于驱动业务的指标,了解我们可以解决的客户问题从而推动指标的增长,产生改善这些客户问题的想法,然后验证这些想法是否正确。

很多精益创业公司都会落入这个陷阱:无法判断一个假设是否被验证了。

精益用户体验设计是以用户为中心的(User Centered Design,UCD)。

精益用户体验设计是敏捷的。

精益用户体验设计是由数据驱动的。

精益用户体验设计是快速和低成本的(某些时候)。

精益用户体验设计是迭代改进的(一直都是)。

验证

思考产品点子的时候,人们会犯一个很常见的错,要么是这个产品要解决的问题根本不存在,要么是痛点还不够痛,没有达到人们忍无可忍,想要解决它的程度。

但如果找不到需要解决的问题,那么人们也就没有足够充分的理由购买你的产品。就算你的点子再怎么棒、有创意、具有颠覆性的,早期验证可以让你更好地理解用户和完善你的点子。

创业是在充满各种高度不确定性的情况下进行产品或服务创新,尽早验证问题足以降低这种不确定性。

验证市场的第一个目标是缩小受众人群——那些希望问题能够被彻底解决的人们。第二个目标是去了解他们为什么对你的方案感兴趣,由此你还可以找到相似的市场,他们也有类似的动机。

当你能精确地预测某类人将会遇到的某个特殊问题,并且这个问题也足够严重,以至于他们愿意花钱购买解决方案,那么此时,就算你已经成功验证了你的市场了。

用于早期验证的方法:

  • 用户行为习惯研究(倾听你的用户)

    在做用户行为研究的时候,非常容易犯一个错误,那就是告诉受访者你正在制作什么样的产品,以及这个产品是多么适合他们。如果给用户灌输你的想法的话,没有什么能比这更快地把用户带到沟里去。

  • 着陆页测试

    在制作产品之前先售卖产品。通过制作一个只有一些简单页面的网站,你可以得到一些粗略的数据,它能够揭示出有多少人对你的解决方案感兴趣。这样做有个好处,那就是你可以在制作产品之前就开始制作这样的网站。

  • 原型测试

    正确的方式是,给用户展示一些东西,并且观察他们的反应。最理想的情况是,你给用户展示的东西最好看上去,或者让他们感觉到这确实是一个产品,但是又并不需要你花上几个月的时间来写代码来制作这个东西。原型测试是个永不尽早验证你的产品的最佳方式。

痛点驱动设计(Pain-Driven Design,PDD)要求你在开始设计产品或者新功能之前,首先要弄明白是什么东西让你的用户或者潜在用户感到难受。

如果你在对的时间做对的研究,你最终会节省时间和金钱。我已经提到过你应该做的几种不同类型的用户研究:用户验证和原型测试。

去测试别人的产品,你要做的不是去帮他们修复问题,而是要避免他们犯过的所有错误。

  • 你喜欢这个产品的什么地方?
  • 你不喜欢这个产品的什么地方?
  • 你对这个产品的哪里感到困惑?
  • 这个产品有什么让你感到特别不爽吗?
  • 这个产品缺少什么吗?
  • 你如何学会使用这个产品?
  • 你是在哪里听说这个产品的?
  • 你试用过其他类似的产品吗?
  • 为什么你选了这个产品而不是其他的呢?
  • (用于企业产品)哪部分工作是你必须要做、但这个产品却没有提供相应功能的?你是如何看待这个问题的?

在你打算创业时最重要的决策之一就是如何对用户描述产品。

A/B测试可以告诉你在各个着陆页中哪一个效果最好。

找出用户对你的着陆页反应的唯一方法是问答下面的问题:

  • 用户觉得这个产品是做什么的?
  • 用户认为这个产品是给谁用的?
  • 用户知道如何获取该产品吗?

UsabilityHub是一个远程用户测试平台,通过5秒钟测试、单击测试、导航测试、问题测试和偏好测试等,你可获得真实快捷的用户反馈。UsabilityHub作为研究用户数据的工具可帮助开发,设计以及营销人员改善目标网页和应用程序的用户体验,从而提升你网站的转化率。

为了在任何类型的用户调研中都发挥到极致,在发现共性问题之前,最好找尽量少的人进行测试。然后你会一次次地测试这些人,同时也会在每次测试之间留下足够多的时间来修改产品、样品、原型、讨论方案,或者任何你打算进行测试的东西。

只有一种类型的调研需要亲自面对面进行,那就是你需要了解用户在什么环境中使用你的产品,或者用户和产品在一起的时候才能进行测试。

非监管测试是指,你可以自动获得一个关于真实用户使用你的产品的视频,视频中的人尝试着去执行各种你所指派的任务。它最不适合用来做什么:

  • 了解人们是否喜欢你的产品
  • 了解人们是否会使用你的产品
  • 了解人们在他们的设备上并且在没有任何提示的情况下使用你的产品的时候,是否还能知道他们应该执行什么任务
  • 了解真实用户每天都是怎么使用你的产品的
  • 了解如何修复你发现的易用性问题
  • 其他

定性研究,这主要包括和用户会谈,观察并理解他们的行为。这里跟统计学无关。这里有几个定性研究的例子:

  • 情境问答
  • 易用性研究
  • 客服访谈

定量研究指的是测量真实用户是如何使用你的产品的。它不涉及和某一个人交谈,而更多的是关于研究聚合的数据。这是具有统计学意义的。这里有几个定量研究的例子:

  • 漏斗分析
  • A/B测试
  • 群组分析

定量研究会告诉你问题是什么,定性研究会告诉你为什么有这样的问题。

定性的方法:

  • 定期观察用户使用你的产品
  • 和那些放弃使用你的产品的人交流
  • 观察新用户使用你的产品,问问他们在开始使用产品的最初15分钟里,他们期望的是什么

定量的方法:

  • 观察那些被最有价值的用户使用得最多的产品功能
  • 尝试使用“假功能“测试,这可以通过添加一个按钮或者导航元素

最佳的策略是,确保你总是能够追踪到你的指标,并且观察人们使用你的产品。

定性研究能够很明确地告诉你,用户能否做某件事。它可以告诉你这个功能对于用户而言是否有意义,以及用户能否成功完成某项任务。

定性研究对于判断用户是否愿意做某些事情也是有一定效果的,因为用户很有可能会做这些事情,只要产品或者功能不要太难以使用。

当你在决定到底是选择定性研究还是定量研究的时候,有件很重要事情得记住,那就是问自己,到底是想要知道发生了什么,还是想知道为什么某件事会发生。

设计

设计就是关于解决问题的。一旦你很好地定义你的问题,并确定你想要的产品结果是什么,为了防止万一你的最终产物存在缺陷,精益用户体验设计鼓励你用尽量少的工作量尽快的制作出你期望的结果。这意味着要先做设计并去验证你的假设。

在精益用户体验设计中,你应该尽可能多地区验证你的假设。

  • 工具1:真正了解问题
  • 工具2:先设计测试
  • 工具3:写一些故事
  • 工具4:与团队讨论可能的解决方案
  • 工具5:做出决策
  • 工具6:验证方法是否有效
  • 工具7:一些草绘的方法,Balsamiq 或 OmniGraffle 草绘工具
  • 工具8:创建交互原型
  • 工具9:测试和迭代改进

如果要我说出精益用户体验设计区别于其他设计方式的最明显的一点的话,那答案就是测试。

还要补充一点:给用户真正想要的。一个客户说:”我想要某个功能“,企业家和产品负责人往往写下”客户想要某个功能“,而不是了解他为什么想要这个功能,然后就开始制作这个功能了。

卓越设计当中很重要的一环就是把时间花在重要的事情上,而不要浪费在那些无关紧要的事情上。

  • 这个问题影响到谁了?
  • 影响到他们的频率有多高?
  • 这个问题对哪一个关键指标造成了破坏?

流程图适合用来做什么?当你想要弄清楚用户在产品中的移动路径,以及他们是如何完成某些常规任务的时候,最好是用流程图或者站点地图。它对预估工作量也有极大帮助,还可以辅助你和开发人员就设计问题进行沟通交流。

流程图不合适拿来验证假设,或者用于易用性测试,因为它不是设计给用户看到。

草绘图是你首次开始试着把产品或者功能变得可视化。别再从”用户需要登录“这个角度思考问题,而应该多思考用户在登录过程中可能需要哪些元素,以及这些元素之间的关系。

在你思考问题的时候,应该采用草绘图,因为它非常便捷,灵活度高。你可以毫不费力的在页面上画各种各样的元素,移动调整它们的位置,甚至删除或者再加入一些元素。你也可以制作一堆草绘图,每一个都包含众多的元素,或者把不同的信息归类放置。总之,你可以很容易地做各种试验。

草绘图还可以帮你和其他人沟通交流你的设计思路。

草绘图并不能用来从用户那里收集反馈信息。

在我看来,一个使用的线框图必须包含所有的真实产品中的文字、按钮、关键操作,以及导航元素。而且此时它还没有任何视觉设计,这将在后面的环节中添加进来。但是可以肯定的是,线框图包含了所有在制作草绘图的时候设计出来的元素,并且不仅仅只是把这些元素组合到一起,还要让它们支撑起整个功能或者产品。

重要的是要记住,线框图是用来让你弄清楚屏幕上或者产品的某个工作状态下的每一处细节的。

制作交互式原型产品的最佳理由是,在花费大量时间和金钱制作真正的产品之前,它有助于帮你找出设计里的错误。基本上来讲,当原型产品可以帮你节省时间和金钱的时候,你就应该使用它。

关于交互式原型很重要的一件事情就是,当制作一个要耗费很多时间才能上线的功能的时候,或者后续的修补成本很高的时候,它很适合用来测试你的产品。

在设计的最初阶段,还在和团队成员头脑风暴各种不同的点子的时候,纸上原型可以让每个人都对产品有统一的认识。这种情况下,纸上原型是非常迅速且高效的,但是别期望可以得到更多的更深入的细节。而且不要给潜在用户展示纸上原型,仅仅只把它展示给团队成员。

视觉设计和交互设计是不能互换的,视觉设计是关于一个产品看起来怎么样,而交互设计是关于一个产品如何运作的。

产品

A/B测试(有时称为分桶测试或多变量测试)是指创建多个版本的界面或功能,并在真实产品环境中对不同的用户显示不同版本,以找出哪个版本的测试结果更好。

做设计评估最主要的原因是,可以知道你所做的改动对于公司而言,到底是起到了正面积极的作用,还是带来了负面的影响。

A/B测试的本质是,通过技术解决方案来解决设计问题,正是这一点使得它被大量的工程师文化浓厚的团队所采纳。

净推荐值是指有多少用户会向朋友推荐你的产品。虽然净推荐值是度量用户满意度的最佳指标,但它很难被精确收集。

只有在产品具有延迟注册机制的情况下,注册率才能算是衡量用户满意度的指标。

有时候就算得到的统计数据看上去非常显著,然而却并不具备统计学意义,原因是样本范围太小。

当你观察任何类型的实验或改动的结果时,你应该加入时间因素,看看在较长的时间范围内它将如何影响你的指标。

跨职能团队能起到很好的效果是因为每个人在相同的时间做相同的事情,这意味着信息在传递的过程中不会被混淆或丢失。

WEB 自适应

发表于 2021-02-22   |   分类于 前端知识

多媒体查询

主要针对不同的媒体类型(包括显示器、便携设备、电视机,等等)设置不同的样式规则,主要是页面布局或元素需要发生变化的情况,如从横向排列改为纵向排列,或使用不同图片等。

Flex 布局

可针对容器宽度自行调整元素位置,如果可以不限制元素宽高,自行充满。

相对单位

避免使用 px,使用 vh、vw、rem 等单位。使用 rem 的方式,可以采用工具进行自动的 px 到 rem 的转换。注意 1px 可以不转换,有时转换为 rem 会不显示,写成 1PX 即可不被转换。

rem 方式实现

安装 postcss-pxtorem

1
npm install postcss-pxtorem --save-dev

配置 postcss-pxtorem

添加 .postcssrc.js 文件如下

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
"plugins": {
"autoprefixer": {},
"postcss-pxtorem": {
"rootValue": 16,
"propList": [
"*"
],
"selectorBlackList": ["el-tooltip"],
}
}
};

添加自适应代码

添加 rem.js 文件如下

1
2
3
4
5
6
7
8
9
function setRem() {
var html = document.getElementsByTagName('html')[0];
var width = window.innerWidth; // 获取窗口的文档显示区的宽度
var font_Size = (16 / 1920) * width; // 设计稿以1920为准,在1920的设计稿中:16px = 1rem
html.style.fontSize = font_Size + 'px';
document.documentElement.style.fontSize = font_Size + 'px';
}
setRem();
window.addEventListener('resize', setRem);

修改 main.js 文件如下

1
2
3
...
import './utils/rem';
...

Chrome 开发者工具

发表于 2021-02-03   |   分类于 前端知识

您已进入无痕模式调试性能相关的问题,禁用页面缓存,调整网络状态为 3G

  • F5, Ctrl + R / Cmd + R,刷新页面
  • Ctrl + F5, Ctrl + Shift + R / Cmd + Shift + R,刷新页面并忽略缓存
  • Ctrl + ‘+’ / Cmd + Shift + ‘+’,放大 DevTools
  • Ctrl + ‘-‘ / Cmd + Shift + ‘-‘,缩小 DevTools
  • Ctrl + 0 / Cmd + 0,DevTools 恢复大小

模拟设备

打开调试工具,点击图中【1】所指图标可模拟设备,这个功能能够将你的浏览器变成任意一款移动设备,也能为你的网页设定宽高,点击 Edit… 可选择或添加常用设备。

Elements 面板

Elements 面板主要用于对页面 HTML 和 CSS 的检查以及可视化编辑,左上部分是一棵 DOM 树,左下部分是选中元素及所有父节点,右边是选中元素的样式。

检查页面 DOM 元素

  • 右击页面任意一元素,选择检查
  • 快捷键 Cmd + Opt + C,或点击图中【1】所指图标,在页面中选择元素
  • 鼠标悬停 Elements 面板 DOM 树上的任意一个节点,页面会用淡蓝色的蒙板在页面上标记 DOM 节点对应的页面
  • 按键盘的向上、向下键可以在展开的节点之间进行切换,向左、向右键可以收缩和展开节点

点击图中【2】所指图标,Settings,Elements,勾上 Show user agent shadow DOM,可查看 placeholder 样式,如图中【3】所指。

编辑 DOM

如图中【4】所示,选中 DOM 节点后,可双击或回车编辑,也可右键选择相应的菜单编辑。其中,Scroll into view 可让这个元素快速滚入视图中, Force state 可以触发并保持元素的伪类状态,Break on 可以添加 DOM 节点的监听,subtree modifications 表示子元素改变时、attribute modifications 表示属性改变时、node removal 表示元素被移除时。

在 Console 面板输入 document.body.contentEditable="true",可以直接对页面进行编辑。

检查、编辑样式

Styles 面板,实时编辑与所选元素相关的样式,单击属性或者属性值可进行修改,按 Tab 键修改下一个属性或值,按 Tab + Shift 修改上一个属性或值。当值是数字类型时,按上下键可以以 1 为单位递增或递减,按 Alt + 上下键以 0.1 为单位递增或递减,Shift + 上下键以 10 为单位递增或递减。点击空白处可添加新的样式。

以 “Inherited from …” 为分界,上面的样式都是作用于元素本身的,下面的都是其继承而来的,继承的对象一般不止一个,可能是父元素,父元素的父元素…,将鼠标悬停在一个选择器上时,可以看到这个选择器所影响的所有页面元素(不包括可视区域外的元素)。

图中【5】所指部分,:hov 可触发并保持元素的伪类状态,用于查看伪类的 css 样式。.cls 可临时增删元素 class。+ 可添加新的样式规则。

长时间悬停在某 CSS 类名上,会突出显示受该属性影响的所有节点。

Event Listeners 面板,查看所选元素相关的监听事件。其中 Ancestors 不勾选则只显示直接定义在所选元素上的监听事件。Ancestors 后面为监听器类型,Blocking 为典型的那些过时的事件监听,Passive 指 Passive events listeners,是一个新的 web 标准,从 Chrome 51 开始添加的一个新特性,主要用来让页面滑动更加流畅。Framework listeners 检查来自框架的事件监听。右键事件监听,弹出菜单可以快速定位到源码。

Computed 面板,检查、编辑所选元素的盒模型。

Layout 面板,Grid 布局样式调试。

DOM Breakpoints 面板,管理添加的 DOM 节点监听。

Properties 面板,所选节点对应的对象及父类们。

Accessibility 面板,查看 Accessibility Tree,无障碍树。

Console 面板

Console 面板一方面用来记录页面在执行过程中的信息(一般通过各种 console 语句来实现),另一方面用来当做 shell 窗口来执行脚本以及与页面文档、DevTools等进行交互。

打开调试工具,可以看到 Console 面板,如果在其它面板下想同时看 Console,可以按 Esc 键,将 Console 以 “Drawer” 的形式打开。Console Drawer 中,Search 面板,可全局搜索代码,点击搜索结果,会跳到具体的源码文件。

图中【1】所指按钮为清空,可以通过快捷键 Ctrl L 清空 Console 面板,这个清空并不是真正意义的清空,还可以按向上向下的按键查看在 Console 中输入的历史。

图中【2】所指为执行环境选择器,除了当前的页面的执行环境,其它的框架、拓展都有其自己的执行环境,默认的执行环境是 “top”,点击下拉框还有其它选项,保持默认的 top 即可,如果想调试 iframe 可切换到指定的 iframe 环境。

图中【3】所指 Default levels 下拉框中勾选 Verbose,显示日志级别的信息,过滤框中输入 violation,可查看针对代码的最佳实践。

图中【4】创建一个动态监听,实时监听一个变量,如果变化了,这里也会变化,如输入 Date.now()。

Hide network:默认是不勾选的,Console 会报告网络问题,勾选这个功能就能过滤网络报告信息。

Preserve log:默认是不勾选的,如果勾选了,刷新页面之后信息还会被保留。

  • $() 作为 document.querySelector() 的缩写
  • $$() 作为 document.querySelectorAll() 的缩写,返回一个数组
  • $0 ... $4,代表 5 个最近访问过的 DOM 或者堆对象,$0 是最近访问的
  • $_ 记录了最后一次在表达式执行的结果

在 Elements 标签页中选中一个页面元素,在 Console 标签页中,调用函数 monitorEvents($0, 'click'),第一个参数是当前元素($0),第二个参数是事件名(click),按 Enter后,当被选中的元素触发了点击事件之后,Console 标签页会将该点击事件对象打印出来,调用 unmonitorEvents($0) 进行解绑。

如果要打印的变量是一个数组,每一个元素都是一个对象,可以使用 console.table 来打印,其表格化的呈现更加美观易读,如图中【5】所指。

copy() 是一个工具函数方便将任何东西拷贝到系统的粘贴板暂存,传入一个没有格式的JSON,会返回格式化的结果,如图中【6】所指。

console.log() 会在浏览器控制台打印出信息。

console.dir() 可以显示一个对象的所有属性和方法。

Application 面板

Application 面板查看应用信息、网页加载的所有资源,包括存储信息、缓存信息以及页面用到的图片、字体、脚本、样式等信息。

Ctrl Shift P,输入 clear,选择 Clear site data,在 Application 面板中 Storage 中可看到 Clear site data 按钮,一次性清除网站所有数据。

Frames 菜单中显示了该页面所有内容资源,顶级的 top 是一个主文档,在 top 下面是主文档的 Fonts、Images、Scripts、Stylesheets 等资源,最后一个就是主文件自身。在资源上右击后在弹出菜单选择 Reveal in Network Panel,就会跳转到 Network 面板并定位到该资源的位置。

Network 面板

Network 面板可查看页面中各种资源请求的情况,如资源的名称、状态、使用的协议、资源类型、资源大小、资源时间线等情况,右键列头可以添加或隐藏列,可以根据这个进行网络性能优化。Network 面板主要分为3部分,分别为图中【1】【2】【3】,其中【3】所指为 Summary 概览,显示总的请求数、数据传输量、加载时间信息,下面详细说一下【1】【2】部分。

Controls

图中【1】所指为 Controls 控制面板,控制 Network 的外观和功能。其中点击【4】所指过滤图标显示【6】所指过滤面板,Filter 输入框中可输入文本、正则表达式、属性过滤、- 开头表示取反,在显示的文件类型中,可按 Ctrl 键多选,WS 指 WebSocket,都是控制 Requests Table 具体显示哪些内容。Preserve log 勾选后刷新页面不清空 Network,Disable cache 勾选后禁用缓存。点击 Throttling 节流按钮,可模拟网络状态,默认值是 Online。点击【5】所指设置图标显示【7】所示更多控制选项,勾选 Show overview 会显示【8】所指面板,显示获取到资源的时间轴信息,其中蓝色竖线表示 DOMContentLoaded 事件被触发,并且在 Summary 以蓝色文字显示确切的时间,红色竖线表示 load 事件被触发,在 Summary 也会以红色文字显示确切的时间。勾选 Capture screenshots 重新加载页面即可捕获屏幕,双击其中的截屏可以放大显示,在放大的图下方可以点击跳转到上一帧或者下一帧,单击则可以查看该帧被捕获时的网络请求信息,并且在 Overview 上会有一条黄色竖线以标记该帧被捕获的具体时间点。

  • DOMContentLoaded 事件会在页面上 DOM 完全加载并解析完毕之后触发,不会等待 CSS、图片、子框架加载完成
  • load 事件会在页面上所有 DOM、CSS、JS、图片完全加载完毕之后触发

Requests Table

图中【2】所指为 Requests Table 请求表格,按照请求的顺序排序,显示所有获取到的资源信息。点击一个请求的 Name 列可以了解更多信息,其中:

  • Headers 该资源的HTTP头信息
  • Preview 根据你所选择的资源类型(JSON、图片、文本)显示相应的预览
  • Response 显示HTTP的Response信息
  • Cookies 显示资源HTTP的Request和Response过程中的Cookies信息
  • Timing 显示资源在整个请求生命周期过程中各部分花费的时间

在 Timing 标签中可以显示资源在整个请求生命周期过程中各部分时间花费信息,可能会涉及到如下过程的时间花费情况:

  • Queuing 排队的时间花费,可能由于该请求被渲染引擎认为是优先级比较低的资源(图片)、服务器不可用、超过浏览器的并发请求的最大连接数(Chrome的最大并发连接数为6)
  • Stalled 从HTTP连接建立到请求能够被发出送出去(真正传输数据)之间的时间花费,包含用于处理代理的时间,如果有已经建立好的连接,这个时间还包括等待已建立连接被复用的时间
  • Proxy Negotiation 与代理服务器连接的时间花费
  • DNS Lookup 执行DNS查询的时间,网页上每一个新的域名都要经过一个DNS查询,第二次访问浏览器有缓存的话,则这个时间为0
  • Initial Connection 建立连接的时间花费,包含了TCP握手及重试时间
  • SSL 完成SSL握手的时间花费
  • Request sent 发起请求的时间
  • Waiting (Time to first byte (TTFB)) 是最初的网络请求被发起到从服务器接收到第一个字节这段时间,它包含了TCP连接时间,发送HTTP请求时间和获得响应消息第一个字节的时间
  • Content Download 获取Response响应数据的时间花费

通过按住 Shift 并且把光标移到请求行上,在该资源的上方第一个标记为绿色的资源就是该资源的发起者(请求源),有可能会有第二个标记为绿色的资源是该资源的发起者的发起者,以此类推,在该资源的下方标记为红色的资源是该资源的依赖资源,也就是对该资源的请求过程中引发了哪些资源,如下图所示。

右键一个请求行,弹出菜单,其中:

  • 选择 Copy -> Copy as fetch,复制为一个完整的 Fetch 请求的代码
  • 选择 Block request domain 或 Block request URL,可以分别阻塞该请求所在 domain 下的所有请求 和 该请求

Sources 面板

主要用来调试页面中的 JavaScript。Ctrl Shift P,输入 folding,选择 Enable code folding,或通过 Setting -> Preferences -> Sources,勾选 Code folding,可以在 Source 面板的编辑器和 Network 面板的 Preview 窗口下折叠 CSS 和 JavaScript 代码。设置断点可通过如下几种方式:

  • 在 JS 文件中输入 debugger js 文件运行的时候会在此处暂停
  • 在 Sources 面板中打开文件,点击行号可添加断点,有时会出现行内断点,如【4】所示,点击即可激活,右键行号,Add condition breakpoints 可添加条件断点,Add logpoint 可快速注入一条 console.log 消息,设置的行断点可以在 Breakpoints 小窗口做统一管理
  • 在 Elements 面板右键元素,Break on 可添加 DOM 断点
  • XHR/fetch Breakpoints 可添加 URL 包含某一字符串的断点,会在 XHR 调用 send() 那行代码的地方暂停
  • Event Listener Breakpoints 可勾选某一类别的事件或者是某一具体的事件添加事件监听器断点
  • 点击图中【2】所指图标可在捕获的异常处暂停,未捕获的异常不考虑

图中【1】所指按钮可停用所有断点,在不取消断点标记的情况下,使得所有断点失效;【3】所指按钮可将压缩的代码格式化显示。

当在某行代码暂停时,在调用栈窗口(Call Stack)的任意地方右键选择 Restart Frame,DevTools 会在调用栈 top 函数的第一行代码处暂停,top 函数就是指最后一个被调用的函数,在调用栈中位于最上面,所以叫 top 函数。可以在 Scope 窗口查看、编辑某些属性值和变量值,这些属性和变量按照作用域又分属在不同的地方,如局部作用域内、闭包内或者全局作用域内。在编辑器窗口打开对应的 js 文件中,可选择变量添加到 Watch 中进行监听,可在任意地方右键选择 Blackbox script 忽略该脚本文件,如第三方的库文件。可在 Settings -> Blackboxing 中统一管理黑盒中的所有脚本文件。可在编辑器窗口直接修改文件,按Command+S(Mac)或者Ctrl+S保存修改,DevTools会重新编译脚本,继续在页面上进行某些操作,比如点击事件,就可以看到修改代码的效果了。Global Listeners 显示全局监听器,在浏览器中 window 是全局对象,所以在 Global Listeners 面板中显示绑定在 window 对象上的事件监听。

Overrides 子标签,选择 + Select folder for overrides,来为 Overrides 设置一个保存重写属性的目录,启用本地覆盖 ,面板上会显示你刚刚选择的文件夹。在 Page 子标签里选择你要修改的文件,Ctrl+S 保存修改,此时会在 Overrides 中你选择的文件夹下产生目录对应的本地副本文件。当存在本地副本时,本地覆盖启用,且工作空间下有该网站的同名覆盖文件,浏览器会优先执行覆盖文件。

Content scripts 指的是 Chrome 拓展注入在网页中的脚本。

Snippets 子标签来辅助 Debugging,以创建和保存小段代码的工具,比如 可以快速给任何应用添加lodash,右键可执行。

1
2
3
4
5
6
7
8
(function () {
'use strict';
var element = document.createElement('script');
element.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js";
element.type = "text/javascript";
document.head.appendChild(element);
})();

Performance 面板

Performance 面板可查看页面加载过程中的详细信息,不仅可以看到通过网络加载资源的信息,还能看到解析 JS、计算样式、重绘等页面加载的方方面面的信息。Cmd + Shift + N 打开 Chrome 的无痕模式,打开 在线DEMO 链接,Cmd + Option + I 打开开发者工具,点击 Performance 面板。

其中,【1】中三个按钮分别表示手动开始记录、自动重启页面并记录整个页面的加载过程、清除性能录制的记录,点击【2】按钮会进行一次垃圾回收,点击【3】Capture Settings(⚙️)按钮会展示更多设置来模拟各种状况,Disable JavaScript samples 选项勾选会使工具忽略记录 JS 的调用栈,Enable advanced paint instrumentation 选项勾选会详细记录某些渲染事件的细节,Network 选项可模拟网络状态,CPU选项可进行CPU限速。

点击左上角的 Record 按钮开始记录,然后模拟正常用户使用网页,点击 Stop 停止记录,生成性能报告。

第一部分中,右测分别有 FPS、CPU、NET、HEAP:

  • FPS 对应的是帧率,绿色代表帧率正常,绿色条越高,FPS 越高,红色代表帧率低,如果发现了一个红色的长条,那就说明这些帧存在严重问题,可能会降低用户体验,FPS ≥ 60性能最佳,FPS < 24 会让用户感觉到卡顿
  • CPU 部分上有黄色、紫色、绿色等色块,它们的释义看图的左下角 Summary 标签,谁的占比高,说明 CPU 主要的时间花在哪里
  • NET 每条彩色横杠表示一种资源,横杠越长,检索资源所需的时间越长,每个横杠的浅色部分表示等待时间(从请求资源到第一个字节下载完成的时间)
  • HEAP 就是堆内存占用

对于这个DEMO,可以很容易观察到性能问题,现在已经确定到这个页面的动画性能表现不太好,注意第四部分 Summary 面板中,也会发现CPU花费了大量的时间在 Rendering 上,提高性能就是一门做减法的艺术,目标就是减少 Rendering 的时间。

第二部分中,把鼠标移动到 Frames 的绿色条状上时,会展示这个帧的FPS。展开 Main 图表,展示了主线程运行状况,X轴代表着时间,每个长条代表着一个event,长条越长就代表这个event花费的时间越长,Y轴代表了调用栈,上面的event调用了下面的event。在事件长条的右上角出,如果出现了红色小三角,说明这个事件是存在问题的,需要特别注意。可使用键盘A键(选区轨迹左移)、D键(选区轨迹右移)、W键(缩小选区)、S键(增大选区)调整选择区域。

点击带有红色小三角的的事件,在 Summary 面板会看到详细信息,包括警告信息,如果存在 Reveal 的链接,双击它会让高亮触发这个事件的event,Reveal 下面是源码文件的链接,点击就会跳转到对应的代码处。点击 app.js:95 这个链接,就会跳转到对应的代码处,定位到是 update 方法造成的,但还不够明确。

在 app.update 这个事件的长条下方,有很多被触发的紫色长条,放大这些事件长条,会看到它们每个都带有红色小三角,点击其中一个紫色事件长条,在 Summary 面板里展示了更多关于这个事件的信息。这里有“Forced reflow is a likely performance bottleneck.”警告,即强制回流可能是性能瓶颈,点击 Reveal 下面的源码链接 app.js:70 会跳转到需要优化的代码处。

这段代码的问题在于,在每个动画帧中,它会更改每个方块的位置,然后查询页面上每个方块的位置,由于样式发生了变化,浏览器不知道每个方块的位置是否发生了变化,因此必须重新布局方块以计算其位置。优化代码是将 offsetTop 替换成 style.top,后者虽然取的是上一帧动画的元素位置,但并不影响计算下一帧动画位置,省去了重排获取位置的过程,减少了不必要的重排。

Lighthouse 面板

从以下5个方面来对页面的加载进行分析,生成报告,然后给出提高页面性能的建议。

  • Performance 性能检测,如网页的加载速度、响应时间等
    • First Contentful Paint(FCP) 首次内容绘制时间
    • Speed Index(SI)速度指数,是一个页面加载性能指标,明显的页面填充的速度,此指标的分数越低越好
    • Largest Contentful Paint(LCP) 最大内容渲染时间,LCP是一个页面加载时长的技术指标,用于表示当前页面中最重要/占比最大的内容显示出来的时间点。不同于FCP,FCP代表的是第一次页面内容渲染的时间点,LCP是FCP的一个重要的补充,它可以代表当前页面主要内容展示的时间,LCP低于2.5s则表示页面加载速度优良
    • Time to Interactive(TTI)可互动时间,页面中的大多数网络资源完成加载并且CPU在很长一段时间都很空闲的所需的时间,此时可以预期CPU非常空闲,可以及时的处理用户的交互操作
    • Total Blocking Time(TBT)累积阻塞时长,TBT是一个衡量用户事件响应的指标,TBT会统计在FCP和TTI时间之间,主线程被阻塞的时间总和,当主线程被阻塞超过50ms导致用户事件无法响应,这样的阻塞时长就会被统计到TBT中,TBT越小说明页面能够更好的快速响应用户事件
    • Cumulative Layout Shift(CLS)累积布局变化量,CLS是一个衡量页面内容是否稳定的指标,CLS会将页面加载过程中非预期的页面布局的累积变动,CLS的分数越低,表明页面的布局稳定性越高,通常低于0.1表示页面稳定性良好
  • Accessibility 无障碍使用,是指所创建的网站对所有用户都可用/可访问,不管用户的生理/身体能力如何、不管用户是以何种方式访问网站
  • Best practices 最佳实践,实践性检测,如网页安全性,如是否开启HTTPS、网页存在的漏洞等
  • SEO 搜索引擎优化,如网页 title 是否符合搜索引擎的优化标准等
  • Progressive Web App 离线应用检测

点击 View Original Trace 按钮可进入 Performance 面板中进行进一步分析。

Memory 面板

Memory 面板可以记录某个时刻的页面内存情况,一般用于分析性能问题、内存问题。推荐用户在创建堆快照时,不要在 Console 中执行代码,也不要启用调试断点。【1】表示开始记录,【2】表示清除,【3】表示进行一次垃圾回收。开始录制前先点击下垃圾回收,如果要查看JS堆内存动态分配时间线,结束之前要再点击下垃圾回收,再结束录制。

Heap snapshot 用以打印堆快照,堆快照文件显示页面的 javascript 对象和相关 DOM 节点之间的内存分配,如下图所示。【1】可以选择查看内存快照的方式,【2】能够按照列出来的 Constructor 值进行筛选,【3】能够选择查看哪些阶段的对象,如 “Objects allocated before Snapshot1”、”Objects allocated between Snapshot1 and Snapshot2”。

内存快照的查看方式包括:

  • Summary 总览视图,可以显示按构造函数名称分组的对象,使用此视图可以根据按构造函数名称分组的类型深入了解对象(及其内存使用),适用于跟踪 DOM 泄漏
  • Comparison 对比视图,可以显示两个快照之间的不同,比较两个(或多个)内存快照在某个操作前后的差异,检查已释放内存的变化和参考计数,可以确认是否存在内存泄漏及其原因
  • Containment 内容视图,此视图提供了一种对象结构视图来分析内存使用,由顶级对象作为入口,有助于分析对象的引用情况,适用于分析闭包以及深入分析对象
  • Statistic 统计视图,内存使用饼状的统计图

内存快照信息中,各个字段代表信息如下:

  • Constructor - 表示使用此构造函数创建的所有对象
  • Distance - 显示使用节点最短简单路径时距根节点的距离
  • Shallow Size - 显示通过特定构造函数创建的所有对象浅层大小的总和。浅层大小是指对象自身占用的内存大小(一般来说,数组和字符串的浅层大小比较大)
  • Retained Size - 显示同一组对象中最大的保留大小。保留大小指某个对象删除后(其依赖项不再可到达)可以释放的内存大小
  • #New - 对比视图下特有,新增项
  • #Deleted - 对比视图下特有,删除项
  • #Delta - 对比视图下特有,增量
  • Alloc. Size - 对比视图下特有,内存分配大小
  • Freed Size - 对比视图下特有,释放大小
  • Size Delta - 对比视图下特有,内存增量

Allocation instrumentation on timeline 在时间轴上随着时间变化记录内存信息,显示了对象在什么时候被创建、什么时候存在内存泄漏。当勾选 Record allocation stacks 框后,还可以在 Allocation stack 面板里打印出调用堆栈。每条线的高度与最近分配的对象大小对应,竖线的颜色表示这些对象是否仍然显示在最终的堆快照中,蓝色竖线表示在时间线最后对象仍然显示,灰色竖线表示对象已在时间线期间分配,但曾对其进行过垃圾回收。可以重复执行某个动作,如果最后有不少蓝色柱被保留,这些蓝色柱就是潜在的内存泄露问题。

Allocation sampling 内存信息采样,使用采样的方法记录内存分配,可以查看哪些函数影响了内存的分配,并且该函数所耗内存在内存分配中占比多少,图中函数可以直接点击跳转到函数定义的文件和位置。【1】可以选择查看方式,其中:

  • Chart,整个内存占用堆栈图信息,root是整个标签加载所需内存,向下逐步拆解形成的内存堆栈
  • Heavy,将堆栈图自底向上的罗列出来
  • Tree,将堆栈图自顶向下的罗列出来

JavaScript Profiler 面板

可以记录函数的耗时情况,方便找出耗时较多的函数,分析内存泄露。【1】可以选择查看方式,Chart 表示按时间先后顺序显示 CPU 性能,Heavy(Bottom Up) 根据对性能的消耗影响列出所有的函数,并可以查看该函数的调用路径,Tree(Top Down) 从调用栈的顶端(最初调用的位置)开始,显示调用结构的总体的树状图情况。

在 Chart 视图下,Overview 部分是整个录制结果的概览,柱形条的高度对应了调用堆栈的深度,也就是说柱形条高度越高,调用堆栈的深度越深。Call Stacks 部分在录制过程中被调用的函数的调用堆栈,横轴表示时间,纵轴表示调用栈,自上而下的表示函数的调用情况。视图中的函数颜色是随机显示的,相同的函数颜色标记是相同的。纵轴表示的函数调用堆栈高度仅仅是函数的调用嵌套层次比较深,不表示其重要性很高,但是横轴上一个很宽的柱形条则意味着函数的调用需要一个很长的时间去完成,那么就需要考虑去做一些优化了。将鼠标移到Call Stacks中的函数上可以显示如下信息:

  • Name 函数名称
  • Self time 函数的本次调用运行的时间,不包含它所调用的子函数的时间
  • Total time 函数的本次调用运行的总时间,包含它所调用的子函数的运行时间
  • URL 函数定义在文件中所在的位置,其格式为file.js:100,表示函数在file.js文件中的第100行
  • Aggregated self time 在这次的录制过程中函数调用运行的总时间,不包含它所调用的子函数的时间
  • Aggregated total time 在这次的录制过程中所有的函数调用运行的总时间,包含它所调用的子函数的时间
  • Not optimized 如果优化器检测到该函数有潜在的优化空间,那么该函数会被列在这里

Rendering 面板

Rendering 面板页面的绘制时间。可通过点击 More tools -> Rendering 打开,或者 ESC 弹出 Console Drawer 面板,点击左边竖形排列的三个小点,选择 Rendering 打开。

  • Paint flashing 实时高亮重绘区域(绿色)
  • Layout Shift Regions 实时高亮重排,及重新布局区域(蓝色)
  • Layer Border 高亮成层用边框(橙色、橄榄色、青色)
  • Frame Rendering Stats,显示 GPU 的信息,旧版本还有实时 FPS 显示,但新版本不知道为何没有(chrome 86)
  • Scrolling performance issues 高亮可能会影响滚动性能的元素,这些元素主要指绑定了scroll事件和touch事件的元素
  • Highlight ad frames 高亮用于广告的 iframe
  • Hit-test borders 展示点击测试的区域
  • Emulate CSS media type 模拟媒体查询是打印还是终端屏幕
  • Emulate CSS media feature prefers-color-scheme 模拟媒体查询的系统主题
  • Emulate CSS media feature prefers-reduced-motion 模拟媒体查询的开启动画减弱功能
  • Emulate vision deficiencies 模拟色盲等视觉障碍

Layers 面板

点击图中【1】所指按钮 ,More tools,Layers,展示页面中的分层情况的3D视图,可平移、旋转查看。

Security 面板

通过该面板你可以去调试当前网页的安全和认证等问题并确保您已经在你的网站上正确地实现 HTTPS 和哪些内容没有通过 HTTPS 连接。

Performance monitor 面板

Ctrl Shift P,输入 Show Performance Monitor 回车,可调出性能监视器,具有实时更新的可视化功能,能突出显示页面中的性能瓶颈。

任务管理器

浏览器右上角三个点的符号 -> 更多工具 -> 任务管理器,主要关注内存占用空间、Javascript使用的内存,Javascript使用的内存默认不显示,可以点击右键添加,可结束指定进程。

  • 内存占用空间,表示本机内存,DOM节点存储在本机内存中,如果这个值在增加,则说明正在创建DOM节点。
  • JavaScript 使用的内存,表示JS堆,这一列包含两个值,关注实际使用大小即可(括号中的数字),跳动的数字表示您网页上的可获得的对象正在使用多少内存,如果这个数字在增加,那说明正在创建新对象,或现有对象正在增长。

如果内存占用空间一直在增长但JS内存不增长,可能是浏览器还没有回收,不操作闲置一段时间看下是否会下降。如果是内存占用空间在增长,但JS内存增长得很缓慢,有可能是有JS变量引用了DOM,这个DOM节点本身不大,但影响了其他DOM节点(比如父级节点树)。

其它

浏览器窗口地址栏中输入 chrome://flags/,可以看到实验性的功能。

Settings 中,Experiments 菜单,勾选 CSS Overview,可以看到页面的 CSS 统计信息。

Settings 中,Experiments 菜单,勾选 Record coverage while performance tracing,在 Performance 面板进行性能录制时可选择捕获代码覆盖率。Coverage 录制结果展示了录制过程中加载的所有 JS 和 CSS 文件以及每个文件的的大小、运行时覆盖率,红色表示未执行,绿色表示已执行。

Settings 中,Experiments 菜单,勾选 Automatically pretty print in Sources Panel,开启自动美化代码模式。

vue-dynamic-component

发表于 2021-01-15   |   分类于 Vue

使用动态组件实现菜单加载逻辑,并通过 mixin 提取公用部分。

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
<template>
<div>
<component
:is="item"
v-for="(item, index) in components"
:key="item + index"
:ref="item"
/>
</div>
</template>
<script>
import Vue from 'vue';
import { mapActions } from 'vuex';
export default {
props: {
components: {
type: Array,
},
},
created() {
if (this.components && this.components.length > 0) {
this.components.forEach(component => {
Vue.component(component, resolve =>
require([`@/views/map/dynamicComponents/${component}`], response => {
response.default.mixins = response.default.mixins || [];
response.default.mixins.push({
created() {
this.$store.dispatch('AddDynamicComponent', {
name: component,
handleClose: this.handleClose,
});
},
});
resolve(response);
}, () => {
console.error(`未找到 @/views/map/dynamicComponents/${component}`);
})
);
});
}
},
methods: {
...mapActions(['AddDynamicComponent']),
},
};
</script>
12…17
© 2021 小朱
由 Hexo 强力驱动
主题 - NexT.Pisces