【Javascript】手写题汇总

一、数据类型判断

数据类型判断:typeof、instance of、constructor、toString https://www.cnblogs.com/onepixel/p/5126046.html

二、类和继承

-------------------------------------------Object.create()

Object.create(proto,[propertiesObject])
通过原型创建对象,可添加某些属性。

proto:创建对象的原型对象
propertiesObject:可选。增加到新对象的可枚举属性。必须是对象!

注意:增加的属性是其自身的属性,而不是增加到原型链上。

const apple = {
	shape:'round',
	taste: function() {console.log(`${this.color} apple is ${this.t},it is ${this.price} yuan!`);
}
  //注意!!!!!!!!!!以下:
  // taste:`${this.color} apple is ${this.t},it is ${this.price} yuan!`
  //然后再在全局打印是不正确的,因为在全局调用时,this变成了window,此时这些属性都不存在,变为undefined。
};
const greenapple = Object.create(apple,{
	color:{
		value:'red'},
	t:{
		value:'sweet',
		writable:true
	}
});
greenapple.shape='not round';
greenapple.price=15;
greenapple.taste();
console.log(greenapple.taste);//结果是ƒ () {
        //  console.log(
        //    `${this.color} apple is ${this.t},it is ${this.price} yuan!`
        //  );
        //}
console.log(greenapple.taste());//结果会是red apple is sweet,it is 15 yuan!
								//05-exercise.html:93 undefined
								//因为先执行了内部的打印,但没有返回值,所以打印undefined

1、{}

var o = {a:1};
console.log(o)

打印如下:
在这里插入图片描述
继承了Object的很多方法,如hasOwnPropertytoString等。

2、Object.create(null)

var o = Object.create(null,{
    a:{
        writable:true,
        configurable:true,
        value:'1'
    }
})
console.log(o)

打印如下:
在这里插入图片描述
可以看到,没有继承任何方法,因为原型链上没有任何属性方法。如果o.toString()会报错。

3、Object.create(Object.prototype)

var o = Object.create(Object.prototype,{
    a:{
        writable:true,
        configurable:true,
        value:'1'
    }
})
console.log(o)

打印如下:
在这里插入图片描述
与{}创建的一样,可见{}实际类似于创建原型Object.prototype的对象。

4、Object.create({})

var o = Object.create({},{
    a:{
        writable:true,
        configurable:true,
        value:'1'
    }
})
console.log(o)

打印如下:
在这里插入图片描述
多了一层proto嵌套。

5、总结

  • Object.create(null)创建的对象没有继承任何属性,所以非常纯净,可以定义自己的方法,不用担心覆盖原型链上的同名方法。可用作数据字典。
  • 可以避免for...in循环时遍历到对象原型链上的属性,使用create(null)就可以不用进行hasOwnProperty检查,可以减少性能损失。
八种继承方式:
1、原型链继承
function A() {
	this.AA = 'AAvalue';
 	 this.Acolor = ['red','yellow'];
}
A.prototype.Amethod = function() {

	return this.AA;
};
A.prototype.Anum = [1,2,3];
function B() {
	this.BB = 'BBvalue';
 	 this.Bcolor = ['blue','green'];

}
B.prototype = new A();
B.prototype.Bmethod = function() {
	return this.BB;
};
B.prototype.Bnum = [4,5,6];

let zz = new B();
console.log(zz.Amethod());
// alert(zz.AA);
console.log(zz.Bmethod());
// alert(zz.BB);


//测试对引用类型的修改
zz.Acolor.push('black');
zz.Anum.push(9),
zz.Bcolor.push('white');
zz.Bnum.push(8);
console.log(zz.Acolor);
console.log(zz.Bcolor);
let yy = new B();
console.log(yy.Acolor);
console.log(yy.Anum);
console.log(yy.Bcolor);
console.log(yy.Bnum);

在这里插入图片描述
引用对象会被修改的区域:A的构造函数中、A的原型中、B的原型中。
引用对象不会被修改的区域:B的构造函数中。

特点:

  • 能继承构造函数和原型中的属性和方法。
  • 但多个实例对引用类型的操作会被篡改。
2、借用构造函数继承
function A() {
	this.AA = 'AAvalue';
  	this.Acolor = ['red','yellow'];
}

function B() {
	A.call(this);
	this.BB = 'BBvalue';
 	this.Bcolor = ['blue','green'];

}


let zz = new B();
console.log(zz.AA);
console.log(zz.BB);


//测试对引用类型的修改
zz.Acolor.push('black');
zz.Bcolor.push('white');
console.log(zz.Acolor);
console.log(zz.Bcolor);
let yy = new B();
console.log(yy.Acolor);
console.log(yy.Bcolor);

在这里插入图片描述
特点:

  • 但多个实例对引用类型的操作不会被篡改。
  • 只能继承构造函数中的属性和方法,不能继承原型中的属性和方法。
  • 每个实例都会将构造函数中的属性复制一份,影响性能。
3、组合继承
function A() {
	this.AA = 'AAvalue';
 	this.Acolor = ['red','yellow'];
}
A.prototype.Amethod = function() {
	return this.AA;
};
A.prototype.Anum = [1,2,3];
function B() {
	A.call(this);
	this.BB = 'BBvalue';
 	this.Bcolor = ['blue','green'];
}
B.prototype = new A();
B.prototype.constructor = B;
B.prototype.Bmethod = function() {
	return this.BB;
};
B.prototype.Bnum = [4,5,6];

let zz = new B();
console.log(zz.Amethod());
// alert(zz.AA);
console.log(zz.Bmethod());
// alert(zz.BB);


//测试对引用类型的修改
zz.Acolor.push('black');
zz.Anum.push(9),
zz.Bcolor.push('white');
zz.Bnum.push(8);
console.log(zz.Acolor);
console.log(zz.Anum);
console.log(zz.Bcolor);
console.log(zz.Bnum);
let yy = new B();
console.log(yy.Acolor);
console.log(yy.Anum);
console.log(yy.Bcolor);
console.log(yy.Bnum);

在这里插入图片描述
从原型继承的AA、Acolor在实例化时被从构造函数继承的AA、Acolor覆盖,由此多实例修改时不会造成变化,每次都是新建的。
在这里插入图片描述

特点:

  • 从A的构造函数继承了两次其中的属性方法,所以存在两份相同的属性方法。
4、原型式继承
function createH(obj) {
	function H(){}
	H.prototype = obj;
	return new H();
}
let obj = {objvalue:123};
let h1 = createH(obj);
console.log(h1);
//相当于Object.create();
let h2 = Object.create(obj);
console.log(h2);

在这里插入图片描述
此方法类似于浅拷贝。

特点:

  • 可以得到来自原型的属性和方法。
  • 也存在引用类型被篡改的可能。
5、寄生式继承
function createH(obj) {
	function H(){}
	H.prototype = obj;
	return new H();
}
function createfullH(obj){
	var hh = createH(obj);
	hh.hmethod = function(){};
	return hh;
}
let obj = {objvalue:123};
let h1 = createfullH(obj);
console.log(h1);

在这里插入图片描述
特点:

  • 与原型式继承类似,相当于给继承函数增加了属性方法。
6、寄生组合式继承
function createRelation(A,B) {
	//这种方法也可以,而且会两者同级
	//Object.assign(B.prototype,A.prototype)
	let Bprototype = Object.create(A.prototype);
	B.prototype = Bprototype;
	B.prototype.constructor = B;
}

function A() {
	this.AA = 'AAvalue';
 	this.Acolor = ['red','yellow'];
}
A.prototype.Amethod = function() {
	return this.AA;
};
A.prototype.Anum = [1,2,3];
function B() {
	A.call(this);
	this.BB = 'BBvalue';
 	this.Bcolor = ['blue','green'];
}
createRelation(A,B);

B.prototype.Bmethod = function() {
	return this.BB;
};
B.prototype.Bnum = [4,5,6];

let zz = new B();
console.log(zz.Amethod());
// alert(zz.AA);
console.log(zz.Bmethod());
// alert(zz.BB);


//测试对引用类型的修改
zz.Acolor.push('black');
zz.Anum.push(9),
zz.Bcolor.push('white');
zz.Bnum.push(8);
console.log(zz.Acolor);
console.log(zz.Anum);
console.log(zz.Bcolor);
console.log(zz.Bnum);
let yy = new B();
console.log(yy.Acolor);
console.log(yy.Anum);
console.log(yy.Bcolor);
console.log(yy.Bnum);

在这里插入图片描述
此时输出与方式3组合式继承相同,但此时实例中只有一组AA、Acolor。
在这里插入图片描述

7、混入方式继承多个对象
function A() {
	this.AA = 'Avalue';
}
function B() {
	this.BB = 'Bvalue';
}
function C() {
	A.call(this);
	B.call(this);
	this.CC = 'Cvalue';
}
A.prototype.Amethod = function(){};
B.prototype.Bmethod = function(){};

C.prototype = Object.create(A.prototype);
Object.assign(C.prototype,B.prototype);
C.prototype.constructor = C;
C.prototype.Cmethod = function(){};

let zz = new C();
console.log(zz);

在这里插入图片描述
Object.assign会把B原型上的方法属性拷贝到C原型上,使C的所有实例都可用B的方法。

8、ES6类继承extends
class A{
	constructor(m,n){
		this.Avalue = m;
		this.Anum = n;
	};
	get Aresult(){
		return Acal();
	};
	Acal(){
		return this.Avalue+this.Anum;
	};
}

class B extends A{
	constructor(m,n,p,q){
		super(m,n);
		this.Bvalue = p;
		this.Anum = q;
	};
	get Bresult(){
		return Bcal();
	};
	Bcal(){
		return this.Bvalue+this.Bnum;
	};

}

const b = new B(1,2,3,4);
console.log(b);

来源:https://juejin.cn/post/6844903696111763470

三、数组扁平化、去重、排序

数组扁平化:https://www.jb51.net/article/163767.htm
数组扁平化、去重、排序:https://www.jb51.net/article/181192.htm
数组排序: https://www.jb51.net/article/181187.htm

数组扁平化
let ary = [1, [2, [3, [4, 5]]], 6];
let str = JSON.stringify(ary);
var arr = [[1, 3, 2, 1],
[5, 3, 4, 8, 5, 6, 5],
[6, 2, 8, 9, [4, 11, 15, 8, 9, 12, [12, 13, [10], 14]]], 
16]
  1. flat
arr_flat = arr.flat(Infinity);
  1. 正则表达式
str = str.replace(/(\[|\])/g, '');
str = '[' + str + ']';
ary = JSON.parse(str);

此时是字符串

ary = str.replace(/(\[|\])/g, '').split(',');
  1. 如果arr数组中有空数组,不使用此方法,用下面的方法;同时得到数组的值是字符串,不是数字
var newArr = arr.toString().split(',')
  1. 递归
var newArr3 = []
function flat(arr) {
  for(var i = 0; i < arr.length; i++) {
    if(arr[i] instanceof Array) {
      flat(arr[i])
    } else {
      newArr3.push(arr[i])
    }
  }
}
flat(arr)
  1. reduce和concat
function flatten(arr) {
  return arr.reduce(
    (a, b) => [].concat(a, Array.isArray(b) && b ? flatten(b) : b),[]);
}
var newArr2 = flatten(ary);
  1. 扩展运算符
    每次展开一层
function flatten(arr) {
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}

数组去重:
var newArr1 =[1, 3, 2, 1, 5, 3, 4, 8, 5, 6, 5, 6, 2, 8, 9, 4, 11, 15, 8, 9, 12, 12, 13, 10, 14, 16]
  1. 去重方法一(Set)

Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

var duplicate = Array.from(new Set(newArr1))
  1. reduce和indexof
    通过数组reduce方法,利用indexOf判断上一次回调返回数组a中是否包含当前元素b的索引,如果不存在,则把b元素加入a数组,否则直接返回a
var duplicate1 = newArr1.reduce((a, b) => {
  if(a.indexOf(b) === -1) {
    a.push(b)
  }
  return a
}, [])
  1. for和indexof
var duplicate3 = []
for(var i = 0; i < newArr1.length; i++) {
  if(duplicate3.indexOf(newArr1[i]) === -1) {
    duplicate3.push(newArr1[i])
  }
}
  1. 利用下标去重
    通过数组的过滤filter方法,利用indexOf获取当前元素ele在被过滤数组farr中的第一个索引值,如果值与当前索引值index相等则返回,如果不相等则过滤。
var duplicate2 = newArr1.filter((ele, index, farr) => {
  return farr.indexOf(ele) === index
})
  1. 排序后去重
function unique3(arr) {
  arr.sort();
  var newArr = [arr[0]];
  for(var i = 1, len = arr.length; i < len; i++) {
    if(arr[i] !== newArr[newArr.length - 1]) {
      newArr.push(arr[i]);
    }
  }
  return newArr;
}
var duplicate4 = unique3(newArr1)
数组排序
  1. sort
function systemSort(arr) {
  return arr.sort(function(a, b) {
    return a - b
  })
}
var sort = systemSort(duplicate)
  1. 冒泡
    // 编写方法,实现冒泡
    var arr = [29,45,51,68,72,97];
    //外层循环,控制趟数,每一次找到一个最大值
    for (var i = 0; i < arr.length - 1; i++) {
        // 内层循环,控制比较的次数,并且判断两个数的大小
        for (var j = 0; j < arr.length - 1 - i; j++) {
            // 白话解释:如果前面的数大,放到后面(当然是从小到大的冒泡排序)
            if (arr[j] > arr[j + 1]) {
                var temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
 
    }
    console.log(arr);//[2, 4, 5, 12, 31, 32, 45, 52, 78, 89]

四、深浅拷贝

主要针对对象类型

赋值:

  • 对栈中数据进行复制,对象类型则复制的是堆区地址。

浅拷贝:

  • 在堆区开辟新的内存存储,对原始对象属性值的精准拷贝,如果属性是原始类型拷贝该值,如果属性是对象类型拷贝其地址。所以属性值如果是对象类型则与原数据共享内存。

深拷贝:

  • 在堆区开辟新的内存存储,将其从内存中完整的拷贝出来,如果属性是原始类型则拷贝该值,如果属性是对象类型则对对象类型的属性继续进行深拷贝。

由此可见,赋值在栈区进行简单的精准拷贝,浅拷贝在第一层属性进行简单的精准拷贝,深拷贝则是对对象的完整拷贝。

//赋值:
let aa = {
	name : 'aa',
	num  : [1,[2,3],5]
};
let bb = aa;
bb.name = 'bb';
bb.num[0] = [2,3,4];//注意如果是bb.num=[2,3,4],会将该对象类型的属性重新赋值,由于栈区存放的地址相同,指向的对象没变,不管怎样修改都相当于对同一个对象进行修改,所以两者还是保持同步。
console.log(aa);//{name:'bb',num:[[2,3,4],[2,3],5]}
console.log(bb);//{name:'bb',num:[[2,3,4],[2,3],5]}
//浅拷贝:
let aa = {
	name : 'aa',
	num  : [1,[2,3],5]

};

function shallowClone (obj) {
	let cloneObj = {};
	for ( let i in obj ) {
		if ( obj.hasOwnProperty(i) ) {
		 	cloneObj[i] = obj[i];
		}
	}
	return cloneObj;

}

let bb = shallowClone(aa);
bb.name = 'bb';
bb.num[0] = [2,3,4];//console.log(bb.num===aa.num);为true
//注意如果是bb.num=[2,3,4],会将该属性重新赋值,此时地址改变,不再指向同一块地址,也不改变原属性的值。
//console.log(bb.num===aa.num);为false
console.log(aa);//{name:'aa',num:[[2,3,4],[2,3],5]}
console.log(bb);//{name:'bb',num:[[2,3,4],[2,3],5]}

//深拷贝
let aa = {
	name : 'aa',
	num  : [1,[2,3],5]

};

function deepClone (obj) {
	if (obj===null) return obj;
	if (obj instanceof Date) return new Date(obj);
	if (obj instanceof RegExp) return new RegExp(obj);
	if (typeof obj!=='object') return obj;
	let cloneObj = {};
	for ( let i in obj ) {
		if ( obj.hasOwnProperty(i) ) {
		 	cloneObj[i] = deepClone(obj[i]);
		}
	}
	return cloneObj;

}

let bb = deepClone(aa);
bb.name = 'bb';
bb.num[0] = [2,3,4];//console.log(bb.num===aa.num);为false
console.log(aa);//{name:'aa',num:[1,[2,3],5]}
console.log(bb);//{name:'bb',num:[[2,3,4],[2,3],5]}


-------------------------------------------Object.prototype.hasOwnProperty()

表示是否有自己的属性,会检查对象中是否有某个属性,如果没有也不会沿着原型链查找。

var obj = {
    a: 1,
    fn: function(){
 
    },
    c:{
        d: 5
    }
};
console.log(obj.hasOwnProperty('a'));  // true
console.log(obj.hasOwnProperty('fn'));  // true
console.log(obj.hasOwnProperty('c'));  // true
console.log(obj.c.hasOwnProperty('d'));  // true
console.log(obj.hasOwnProperty('d'));  // false, obj对象没有d属性
console.log(obj.hasOwnProperty("hasOwnProperty"));//false 继承自Object原型上的方法
 
var str = new String();
// split方法是String这个对象的方法,str对象本身是没有这个split这个属性的
console.log(str.hasOwnProperty('split'));  // false
console.log(String.prototype.hasOwnProperty('split'));  // true

原型中的属性不算。

var o={name:'jim'};
function Person(){
  this.age=19;
}
Person.prototype=o;//修改Person的原型指向
p.hasOwnProperty("name");//false 无法判断继承的name属性
p.hasOwnProperty("age");//true;

for in不同,会忽略从原型链上继承的属性。

var o={
  gender:'男'
}
function Person(){
  this.name="张三";
  this.age=19;
}
Person.prototype=o;
var p = new Person();
for(var k in p){
  if(p.hasOwnProperty(k)){
    console.log("自身属性:"+k);// name ,age
  }else{
    console.log("继承的属性:"+k);// gender
  }
}

可以被重写覆盖

var o={
  gender:'男',
  hasOwnProperty:function(){
    return false;
  }
}

o.hasOwnProperty("gender");//不关写什么都会返回false
//解决方式,利用call方法
({}).hasOwnProperty.call(o,'gender');//true
Object.prototype.hasOwnProperty.call(o,'gender');//true

-------------------------------------------Object.prototype.isPrototypeOf()

测试某方法是否在另一个对象的原型链上。

var o={};
function Person(){};
var p1 =new Person();//继承自原来的原型,但是现在已经无法访问
Person.prototype=o;
var p2 =new Person();//继承自o
console.log(o.isPrototypeOf(p1));//false o是不是p1的原型
console.log(o.isPrototypeof(p2));//true  o是不是p2的原型
console.log(Object.prototype.isPrototypeOf(p1));//true
console.log(Object.prototype.isPrototypeOf(p2));//true

来源:https://juejin.cn/post/6844903855474343950

浅拷贝的实现方式:

1、Object.assign()

Object.assign()可以将原对象的可枚举属性拷贝给目标对象,然后返回目标对象。

let aa = {
	name : 'aa',
	num  : [1,[2,3],5]
};
let bb = Object.assign({},aa);
bb.name = 'bb';
bb.num[0] = [2,3,4];
console.log(aa);//{"name": "aa","num": [[2,3,4],[2,3], 5]}

2、lodash的clone方法

let _ = require('lodash');
let aa = {
	name : 'aa',
	num  : [1,[2,3],5]
};
let bb = _.clone(aa);
bb.name = 'bb';
console.log(aa.num===bb.num);//true

3、展开运算符

let aa = {
	name : 'aa',
	num  : [1,[2,3],5]
};
let bb = {...aa};
bb.name = 'bb';
console.log(aa.num===bb.num);//true

//数组用展开运算符时
let ary = [1, [2, [3, [4, 5]]], 6];
let arr = [...ary]
console.log(ary)
console.log(arr)
//此时都会变
arr[1].push(3)
//此时相当于被重新赋值
arr[1] = [333]
console.log(ary)
console.log(arr)

4、利用Array.prototype.concat()

但只能用于数组:

let aa = [1, { name: "aa", num: [1, [2, 3], 5] }];
let bb = aa.concat();
bb[1].name = "bb";
console.log(aa[1].num === bb[1].num); //true
console.log(bb);
console.log(aa);

在这里插入图片描述

用于对象时会将该对象转换为数组第一个元素

let aa = {
	name : 'aa',
	num  : [1,[2,3],5]
};
let bb = Array.prototype.concat.call(aa);
bb.name = 'bb';
console.log(aa.num===bb.num);//false
console.log(aa);
console.log(bb);

在这里插入图片描述

5、Array.prototype.slice()

只能用于数组

let aa = [1, { name: "aa", num: [1, [2, 3], 5] }];
let bb = aa.slice();
bb[1].name = "bb";
console.log(aa[1].num === bb[1].num); //true
console.log(aa[1].num);
console.log(aa);
console.log(bb);

在这里插入图片描述

用于对象时会将其删掉

let aa = {
	name : 'aa',
	num  : [1,[2,3],5]
};
let bb = Array.prototype.slice.call(aa);
bb.name = 'bb';
console.log(aa.num===bb.num);//false
console.log(aa);
console.log(bb);

在这里插入图片描述

深拷贝的实现方式:

1、JSON.parse(JSON.stringify())

可用于数组或对象,相当于将对象先转换为字符串,然后再解析成新对象,但是如果其中有函数和正则,函数会变为null,正则会变为空对象。

let aa = [1,
	{name : 'aa',
	num  : [1,[2,3],5]},
	{function(){}}
];
let bb = JSON.parse(JSON.stringify(aa));
console.log(aa[1].num===bb[1].num);//false
aa[1].num[0] = [1,2,3];
console.log(aa);
console.log(bb);

在这里插入图片描述
2、lodash的cloneDeep方法

let _ = require('lodash');
let aa = [1,
	{name : 'aa',
	num  : [1,[2,3],5]},
];
let bb = _.cloneDeep(aa);
console.log(aa[1].num===bb[1].num);//false

3、jQuery.extend()

可用于对象

let $ = require('jquery');
let aa = 
	{name : 'aa',
	num  : [1,[2,3],5],
	fun: function(){}};
let bb = $.extend(true,{},aa);
console.log(aa.num===bb.num);//false
aa.num[0] = [1,2,3];
console.log(aa);
console.log(bb);

在这里插入图片描述

用于数组时会发生转换

let $ = require('jquery');
let aa = [1,
	{name : 'aa',
	num  : [1,[2,3],5]},
	{function(){}}
];
let bb = $.extend(true,{},aa);
console.log(aa[1].num===bb[1].num);//false
aa[1].num[0] = [1,2,3];
console.log(aa);
console.log(bb);

在这里插入图片描述
4、手写递归方法

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);


来源:https://juejin.cn/post/6844904197595332622
深拷贝:https://segmentfault.com/a/1190000020255831

五、事件总线(发布订阅模式)

class EventEmitter {
    constructor() {
        this.cache = {}
    }
    on(name, fn) {
        if (this.cache[name]) {
            this.cache[name].push(fn)
        } else {
            this.cache[name] = [fn]
        }
    }
    off(name, fn) {
        let tasks = this.cache[name]
        if (tasks) {
            const index = tasks.findIndex(f => f === fn || f.callback === fn)
            if (index >= 0) {
                tasks.splice(index, 1)
            }
        }
    }
    emit(name, once = false, ...args) {
        if (this.cache[name]) {
            // 创建副本,如果回调函数内继续注册相同事件,会造成死循环
            let tasks = this.cache[name].slice()
            for (let fn of tasks) {
                fn(...args)
            }
            if (once) {
                delete this.cache[name]
            }
        }
    }
}

// 测试
let eventBus = new EventEmitter()
let fn1 = function(name, age) {
	console.log(`${name} ${age}`)
}
let fn2 = function(name, age) {
	console.log(`hello, ${name} ${age}`)
}
eventBus.on('aaa', fn1)
eventBus.on('aaa', fn2)
eventBus.emit('aaa', false, '布兰', 12)
// '布兰 12'
// 'hello, 布兰 12'

发布订阅模式:https://juejin.cn/post/6844903616172539917

六、解析URL参数为对象

function parseParam(url) {
    const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
    const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
    let paramsObj = {};
    // 将 params 存到对象中
    paramsArr.forEach(param => {
        if (/=/.test(param)) { // 处理有 value 的参数
            let [key, val] = param.split('='); // 分割 key 和 value
            val = decodeURIComponent(val); // 解码
            val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
    
            if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
                paramsObj[key] = [].concat(paramsObj[key], val);
            } else { // 如果对象没有这个 key,创建 key 并设置值
                paramsObj[key] = val;
            }
        } else { // 处理没有 value 的参数
            paramsObj[param] = true;
        }
    })
    
    return paramsObj;
}

七、防抖和节流

防抖和节流:https://juejin.cn/post/6844903669389885453
防抖和节流:https://juejin.cn/post/6844903651278848014

防抖

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

函数内部支持使用 this 和 event 对象;除了支持 this 和 event 外,还支持以下功能:

支持立即执行;
函数可能有返回值;
支持取消功能;

//简单版
function debounce(func, wait) {
    var timeout;
    return function () {
        var context = this;
        var args = arguments;
        clearTimeout(timeout)
        timeout = setTimeout(function(){
            func.apply(context, args)
        }, wait);
    }
}

//最终版
function 
debounce(func, wait, immediate) {
    var timeout, result;
    
    var debounced = function () {
        var context = this;
        var args = arguments;
        
        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 如果已经执行过,不再执行
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            if (callNow) result = func.apply(context, args)
        } else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
        return result;
    };

    debounced.cancel = function() {
        clearTimeout(timeout);
        timeout = null;
    };

    return debounced;
}

节流

规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

支持取消节流;另外通过传入第三个参数,options.leading 来表示是否可以立即执行一次,opitons.trailing 表示结束调用的时候是否还要执行一次,默认都是 true。
注意设置的时候不能同时将 leading 或 trailing 设置为 false。

//简单版
function throttle(func, wait) {
    var context, args;
    var previous = 0;

    return function() {
        var now = +new Date();
        context = this;
        args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}
//最终版
function throttle(func, wait, options) {
    var timeout, context, args, result;
    var previous = 0;
    if (!options) options = {};

    var later = function() {
        previous = options.leading === false ? 0 : new Date().getTime();
        timeout = null;
        func.apply(context, args);
        if (!timeout) context = args = null;
    };

    var throttled = function() {
        var now = new Date().getTime();
        if (!previous && options.leading === false) previous = now;
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
            if (!timeout) context = args = null;
        } else if (!timeout && options.trailing !== false) {
            timeout = setTimeout(later, remaining);
        }
    };
    
    throttled.cancel = function() {
        clearTimeout(timeout);
        previous = 0;
        timeout = null;
    }
    return throttled;
}



function throttle(func, wait ,type) {
    if(type===1){
        let previous = 0;
    }else if(type===2){
        let timeout;
    }
    return function() {
        let context = this;
        let args = arguments;
        if(type===1){
            let now = Date.now();

            if (now - previous > wait) {
                func.apply(context, args);
                previous = now;
            }
        }else if(type===2){
            if (!timeout) {
                timeout = setTimeout(() => {
                    timeout = null;
                    func.apply(context, args)
                }, wait)
            }
        }

    }
}




  function throttle(fun, delay) {
        let last, deferTimer
        return function (args) {
            let that = this
            let _args = arguments
            let now = +new Date()
            if (last && now < last + delay) {
                clearTimeout(deferTimer)
                deferTimer = setTimeout(function () {
                    last = now
                    fun.apply(that, _args)
                }, delay)
            }else {
                last = now
                fun.apply(that,_args)
            }
        }
    }

    let throttleAjax = throttle(ajax, 1000)

    let inputc = document.getElementById('throttle')
    inputc.addEventListener('keyup', function(e) {
        throttleAjax(e.target.value)
    })

结合应用场景

debounce

  • search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
  • window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次

throttle

  • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
  • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断

八、排序算法

排序算法及稳定性汇总:https://blog.csdn.net/leishao_csdn/article/details/83589670
排序算法:https://www.jb51.net/article/181187.htm

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值