单例模式定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点
主要思想<通用惰性单例>
管理单例逻辑(保证实现单例的过程)和创建过程(具体业务逻辑)分开,创建过程函数作为参数给到管理单例逻辑函数中,
通过调用单例逻辑函数,业务逻辑函数只执行一次
用途
1.创建单一对象
2.利用业务逻辑仅执行一次,进行事件挂载
注意事项
业务逻辑函数一定要return 一个真正
1 | var getSingle = function(fn){ |
learn and share
单例模式定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点
主要思想<通用惰性单例>
管理单例逻辑(保证实现单例的过程)和创建过程(具体业务逻辑)分开,创建过程函数作为参数给到管理单例逻辑函数中,
通过调用单例逻辑函数,业务逻辑函数只执行一次
用途
1.创建单一对象
2.利用业务逻辑仅执行一次,进行事件挂载
注意事项
业务逻辑函数一定要return 一个真正
1 | var getSingle = function(fn){ |
把不变的部分隔离出来,把可变的部分封装起来
多态最根本的作用就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句
(每个对象应该做什么,已成为了该对象的一个方法,被安装在对象的内部,每个对象负责它们自己的行为,所以这些对象可以根据同一个消息,有条不紊的分别进行各自的工作 ====> 将行为分布在各个对象中,并让对象各自负责自己的行为<—面向对象设计优点)
对象以方法的形式包含了过程,而闭包则是在过程中以环境的形式包含了数据
1.原型继承过程:对象把请求委托给它的构造器的原型
1 | var A = function(){}; |
2.用new 调用构造器时,如果构造器显示返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,而不是this;
若不显示返回任何数据,或者是返回一个非对象类型的数据,则会正常返回this
3.使用Dom 方法时,注意是否会造成this 丢失1
2
3
4<script>
let getId = document.getElementById; //原来 getElementById this指向document
getId('DIV1') //现在指向window
</script>
4.使用call/apply时,如果第一个参数传入null,那么函数体内的this会指向window
5.闭包作用:封装变量;延续局部变量寿命
闭包和内存泄露有关系的地方是,使用闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些DOM节点,就比较可能造成内存泄露。因为IE浏览器中,由于BOM和DOM对象是使用C++以COM对象的方式实现的,而COM对象的垃圾收集机制采用引用计数策略,这时,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收。
解决办法是将循环引用中的变量设为null,方便回收。
1 | var getSingle = function(fn){ |
1 | Function.prototype.before = function(beforeFn){ |
一个curring的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来,待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
1 | var currying = function(fn){ |
多次重复调用,按时间限制调用次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var throttle = function(fn,interval){
var _self = fn,timer,firstTime = true;
return fucntion(){
var args = arguments,_me = this;
if(fiestTime){
_self.apply(_me,args);
return firstTime = false;
}
if(timer){
return false;
}
timer = setTimeout(function(){
clearTimeout(timer);
timer = null;
_self.apply(_me,args);
},interval||500)
}
}
大批量数据要执行同一函数,分批次执行任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 var timeChunk = function(ary,fn,count){
var obj,t;
var len = ary.length;
var start = function(){
for(var i = 0;i<Math.min(count||1,ary.length);i++){
var obj = ary.shift();
fn(obj);
}
}
return function(){
t=setInterval(function(){
if(ary.length === 0){
return clearInterval(t)
}
start();
},200)
}
}
1 | var addEvent = function(elem,type,handler){ |
1 | <edit-modal dismiss="dismiss()" config="model.config" after-save="model.afterSave()" aaa="model.hello"></edit-modal> |
1 | export function editModalDirective() { |
link函数仅在编译时执行一次,三个参数:scope, ele, attrs
即是当前的指令DOM对象,因此可以在ele上绑定触发事件,
但谨慎在数据上利用事件订阅触发机制进行事件绑定,
因为如果在外层包react组件的情况下,可能会导致数据刷新,但没有事件绑定,从而无法触发事件
注意在获取深层子dom时可能会获取不到
指ele所在dom上的属性集合,没有在scope属性中声明的属性也能看到
但要注意,属性值就是标签上变量的名,不是标签上变量指代的值
标签上属性赋值变量的值在参数scope中
link函数参数scope指向当前指令的作用域
参数scope的ctrl属性(controllerAs属性的值)即EditModalCtrl
因为这里配置了controller,bindToController,controllerAs,
所以在scope属性中声明的属性(config)方法(dismiss,after)会被归并到controller中,会以ctrl的属性形式出现,
即如果我想拿到属性scope中声明的config,可以这样:scope.ctrl.config
同时可以在EditModalCtrl中访问获取config,即this.config
另外如果没有在属性scope中声明的属性aaa,但在标签中进行了配置(aaa=”model.hello”),是无效的,ctrl上是不会有aaa属性的,即传递不进来
因此可以在link函数中拿到父作用域传递过来的值,分情况进行不同处理
建立子域,声明所在标签的属性名,以及传值方式,controler存在情况下被合并
@ 在 directive 中使用 xxx 属性绑定父 scope 中的属性。当改变父 scope 中属性的值的时候,directive 会同步更新值,当改变 directive 的 scope 的属性值时,父 scope 无法同步更新值。使用{{}}引用绑定值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24app.controller("myController", function ($scope) {
$scope.name = "hello world";
}).directive("isolatedDirective", function () {
return {
scope: {
name: "@"
},
template: 'Say:{{name}} <br>改变隔离scope的name:<input type="buttom" value="" ng-model="name" class="ng-pristine ng-valid">'
}
})
<div ng-controller="myController">
<div class="result">
<div>父scope:
<div>Say:{{name}}<br>改变父scope的name:<input type="text" value="" ng-model="name"/></div>
</div>
<div>隔离scope:
<div isolated-directive name="{{name}}"></div>
</div>
<div>隔离scope(不使用{{name}}):
<div isolated-directive name="name"></div>
</div>
</div>
= 无论是改变父 scope 还是隔离 scope 里的属性,父 scope 和隔离 scope 都会同时更新属性值,因为它们是双向绑定的关系,使用“”引用绑定值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25app.controller("myController", function ($scope) {
$scope.user = {
name: 'hello',
id: 1
};
}).directive("isolatedDirective", function () {
return {
scope: {
user: "="
},
template: 'Say:{{user.name}} <br>改变隔离scope的name:<input type="buttom" value="" ng-model="user.name"/>'
}
})
<div ng-controller="myController">
<div>父scope:
<div>Say:{{user.name}}<br>改变父scope的name:<input type="text" value="" ng-model="user.name"/></div>
</div>
<div>隔离scope:
<div isolated-directive user="user"></div>
</div>
<div>隔离scope(使用{{name}}):
<div isolated-directive user="{{user}}"></div>
</div>
</div>
& 用来绑定函数,在directive中调用父域中的函数
1 | app.controller("myController", function ($scope) { |
参考: https://blog.coding.net/blog/angularjs-directive-isolate-scope?type=early
类似于VUE的slot,但不如slot的灵活强,更倾向于定制插入,在固定位置插入
设置属性 transclude:true
在模板中将 <ng-transclude></ng-transclude>放到要插入的位置
应用时<my-labe><span>1233</span></my-label>;
<span>1233</span> 会自动插入<ng-transclude></ng-transclude>之间
设置属性 transclude :{模板中flag:页面应用时标签名的驼峰式,}
在模板中制定位置放置flag :<div ng-transclude=”flag”></div>
应用时 <my-label><my-title>123</my-title></my-label>
123 会自动插入<div ng-transclude=”flag”></div>之间
参考:https://segmentfault.com/a/1190000004586636
使用transcludeFn给transclude进来的Dom手动制定scope,
transcludeFn可来自compile,link的参数,或者controller的注入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.directive("todo", function(){
return {
restrict:"E",
transclude:"true",
template:"<header>{{header}}</header><div><span>这里是自定义区域</span><content-transclude></content-transclude></div>"
scope:{
header:"@"
},
controller:["$transclude",function(transcludeFn){
this.transcludeFn = transcludeFn;
}]
};
})
.directive("contentTransclude",funtion(){
return {
restrict:"E",
require:"^todo",
link:function(scope,element,attr,todoController){
todoController.transcludeFn(scope.$parent, function(transcludeContent){
element.append(transcludeContent);
});
}
};
})
1.V8占用操作系统内存很少,为什么?
类似于体育场办演唱会,演唱会结束后,清完垃圾才能开下一场,
如果场子小,2小时搞定,如果场子大,人多产生垃圾多,清理时间就会变长
在64位操作系统中占用1.4G,32位操作系统中占0.7G
表面原因是JS设计之初是用于给浏览器跑脚本,脚本跑完一次就结束了,不需要长期运行占用内存,以上这些空间足够
实际原因是垃圾回收机制在进行时,会停掉正在运行的整个程序,回收结束后继续运行,
而一次小型垃圾回收几MB会消耗50ms,程序运行就会停止50ms
如果说给的内存太大,那么跑的程序肯定会多来,产生的垃圾也会随之增多,回收时间就变长,结果就会导致程序运行间歇性执行
这肯定是不能让人接受的
2.垃圾回收算法
V8引擎将所占内存分成两块,
一个叫新生代空间,用于存放生命周期较短的变量,比如一个函数里的局部变量,函数运行完即释放
一个叫老生代空间,占引擎内存的90%以上,用于存放生命周期较长的变量,比如全局变量,从程序运行开始就存在
新生代空间垃圾回收算法就是复制
新生代空间又分两部分,一个叫from一个叫to
回收时会将from中活着的变量复制到TO空间,然后将from空间清空,再将from和to空间内容对调
原理是用空间换取时间,有一半新生代空间处于空置状态,而对调减少了清理磁盘空间的过程,从而节省了时间
老生代垃圾回收算法 就是标记删除整理
标记死掉的没有生命周期的变量,边删除标记的变量,边整理碎片(内存空间),将删除导致的磁盘碎片用活着的变量通过移动补齐
3.新生代晋升老生代条件
新生代空间的变量经历过一次复制,即经过一次垃圾回收,而且TO空间已经使用了25%,那么这个变量就会被安排到老生代空间
4.利用node查看内存情况
cmd 敲击node命令
使用process.memoryUsage()查看,返回包含以下属性对象
rss:V8占用的总内存(占用比使用会多一些)
heapTotal:当前程序被分配到的总内存
heapUsed:当前程序使用的内存
external:调用的部分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 function getMB(){
var mem = process.memoryUsage()
var format = function(bytes){
return (bytes/1024/1024).toFixed(2)+'MB'
}
console.log('heapTotal:'+format(mem.heapTotal)+' heapUsed:'+format(mem.heapUsed))
}
let a = ()=>{
let b = new Array(1024*1024)
return b
}
let b = a()
let d = a()
let e = a()
let f = a()
let g = a()
// let h = a()
// let i = a()
// let j = a()
// let k = a()
// let l = a()
// let m = a()
// let n = a()
// let o = a()
// let p = a()
// let q = a()
getMB()
在cmd中转到文件所在目录,node 包含以上代码的js文件,即可打印内存使用情况
5.优化内存技巧
尽量不要定义全局变量
全局变量记得销毁掉
用匿名自执行函数变全局为局部
尽量避免闭包
在使用立即执行函数实现闭包时,
函数内代码,为同步操作时,占用内存与不使用闭包相同
但如果包含异步操作,则函数内用到的外界变量会一直处于被引用状态
得不到释放从而导致内存增加
解决:使用完释放;避免使用闭包
6.导致内存泄露
滥用缓存
js中导致内存泄露的常见场景
闭包函数
全局变量
对象属性循环使用
Dom节点删除时未解绑事件
Map和Set数据类型的数据属性直接删除,导致指向的数据没有被删除
按照网站指引,配置服务器相关参数,提交订单,付钱
需要注意的是镜像就是将来在服务上安装的操作系统
如果这里选错,稍后还可以更换
相当于更换操作系统
更换操作系统
更换系统盘(公共镜像)
在云端服务器搭建nodejs开发环境
远程连接ECS实例
分盘
部署Node.js项目(CentOS)
安装git(仅供参考)
安装好开发环境后就可以像在本地运行项目一样克隆项目运行项目
我们在购买域名后,还要对域名进行备案
域名备案系统
按照流程进行备案即可
我们需要购买云盾证书服务
购买页面
在专业版OV SSL下选择品牌Symantec,
然后保护类型选择1个域名
会发现证书类型出现‘免费型DV SSL’,选择这个就可以买到免费证书
购买后根据申请流程填写资料等待流程走完就可以拿到证书
注意1.购买的域名在云解析DNS服务中又绑定,DNS会自动生成映射,2.选择系统生成CSR
拿到证书后
在CA证书管理页面可以下载到xxxxx.key,xxxx.pem文件
因为我这里用nodejs搭建服务器,所以只需将文件放入项目,修改项目文件为1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const Koa = require('koa');
const fs = require('fs');
const https = require('https');
const http = require('http');
var enforceHttps = require('koa-sslify');
const app = new Koa();
//将http请求强制转换成https请求
app.use(enforceHttps());
var options = {
key: fs.readFileSync('./ssl/214645895910665.key'),
cert: fs.readFileSync('./ssl/214645895910665.pem')
};
http.createServer(app.callback()).listen(80);
https.createServer(options, app.callback()).listen(443);
console.log('server is running....');
还要注意安全组有没有设置放行https的443端口
进入云服务器控制台
网络和安全下安全组, 点击配置规则, 进入规则列表
如果没有443端口,则点击添加安全组规则,端口设置为443即可
generator函数即生成器函数,可以理解为用于产生生成器的函数
yield 关键字是只能用于generator函数,作为异步断点,相当于continue+return功能
next 关键字是生成器用于执行生成器函数的关键字,
即每调用一次,执行到yield定义的语句,把yield 后面的表达式结果当做返回值返回来
如果调用时传递了参数,则覆盖上次yield 的结果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function* helloworld(){
yield 'hello';
yield 'world';
let aa = 0;
let a = yield aa++; //yield赋值不用小括号
console.log(a);
yield 123+1;
console.log('Hello' + (yield 123)); //使用yield表达式用小括号
return 'endig';
}
let obj = helloworld();//并不会执行generator函数,只是生成生成器
obj.next();//{value: "hello", done: false}
obj.next();//{value: "world", done: false}
obj.next();//{value: 0, done: false}
obj.next('456');// 456 {value: 124, done: false} 如果不传值的话,a的值为undefined
obj.next();//{value: 123, done: false}
obj.next();// Helloundefined {value: "endig", done: true}; 不传参,yield返回undefined
next()是将yield表达式替换成一个值。
throw()是将yield表达式替换成一个return语句。
return()是将yield表达式替换成一个return语句。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;
如果yield表达式后面跟的是一个遍历器对象,需要在yield表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为yield表达式
yield后面的 Generator 函数(没有return语句时),等同于在 Generator 函数内部,部署一个for…of循环。
for…of循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用next方法。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例1
function* inner() {
yield 'hello!';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
例2
let delegatedIterator = (function* () {
yield 'Hello!';
yield 'Bye!';
}());
let delegatingIterator = (function* () {
yield 'Greetings!';
yield* delegatedIterator;
yield 'Ok, bye.';
}());
for(let value of delegatingIterator) {
console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye."
如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员。1
2
3
4
5function* gen(){
yield* ["a", "b", "c"];
}
gen().next() // { value:"a", done:false }
上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象。
Generator 函数返回的遍历器对象,可以继承prototype,但不能当做普通构造函数,不会返回this1
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
57function* g() {
this.a = 11;
}
let obj = g();
obj.next();
obj.a // undefined
//可以进行改造
//执行的是遍历器对象f,但是生成的对象实例是obj
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var f = F.call(F.prototype);
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var f = F.call(F.prototype);
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
function F() {
return gen.call(gen.prototype);
}
var f = new F();
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
1 | var clock = function*() { |
IMPORTANT!!!
Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。
只有当yield后面跟的函数先执行完,无论执行体里面有多少异步回调,都要等所有回调先执行完,才会执行等号赋值,以及再后面的操作。这也是yield最大的特性。
1 | function request(url) { |
第1步:将所有异步代码的每一步都封装成一个普通的、可以有参数的函数,比如上面的request函数。上面例子三个异步代码却只定义了一个request函数,因为request函数能复用。如果不能复用的话,请老老实实定义三个普通函数,函数内容就是需要执行的异步代码。
第2步:定义一个生成器函数,把流程写进去,完全的同步代码的写法。生成器函数可以有参数。
第三步:定义一个变量,赋值为迭代器对象。迭代器对象可以加参数,参数通常将作为流程所需的初始值。
第四步:变量名.next()。不要给这个next()传参数,传了也没用,因为它找不到上一个yield语句。
1 | //在上述每一步异步中间,都间隔3秒 |
async 用于定义异步函数,相当于generator的*号,只不过async函数不需要像generator那样用next()去出发,只需要调用就能实现异步
await 用于async函数内部,作用是等待await 后面的异步流程结束后,执行asyn函数接下来的语句,相当于暂停符
async返回promise对象,因此可以连接then()或者catch()继续进行异步操作或者捕获错误
async函数内部return语句返回的值,会成为then方法回调函数的参数。
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,
除非遇到return语句或者抛出错误。
也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。即可以等任意表达式的结果
因此 await 返回异步函数return的结果,或者异步函数返回的promise的resolve的参数1
2
3如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。
解决中断执行的方法有两种,
一种是把await放在try…catch…里面,例5
另一种是给await的promise链接catch{},例6
优化:如果两个异步操作没有任何联系,不存在继发关系,最好让他们同时触发 例8,例9
1 | 例1. |
加载速度
呈现效果
常见图片有三类
矢量图:几何图形
位图:像素
交错图:随加载过程模糊到清晰
使用策略
·是否有必要使用图片(可不可以不使用;效果能不能用CSS代替–>套路大列表)
·使用合适的格式(小体积webp;矢量图>位图;内容jpeg,修饰png,动画video/svg>gif)
加载策略<提高用户体验>
·先加载小图onload之后再加载交错大图
·懒加载
·尽量减少http请求,合理的使用雪碧图和base64编码的图片
CssSprites:页面风格多变,经常换肤;无需重复图形内容;不会增加css文件体积
Base64:极小极简单图片,可复用,适用小图标(缺点:css文件体积增大,导致解析时间变长)
也适用于图片
·懒加载:图片;组件,模块
·预加载:提前加载下一页;异步加载,提前发送数据请求加载数据资源
·减小传输体积——压缩;减少传输数量——打包
·打包分块,资源复用
·加快速度——静态资源放CDN
·网速,服务器响应,CDN
·雪碧图
·js放底部,css放头部
·减少DOM数量
·script标签设置异步加载async
·减少DNS查找,允许高度并行下载;
·减少http请求
·设置http头部缓存字段expires,E-tag,last-modified;减少/不使用cookie
对自己的网站做到错误提前预知和排查
统计自己的网站相关数据,对网站进行优化
业务数据:
PV(PAGE VIEW,页面浏览量,一个用户打开了网站的几个页面)
UV(unique visitor,访问某个站点或点击某条新闻的不同IP地址的人数)
点击率
转化率(浏览人数与注册人数之间的比率)
跳出率(浏览与离开/不喜欢的人数之间的比率)
稳定性:
所提供服务是否稳定,能否得知偏远地区数据稳定性
网络挂断情况能否立即得知,而不是通过用户告知
异地双活(A地的机器中断工作,通过切换其他地方的机器来提供服务)
性能:
首屏打开的性能
……
错误:
用户端的错误上报
用户操作路径:
操作流程,方便跟踪错误
根据监控的对象/内容将监控的数据进行上报,然后根据上报寻找异常
PV/UV,业务操作的上报;
将页面性能数据进行上报; ======>huatuo
将页面产生的错误上报;========>badjs
跟踪用户操作,将操作路径/流程上报;
接口请求成功与否的上报
用户数据的上报
一般的上报方案是1
2
3var img = new Image
img.src = `${url}`
//url是上报的接口,上报的数据随接口给到后台
1.上报不需要返回值,Image请求不会返回值
2.可以自动跨域,拿不到返回值,上报也不需要确定是否上报成功
服务端拿到接口传过来的数据,将数据写到log文件中,
在需要时进行搜索统计,或者在定时/计划任务时进行查询
对onclick等触发事件手动帮上上报函数1
2
3
4
5
6
7
function report(data){
var url = '....'+data
var img = new Image
img.src = `${url}`
}
dom.addEventListener("click", report);
通过事件冒泡和DOM上的标记找到对应行动点,获取具体操作对象DOM进行上报
通过发送带上COOKIE的Image请求来定位用户与站点信息,对整个页面做定位
若用户把cookie清除了怎么办?
1.尝试让用户啊登录
2.百度、谷歌一般有多个登录态,可以给没有cookie的用户先注册个临时ID,等登录后再映射回来
3.浏览器终端机器本身可以产生一些类似mac地址的唯一识别码的东西,根据识别码进行跟踪,
或者使用指纹采集,fingerprint.js等手段为用户生成指纹进行跟踪
将log数据线存储的本地IndexDB等地方,必要时服务器发起收集;
基于DSL的切面事件(before,after),将监控事件可以自动注入,这样实现配置平台让产品也可以自定义上报
查看统计业务情况
检查系统稳定性
错误如何产生
是否被非法套嵌
检测性能优化程度
查看真实用户页面打开情况
通过时间这个维度去看
在指定位置标记一些时间点标识,然后进行统计
用户真实数据,如白屏时间,从产品/经验值看是否能接收,是否需要优化
对于某些地区的响应慢,是不是网络问题,是否推动网络或CDN优化
验证性能优化效果
找到一个参考值,通过流程化的方式让未来我们的项目都能达到一个基准线
主要靠后端,前端做辅助
防止出现问题时,能不能及时得知,方不方便排查
偏远地区CDN出现故障,导致页面空白
边界条件未验证导致错误
后台返回结果不符合预期导致错误
用户产生了不可预知的操作
使用window.error捕获错误,可以将将msg,url,row行,col列,error错误等信息拿到
但以上信息不能确定是那个文件出了错误,
在firefox,chrome中的堆栈信息中可以根据上面的error找到是哪个文件出了错误
所以在上报之前根据堆栈信息找到文件再上报
一般是文件跨域出现的错误
出现的场景和条件如下
通过window.onerror注册监听脚本错误事件
浏览器是firefox,chrome,safari,ie7+
页面内使用script标签引入非同域资源,且发生脚本错误
解决
1.设置cors
将跨域资源的response header的Access-Control-Allow-Origin设置为或者本域,
(一般设为,因为防止第三个域也来请求该文件时,本域请求完,response header有缓存,导致第三个域资源无法使用)
然后在script标签添加属性 crossorigin
2.对入口和异步方法全部用try-catch包裹,做到任何时刻错误都能捕获
setTimeout setInterval
事件绑定
ajax callback
define require
业务主入口
1 | t1 http://a-doppler.facebook.com/test_pixel?HTTP1.0&t=1&size=0k |
第一次请求使用HTTP1.0请求,保证后面的第二次请求可以重新建立TCP/IP链接
因为进行过第一请求,DNS已经在浏览器有缓存,所以进行第二次请求时直接在浏览器查找
所以t2-t1就是DNS查询时间
DNS = t1-t2;
第三次请求在第二次请求已经建立TCP/IP的情况下发起,所以
TCP/IP = t2-t3;
第四次请求只是在第三次基础上新增请求资源大小,利用这个大小,可以计算出大致贷款
bandwidth = 10k/(t4-t3)
var time = window.performance.timing
DNStime = time.domainLookupEnd - time.domainLookupStart;
目前safari浏览器移动端均不支持,移动端Android不支持
performance.timing MDN
1,js是单线程的,一个时间只能干一件事
2.浏览器是多线程的,一个时候可以并行干多件事
3.一个浏览器打开一个页面,就是一个js线程
4.浏览器的多线线程一般会有:js引擎线程,界面渲染UI线程,浏览器事件触发线程,http请求线程等
5.当我们打开一个页面时,浏览器渲染html文件是从上往下渲染的,
UI引擎会根据html文件里面图片,css等的书写先后顺序依次去download,通过http请求拿到资源即加载结束
但如果碰到js文件,会将js文件先加载,加载结束后通知js引擎线程去执行
6.一旦js文件里面需要请求大量数据或者有对页面DOM的操作,就会造成页面空白或者由于DOM还没有加载完直接报错
7.为避免这样的情况,所以尽量将js放页面底部
1.操作在document.ready之后执行的
2.处理兼容性的文件,例如早期IE兼容H5新增标签的支持文件html5shiv
3.jquery类库,通常引用CDN地址,经压缩后不会很大,而且很大几率已被用户缓存下来