0%

前端知识体系梳理(三)—— 聊聊算法及设计模式

算法的世界博大精深,路漫漫,上下求索

  • 算法
    • 先说数据结构
    • 算法复杂度分析
    • 详情
  • 设计模式

算法

下边说的都还比较简单,感兴趣的同学可以去各大OJ上刷刷题:
https://leetcode.com/
http://acm.hdu.edu.cn/
http://poj.org/

先说数据结构

  • 简单数据结构
    • 有序数据结构:栈、队列、链表,有序数据结构省空间(存储空间小)
    • 无序数据结构:集合、字典、散列表,无序数据结构省时间(读取时间快)
  • 复杂数据结构
    • 树、堆

对于简单数据结构,在 ES 中对应的是数组(Array)和对象(Object)。可以想一下,数组的存储是有序的,对象的存储是无序的,我要在对象中根据key找到一个值是立即返回的,数组则需要查找的过程。

数组&链表的区别:
数组在内存中连续,链表不连续;
数组静态分配内存,大小固定,不能动态拓展,链表动态分配内存拓展灵活; (js中数组是特殊的对象)
数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。

算法复杂度分析

这里只说时间复杂度

常见的时间复杂度有:

  • 常数阶 O(1)
  • 对数阶 O(logN)
  • 线性阶 O(n)
  • 线性对数阶 O(nlogN)
  • 平方阶 O(n^2)
  • 立方阶 O(n^3)
  • !k次方阶 O(n^k)
  • 指数阶O(2^n)
  1. 看看有几重循环,一般来说一重就是O(n),两重就是 O(n^2),以此类推
  2. 如果有二分,则为O(logN)
  3. 保留最高项,去除常数项

例子:

1
2
3
4
let number = 1; // 语句执行一次 
while (number < n) { // 语句执行 logN 次
number *= 2; // 语句执行 logN 次
}

上面代码while的跳出判断条件是number<n,而循环体内number增长速度是(2^n),所以循环代码实际执行logN次,复杂度为:1 + 2 * logN = O(logN)

详情

前端相对来说需要高深复杂算法的情况不多,下边简单介绍一些基础算法:

递归

实现一个深拷贝:

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
(function () {
'use strict';

var types = 'Array Object String Date RegExp Function Boolean Number Null Undefined'.split(' ');

function type () {
return Object.prototype.toString.call(this).slice(8, -1);
}

for (var i = types.length; i--;) {
$['is' + types[i]] = (function (self) {
return function (elem) {
return type.call(elem) === self;
};
})(types[i]);
}

return $;
})(window.$ || (window.$ = {}));//类型判断

function copy (param,deep) {

// 原始类型
if (param === null || (typeof param !== "object" && !$.isFunction(param))) {
return param;
}

// 函数
if ($.isFunction(param)) {
return new Function("return " + param.toString())();
}
else {

// 数组、对象
var target = $.isArray(param) ? [] : {},
name,
value;

for (name in param) {
value = param[name];

// 防止循环引用 和不是自身属性的拷贝
if (value === param || !param.hasOwnProperty(name)) {
continue;
}

// 深拷贝
if (deep) {
target[name] = copy(value,deep); // 数组的索引可以是非负整数字符串 eg. target['1']
} else {
// 浅拷贝
target[name] = value;
}
}
return target;
} 
}

快排

O(NlogN)

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
const Arr = [85, 24, 63, 45, 17, 31, 96, 50];
function quickSort(arr) {
if(!arr) {
return [];
}
if(arr.length <= 1) {
return arr;
}

// 取出中间值
let pivotIndex = Math.floor(arr.length / 2);
let pivot = arr.splice(pivotIndex, 1)[0];
let left = [];
let right = [];
for (let i = arr.length - 1; i >= 0; i--) {
if (arr[i] < pivot) { // 左边值
left.push(arr[i]);
} else {
right.push(arr[i]); // 右边值
}
}
// 递归
return quickSort(left).concat([pivot], quickSort(right)); // 合并
}

console.log(quickSort(Arr));

二分查找

O(logN)

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
function find(arr, value) {
if(!arr || arr.length === 0) {
return false;
}
let low = 0;
let high = arr.length - 1;
let mid;
let midValue;
while( low <= high ) {
mid = Math.floor((low + high)/2);
midValue = arr[mid];
if(midValue === undefined) {
console.log('未查找到数的index', );
return false;
}
if(value === midValue) {
console.log('查找到数的index为:', mid);
return mid;
}
if(value > midValue) {
low = mid + 1;
}
if(value < midValue) {
high = mid - 1;
}
}
return false;
}

//测试用例
find([1], 2);
find([1,2,3,4], 3);

设计模式

工厂模式

可以通过工厂产生大量的相似的东西

简单的工厂模式就是一个类的多次实例化;复杂的工厂模式就是公共部分放在父类中,创建时对可指定自己特性的子类实例化。

简单工厂

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
function CreatePerson(name,age,sex) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.sex = sex;
obj.sayName = function(){
return this.name;
}
return obj;
}
var p1 = new CreatePerson("longen",'28','男');
var p2 = new CreatePerson("tugenhua",'27','女');
console.log(p1.name); // longen
console.log(p1.age); // 28
console.log(p1.sex); // 男
console.log(p1.sayName()); // longen

console.log(p2.name); // tugenhua
console.log(p2.age); // 27
console.log(p2.sex); // 女
console.log(p2.sayName()); // tugenhua

// 返回都是object 无法识别对象的类型 不知道他们是哪个对象的实列
console.log(typeof p1); // object
console.log(typeof p2); // object
console.log(p1 instanceof Object); // true

复杂工厂

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
// 定义自行车的构造函数
var BicycleShop = function(name){
this.name = name;
this.method = function(){
return this.name;
}
};
BicycleShop.prototype = {
constructor: BicycleShop,
/*
* 买自行车这个方法
* @param {model} 自行车型号
*/
sellBicycle: function(model){
var bicycle = this.createBicycle(model);
// 执行A业务逻辑
bicycle.A();

// 执行B业务逻辑
bicycle.B();

return bicycle;
},
createBicycle: function(model){
throw new Error("父类是抽象类不能直接调用,需要子类重写该方法");
}
};
// 实现原型继承
function extend(Sub,Sup) {
//Sub表示子类,Sup表示超类
// 首先定义一个空函数
var F = function(){};

// 设置空函数的原型为超类的原型
F.prototype = Sup.prototype;

// 实例化空函数,并把超类原型引用传递给子类
Sub.prototype = new F();

// 重置子类原型的构造器为子类自身
Sub.prototype.constructor = Sub;

// 在子类中保存超类的原型,避免子类与超类耦合
Sub.sup = Sup.prototype;

if(Sup.prototype.constructor === Object.prototype.constructor) {
// 检测超类原型的构造器是否为原型自身
Sup.prototype.constructor = Sup;
}
}
var BicycleChild = function(name){
this.name = name;
// 继承构造函数父类中的属性和方法
BicycleShop.call(this,name);
};
// 子类继承父类原型方法
extend(BicycleChild,BicycleShop);
// BicycleChild 子类重写父类的方法
BicycleChild.prototype.createBicycle = function(){
var A = function(){
console.log("执行A业务操作");
};
var B = function(){
console.log("执行B业务操作");
};
return {
A: A,
B: B
}
}
var childClass = new BicycleChild("龙恩");
console.log(childClass);

单例模式

单例是如果它可以被实例化,那么它只能被实例化一次的对象,应用:每一个弹窗需要创建一个div,用单例所有弹窗只创建一个div减少页面重绘。

单体模式的优点是:

  • 可以用来划分命名空间,减少全局变量的数量。
  • 使用单体模式可以使代码组织的更为一致,使代码容易阅读和维护。
  • 可以被实例化,且实例化一次。

通用单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 创建div
var createWindow = function(){
var div = document.createElement("div");
div.innerHTML = "我是弹窗内容";
div.style.display = 'none';
document.body.appendChild(div);
return div;
};
// 创建iframe
var createIframe = function(){
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
return iframe;
};
// 获取实例的封装代码(存储在闭包中,读到了直接返回,读不到创建保存并返回)
var getInstance = function(fn) {
var result;
return function(){
return result || (result = fn.call(this,arguments));
}
};
// 测试创建div
var createSingleDiv = getInstance(createWindow);
document.getElementById("Id").onclick = function(){
var win = createSingleDiv();
win.style.display = "block";
};
// 测试创建iframe
var createSingleIframe = getInstance(createIframe);
document.getElementById("Id").onclick = function(){
var win = createSingleIframe();
win.src = "http://cnblogs.com";
};

观察者模式(又叫发布—订阅模式)

在稍微复杂点的页面中,比如组件化开发的页面,同一个页面由两三个人来开发,为了保证组件的独立性和降低组件间耦合度,我们往往使用「订阅发布模式」,即组件间通信使用事件监听和派发的方式,而不是直接相互调用组件方法
为此我们可以封装一个全局订阅发布模式对象;

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
var Event = (function(){
var list = {},
listen,
trigger,
remove;

// 无数组,创建数组后push
listen = function(key,fn){
if(!list[key]) {
list[key] = [];
}
list[key].push(fn);
};

// 不触发(触发函数数组不存在||为空)
// 触发(有数组,依次触发)
trigger = function(){
var key = Array.prototype.shift.call(arguments), // 也可以用splice(0,1)[0]
fns = list[key];
if(!fns || fns.length === 0) {
return false;
}
for(var i = fns.length; i >= 0; i--) { // 必须倒序,因为once函数执行完删除函数会影响数组长度
fns[i].apply(this,arguments);
}
};

// 无法删除 (数组不存在或为空return false)
// 全部删除 (不指定具体删除函数)
// 删除某个(指定具体的函数)
remove = function(key,fn){
var fns = list[key];
if(!fns || fns.length === 0) {
return false;
}
if(!fn) {
fns.length = 0;
return false;
}
for(var i = fns.length - 1; i >= 0; i--){
var _fn = fns[i];
if(_fn === fn) {
fns.splice(i,1);
}
}
};
once = function(key, fn) {
function fun() {
// var arr = Array.prototype.slice.apply(arguments);
fn.apply(this, arguments);
this.remove(key, fun);
}
this.add(key, fun);
};
return {
listen: listen,
trigger: trigger,
remove: remove,
once: once
}
})();
// 测试代码如下:
Event.listen("color",function(size) {
console.log("尺码为:"+size); // 打印出尺码为42
});
Event.trigger("color",42);