JAVASCRIPT中的回调函数

什么是回调函数?

把一个函数当作实参传递给函数的形参变量(获取传递给函数,通过arguments获取),再另外一个函数中把传递的函数执行这种机制就叫回调函数机制

凡是在某一个函数的某一个阶段需要完成某一件事(而这件事是不确定的),都可以利用回调函数机制,把需要处理的事情当作值传递进来

1
2
3
4
5
6
7
8
9
function fn(num, callback){
// callback 传递进来的回调函数
typeof callback === 'function' ? callback() : null;
// callback && callback(); 这种方式默认没有上面的严谨
}
fn(10);
fn(20, function(){
// 此处的匿名函数就是给callback传递的值
})

既然我们已经把函数作为值传递给FN了,此时在FN中我们可以操作传递的函数

  1. 我们可以在FN中把回调函数执行0-N次
  2. 我们还可以给回调函数传递参数值
  3. 我们还可以把回调函数中的this进行修改
  4. 我们还可以接收回调函数执行返回的值
  5. …..
1
2
3
4
5
6
7
8
9
//=> 需求:执行fn可以实现任意数求和,把求出的和传递给回调函数
function fn(callback){
var argNumAry = Array.prototype.slice.call(arguments, 1),
total = eval(argNumAry.join('+'));
typeof callback === 'function' ? callback.call(fn, total) : null;
}
fn(function(result){
console.log(result, this); // 100 fn
}, 10,20,30,40);

很多的方法都是依托于回调函数来完成的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var ary = [12, 23, 34];
ary.sort(function(a, b){
//=> a:当前项
//=> b:后一项
return a-b; //=> 返回一个大于零的值,则a和b位置交换
});
ary.forEach(function(item, index, input){
//=> item:当前便利的这一项
//=> index:当前便利这一项的索引
//=> input:原始便利的数组
//=> forEach每当循环便到数组中的每一项都会把当前传递的回调函数执行一次(不仅执行,还把便利的这一项的值传递给回调函数)
});
//=> map遍历数组中的每一项,原有数组不变,返回结果是修改后的新数组(map相对于forEach来说,增加了对原有想的修改)
ary.map(function(item, index, input){
//=> item:当前便利的这一项
//=> index:当前便利这一项的索引
//=> input:原始便利的数组
return item*10; //=> 回调函数中返回的是啥,详单与把当前便利的这一项改为啥(回调函数中不屑return,默认返回的是undefined)
});

JSONP 原理

JSONP和AJAX相同,都是客户端向服务端发送请求:给服务器传递数据或者从服务器获取数据的方式

AJAX(async javascript and xml)属于同源策略

JSONP属于非同源策略(跨域请求)->实现跨域请求额方式有很多,只不过JSONP是最常用的

判断是否同源:

  1. 域名/IP
  2. 协议
  3. 端口号

以上三个都一样就是同源,其中有一个不一样就是非同源,我们一般用JSONP来获取数据

在script的世界中,没有同源跨域这一说,只要你给我src属性中的地址是一个合法的地址,script都可以把对应的内容请求回来(JSONP请求一定要对方服务器做支持才可以)

  1. 我们首先把需要请求数据的那个跨域API接口的地址赋值给src
  2. 把当前页面中的某一个函数名当作参数值传递给跨域服务器(URL问号传参:?callback=fn
  3. 服务器收到请求后,需要进行特殊处理,把你传递进来的函数名和它需要给你的数据拼接成一个字符串,例如 fn([{"name":"xxx"}])
  4. 最后,服务器把准备好的数据通过http协议返回给客户端,客户端只需要执行fn即可

JQ中的JSONP

1
2
3
4
5
6
7
8
9
$.ajax({
url: '',
dataType: 'jsonp',
jsonpCallback: 'fn', //=> 自定义传递给服务器的函数名,而不是用JQ自动生成的
jsonp: 'cb', //=> 把传递函数名的那个形参改为cb
success: function(){

}
})

数据类型检测

typeof

用来检测数据类型的运算符

语法:typeof [value]

返回值:首先是一个字符串,字符串中包含了我们需要检测的数据类型

使用typeof有自己的局限性,不能细分出当前的值是数组还是正则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typeof 12; // 'number'
typeof NaN; // 'number'
typeof ''; // 'string'
var flag = true;
typeof flag; // 'boolean'
typeof undefined; // 'undefined'

null == undefined; // true
function fn(n, m){
if (typeof n === 'undefined') {
//=> 比双等号更严谨
}
}
typeof null; // 'object' 虽然是基本数据类型值,但是他属于空对象指针,检测的结果是对象
typeof {}; // 'object'
typeof function(){}; // 'function'
typeof []; // 'object'
typeof /^$/; // 'object'
typeof 1>1?0:2; // 2
typeof (1>1?0:2); // 'number'

instanceof & constructor

instanceof:检测当前对象是否属于某一个类的实例

使用instanceof检测某个值是否是属于某一个数据类型的内置类,从而检测出它是否是这个类型值,使用instanceof可以实现typeof实现不了的对对向类型值得区分检测

【弊端】

  1. 基本类型值无法基于它检测
  2. instanceof检测的原理是基于原型链检测的,只要当前类在实例的原型链上,最后返回的结果都是true

constructor:构造函数

  1. 获取当前要检测数据值得constructor,判断当前数据是不是某一个数据类型内置类来检测
  2. 检测数据类型非常不可靠,因为这个属性经常容易被修改
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
[] instanceof Array; // true
[] instanceof RegExp; // false

var num = 12;
num.toFixed(2); // '12.00' 12是Number类的一个实例,可以调取Number.prototype上的方法,但是他是基本类型值

var num2 = new Number(12);
num2.toFixed(); // '12.00'
typeof num2; // 'object'

//=> 不管哪一种方式创建的基本类型值,都是自己所属类的实例(只不过类型不一样而已)
num instanceof Number; // false
num2 instanceof Number; // true

var ary = [];
ary instanceof Array; // true
ary instanceof Object; // true

function Fn(){}
Fn.prototype = new Array(); // 原型继承(Fn是Array的子类)
var f = new Fn();
f instanceof Array; // true f其实不应该是数组,虽然在他的原型上可以找到数组,但是它不具备数组的基础结构,这也是instanceof的弊端

ary.constructor === Array; // true 说明ary是数组
ary.constructor === RegExp; // false
ary.constructor === Object; // false

Object.prototype.toString.call([value])

获取Object.prototype上的toString()方法,让发发中的this变为需要检测的数据类型值,并且执行这个方法

在Number/String/Boolean/Array/Function/Regexp…这些类的原型上都有一个toString方法,这个方法就是把本身的值转换为字符串

在Object这个类的原型上也有一个方法toString,但是这个方法并不是把值转换为字符串,而是返回当前值所属类的详细信息,固定结构:[object 所属的类]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(12).toString(); // '12'
(true).toString(); // 'true'
[12,23].toString(); // '12.23'
var obj = {name: 'aa'};
obj.toString(); // "[object Object]" 调取的正是Obejct.prototype.toString
Object.prototype.toString.call(12); // "[object Number]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(''); // "[object String]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object undefined]"
Object.prototype.toString.call([]); // '[object Array]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call(/^$/); // "[object RegExp]"
Object.prototype.toString.call(function(){}); // "[object Function]"
Object.prototype.toString.call(Math); // "[object Math]"
Object.prototype.toString.call(document.body); // "[object HTMLBodyElement]"
Object.prototype.toString.call(document); // "[object HTMLDocument]"


//=> obj.toString(); 执行
//=> 执行的是Object.prototype.toString
//=> 方法中的this指向的是obj
//=> object.prototype.toString执行的时候返回的是当前方法中this所属类的信息
//=> 也就是,我们想知道谁的所属类信息,我们就把这个toString方法执行,并且让this变为我们检测的这个数据值,那么方法返回的结果就是当前检测这个值得所属累信息

使用toString检测数据类型,不管你是什么类型值,我们都可以正常检测出需要的结果

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
~function(){
var obj = {
isNumber: 'Number',
isString: 'String',
isBoolean: 'Boolean',
isNull: 'Null',
isUndefined: 'Undefined',
isPlanObject: 'Obejct',
isArray: 'Array',
isRegExp: 'RegExp',
isFunction: 'Function'
};
var check = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var value = obj[key];
check[key] = (function(classValue){
return function(val){
return new RegExp('\\[object '+ classValue +'\\]').test(Object.prototype.toString.call(val));
}
})(obj[key]);
}
}
window.check = check;
}();

关于JS中面向对象的理解

oop(Object Oriented Programming)

它是一种编程思想,我们的编程或者学习其实是按照实例来完成的

学习类的继承/封装/多态

封装

  • 把实现一个功能的代码封装到一个函数(类),以后再想实现这个功能,只需要执行函数方法即可,不需要再重复的编写代码
  • 低耦合,高内聚:减少页面中冗余代码,提高代码的重复使用率

多态

  • 一个类(函数)的多种形态
    • 重载
      • 后台编程语言中,对于重载的改变:方法名相同参数不同,叫做方法的重载
      • JS中的重载,同一个方法,通过传递不同的实参我们完成不同的功能,我们把这个也可以理解为重载
    • 重写
      • 不管是后台语言还是JS都有重写,子类重写父类的方法

类的继承

什么是继承?

让子类的原型指向父类的实例

1
Children.prototype = new Parent();

[细节]

  1. 我们首先让子类的原型指向父类的实例,然后再向子类原型上扩展方法。,目的是为了防止提前增加方法,等原型重新趾向后,之前再子类圆形上扩展的方法都没用了(子类原型已经指向新的空间地址)
  2. 让子类原型才重新指向父类实例,子类原型上原有的constructor就没了,为了保证构造函数的完整性,我们最好给子类的原型冲洗手动设置constructor Children.prototype.constructor = Children

[原理]

原型继承,并不是把父类的属性和方法copy一份给子类,而是让子类的原型和父类原型之间搭建一个链接的桥梁,以后子类(或者子类的实例),可以通过原型链的查找机制,找到父类原型上的方法,从而调取这些方法使用即可

Children.prototype.__proto__.setX = function... 子类通过原型链找到父类的原型,再父类原型上增加新的属性和方法(重写:子类重写父类原型上的方法)

[特征]

  1. 子类不仅可以继承父类原型上的共有属性和方法,而且父类提供给实例的那些私有属性和方法,也被子类继承了(存放在子类的原型上,作为子类共有的属性和方法)

call继承

1
2
3
4
5
6
7
8
9
10
11
function Parent(){
this.x = 100;
}
Parent.prototype.getX = function(){
console.log(this.x);
}
function Children(){
Parent.call(this); //=> Parent执行,方法中的this依然是子类的实例(再父类构造体中写一些this.xxx=xxx 都相当于给子类的实例增加一些私有的属性和方法)
this.y = 200;
}
var child = new Children();

[原理]

原理是把父类构造体当中私有的属性和方法,复制一份给子类的实例(继承完成后子类和父类是没关系的)

[细节]

我们一般把call继承放在子类构造体的第一行,也就是创建子类实例的时候,进来的第一件事情就是先继承,然后再给实例赋值自己私有的(好处:可以把继承过来的记过替换掉)

寄生组合继承

Object.create([obj]):创建一个空对象,把[obj]作为心创建对象的原型

子类公有的继承父类公有的(原型继承)

子类私有的继承父类私有的(call继承)

1
2
3
4
5
//=> Obejct.create
var Obj = {name: 'aa'}
//=> 创建一个空对象,把obj作为当前空对象的原型
var newObj = Obejct.create(obj);
newObj.__proto__ = obj;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Parent(){
this.x = 100;
}
Parent.prototype.getX = function(){
console.log(this.x);
}
var obj = Obejct.create(Parent.prototype);
//=> console.log(obj instanceof Parent); //=> true

function Children(){
Parent.call(this); //=> 继承父类私有
this.y = 200;
}
Children.prototype = Object.create(Parent.prototype); //=> 继承父类公有
Children.prototype.constructor = Children;
Children.prototype.getY = function(){
console.log(this.y);
}
1
2
3
4
5
6
7
Object.myCreate = function myCreate(obj){
var Fn = new Function();
Fn.prototype = obj;
return new Fn();
};
var aa = {name: 'aa'};
Object.myCreate(aa);

ES6中的类

创建类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Fn {
constructor(){
//=> constructor:Fn
//=> 这里边的 this.xxx = xxx;是给当前实例设置的私有属性
//=> 给实例设置的私有属性
this.xxx = xxx;
}
//=> 这里设置的方法都放在Fn.prototype上(给实例提供的公有的属性方法)
//=> getX setX 都是给fn的prototype设置方法
getX(){

}
setX(){

}
//=> 把Fn当做一个普通对象,增加的属性和方法
static xxx(){

}
};
var f = new Fn();

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A{
constructor(){
this.x = 100;
}
getX(){
console.log(this.x);
}
}
class B extends A {
constructor(){
super(); //=> call继承
this.y = 200;
}
getY(){
console.log(this.y);
}
}
var b = new B();

for in 循环问题

不仅可以遍历当前对象(或者实例)所有的私有属性和方法,还可以把原型上自己创建的公共属性和方法进行遍历

for循环只会遍历私有地属性和方法,自己在原型上扩展的方法不会被遍历出来

1
2
3
4
5
6
7
Object.prototype.myXX = function(){

};
var obj = {name: 'aa'};
for(var i in obj){
console.log(i);
}

倒计时抢购

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
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>倒计时抢购</title>
</head>
<body>
<div id="box"></div>
<script>
~function(){
let box = document.getElementById('box'),
serverTime = null,
autoTimer = null;
//=> 为了时间显示统一,小于10,则补0
let formatTime = (value) => {
value < 10 ? value = `0${value}` : null;
return value
};
let fn = () => {
serverTime += 1000;
let startTime = new Date('2019/6/4 15:31:08').getTime(),
remainTime = startTime - serverTime;
if (remainTime < 0) {
box.innerHTML = '开抢啦!';
clearInterval(autoTimer);
} else {
let hours = Math.floor(remainTime / (1000 * 60 * 60));
remainTime -= hours * 1000 * 60 * 60;
let minute = Math.floor(remainTime / (1000 * 60));
remainTime -= minute * 1000 * 60;
let second = Math.floor(remainTime / 1000);
hours = formatTime(hours);
minute = formatTime(minute);
second = formatTime(second);
box.innerHTML = `距离抢购还有:${hours}:${minute}:${second}`;
}
};

let getServeTime = () => {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
//=> 状态码以2|3开头的都代表成功,取反请求失败不做任何操作
if (!/^(2|3)\d{2}$/.test(xhr.status)) return;
//=> 这里要注意因为是请求方式是head,所以 readyState的状态不会出现3
if (xhr.readyState === 2) {
//=> 首次进入页面,请求服务器时间成功后。手动调用fn判断是否到期,然后开启定时函数,每秒在请求回来的服务器时间上+1000ms
serverTime = new Date(xhr.getResponseHeader('date')).getTime();
fn();
autoTimer = setInterval(fn, 1000);
}
};
xhr.open('head', 'data.json', true);
xhr.send();
};
getServeTime();
}();

//=> 重点 1、就是在抢购的活动中,时间要以服务器时间为准
//=> 重点 2、在客户端请求服务端时间的这个过程中,避免不了会存在延迟。这里采用head请求方式,以减少延迟的时间
</script>
</body>
</html>

JS中常用的编码解码方式

正常的编码解码(非加密)

  1. escape/unescape:主要就是把中文汉字进行编码和解码(一般只有JS语言支持,也经常应用于前端页面通信时候的中文汉字编码)
1
2
3
let str = '我是一串中文 @China'
escape(str); //=> "%u6211%u662F%u4E00%u4E32%u4E2D%u6587%20@China"
unescape('%u6211%u662F%u4E00%u4E32%u4E2D%u6587%20@China'); //=> "我是一串中文 @China"
  1. encodeURI/decodeURI:基本上所有的编程语言都支持
1
2
3
let str = '我是一串中文 @China'
encodeURI(str); //=> "%E6%88%91%E6%98%AF%E4%B8%80%E4%B8%B2%E4%B8%AD%E6%96%87%20@China"
decodeURI('%E6%88%91%E6%98%AF%E4%B8%80%E4%B8%B2%E4%B8%AD%E6%96%87%20@China'); //=> "我是一串中文 @China"
  1. encodeURIComponent/decodeURIComponent:和第二种方式非常类似,区别在于encodeURIComponent/decodeURIComponent 会将关键字和保留字也转码掉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let obj = {
name: '小明'
age: 12,
url: 'http://www.baidu.com?lx=1&Thyme &time=again',
}
//=> 此处不能使用encodeURI,因为不能处理特殊字符
for (let key in obj){
str += '${key}=${encodeURIComponent(obj[key])}&';
}
str = str,replace(/&$/g, '');

//=> 后期获取URL问号参数的时候,我们把获取的值依次解码即可
String.prototype.myQueryUrlParameter = function myQueryUrlParameter(){
let reg = /[?&]([^?&=]+)(?:=([^?&=]*))?/g;
obj = {};
this.repalce(reg, (...arg)=> {
let [,key, value] = arg;
obj[key] = decodeURIComponent(value);
});
return obj;
}

a加密编码方式

  1. 可逆转加密 (一般使用内部约定的规则加密解密)
  2. 不可逆转加密(一般都是基于MD5加密完成,可能会把MD5加密后的结果进行二次加密)

AJAX中同步和异步

ajax这个任务:发送请求接收到相应主题内容(完成一个完整的HTTP请求)

xhr.send():任务开始

xhr.readyState === 4; 任务结束

同步-一定要等主任务队列完成后,再执行等待任务队列任务

1
2
3
4
5
6
7
8
9
10
let xhr = new XMLHttpRequest();
xhr.open('get', 'test.json', false);
xhr.onreadystatechange = () => {
console.log(xhr.readyState);
}
xhr.send();
//=> 只输出一次,结果是4
// 由于采用的是异步编程,所以主任务队列没有完成(其他事情都做不了)1=>2 2=>3 3=>4状态改变3次
// xhr.readyState === 4 AJAX任务完成,主任务队列也执行完成,执行等待任务队列中的事情
// 此时的onreadystatechange只是别到1=>4,执行一次输出结果是4
1
2
3
4
5
6
7
8
9
let xhr = new XMLHttpRequest();
xhr.open('get', 'test.json', false);

//=> 同步,开始发送ajax发送,开启ajax任务,在任务没有完成之前什么都做不了(下面绑定事件也做不了) => loading => 当readyState === 4的时候ajax任务完成,开始执行下面的操作
xhr.send();
xhr.onreadystatechange = () => {
console.log(xhr.readyState);
}
//=> 绑定方法之前状态已经是4,此时ajax的状态不会改变成其他值,所以事件不会被触发,一次都没有执行(使用ajax同步变成,不要把send放在事件监听前,这样我们无法再绑定的方法中获取到相应主体的内容)
1
2
3
4
5
6
7
8
9
10
11
let xhr = new XMLHttpRequest();
xhr.open('get', 'test.json');
xhr.onreadystatechange = () => {
console.log(xhr.readyState);
}
xhr.send();
// 不需要等待ajax任务结束,此时等待任务队列就已经完成了
// 1=>2 响应头信息返回 状态改变会触发事件监听,把绑定的方法执行(2)
// 2=>3 绑定的方法再次执行(3)
// 3=>4 绑定的方法再次执行(4)
// ajax任务完成
1
2
3
4
5
6
7
let xhr = new XMLHttpRequest();
xhr.open('get', 'test.json');
xhr.send();
xhr.onreadystatechange = () => {
console.log(xhr.readyState);
}
// 2=>3=>4
1
2
3
4
5
6
7
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
console.log(xhr.readyState);
}
xhr.open('get', 'test.json', false);
// xhr.readyState === 1 AJAX特殊处理的一件事,执行open状态变为1,会主动把之前监听的方法执行一次,然后再去执行send方法
xhr.send();

ajax类库封装

JQ中的AJAX使用以及配置项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$.ajax({
url: 'xxx.json',
method: 'get', 、、//=> 在老版本的JQ中使用的是type,使用type或method效果相同
dataType: 'json', //=> |只是预设获取结果的类型,不会影响服务器结果的返回(服务器端一般给我们返回的都是json格式的字符串),如果我们预设的是json,那么类库中将把服务器返回的字符串转成JSON对象,如果我们预设的是text(默认值),我们把服务器获取的结果直接拿过来操作即可,我们预设的值还可以是xml等。
cache: false, //=> 设置是否清除缓存,只对get系列请求有作用,默认是true不清除缓存,手动设置为false,JQ类库会在请求URL的末尾追加一个随机数来清除缓存
data: null, //=> 我们通过data可以把一些信息传递给服务器,get系列请求会把data中的内容拼接在URL的末尾通过问号传参的方式传递给服务器,POST请求会把内容放在请求主题中传递给服务器;data的值可以设置为两种格式:字符串、对象,如果是字符串,设置的值是什么传递给服务器的就是什么,如果是对象,JQ会把对象变为 xxx=xxx&xxx=xxx 这样的字符串传递给服务器
async: true, //=> 设置同步或者异步,默认是异步(true)、同步(false)
success: function(res){
//=> 当ajax请求成功(readyState === 4 && status是以2|3开头的)
//=> 请求成功后JQ会把传递的回调函数执行,并且把获取的结果当做实参传递给回调函数(res就是我们从服务器端获取的结果)
},
error: function(){}, //=> 请求错误触发
complate: function(){}, //=> 不管请求是错误还是正确都会触发回调函数
})

jQuery-ajax实现

封装属于自己的AJAX类库

[支持的参数]

  • url
  • method/type
  • data
  • dataType
  • anync
  • cache
  • success
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
~function(){
class ajaxClass {
//=> send ajax
init(){
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && /^[23]\d{2}$/.test(xhr.status)) {
let response = xhr.responseText;
//=> dataType 处理
try {
switch(this.dataType.toUpperCase()) {
case 'TEXT':
case 'HTML':
break;
case 'JSON':
response = JSON.parse(response);
break;
case 'XML':
response = xhr.responseXML;
break;
}
} catch (e) {
console.log(e);
}
this.success(response);
}
}

//=> data
if (this.data !== null) {
this.formatData();
if (this.isGET) {
this.url += this.querySymbol() + this.data;
this.data = null;
}
}
//=> cache
this.isGET ? this.cacheFn() : null;
xhr.open(this.method, this.url, this.async);
xhr.send(this.data);
}

//=> 把传递的对象格式转化为字符串data
formatData() {
//=> this:example
//=> 检测数据类型的四中方法 instanceof typeof constructor Object.prototype.toString.call()
if (Object.prototype.toString.call(this.data) === '[object Object]') {
let obj = this.data,
str = ``;
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
str += `${key}=${obj[key]}&`;
}
}
this.data = str.replace(/&$/g,'');
}
}

cacheFn(){
//=> this:example
!this.cache ? this.url += `${this.querySymbol()}=${Math.random()}` : null;
}

querySymbol() {
//=> this:example
return this.url.indexOf('?') > -1 ? '&' : '?'
}
}

//=> init parameters
window.ajax = function({
url=null,
method='GET',
type='GET',
data=null,
dataType='JSON',
cache=true,
async=true,
success=null
} = {}){
let _this = new ajaxClass();
['url', 'method', 'data', 'dataType', 'cache', 'async', 'success'].forEach((item) => {
if (item === 'method') {
_this.method = type === null ? method : type;
return;
}
if (item === 'success') {
_this.success = typeof success === 'function' ? success : new Function();
return;
}
_this[item] = eval(item);
});
_this.isGET = /^(GET|DELETE|HEAD)$/i.test(_this.method);
_this.init();
return _this;
}
}();

前端常用的优化技巧汇总

  1. 项目中我们需要把JS和CSS合并压缩,如果项目中CSS和JS内容不是很多,我们可以采取内嵌式,以此减少HTTP请求的次数,加快页面的加载速度

    1. CSS压缩成1个,JS最好合并为一个
    2. 首先通过一些工具(例如:webpack)把合并后的CSS或者JS压缩成xxx.min.js,减少文件的大小
    3. 服务端开启资源文件GZIP压缩

    通过一些自动化工具完成对CSS以及JS的合并压缩,或者在完成LESS转CSS,ES6转ES5,我们把这种自动化构建模式,称之为前端“工程化”开发

  2. 在客户端和服务端进行数据通信的时候,我们尽量使用JSON格式进行数据传输

    1. 优势
      1. JSON格式的数据,能够清晰的展示出数据结构
      2. 相对于很早以前的XML格式传输,JSON格式的数据更加轻量级
      3. 客户端和服务端都支持JSON格式数据的处理,处理起来方便
    2. 项目中,并不是所有的数据都基于JSON,对于某些特殊需求(例如:文件流传输或者文档传输),使用JSON就不合适了
  3. 编写代码的优化技巧

    1. 除了减少HTTP请求次数和大小可以优化性能,我们在编写代码的时候也可以进行一些优化,让页面性能有所提升(有些不好的代码编写习惯,会导致页面性能消耗过大,例如:内存泄露)
    2. 在编写JS代码的时候,尽量减少对DOM的操作
      1. 在JS中操作DOM是一个非常消耗性能的事情,但是我们又不可避免的操作DOM,我们只能尽量减少对于它的操作
      2. 操作DOM的弊端
        1. DOM存在映射机制(JS中的DOM元素对象和页面中的DOM结构是存在映射机制的,一改则全改),这种映射机制是浏览器按照W3C标准对JS语言的构建和DOM的构建(其实就是构建了一个监听机制),操作DOM是同事要修改两个地方,相对于一些其他的JS编程来说是消耗性能的
        2. 因为页面中DOM结构改变或样式改变会触发浏览器的回流重绘
          1. 回流:浏览器重新计算DOM结构
          2. 重绘:把元素的样式重新渲染
  4. 采用图片兰姐在技术,在页面开始家在的时候不请求真实的图片地址,而是使用默认占位图,当页面加载完成后,再根据相关的条件一次加载真实图片(减少页面首次加载HTTP请求的次数)

    1. 项目中,我们开始图片都不加载,页面首次加载完成,先把第一屏中可以看见的图片进行加载随着页面的滚动,再把下面区域中能够呈现的图片进行加载
    2. 根据图片懒加载技术,还可以扩充出:数据懒加载
      1. 开始加载页面的时候,我们只把首屏或者前两屏的数据从服务器端进行请求(有些网站首屏数据是后台渲染好,整体返回给客户端呈现的)
      2. 当页面下拉,滚动到那个区域,再把这个区域需要的数据进行请求(请求回来做数据绑定、图片延迟加载)
      3. 分页展示技术采用的也是数据的懒加载思想实现的:如果我们请求获取的数据是很多的数据,我们最好分批请求,开始只需要请求第一页的数据,当用户点击第二页再请求第二页的数据
  5. 如果当前页面中出现了audio/video标签,我们最好设置他们的preload=none页面加载的时候,音视频资源不进行加载,播放的时候开始加载(减少首次加载的HTTP请求的次数)

    1. preload=auto 页面首次加载的时候就把音视频资源加载了
    2. preload=metadata 页面首次加载的时候只把音视频资源的头部信息进行加载
  6. 编写代码的时候,更多的使用异步变成

    1. 同步编程可能会导致,上面的东西完不成,下面的任务也做不了,我们开发的时候可以把某一个区域模块都设置为异步变成,这样只要模块之间没有必然的先后顺序,都可以独立进行加载,不会受上面模块的影响
    2. 尤其是ajax数据请求,我们一半都要使用异步变成,最好基于promise设计模式进行管理(项目中经常使用fetch、vue axios等插件来进行处理,因为这些插件就是基于promise设计模式对ajax进行的封装处理)
  7. 在项目中,我们尽可能避免一次性循环过多的数据(因为循环操作是同步变成),尤其是要避免while导致的死循环操作

  8. CSS选择器优化

    1. 尽量减少标签选择器使用
    2. 尽可能少使用ID选择器,多食用样式类选择器(通用性强)
    3. 减少选择器前面的前缀,例如:.headerBox .nav .left a{}(选择器是从右向左查找的)
    4. 避免使用CSS表达式
  9. 减少页面中的冗余代码,尽可能提高方法的重复使用率:“低耦合高内聚”

  10. 最好CSS放在head中,JS放在body底部,让页面加载的时候限价在CSS再加载JS(目的:先呈现页面,再给用户提供操作)

  11. 对于不经常更新的数据,最好采用浏览器的304缓存做处理

    • 第一次请求CSS、JS下来,浏览器会把请求的内容缓存起来,如果做了304处理,用户在此请求CSS、JS直接从缓存中读取,不需要再去服务器获取了(减少HTTP请求的次数)

    • 当用户强制刷新或者当前缓存的CSS、JS发生变动,都会重新从服务器拉去

    • 主要由服务端处理,对于客户端来讲,我们还可以基于localstorage来做一些本地存储

  12. JS中尽量较少闭包的使用

    1. 闭包会形成一个不销毁的堆内存,过多的堆内存累积会影响页面的性能
    2. 还容易导致内存泄露
  13. 在做DOM事件绑定的时候,尽量避免一个个的时间绑定,而是采用性能更高的事件委托来实现

    1. 事件委托(事件代理)
    2. 把时间绑定给外层容器,当里面的后代元素相关行为被触发,外层容器绑定的方法会被触发,(冒泡传播机制),通过事件源是谁,我们做不同的操作即可
  14. 尽量使用CSS3动画,代替JS动画(因为CSS3的动画会开启硬件加速,性能比JS好)

  15. 编写JS代码的时候尽可能使用设计模式来构建体系,方便后期的卫华,也提高代码的可读性

  16. CSS中减少对滤镜的使用,页面中也减少对flash的使用

  17. 采用cdn加速

在客户端输入一个网址并访问,到我们看到页面,其中都经历了什么?


  • 简单回答:

    • 【Request 请求阶段】
    1. 首先根据客户端输入的域名,到DNS服务器进行反解析(通过域名找到对应服务器的外网IP)
    2. 通过找到的外网IP,找到对应的服务器
    3. 通过在地址栏中输入的端口号(没输入是因为不同协议有自己默认的端口号),找到服务器上发布的对应项目
    • 【Response 响应阶段】
    1. 服务器获取到请求资源文件的地址例如:xxx/xxx/index.html,把资源文件中的源代码 找到
    2. 服务器端会把找到的源代码返回给客户端(通过http等传输协议返回)
    • 【浏览器自主渲染阶段】
    1. 客户端接收到源代码后,会交给浏览器的内核(渲染引擎)进行渲染,最后由浏览器绘制出对应页面

    知识点:

    • 浏览器内核:IE(Trident) | Firefox(Gecko) | Safari/Chrome(Webkit)
    • 360浏览器、猎豹浏览器都是采用IE+Chrome双内核,搜狗、遨游、QQ浏览器也是双内核:Trident(兼容模式)+Webkit(高速模式); UC浏览器电脑版采用Blink内核和Trident内核,百度浏览器、世界之窗内核都是单核(IE内核)
    • 常见协议以及默认端口号
      • ftp:21 文件传输协议
      • ssh:22
      • pop3:110 因特网电子邮件第一个离线协议标准
      • smtp:25 简单邮件传输协议
      • http:80 超文本传输协议(客户端和服务端传输的内容除了文本外,还可以传输图片音视频等文件流(二进制编码、base64码、以及传输xml格式的数据等))
      • https:443 http下加入ssl
      • svn:3690
      • tomcat:8080
      • oracle:1521
      • mysql:3306

URL URI URN

uri:统一资源标识符

url:统一资源路径地址

urn:统一资源名称

uri = url + urn

一个完成的URL包含很多部分

https://baike.baidu.com/item/%E4%B8%8D%E5%88%B0/134029?fr=aladdin#3

  1. 传输协议:(例如:https)用来完成客户端和服务器端的数据(内容)传输的,类似于快递小哥负责把客户和商家的物品来回传递

    1. 客户端不仅可以向服务器发送请求,还可以把一些内容传递给服务器
    2. 服务器端也可以把内容返回给客户端
    3. 服务端和客户端传输的内容总成为HTTP报文,这些报文信息都是基于传输协议完成的传输,客户端传递给服务器叫做请求(Request),服务器返回给客户端叫做响应(Response),request+response两个阶段统称为:HTTP事务 (事务:一件完整的事情)
    4. 当客户端向服务端发送请求,此时客户端和服务端会建立一个传输通道,传输协议就是基于这个通道把信息进行传输的
    5. 当服务端接收到请求信息,把内容返回给客户端后,传输通道会自动销毁关闭
  2. HTTP报文

    1. 起始行
      1. 请求起始行
      2. 响应起始行
    2. 首部
      1. 请求头:内置请求头、自定义请求头
      2. 响应头:内置响应头、自定义响应头
      3. 通用头:请求和响应都有的
    3. 主体
      1. 请求主题
      2. 响应主题

    请求XXX,都是客户端设置的信息,服务端获取这些信息

    响应XXX,都是服务端设置的信息,客户端接收这些信息

    总结:

    1. 客户端传递给服务端数据
      1. URL问号传参
      2. 设置请求头
      3. 设置请求主题
    2. 服务端返回给客户端内容
      1. 设置响应头
      2. 设置响应主题

网站中的SEO和SEM初步了解

SEO和SEM是什么?

我们开发的产品最后总要运营推广出去,这就需要很多的运营推广手段

SEO和SEM就是两种常规的推广手段

SEO:网络推广(以互联网为媒介)

SEM:百度竞价(只要花钱就能排名靠前的)


搜索引擎:百度、谷歌、360、搜狗…

当我们在百度搜索框当中输入一个关键词,下面会呈现出很多网站,有的靠前有的考后,这就是SEO优化技巧需要考虑的

  1. 各大搜索引擎都有一个专属的爬虫,爬虫每天都会去各个网站中检索内容,把一些内容或者关键词收录到自己的搜索引擎中(记录:某某内容来源于那个网站)
  2. 当用户在搜索引擎输入框中输入一个关键词,搜索引擎会通过关键词到自己的词库中进行检索,把所有匹配到的内容对应的网站检索出来,并且呈现给用户(谁的关键词被检索的次数多或者其他原因),决定排名的前后
  3. 通过site:网站域名可以查看出当前网站被搜索引擎收录的内容,我们也可以下载一些专门做SEO优化的工具爱站工具

对于前端开发而言,我们需要注意那些事情,用来有助于SEO的优化

  1. 给网站摄制META标签以及摄制title(设置的内容可以找专业的推广人员要)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>网站标题</title>
<meta name="keywords" content="">
<meta name="description" content="">
<!--
1. title 不可以乱写,这个是很重要的优化技巧
2. keywords 这个meta标签是用来设置网站关键词
3. description 这个meta标签是设置网站的描述
-->
</head>
</html>
  1. 注意代码上的优化,合理使用HTML标签,以及注意代码的SEO优化技巧

    1. 标签语义化
    2. 一个页面中的H1标签只能使用一次
    3. img标签都要设置一个alt属性,在这个属性中声明当前图片的信息(爬虫不能收录图片,但是可以抓取到图片alt属性的值)
    4. html得层级不要太深,太深的层级,爬虫可能不会抓取和收录
    5. 把需要推广的关键词尽可能的在页面中曝光(最好写在h2-6标签中)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <style>
    .logo {
    font-size: 0;
    }
    </style>
    <h1 class="logo">
    <img src="xxx.png" alt="站点名称">
    站点名称
    </h1>
    1. 尽量不用使用前后段分离,页面中的数据绑定交给后台处理,由服务器来渲染页面,交给前端处理由客户端渲染页面,搜索引擎的爬虫是爬取不到的(在网站源码中,无法看见通过客户端渲染数据的代码)

所有的SEO优化技巧相对于花钱做SEM都是浮云

SEM:百度竞价,用户花钱做关键词排名