# 手撕问题
# 5种继承方式
# 必要知识
首先,我们先来回顾一下构造函数,原型和实例之间的关系。当我们创建一个构造函数时,构造函数会获得一个prototype属性,该属性是一个指针,指向一个原型对象 ,原型对象包含一个constructor属性,该属性也是一个指针,指向构造函数,而当我们创建构造函数的实例时,该实例其实会获得一个[[Prototype]]属性,指向原型对象。
# 原型链
原型链是由原型对象组成
定义:每个对象都有 proto 属性,指向了创建该对象的构造函数的原型,proto 将对象连接起来组成了原型链
作用:是一个用来实现继承和共享属性的有限的对象链。
属性查找机制: 当查找对象的属性时,如果实例对象自身不存在该属性,则沿着原型链往上一级查找,找到时则输出,不存在时,则继续沿着原型链往上一级查找,直至最顶级的原型对象Object.prototype,如还是没找到,则输出undefined;
属性修改机制: 只会修改实例对象本身的属性,如果不存在,则进行添加该属性,如果需要修改原型的属性时,则可以用: b.prototype.x = 2;但是这样会造成所有继承于该对象的实例的属性发生改变。
# 原型链继承也叫原型对象继承
基本思想:使用父类实例对象作为子类原型对象。此时子类的原型包含父类定义的实例属性,享有父类原型定义的的属性。
缺点: 父类中的属性如果是引用类型的话,对子类实例的修改会影响其他实例
注意:
- 在给子类原型添加属性或者方法的时候需要在将父类实例赋值给子类的原型对象后
- 给子类的原型添加方法或者属性的时候要通过【.】的方式来添加。不能 用subType.prototype = {} 来添加
DETAILS
function Animal(color) {
this.type = '禽兽'
this.color = color
this.leg = ['左腿', '右腿']
}
Animal.prototype.say = function() {
console.log('this.name :>> ', this.color + '-' + this.name);
}
function Dog(name) {
this.name = name
}
Dog.prototype = new Animal('red')
Dog.prototype.run = function(){
console.log('我跑的非常快');
}
const hashiq = new Dog('哈士奇')
// 父类中的属性如果是引用类型的话,对子类实例的修改会影响其他实例
hashiq.leg.push('第三条')
// --------------
hashiq.color = 'green'
console.log('hashiq :>> ', hashiq);
console.log('hashiq :>> ',hashiq.leg);
hashiq.say()
hashiq.run()
const jinmao = new Dog('金毛')
jinmao.say()
jinmao.run()
console.log('jinmao :>> ', jinmao);
console.log('jinmao color :>> ', jinmao.color);
console.log('jinmao :>> ',jinmao.leg);
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
# 原型式继承
基本思想:利用一个空的构造函数为桥梁,将一个对象作为原型创建新对象,这样新生成的对象都可以通过原型链共享这个原型对象的属性。
缺点: 父类中的属性如果是引用类型的话,对子类实例的修改会影响其他实例
DETAILS
// 原型式继承
function Animal() {}
Animal.prototype = {
say: function() {
console.log('this.name :>> ',this.name);
},
type: '走兽',
leg: ['左腿', '右腿'],
constructor: Animal
}
function extend(Super,Sub) {
function f() {}
f.prototype = Super.prototype
Sub.prototype = new f()
Sub.prototype.constructor = Sub;
}
extend(Animal, Dog)
function Dog(name) {
this.name = name
}
Dog.prototype.run = function(){
console.log('我跑的非常快');
}
const hashiq = new Dog('哈士奇')
// 父类中的属性如果是引用类型的话,对子类实例的修改会影响其他实例
hashiq.leg.push('第三条')
// --------------
console.log('hashiq :>> ', hashiq);
console.log('hashiq :>> ',hashiq.leg);
hashiq.say()
hashiq.run()
const jinmao = new Dog('金毛')
jinmao.say()
jinmao.run()
console.log('jinmao :>> ', jinmao);
console.log('jinmao :>> ',jinmao.leg);
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
# 构造函数继承
基本思想:使用call或者apply借用其他构造函数的成员, 可以解决给父构造函数传递参数的问题, 但是获取不到父构造函数原型上的成员.也不存在共享问题
缺点:
- 浪费内存,每个实例都会复制父类构造函数的所有内容。
- 父类原型定义的公共属性和方法无法被继承。
- 继承关系难以判定,只能判断实例与子类的直接继承关系,实例与父类的继承关系无法判定。
优点:
- 由父类构造函数定义的实例属性被子类实例继承以后仍然是独立的实例属性。
- 在创建继承关系时,可以传参
- 可以实现多继承。因为在子类构造函数内部可以借用多个父类构造函数。
DETAILS
function Animal(type) {
this.type = type
this.fav = ['肉', '西蓝花']
this.say = function () {
console.log('this.name :>> ', this.name + '-' + this.type);
}
}
Animal.prototype.smile = function () {
console.log('狗狗大笑');
}
function Dog(name) {
Animal.call(this, '犬类')
this.name = name
}
const hashiqi = new Dog('哈士奇')
hashiqi.say()
// hashiqi.smile() // 执行报错
hashiqi.fav.push('玩具') // 不会 影响其他子类
console.log('hashiqi :>> ', hashiqi);
const jinmao = new Dog('金毛')
jinmao.say()
jinmao.fav.push('牛肉') // 不会 影响其他子类
console.log('jinmao :>> ', jinmao);
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
# 组合继承
基本思想:构造函数+原型链的方式,属性都定义在父类的构造函数内部,方法都定义在父类构造函数的prototype上。
优点:
- 组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,现在已经成为js中最常用的继承方法。
缺点:
- 无论什么情况下,都会调用两次超类型构造函数,一次是在创建子类原型的时候,另一次是在子类型构造函数内部,
DETAILS
function Animal(type) {
this.type = type
this.fav = ['肉']
}
Animal.prototype.say = function() {
console.log('this.name :>> ', this.name );
}
Animal.prototype.smail = function() {
console.log('汪汪汪 ');
}
function Dog(name) {
Animal.call(this,'犬类')
this.name = name
}
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog
Dog.prototype.run = function() {
console.log(this.name + '跑得快')
}
const hsq = new Dog('哈士奇')
hsq.fav.push('玩具')
console.log('hsq :>> ', hsq);
hsq.say()
hsq.smail()
hsq.run()
const jm = new Dog('金毛')
console.log('jm :>> ', jm);
jm.say()
jm.smail()
jm.run()
console.log('has.instanceof :>> ', haq instanceof Dog);
console.log('jm.instanceof :>> ', jm instanceof Dog);
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
# 寄生式继承
基本思想:在原型式继承的基础上,对返回的原型进行了增强。
我们其实可以把寄生式继承看做是传进去一个对象,然后对该对象进行一定的加工,也就是增加一些方法来增强该对象,然后再返回一个包含新方法的对象的一个过程。
优点:
- 新增加的函数无法复用
缺点:
- 为原型添加属性和方法更加方便。
- 新增加的属性和方法是独立的。
DETAILS
function extend(obj) {
var obj = Object.create(obj, {
smail: {
value () {
console.log('汪汪汪');
},
},
fav: {
value: ['肉','玩具']
}
})
return obj
}
var dog = {
type: '犬类',
say() {
console.log(this.name + '-' + this.type);
}
}
var hsq = extend(dog)
hsq.name = '哈士奇'
hsq.say()
hsq.smail()
hsq.fav.push('拆家')
console.log('hsq :>> ', hsq);
console.log('hsq.fav :>> ', hsq.fav);
var jm = extend(dog)
jm.name = '金毛'
jm.say()
jm.smail()
console.log('jm :>> ', jm);
console.log('jm.fav :>> ', jm.fav);
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
# 寄生组合式继承 )—— 最完美的继承模式
组合 + 寄生 在原型式继承的基础上,对返回的原型进行了增强。
缺点:
- 不能判断实例与父类型的继承关系
DETAILS
function inheritPrototype(subType,superType) {
var prototype = Object.create(superType.prototype)
prototype.constructor = subType
subType.prototype = prototype
}
function Animal(type) {
this.type = type;
this.colors = ['red', 'green']
}
Animal.prototype.say = function() {
console.log('this.name :>> ', this.name + '=' + this);
}
function Dog(name) {
Animal.call(this, '犬类')
this.name = name
}
inheritPrototype(Dog,Animal)
Dog.prototype.run = function() {
console.log('跑的快');
}
const hsq = new Dog('哈士奇')
hsq.colors.push('yelow')
hsq.say()
hsq.run()
console.log('haq', hsq)
const jm = new Dog('金毛')
jm.say()
jm.run()
console.log('jm', jm)
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
# 小结
其实理解继承,主要是理解构造函数,实例属性和原型属性的关系。要想实现继承,将不同的对象或者函数联系起来,总共就以下几种思路:
- 原型链:父类的实例当做子类的原型。如此子类的原型包含父类定义的实例属性,享有父类原型定义的的属性。
- 借用构造函数:子类直接使用父类的构造函数。如此子类的实例直接包含父类定义的实例属性。
- 原型式:复制父类原型属性给子类原型。如此,子类实例享有父类定义的原型属性。
- 寄生式:思路与3一样,只是利用工厂模式对复制的父类原型对象进行增强。
- 然后,1,2思路结合,实例属性继承用借用构造函数保证独立性,方法继承用原型链保证复用性,就是组合模式。
- 4,2思路结合,或者说3,4与1,2思路结合,实例属性继承用借用构造函数保证独立性,方法继承用原型复制增强的方式,就是寄生组合模式。
# 同步异步的sleep
function creatSleep(type, sync = true) {
if(['s','m','l','xl'].indexOf(type) == -1) throw Error("creatSleep函数第一个参数仅支持's','m','l','xl'")
const syncTypes = {
's':40,
'm':41,
'l':42,
'xl':43
}
const asyncTypes = {
's':1000,
'm':2000,
'l':3000,
'xl':4000
}
function fib(n){
if(n<=1) return 1;
return fib(n-1)+ fib(n-2)
}
function sleep(time){
return new Promise((resolve) =>{
setTimeout(() => {
resolve()
},time )
})
}
if(sync) {
return () => {
console.time('sleepTime')
fib(syncTypes[type])
console.timeEnd('sleepTime')
}
} else {
return async () =>{
console.time('sleepTime')
await sleep(asyncTypes[type])
console.timeEnd('sleepTime')
}
}
}
var syncs = creatSleep('s')
syncs()
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
# 深拷贝
DETAILS
var obj = {
name: 1,
fav: ['美女','钱'],
mile: {
name: '凯里',
fav: ['钱','淘宝'],
sun: {
name: '米乐',
fav: ['看电视','玩',null],
test:undefined,
},
say() {console.log(this)}
},
test:null
}
function isObject(value) {
return value !== null && (typeof value !== 'function' && typeof value === 'object')
}
function deepClone(source) {
if(isObject(source)) {
const isArray = Array.isArray(source)
const target = isArray ? [] : {}
const sourcekey = Object.keys(source)
sourcekey.forEach((key) => {
target[key] = deepClone(source[key])
});
return target
}
return source
}
var target = deepClone(obj)
console.log('target :>> ', target);
// 方式二
export function find (list, f) {
return list.filter(f)[0]
}
export function deepCopy (obj, cache = []) {
// just return if obj is immutable value
if (obj === null || typeof obj !== 'object') {
return obj
}
const hit = find(cache, c => c.original === obj)
if (hit) {
return hit.copy
}
const copy = Array.isArray(obj) ? [] : {}
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key => {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}
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
# 数组拍平
DETAILS
const oldArr = [[1,3],[[[6,6,6,8,7], [9,5]]]]
function flat(arr) {
const result = []
while(arr.length) {
const first = arr.shift()
if(Array.isArray(first)){
result.push(...flat(first))
}else {
result.push(first)
}
}
return result
}
console.log(flat(oldArr));
function flat(data) {
var arr = []
function rec(arrs) {
arrs.forEach(item => {
if( Array.isArray(item) ) {
return rec(item)
}
arr.push(item)
});
}
rec(data)
return arr
}
flat(oldArr)
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
# instanceof
A instanceof B
,判断 B是否在A的原型链上
DETAILS
// 循环
function instanceOf(l,r){
if(l === null) return false
const baseType = ['number','boolean', 'undefined', 'string','symbol']
const lType = typeof l
if(baseType.includes(lType)) return false
let _proto = Object.getPrototypeOf(l)
let prototype = r.prototype
while(true) {
if(_proto === prototype) return true
if(_proto === null) return false
_proto = Object.getPrototypeOf(_proto)
}
}
// 递归
function instanceOf(l,r) {
let is = false
if(l === null) return is
const baseType = ['number','boolean', 'undefined', 'string','symbol']
const lType = typeof l
if(baseType.includes(lType)) return is
const prototype = Object.getPrototypeOf(l)
if(prototype) {
is = prototype === r.prototype
if(!is) {
return instanceOf(prototype, r)
}
}
return is
}
// 迭代代码
function instanceOf(l,r) {
let is = false
const prototype = Object.getPrototypeOf(l)
if(prototype) {
is = prototype === r.prototype
if(!is) {
return (function instanceOf(l,r) {
let is = false
const prototype = Object.getPrototypeOf(l)
if(prototype) {
is = prototype === r.prototype
if(!is) {
return (function instanceOf(l,r) {
let is = false
const prototype = Object.getPrototypeOf(l)
if(prototype) {
is = prototype === r.prototype
if(!is) {
return instanceOf(prototype, r)
}
}
return is
})(prototype,r)
}
}
return is
})(prototype,r)
}
}
return is
}
class Animal {}
class Dog extends Animal {}
const dog = new Dog()
console.log(instanceOf(dog, Object)); // true
console.log(instanceOf(dog, Array)); // false
console.log(instanceOf([], Array)); // true
console.log(instanceOf([], Object)); // true
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
# 柯里化
const curry = fn =>
(judge = (...args) =>
args.length >= fn.length
? fn(...args)
: (...arg) => judge(...args, ...arg));
function curry(fn) {
return judge = function(...args) {
return args.length >= fn.length ? fn(...args) : (...arg) => judge(...args, ...arg)
}
}
const sum = (a, b, c, d) => a + b + c + d;
const currySum = curry(sum);
currySum(1)(2)(3)(4); // 10
currySum(1, 2)(3)(4); // 10
currySum(1)(2, 3)(4); // 10
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# new
- 创建一个新的对象
- 将构造函数的prototype属性设置为新对象的__proto__
- 通过Apply 方法 将新对象和调用参数传递给构造器并执行
- 如果第三步构造器执行后没有手动返回对象类型Object(包含 Functoin, Array, Date, RegExg, Error),则返回新对象,如果有,则舍弃掉第一步创建的新对象,返回手动return的对象。
MDN 解释
- 创建一个空的简单JavaScript对象(即{});
- 链接该对象(设置该对象的constructor)到另一个对象 ;
- 将步骤1新创建的对象作为this的上下文 ;
- 如果该函数没有返回对象,则返回this。
DETAILS
function myNew(Constructor, ...args) {
const obj = {}
Object.setPrototypeOf(obj, Constructor.prototype)
const result = Constructor.apply(obj, args)
// 如果返回的result是一个对象则返回
// new方法失效,否则返回obj
return result instanceof Object ? result : obj
}
function Animal(name) {
this.name = name
}
// 如果第三步构造器执行后没有手动返回对象类型Object(包含 Functoin, Array, Date, RegExg, Error),则返回新对象,如果有,则舍弃掉第一步创建的新对象,返回手动return的对象。
function Animal1(name) {
this.name = name
return {
name:100,
age: 200
}
}
Animal.prototype.say = function() {
console.log('this.name :>> ', this.name);
}
var dog = myNew(Animal, 'hsq')
console.log('dog :>> ', dog);
dog.say()
console.log(dog instanceof Animal );
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
# create
DETAILS
function create(proto, properies) {
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object: ' + proto);
} else if (proto === null) {
throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
}
function F() {}
F.prototype = proto
const obj = new F()
const hasOwn = Object.prototype.hasOwnProperty
if(properies && typeof properies === 'object') {
console.log('properies :>> ', properies);
for(const key in properies) {
console.log('key :>> ', key);
if(hasOwn.call(properies,key)) {
obj[key] = properies[key]
}
}
}
return obj
}
var user = {name:1,age:2}
var properties = {sex:10}
properties.__proto__.say = function(){
}
var x = create(user,properties)
console.log('x :>> ', x);
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
# 防抖
连续触发一件事,直到停止触发后的一段时间才真正执行
DETAILS
function debounce(fn,delay) {
let timer
return function(...args) {
const ctx = this
timer && clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(ctx, args)
}, delay);
}
}
2
3
4
5
6
7
8
9
10
11
# 节流
连续触发一件事,当等待时间到了就触发
比如射手攻速 1.0,代表每秒攻击1次。当你1秒点了10次,也只攻击1次
DETAILS
// 第一次会立即执行
function throttle(fn,delay) {
var last = 0
return function(...args) {
const self = this
const now = Date.now()
if(now - last >= delay) {
last = now
fn.apply(self,args)
}
}
}
// 第一次和最后一次都会延迟
function throttle(fn,interval) {
let timer = null;
return function(...args) {
let ctx = this;
if(!timer) {
timer = setTimeout(() => {
fn.apply(ctx,args)
timer = null
}, interval);
}
}
}
// 两个结合
function throttle(fn,delay){
let timer = null;
let start = Date.now();
return function() {
let now = Date.now();
let remainning = delay - (now - start);
let ctx = this;
let args = arguments;
clearTimeout(timer);
if(remainning <= 0) {
start = Date.now()
fn.apply(ctx,args);
} else {
timer = setTimeout(fn,remainning)
}
}
}
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
# call
参数是一个个进行传递
DETAILS
Function.prototype.call = function(ctx, ...args) {
const self = ctx || window
const f = Symbol('f')
self[f] = this
const result = self[f](...args)
delete self[f]
return result
}
2
3
4
5
6
7
8
9
# apply
参数是传递一个数组
DETAILS
Function.prototype.apply = function(ctx,...args) {
const self = ctx || window
const f = Symbol('f')
self[f] = this
console.log('args :>> ', args);
const result = self[f](...( args[0] ? args[0] : []))
delete self[f]
return result
}
function add(x,y) {
console.log(this.name);
return x + y
}
const user = {name:1000}
console.log(add.apply(user));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# bind
绑定this 返回一个函数
DETAILS
Function.prototype.bind = function(ctx, ...args){
const fn = this
return function() {
return fn.apply(ctx, [...args, ...arguments])
}
}
function add(a,b) {
console.log('result :>> ', this.x + this.y + a + b);
}
var user = {x :1,y:1}
var add1 = add.bind(user,'===')('++++')
2
3
4
5
6
7
8
9
10
11
12
13
# reduce
DETAILS
const arr = [3,4,5,6,7]
var num = arr.reduce((accumulator, currentValue) => {
return accumulator + currentValue
}, 100)
// console.log(num)
// 当有默认值的时候 ,reduce第一次运行的时候 accumulator = 默认值,currentValue是数组的第一个值
// 当么有默认值的时候,reduce第一次运行的时候 accumulator = 数组的第一个值,currentValue是数组的第二个值
// reduce 实现 map
Array.prototype.map = function (cb,ctx) {
return this.reduce((prev,curr,index,array) => {
prev[index] = (cb.call(ctx || null, curr,index,array))
return prev
},[])
};
[1,3,4].map((item,index,arr) => {
console.log(arr)
return item * 4
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# compose
DETAILS
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
// [add,dis,reduce]
return funcs.reduce(function reducer(a, b) {
return function nextWrapper(...args) {
return a(b(...args));
};
});
}
var add = function(a, b) {
console.log(a + b )
}
var dis = function(c,d) {
console.log(c * d)
}
var reduce = function(e, f) {
console.log(e - f)
}
var multiply = function(g, h) {
console.log(g * h)
}
var fns = compose(add,dis,reduce, multiply)
fns = function nextWrapper(...args) {
return (function add(dis) {
console.log('第四个执行')
})(
(function dis(a, b) {
console.log('第三个执行')
})(
(function reduce(a, b) {
console.log('第二个执行')
})(
(function multiply(g,h){
console.log('第一个执行')
})(...args)
)
)
)
};
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
测试中间件
function next(action) {
console.log("[next]", action);
}
function fooMiddleware(next) {
console.log("[fooMiddleware] trigger");
return function next_from_foo(action) {
console.log("[fooMiddleware] before next");
next(action);
console.log("[fooMiddleware] after next");
};
}
function barMiddleware(next) {
console.log("[barMiddleware] trigger");
return function next_from_bar(action) {
console.log("[barMiddleware] before next");
next(action);
console.log("[barMiddleware] after next");
};
}
function bazMiddleware(next) {
console.log("[bazMiddleware] trigger");
return function next_from_baz(action) {
console.log("[bazMiddleware] before next");
next(action);
console.log("[bazMiddleware] after next");
};
}
const chain = compose(fooMiddleware, barMiddleware, bazMiddleware);
const nextChain = chain(next);
// nextChain("{data}");
/*
const chain = function nextWrapper(...args) {
return (function fooMiddleware(next) {
console.log("[fooMiddleware] trigger");
return function next_from_foo(action) {
console.log("[fooMiddleware] before next");
next(action);
console.log("[fooMiddleware] after next");
};
})(
(function barMiddleware(next) {
console.log("[barMiddleware] trigger");
return function next_from_bar(action) {
console.log("[barMiddleware] before next");
next(action);
console.log("[barMiddleware] after next");
};
})(
(function bazMiddleware(next) {
console.log("[bazMiddleware] trigger");
return function next_from_baz(action) {
console.log("[bazMiddleware] before next");
next(action);
console.log("[bazMiddleware] after next");
};
})(...args)
)
)
}
chain(function next(action) {
console.log("[next]", action);
})("{data}")
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
# Co
DETAILS
function run(gen) {
//把返回值包装成promise
return new Promise((resolve, reject) => {
var g = gen()
function step(val) {
//错误处理
try {
var res = g.next(val)
} catch(err) {
return reject(err);
}
if(res.done) {
return resolve(res.value);
}
//res.value包装为promise,以兼容yield后面跟基本类型的情况
Promise.resolve(res.value).then(
val => {
step(val);
},
err => {
//抛出错误
g.throw(err)
});
}
step();
});
}
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
# async 函数的实现原理
# [深入原理系列]
- git (opens new window)
- stackoverflow (opens new window)
- async await 和 promise微任务执行顺序问题 (opens new window)
- 知乎 (opens new window)
- 如何理解 resolve(Promise.resolve())内部执行了什么 (opens new window)
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里
DETAILS
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
function spawn (genF, self) {
return new Promise(function (resolve, reject) {
var gen = genF.call(self)
function step (nextF) {
var next
try {
next = nextF()
} catch (e) {
// finished with failure, reject the promise
reject(e)
return
}
if (next.done) {
// finished with success, resolve the promise
resolve(next.value)
return
}
// not finished, chain off the yielded promise and `step` again
Promise.resolve(next.value).then(
function (v) {
step(function () {
return gen.next(v)
})
},
function (e) {
step(function () {
return gen.throw(e)
})
}
)
}
step(function () {
return gen.next(undefined)
})
})
}
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
# 手写 requestAnimationFrame
;(function () {
var lastTime = 0
var vendors = ['ms', 'moz', 'webkit', 'o']
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']
window.cancelAnimationFrame =
window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function (callback, element) {
var currTime = new Date().getTime()
var timeToCall = Math.max(0, 16 - (currTime - lastTime))
var id = window.setTimeout(function () {
callback(currTime + timeToCall)
}, timeToCall)
lastTime = currTime + timeToCall
return id
}
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function (id) {
clearTimeout(id)
}
})()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# (10).add(2).minus(4) = 8
原型增加属性
DETAILS
Number.prototype.add = function(n) {
console.log(this, '+')
return Number(this + n)
}
Number.prototype.minus = function(n) {
console.log(this, '-')
return Number(this - n)
};// 注意分好
(10).add(2).minus(4)
2
3
4
5
6
7
8
9
# a == 1 && a == 2 && a == 3
==
比较会调用toString方法
DETAILS
var a = {
n: 1,
toString(){
return this.n ++
}
}
a == 1 && a == 2 && a == 3
var a = [1,2,3]
a.toString = a.shift
2
3
4
5
6
7
8
9
10
11
12
13
14
# 随机颜色
DETAILS
function getColor() {
const colors = '0123456789abcdef'
let color = '#'
while(color.length < 7) {
const randomIndex = Math.floor(Math.random() * 16)
color += colors[randomIndex]
}
return color
}
getColor()
2
3
4
5
6
7
8
9
10
11
# #ffaacc转rgb
DETAILS
function checkStr(str) {
if (str.length !== 7 && str.length !== 4) return false;
return /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g.test(str)
}
function toRgb(str) {
if (!checkStr(str)) throw new TypeError('参数不合法')
return '#ffaacc'.replace(/^(?:#)(\w)(\w)(\w)(\w?)(\w?)(\w?)/g,function(matchString, $1, $2, $3, $4, $5, $6,index,origin) {
if($6) {
return `rgb(${parseInt('0x' + $1 + $2)},${parseInt('0x' + $3 + $4)}, ${parseInt('0x' + $5 + $6) })`
} else {
return `rgb(${parseInt('0x' + $1.repeat(2))},${parseInt('0x' + $2.repeat(2))},${parseInt('0x' + $3.repeat(2))})`
}
})
}
console.log(toRgb('#0f0'))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# query转对象用正则
DETAILS
function parseUrl(url) {
var reg = /([^=&?]*)=([^=&?]*)/g
var obj = {}
url.replace(reg,function(matchStr,$1,$2,index,origin) {
obj[$1] ? (obj[$1].push($2)) : (obj[$1] = [$2])
})
return obj
}
var str = 'https://mbd.baidu.com?names=cao&age=100&sex=男&sex=女'
function parseUrl(str) {
var obj = {}
var reg = /([^&=?]+)=([^&=?]+)/g
while(true) {
var result = reg.exec(str)
if(result === null) return obj
const key = result[1]
const value = result[2]
obj[key] ? (obj[key].push(value)): (obj[key] = [value])
}
}
parseUrl(str)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 斐波那契
DETAILS
// 递归实现
function fib(num) {
if(num <2) return num
return fib(num-1) + fib(num-2)
}
// 尾调用优化
function fibonacci(n, current = 0, next = 1) {
if(n === 1) return next;
if(n === 0) return 0;
return fibonacci(n - 1, next, current + next);
}
// 通用公式计算
function fib(num) {
return parseInt(
( Math.pow(((1 + Math.sqrt(5)) / 2), num)
-
Math.pow(((1 - Math.sqrt(5)) / 2), num)
)
/
Math.sqrt(5)
)
}
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
前N项的和
DETAILS
function sum(n) {
let result = 0
while(n) {
result += fibonacci(n)
n--
}
return result
}
2
3
4
5
6
7
8
9
10
# 空对象判断
DETAILS
function isEmptyObject(obj){
if(obj === null || obj === undefined) return true
return Object.keys(obj).length === 0
}
function isEmptyObject (obj){
for (let k in obj) {
if(obj.hasOwnProperty(k)) return false
}
return true
};
2
3
4
5
6
7
8
9
10
11
# 实现 getValue(obj,'a.b.c.d')
DETAILS
function getValue(obj,str){
if(typeof str !== string ) return
if(str.length === 0 ) return
var keys = str.split('.')
let target = obj
while(true) {
const curr = keys.shift()
if(!curr) return target
target = target[curr]
if(target === undefined) return undefined
if(target === null) return null
}
}
function getValue(obj,path) {
if(typeof str !== string ) return
if(str.length === 0 ) return
var reg = /\w+(?=\.)?/g
let target = obj
while(true) {
var key = reg.exec(path)
if(key === null) return target
target = target[key[0]]
if(target === undefined) return undefined
if(target === null) return null
}
}
function getValue(str) {
if (typeof str !== 'string' ) return undefined
const keys = str.split('.')
let data = this[keys.shift()]
while(keys.length) {
const key = keys.shift()
data = data[key]
if(!data) {
return
}
}
return data
}
console.log('object :>> ', getValue.call(obj));
var obj = { a: { b : false} };
var obj = { a: { b : { c: { d: 1 } } } };
var obj = { a: { b : { c: { d: 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
# Ajax
DETAILS
myButton.addEventListener('click', function () {
ajax()
})
function ajax() {
let xhr = new XMLHttpRequest() //实例化,以调用方法
xhr.open('get', 'https://www.google.com') //参数2,url。参数三:异步
xhr.onreadystatechange = () => { //每当 readyState 属性改变时,就会调用该函数。
if (xhr.readyState === 4) { //XMLHttpRequest 代理当前所处状态。
if (xhr.status >= 200 && xhr.status < 300) { //200-300请求成功
let string = request.responseText
//JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象
let object = JSON.parse(string)
}
}
}
request.send() //用于实际发出 HTTP 请求。不带参数为GET请求
}
function ajax(url) {
const p = new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open('get', url)
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status <= 300) {
resolve(JSON.parse(xhr.responseText))
} else {
reject('请求出错')
}
}
}
xhr.send() //发送hppt请求
})
return p
}
let url = '/data.json'
ajax(url).then(res => console.log(res))
.catch(reason => console.log(reason))
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
# 请求并发控制
方式一
const arr = [1,2,3,4,5,6,7,8,9,10];
const handleRequest = (arr, callback) => {
const result = [];
let ajaxNum = 0;
const maxNum = 5;
const length = arr.length;
const newAjax = (id) => {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve(`${id}的结果`);
}, 2000);
})
}
const handleMain = (ids) => {
while (ajaxNum <= 3 && ids.length > 0) {
ajaxNum++;
const id = ids.shift();
newAjax(id).then((res) => {
result.push(res);
handleMain(ids);
}).catch(() => {
ids.push(id);
}).finally(() => {
ajaxNum--;
result.length === length && typeof callback === 'function' && callback(result)
})
}
}
handleMain(arr);
}
handleRequest([arr], (res) => {
console.log('结果数组',res)
})
方式二
function sendRequest(arr, max, callback) {
let fetchArr = [], // 存储并发max的promise数组
i = 0;
function toFetch() {
if (i === arr.length) { // 所有的都处理完了, 返回一个resolve
return Promise.resolve();
}
let one = fetch(arr[i++]); // 取出第i个url, 放入fetch里面 , 每取一次i++
one.then( () => {fetchArr.splice(fetchArr.indexOf(one), 1)}); // 当promise执行完毕后,从数组删除
fetchArr.push(one); //将当前的promise存入并发数组中 其实将这个push放到上一行会更好理解,那样就是我们同步的思维顺序,先push进去,再等promise执行完了之后再删除。 但由于then是异步的,所以怎么放都可以。
let p = Promise.resolve();
if (fetchArr.length >= max) { // 当并行数量达到最大后, 用race比较 第一个完成的, 然后再调用一下函数自身。
p = Promise.race(fetchArr);
}
return p.then(() => toFetch());
}
// arr循环完后, 现在fetchArr里面剩下最后max个promise对象, 使用all等待所有的都完成之后执行callback
toFetch().then(() => Promise.all(fetchArr)).then(() => {
callback();
})
}
方式三
function createRequest (tasks, pool) {
const max = pool || 5
const results = []
let index = 0
const together = new Array(max).fill(null).map(() => {
return new Promise((resolve, reject) => {
const run = function run () {
if (index >= tasks.length) {
resolve()
return
}
const oldIndex = index
const task = tasks[index++]
task().then(result => {
results[oldIndex] = result
run()
}).catch(err => {
reject(err)
})
}
run()
})
})
return Promise.all(together).then(_ => results)
}
const tasks = []
createRequest(tasks, 2).then(res => {
}).catch(err => {
console.log('err', err)
})
方式四
async function asyncPool(poolLimit, array, iteratorFn) {
const ret = [];
const executing = [];
for (const item of array) {
const p = Promise.resolve().then(() => iteratorFn(item, array));
ret.push(p);
if (poolLimit <= array.length) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= poolLimit) {
await Promise.race(executing);
}
}
}
return Promise.all(ret);
}
方式五
function asyncPool(poolLimit, array, iteratorFn) {
let i = 0;
const ret = [];
const executing = [];
const enqueue = function() {
if (i === array.length) {
return Promise.resolve();
}
const item = array[i++];
const p = Promise.resolve().then(() => iteratorFn(item, array));
ret.push(p);
let r = Promise.resolve();
if (poolLimit <= array.length) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= poolLimit) {
r = Promise.race(executing);
}
}
return r.then(() => enqueue());
};
return enqueue().then(() => Promise.all(ret));
}
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# 队列请求
DETAILS
class Queue {
promise = Promise.resolve();
excute(promise) {
this.promise = this.promise.then(() => promise);
return this.promise;
}
}
const queue = new Queue();
const delay = (params) => {
const time = Math.floor(Math.random() * 5);
return new Promise((resolve) => {
setTimeout(() => {
resolve(params);
}, time * 500);
});
};
const handleClick = async (name) => {
const res = await queue.excute(delay(name));
console.log(res);
};
handleClick('A');
handleClick('B');
handleClick('C');
handleClick('A');
handleClick('C');
handleClick('B');
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
# 实现 add(1)(2)(3).sum()
实现add函数,满足
- add(1)(2).sum() // 3
- add(1)(2)(3).sum() // 6
- add(1)(2)(3)(4).sum() // 10
- let addOne = add(1);
- addOne(2).sum() // 3
- addOne(2).sum() // 3
function add(...two) {
function three(...three) {
return add(...two ,...three)
}
three.sum = () => {
return two.reduce((total,val) => {
return total + val
})
}
return three
}
2
3
4
5
6
7
8
9
10
11
12
# looseEqual 宽松相等
export function looseEqual (a: any, b: any): boolean {
if (a === b) return true
const isObjectA = isObject(a)
const isObjectB = isObject(b)
if (isObjectA && isObjectB) {
try {
const isArrayA = Array.isArray(a)
const isArrayB = Array.isArray(b)
if (isArrayA && isArrayB) {
return a.length === b.length && a.every((e, i) => {
return looseEqual(e, b[i])
})
} else if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime()
} else if (!isArrayA && !isArrayB) {
const keysA = Object.keys(a)
const keysB = Object.keys(b)
return keysA.length === keysB.length && keysA.every(key => {
return looseEqual(a[key], b[key])
})
} else {
/* istanbul ignore next */
return false
}
} catch (e) {
/* istanbul ignore next */
return false
}
} else if (!isObjectA && !isObjectB) {
return String(a) === String(b)
} else {
return false
}
}
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
# 执行一次的函数
function once (fn) {
let called = false
let result;
return function () {
if (!called) {
called = true
result = fn.apply(this, arguments)
}
return result
}
}
function add(a,b){return a + b}
var catchadd = once(add)
catchadd(10,12)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 订阅与发布
class Event {
constructor() {
this._events = {}
}
$on(event, fn) {
if(Array.isArray(event)) {
for(let i = 0,l = event.length; i < l ; i++) {
this.$on(event[i],fn);
}
} else {
(this._events[event] || (this._events[event] = [])).push(fn)
}
return this
}
$once(event, fn) {
const self = this
function on() {
this.$off(event, on)
fn.apply(self, arguments)
}
on.fn = fn;
this.$on(event,on)
return this
}
$emit(event) {
const cbs = this._events[event]
const args = Array.prototype.slice.call(arguments,1)
if(cbs) {
for(var i = 0, l = cbs.length; i < l ;i++ ){
cbs[i](args)
}
}
return this
}
$off(event,fn){
// 没有参数,取消所有事件
if(!arguments.length) {
this._events = {}
return this
}
// 多个事件名,同一个事件
// $on(['say','he'], function(){ console.log(1)})
if(Array.isArray(event) ){
for(var i$1 = 0, l = event.length; i$1 < l; i$1 ++) {
this.$off(event[i$1], fn)
}
return this
}
// 没有找到事件
var cbs = this._events[event];
if (!cbs) {
return this
}
//
if (!fn) {
this._events[event] = null;
return this
}
var cb;
var i = cbs.length;
while (i--) {
cb = cbs[i];
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1);
break
}
}
return this
}
}
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
# 并发请求控制
const req = (url) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('get', url, true); //这里第三个参数不能为false,会变成同步
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.response)
} else {
reject(xhr)
}
}
xhr.send();
})
}
const multiRequest = (urls, maxNum) => {
let i = 0;
const ret = []; // 完成集合
const executing = [];// 执行集合
const enqueue = () => {
debugger
if (urls.length === i) { // 判断是否全部执行完
return Promise.resolve();
}
const p = Promise.resolve().then(() => {
console.log(1)
return req(urls[i++])
});
ret.push(p);
const e = p.then(() => {
console.log(2)
return executing.splice(0, 1)
});// 执行完从executin中剔除一个
executing.push(e);
let r = Promise.resolve();
if (executing.length >= maxNum) {// 判断executing中的长度是否大于等于限制数maxNum
r = Promise.race(executing);
}
return r.then(() => {
console.log(3)
return enqueue()
});// 当 r = Promise.race 时会等到其中一个执行完才执行下一步
}
return enqueue().then(() =>{
console.log(4)
return Promise.all(ret)
}) //全部执行完按顺序返回
}
const urls = Array.from({length: 10},(item,index) => {
return `http://localhost:8099/?ad=${index}`
})
multiRequest(urls,2).then(res => {
console.log('res', res)
})
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
# 接雨水
function trap(height = []) {
let n = height.length
if(n === 0) return
let res = 0;
let l_max = new Array(n)
let r_max = new Array(n)
l_max[0] = height[0]
r_max[n-1] = height[n-1]
for(let i = 1; i < n; i++) {
l_max[i] = Math.max(height[i], l_max[i - 1])
}
for(let i = n - 2; i >= 0; i--) {
r_max[i] = Math.max(height[i], r_max[i + 1])
}
for(let i = 1; i<n -1; i++) {
res+=Math.min(l_max[i], r_max[i]) -height[i]
}
return res
}
console.log(trap([0,1,0,2,1,0,1,3,2,1,2,1]));
// 双指针
function trap(height = []) {
let n = height.length
if(n === 0) return
let res = 0;
let left = 0;
let right = n -1;
let l_max = height[0]
let r_max = height[n - 1]
while(left <=right) {
l_max = Math.max(l_max, height[left])
r_max = Math.max(r_max, height[right])
if(l_max < r_max) {
res += l_max - height[left]
left ++
}
else {
res += r_max - height[right]
right --
}
}
return res
}
console.log(trap([0,1,0,2,1,0,1,3,2,1,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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
← 面向对象编程基本原则 你没用过的JS →