JS手撕代码

深浅拷贝

基本数据类型

number、boolean、string、null、undefined、symbol
基本数据类型是以名值得形式存储在栈内存中的。

进行赋值操作时会新开辟一片栈内存存储新的名值。

引用数据类型

数组、对象、函数等
以上的类型会在栈内存和堆内存中分别开辟一片空间进行存储。

当直接赋值时其实是将a的堆地址赋值给了b,两者最终指向了同一个堆内存。这就是浅拷贝,带来的后果就是你在对b的元素进行操作时,同时改变了a对应的值。

深拷贝方法

方法一

第一种方法简单粗暴,使用JSON.stringify和JSON.parse进行两次转换。

  let a = [1,3,5,7];
  let b = JSON.parse(JSON.stringify(a));

  b[0] = 99;
  console.log("a", a); // [1,3,5,7]
  console.log("b", b); // [99,3,5,7]

这种方法存在一种很大的缺点,转换时会自动忽略undefined,Symbol、function

方法二

用递归的方法去遍历复制所有的层级

function deepClone(obj){
    // 判断数据形式
    let clone = Array.isArray(obj)?[]:{};
    if(obj && typeof obj === "object"){
      for(key in obj){
        if(obj.hasOwnProperty(key)){
          // 属性是对象则进行递归
          if(obj[key] && typeof obj[key] === "object"){
            clone[key] = deepClone(obj[key]);
          }else{
            clone[key] = obj[key];
          }
        }
      }
    }
    return clone;
  }
  
  let a = [1,3,5,7],
  b = deepClone(a);
  a[0] = 999;
  console.log(a); // [1,3,5,7]
  console.log(b); // [999,3,5,7]
//写一个函数
for (let i = 0; i < 5; i++) {
	setTimeout(function() {
		console.log(i)
	},1000*i)
}

for(var i = 0; i < 5; i ++) {
	(function(i){
		setTimeout(function(){
			console.log(i);
		},1000*i)
	})(i)
}

数组去重

var arr = [1, 1, "true", "true", true, 15, false]
//利用ES6去重
function unique(arr) {
	return Array.from(new Set(arr))
}
//利用indexOf方法(该方法用于返回指定数组元素首次出现的地方,若没有则返回-1)
function unique(arr) {
	if(!Array.isArray(arr)) {
		console.log("error");
		return ;
	}
	let result = [];
	for (let i = 0; i < arr.length; i ++) {
		if (result.indexOf(arr[i]) == -1) {
			result.push(arr[i])
		}
	}
	return result;
}
//利用对象属性不能相等
function unique(arr) {
	if (! Array.isArray(arr)) {
		return
	}
	let result = [];
	let obj = {};
	for (let i = 0; i < arr.length; i ++) {
		if (!obj[arr[i]]) {
			result.push(arr[i])
			obj[arr[i]] = 1;
		} else {
			obj[arr[i]] ++
		}
	}
	return array;
}

实现sleep的效果

//使用Promise   先输出111,延迟500ms后输出222
function sleep(ms) {
	return new Promise((resolve) => {
		console.log("111");
		setTimeout(resolve,ms)
	})
}
sleep(500).then(function(){
	console.log("222")
})

//使用async/await  过了500ms后才输出111
function sleep(ms) {
	return new Promise((resolve) => {
		setTimeout(resolve,ms)
	})
}
async function test() {
	let temp = await sleep(500);
	console.log("111");
	return temp;
}

实现Promise

class PromiseM {
	constructor(process) {
		this.status = "pending";
		this.msg = "";
		process(this.resolve.bind(this),this.reject.bind(this))
		return this;
	}
	resolve(val) {
		this.status = "fulfilled";
		this.msg = val;
	}
	reject(val) {
		this.status = "rejected";
		this.msg = err;
	}
	then(fulfilled,reject) {
		if (this.status ===  "fulfilled") {
			fulfilled(this.msg)
		}
		if (this.status === "rejected") {
			reject(this.msg)
		}
	}
}

类的创建

new一个function,在这个function的prototype里面增加属性和方法。

// 定义一个动物类

function Animal (name) {
// 属性
	this.name = name || 'Animal';
// 实例方法
	this.sleep = function(){
	console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};

//这样就生成了一个Animal类,实力化生成对象后,有方法和属性。

类的继承

//1. 原型链继承,将父类的实例作为子类的原型
function Cat() {

}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
var cat = new Cat;
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
//特点:
//非常纯粹的继承关系,实例是子类的实例,也是父类的实例
//父类新增原型方法/原型属性,子类都能访问到
//简单,易于实现
//缺点:
//要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
//无法实现多继承
//来自原型对象的所有属性被所有实例共享(来自原型对象的引用属性是所有实例共享的)
//创建子类实例时,无法向父类构造函数传参

//2. 构造继承,使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类
function Cat(name) {
	Animal.call(this);
	this.name = name || 'Tom';
}
console.log(cat instanceof Animal); //false
console.log(cat instanceof Cat); //true
//特点:
//解决了1中,子类实例共享父类引用属性的问题
//创建子类实例时,可以向父类传递参数
//可以实现多继承(call多个父类对象)
//缺点:
//实例并不是父类的实例,只是子类的实例
//只能继承父类的实例属性和方法,不能继承原型属性/方法
//无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

// 3. 实例继承 为父类实例添加新特性,作为子类实例返回
function Cat(name) {
	var instance = new Animal();
	instance.name = name || 'Tom';
	return instance
}
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //false
//特点:
//不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果
//缺点:
//实例是父类的实例,不是子类的实例
//不支持多继承


//4. 组合继承 通过调用父类构造,继承父类的属性并保留传参的优点
//然后通过将父类作为子类原型,实现函数复用
function Cat(name) {
	Animal.call(this);
	this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
console.log(cat instanceof Animal); //true
console.log(cat insertSort Cat); //true
//特点:
//弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
//既是子类的实例,也是父类的实例
//不存在引用属性共享问题
//可传参
//函数可复用
//缺点:
//调用了两次父类构造函数,生成了两份实例

//5. 寄生组合继承
//通过寄生方式,砍掉父类的实例属性,
//这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性
function Cat(name) {
	Animal.call(this);
	this.name = name || 'Tom';
}
(function() {
	var Super = function(){};
	Super.prototype = Animal.prototype;
	Cat.prototype = new Super()
})()

console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
//特点:
//堪称完美
//缺点:
//实现较为复杂

链表

class Node{
	constructor(data) {
		this.data = data;
		this.next = null;
	}
}

class LinkList{
	constructor() {
		this.head = new Node('head');
	}
	findByValue = (value) => {
		let currentNode = this.head;
		while (currentNode !== null && currentNode.data !== value) {
			currentNode = currentNode.next;
		}
		return currentNode === null ? -1 : currentNode;
	}
	findByIndex = (index) => {
		let pos = 0;
		let currentNode = this.head;
		while (currentNode !== null && pos !== index) {
			pos ++;
			currentNode = currentNode.next;
		}
		return currentNode === null ? -1 : currentNode;
	}
	insert = (value, element) => {
		let currentNode = this.findByValue(element);
		if (currentNode == -1) {
			console.log('error');
			return;
		}
		let newNode = new Node(value);
		newNode.next = currentNode.next;
		currentNode.next = newNode;

	}
	delete = (value) {
		let currentNode = this.head;
		preNode = null;
		while (currentNode !== null && currentNode.data !== value) {
			preNode = currentNode;
			currentNode = currentNode.next;
		}
		if (currentNode == null) {
			return -1
		}
		preNode.next = currentNode.next;


	}

	print = () => {
		let currentNode = this.head
		while (currentNode !== null) {
			console.log(currentNode.data)
			currentNode = currentNode.next;
		}
	}

}

JS控制一次加载一张图片, 加载完后再加载下一张

https://blog.csdn.net/weixin_44462907/article/details/88756890

let loadImg = (src) => {
        return new Promise((resolve, reject) => {
            let img = document.createElement('img')
            img.src = src
            document.body.append(img)
            setTimeout(() => {
                resolve(true)
            }, 1000)
        })
    }
    const imgs = ['../src/img/1.PNG', '../src/img/1.PNG', '../src/img/1.PNG'];
    // 依次加载图片
    async function fSync(imgs) {
        for (let i of imgs) {
            await loadImg(i)
        }
    }
    fSync(imgs);

js防抖 节流

防抖:

对于短时间内连续触发的事件(如滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。

防抖实现思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后:

  • 如果在200ms内没有再次触发滚动事件,那么就执行函数
  • 如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时

效果:如果短时间内大量触发同一事件,只会执行一次函数。

实现:既然前面都提到了计时,那实现的关键就在于setTimeOut这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现:

/*
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn, delay) {
    let timer = null;
	return function() {
        if (timer) {
            claerTimeout(timer)
        }
        timer = setTimeout(fn, delay)
    }
}
节流

效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效

function throttle(fn, delay) {
    let valid = true;
    return function() {
        if (!valid) { //valid = false,休息时间,不执行
            return false
        }
        valid = false
        setTimeout(() => {
            fn()
            valid = true;
        }, delay)
    }
}

基于promise实现jsonp

function p(url){
    let json;
    let s = document.createElement('script');
    s.src = url + '?callback=fn';
    window.fn = function(data){
        json = data;
    }
    //当script被插入文档中时,src中的资源就会开始加载
    document.body.appendChild(s);
    
    return new Promise((resolve,reject)=>{
        /* throw('err in promise'); */
        s.onload = function(e){
            resolve(json);
        }
        s.onerror = function(){
            reject(json);
        }
    });
}
p('http://localhost:8082').then(data=>{
    console.log(data);
    throw('err before then');
}).catch(err => {
    //可以捕捉到then里的err befor then也可以捕捉到new Promise里的err in promise。
    console.log(err)

new一个对象中间做了什么操作

  1. new 操作符新建了一个空对象
  2. 这个对象原型指向构造函数的prototype
  3. 执行构造函数后返回这个对象。

自己实现new函数

function create(){
  //创建一个空对象
  let obj = new Object();
  //1.拿到传入的参数中的第一个参数,即构造函数名Func
  let Constructor = [].shift.call(arguments);
  //链接到原型
  obj.__proto__ = Constructor.prototype;
  //绑定this值
  let result = Constructor.apply(obj,arguments);//使用apply,将构造函数中的this指向新对象,这样新对象就可以访问构造函数中的属性和方法
  //返回新对象
  return typeof result === "object" ? result : obj;//如果返回值是一个对象就返回该对象,否则返回构造函数的一个实例对象
}

如何判断一个对象是不是空对象?

//for...in...遍历属性
function judgeObj(obj) {
	for (var attr in obj) {
	return alert("非空对象")
	}
    return alert("空对象")
}
//JSON自带的stringify()方法
if(JSON.stringify(obj) == "{}") {
    console.log("空对象")
}
//ES6新增的方法Object.keys()
if(Object.keys(obj).length > 0) {//会转化为一个数组
    console.log("非空对象")
}

async和generator有什么区别,写一个async和generator函数,并介绍区别

async函数是Generator函数的语法糖,将Generator的星号换成async,将yield换成awaitasync函数比Generator函数更好用.

Generatorasync function都是返回一个特定类型的对象:

  1. Generator: 一个类似{ value: XXX, done: true }这样结构的Object
  2. Async: 始终返回一个Promise,使用await或者.then()来获取返回值

Generator是属于生成器,一种特殊的迭代器,用来解决异步回调问题感觉有些不务正业了。。 而async则是为了更简洁的使用Promise而提出的语法,

//Generator
function * oddGenerator () {
 2   yield 1
 3   yield 3
 4
 5   return 5
 6 }
 7 
 8 let iterator = oddGenerator()
 9 
10 let first = iterator.next()  // { value: 1, done: false }
11 let second = iterator.next() // { value: 3, done: false }
12 let third = iterator.next()  // { value: 5, done: true  }
//async
1 function getRandom () {
 2   return new Promise(resolve => {
 3     setTimeout(_ => resolve(Math.random() * 10 | 0), 1000)
 4   })
 5 }
 6 
 7 async function main () {
 8   let num1 = await getRandom()
 9   let num2 = await getRandom()
10 
11   return num1 + num2
12 }

用promise和async实现每间隔1s,2s,3s…打印i

function sleep(interval){
return new Promise((resolve)=>	
    setTimeout(resolve, interval);
		  });
}
async function stepPrint(n){
    for(let i=0;i<=n;i++){
         console.log(i);
        await  sleep(i*10000);
         }
}
stepPrint(5)

手写bind

Function.prototype._bind = function(){
    var self = this  // 保存原函数
    var context = [].shift.call(arguments)  // 保存需要绑定的this上下文
    var args = [].slice.call(arguments)   // 剩余的参数转为数组
    return function(){// 返回一个新函数
        self.apply(context, args.concat([].slice.call(arguments)))
    }
}

https://blog.csdn.net/lovefengruoqing/article/details/80186401

循环对象的方法有哪几种以及它们的区别

https://www.cnblogs.com/nerrol/p/8137065.html

检测数据类型的方法有哪几种

https://www.cnblogs.com/MrZhujl/p/9837152.html

数组的typeof是什么,如何判断数组,几种方法

Object.prototype.toString.call(arg)==='[object Array]'

https://blog.csdn.net/weixin_33744854/article/details/91370655

将原生的ajax封装成promise

var  myNewAjax=function(url) {
    return new Promise(function(resolve,reject){
        var xhr = new XMLHttpRequest();
	  	xhr.open('get',url);
        xhr.send(data);
        xhr.onreadystatechange=function(){
            if(xhr.status==200&&readyState==4){
            var json=JSON.parse(xhr.responseText);
            resolve(json)
        }else if(xhr.readyState==4 &&xhr.status!= 200){
            reject('error'); 
        }
                                         }
    })
}

如何实现一个私有变量,用getName方法可以访问,不能直接访问

function People(){
 25       var name='张三'; // 私有变量; 外部无法访问
 27       function say(){  // 私有函数;
 28         return '我是......';
 29       }
 31       this.getName=function(){ // 对外公共的特权方法; 外部可以访问
 32         return name;
 33       }
 38   }
 39   var p=new People()
 40   alert(p.getname())

怎么获得对象上的属性:比如说通过Object.keys()

  1. 对象内置属性方法:Object.keys();该方法返回一个数组,数组内包括对象内可枚举属性以及方法名称
var keys= Object.keys(testObj);
console.log(keys); // 输出 keys ["name", "age", "action"]

2.Object.getOwnPropertyNames():方法返回一个指定对象所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组.不可枚举: 比如属性是一个js方法

 var keys = Object.getOwnPropertyNames(testObj);
console.log(keys);  // 输出 keys ["name", "age", "action"]

3.最原始的 for…in 循环。

var keys =[];
	for(var i in testObj){
	keys.push(i);
}
console.log(keys);   // keys ["name", "age", "action"]

同时并发请求如何保证返回有序

回调

只适合并发数少的情况,多层嵌套回调会让代码的可读性大大降低

ajax改为同步

如在jquery中将async参数设置为false

Promise

async/await

https://www.jb51.net/article/74018.htm

JavaScript中的轮播实现原理

<!DOCTYPE html>
<html>
<head>
	<title>Test</title>
</head>
<body>
	<div class="imgBox">
		<img class="img-slide img1" src="images/1.png" alt="1">
		<img class="img-slice img2" src="images/2.png" alt="2">
		<img class="img-slice img3" src="images/3.png" alt="3">
	</div>

<style>
	.img1{
		display:block;
	}
	.img2{
		display: none;
	}
	.img3{
		display: none;
	}
</style>

<script type="text/javascript">
	var index = 0;
	function changeImg() {
		index ++;
		var a = document.getElementsByClassName("img-slice");
		if (index >= a.length) {
			index = 0;
		}
		for (let i = 0; i < a.length; i ++) {
			a[i].style.display = "none";
		}
		a[index].style.display = "block";
	}
	setInterval(changeImg, 2000)
</script>
</body>
</html>

https://www.jianshu.com/p/366e374e108d

JS的垃圾回收机制 两种的区别 为啥引用计数不常用

垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。

标记清除法

在函数声明一个变量的时候,就将这个变量标记为“进入环境”。从逻辑上讲,永远都不能释放进入环境的变量作占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。垃圾回收器在运行时候会给存储在内存中中的所有变量都加上标记。然后它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。在此之后再被标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。

引用计数法

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。
  但是很重要的一点是当遇到循环引用的时候,函数的引用次数就不会为0,所以不会被垃圾回收器回收内存,会造成内存泄露。在IE中涉及COM对象,就会存在循环引用的问题。

https://blog.csdn.net/qq_21325977/article/details/79973761

bind,apply,call

Call和apply的作用是一模一样的,只是传参的形式有区别而已

1、改变函数体内部this的指向

2、借用别的对象的方法,

3、调用函数,因为apply,call方法会使函数立即执行

https://segmentfault.com/a/1190000018270750

https://www.cnblogs.com/humin/p/4556820.html

定义一个log方法

function log(){
  console.log.apply(console, arguments);
};
log(1);    //1
log(1,2);    //1 2

内存泄漏

https://www.cnblogs.com/libin-1/p/6013490.html

http://www.fly63.com/article/detial/225?type=2

https://blog.csdn.net/qappleh/article/details/80337630

https://segmentfault.com/a/1190000004896090

转换

https://www.cnblogs.com/chenmeng0818/p/5954215.html

如何保证表单安全!!

https://blog.csdn.net/weixin_33769125/article/details/88626742

防止表单重复提交

https://www.cnblogs.com/huanghuizhou/p/9153837.html

https://www.jb51.net/article/41825.htm

事件委托

https://www.jianshu.com/p/c3c98c71e34b

https://www.cnblogs.com/xxflz/p/10588344.html

事件轮询

http://www.fly63.com/article/detial/5355?type=2

fetch API与传统request的区别

https://www.cnblogs.com/iifeng/p/11199058.html

https://www.jianshu.com/p/c7b67a0b67da

数组方法 哪些返回新数组 哪些改变数组‘

https://blog.csdn.net/weixin_46124214/article/details/104334640

addEventListener参数

addEventListener(event, function, useCapture)

其中,event指定事件名;function指定要事件触发时执行的函数;useCapture指定事件是否在捕获或冒泡阶段执行。

作用域不同sessionStorage:不在不同的浏览器窗口中共享,即使是同一个页面;localStorage:在所有同源窗口都是共享的;cookie:也是在所有同源窗口中共享的

js监听对象属性的改变

http://www.zhangchen915.com/index.php/archives/581/

事件模型常用方法:

event.stopPropagation:阻止捕获和冒泡阶段中,当前事件的进一步传播,

event.preventDefault,取消该事件(假如事件是可取消的)而不停止事件的进一步传播,

event.target:指向触发事件的元素,在事件冒泡过程中这个值不变

Ajax解决浏览器缓存问题

禁止浏览器缓存功能有如下几种方法:

在ajax发送请求前加上 anyAjaxObj.setRequestHeader("If-Modified-Since","0")
在ajax发送请求前加上 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")
在URL后面加上一个随机数:"fresh=" + Math.random();
在URL后面加上时间搓:"nowtime=" + new Date().getTime();
如果是使用jQuery,直接这样就可以了$.ajaxSetup({cache:false})。这样页面的所有ajax都会执行这条语句就是不需要保存缓存记录。

js拖拽功能的实现

在允许拖拽的节点元素上,使用on来监听mousedown(按下鼠标按钮)事件,鼠标按下后,克隆当前节点

监听mousemove(鼠标移动)事件,修改克隆出来的节点的坐标,实现节点跟随鼠标的效果

监听mouseup(放开鼠标按钮)事件,将原节点克隆到鼠标放下位置的容器里,删除原节点,拖拽完成。

拖拽的基本原理就是根据鼠标的移动来移动被拖拽的元素。鼠标的移动也就是x、y坐标的变化;元素的移动就是style.position的top和left的改变。当然,并不是任何时候移动鼠标都要造成元素的移动,而应该判断鼠标左键的状态是否为按下状态,是否是在可拖拽的元素上按下的。

https://www.cnblogs.com/psxiao/p/11547834.html

实现一个tab组件

https://blog.csdn.net/qq_23244029/article/details/93517575

<!DOCTYPE html>
<html>
 
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>实现Tab组件</title>
  <link rel="stylesheet" type="text/css" href="tab.css">
</head>
 
<body>
  <div id="container1">
    <ul class="tab-list">
      <li class="tab-item" href="panel-1">tab1</li>
      <li class="tab-item" href="panel-2">tab2</li>
      <li class="tab-item" href="panel-3">tab3</li>
    </ul>
    <div class="panel active" id="panel-1">content1</div>
    <div class="panel" id="panel-2">content2</div>
    <div class="panel" id="panel-3">content3</div>
  </div>
  <script>
 
 
    /**
     * Tab 组件
     * @param {String} containerId 容器Id
     */
    function Tab(containerId) 
    {
      var cont1 = document.getElementById(containerId);
      var tab_items = cont1.getElementsByClassName('tab-item');
      var pls = cont1.getElementsByClassName('panel');
       
      for(let i = 0;i < tab_items.length;i++){
        tab_items[i].onclick = function(e){
          console.log(e);
          for(let j = 0;j<tab_items.length;j++){
            if(i == j){
              pls[i].style.display = "block";
            }else{
              pls[j].style.display = "none";
            } 
          }
          
        }
      }
      
    }
 
    /**
     * active 方法,可以控制第几个 Tab 组件显示
     * @param {Number} index 要显示的 tab 的序号,从0开始
     */
    Tab.prototype.active = function (index) 
    {
 
    }
 
    var tab = new Tab('container1');
 
  </script>
</body>
 
</html>

写一个观察者模式

写一个发布订阅模式

https://www.jianshu.com/p/e0575e17de2a

https://www.cnblogs.com/suyuanli/p/9655699.html

// 发布订阅模式
class EventEmitter {
    constructor() {
        // 事件对象,存放订阅的名字和事件
        this.events = {};
    }
    // 订阅事件的方法
    on(eventName,callback) {
       if (!this.events[eventName]) {
           // 注意时数据,一个名字可以订阅多个事件函数
           this.events[eventName] = [callback]
       } else  {
          // 存在则push到指定数组的尾部保存
           this.events[eventName].push(callback)
       }
    }
    // 触发事件的方法
    emit(eventName) {
        // 遍历执行所有订阅的事件
       this.events[eventName] && this.events[eventName].forEach(cb => cb());
    }
    // 移除订阅事件
    removeListener(eventName, callback) {
        if (this.events[eventName]) {
            this.events[eventName] = this.events[eventName].filter(cb => cb != callback)
        }
    }//filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
    // 只执行一次订阅的事件,然后移除
    once(eventName,callback) {
        // 绑定的时fn, 执行的时候会触发fn函数
        let fn = () => {
           callback(); // fn函数中调用原有的callback
           this.removeListener(eventName,fn); // 删除fn, 再次执行的时候之后执行一次
        }
        this.on(eventName,fn)
    }
}


使用方式
let em = new EventEmitter();
let workday = 0;
em.on("work", function() {
    workday++;
    console.log("work everyday");
});

em.once("love", function() {
    console.log("just love you");
});

function makeMoney() {
    console.log("make one million money");
}
em.on("money",makeMoney);

let time = setInterval(() => {
    em.emit("work");
    em.removeListener("money",makeMoney);
    em.emit("money");
    em.emit("love");
    if (workday === 5) {
        console.log("have a rest")
        clearInterval(time);
    }
}, 1);

预先加载,是什么如何实现

图片等静态资源在使用前提前请求。

资源后续使用可以直接从缓存中加载,提升用户体验。

加载不是为了减少页面加载时间

预加载只是提前加载除去首轮加载的图片以后要用到的图片,比如通过点击等事件才会用到的图片。

在js中,需要多少预加载图片,就创建多少image对象,再为每个image对象添加图片的src,此时图片也会被提前请求。

var images = new Array();
function preload(){
    for(var i = 0;i < preload.arguments.length;i ++){
        iamges[i] = new Image();
        images[i].src = preload.arguments[i];
    }
}
preload(url1,url2,url3);

//也可以将上面的代码改写一下
function preload(){
    if(document.images){//document.images:页面上所有图片的集合
        var img1 = new Image();
        var img2 = new Image();
        var img3 = new Image();
        img1.src = url1;
        img2.src = url2;
        img3.src = url3;
    }
}

使用ajax
只要是静态资源都可以预加载,包括图片,css,js,可以使用ajax请求这些静态资源,这样也不会影响当前页面。

window.onload = function(){
    setTimeout = (function(){
        var xhr = new XMLHttpRequest();
        xhr.open('GET','js文件地址');
        xhr.send('');
        xhr = new XMLHttpRequest();
        xhr.open('GET','css文件地址');
        xhr.send('');
        new Image().src = '图片地址';
    },1000);
}

手写 call bind apply 内部实现

https://segmentfault.com/a/1190000016960203

发布了60 篇原创文章 · 获赞 121 · 访问量 7549

猜你喜欢

转载自blog.csdn.net/weixin_46124214/article/details/104780865