防抖
function debounce(fn, delay) {
var timer = null;
return function(...arg) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arg);
}, delay || 500);
};
}
// 不断触发一个函数,在规定时间内只让最后一次生效,前面都不生效
// 注意点:
// 如何在vue中使用 https://zhuanlan.zhihu.com/p/72363385
// 理解工作原理 this指向 参数
节流
function throttle(fn, delay) {
var timer = null;
return function(...args) {
if (!timer) {
fn.apply(this, args);
timer = setTimeout(function() {
timer=null
}, delay || 500);
}
};
}
// 不断触发一个函数后,执行第一次,只有大于设定的执行周期后才会执行第二次
// 防抖和节流的区别 应用场景
// 函数防抖和函数节流都是防止某一时间频繁触发
// 函数防抖是单位事件内 只执行最后一次触发的事件
// 函数节流是单位时间间隔内 只执行一次触发事件
// 比如:
// LOL技能cd就是用的 节流,
// 购物车提交订单就是用的 防抖
深拷贝
function deepClone(obj) {
// 1.先判断是否为对象类型
let isObject = (arg) => typeof arg === "object" && arg !== null
// 2.如果不是,则返回本身
if (!isObject(obj)) return obj;
// 3.定义一个变量,判断是数组还是对象
let cloneObj = Array.isArray(obj) ? [] : {};
// 4.循环每一个元素,如果满足是自身属性,则每一个值再判断是否为对象类型,如果是则继续递归调用,否则返回本身
for (let key in obj) {
cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key];
}
// 5.返回创建的变量值
return cloneObj;
}
浅拷贝
function shallowClone(obj) {
if (typeof obj !== 'object' || obj === null) return obj
// 3.定义一个变量,判断是数组还是对象
let cloneObj = Array.isArray(obj) ? [] : {};
// 4.循环每一个元素,
for (let key in obj) {
cloneObj[key] = obj[key]
}
// 5.返回创建的变量值
return cloneObj;
}
new
function _new(Fn, ...arg) {
//基于fn的prototype构建对象的原型
const thisObj = Object.create(Fn.prototype);
//将thisObj作为fn的this,继承其属性,并获取返回结果为result
const result = Fn.apply(thisObj, arg);
//根据result对象的类型决定返回结果
return result instanceof Object ? result : thisObj
}
call
Function.prototype.myCall = function(obj, ...args) {
// console.log(obj);
// console.log(this);
// if(obj==null||obj==undefined) obj=window //为空时,指向window
const ctx=obj || window
ctx.fn = this // this指向调用call的对象,即我们要改变this指向的函数
return ctx.fn(...args) // 执行函数并return其执行结果
delete ctx.fn //删除该方法,不然会对传入的对象造成污染,添加了该方法
}
apply
Function.prototype.myApply = function(obj, args) {
// console.log(obj);
// console.log(this);
// if(obj==null||obj==undefined) obj=window //为空时,指向window
const ctx=obj || window
ctx.fn = this // this指向调用call的对象,即我们要改变this指向的函数
return ctx.fn(...args) // 执行函数并return其执行结果
delete ctx.fn //删除该方法,不然会对传入的对象造成污染,添加了该方法
}
bind
Function.prototype.myBind = function(thisArg, ...args) {
var self = this //保存当前this,指的是当前fn调用者
const fn = function(...fnArgs) {//提取调用时候的参数
return self.apply(this instanceof self ? this : thisArg, [...args, ...fnArgs])
}//且有返回值
fn.prototype = this.prototype; //继承原型上的属性和方法
return fn
}
// 输入:接受一个或者多个参数,第一个是要绑定的上下文,额外参数当作绑定函数的前置参数。
// 输出:返回原函数的拷贝,即返回一个函数,这个函数呢具备原函数的功能
instanceof
// instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
function myInstanceof(left, right) {
// 左侧是实例对象
// 右侧是某个构造函数
if (typeof left !== 'object' || left === null) return false // 基本数据类型直接返回false
let RP = right.prototype; // 构造函数的原型对象
let LP = Object.getPrototypeOf(left) // 优先用这个方法 去查找实例对象的隐式原型对象
while (true) {
if (LP === null) return false;
if (LP === RP) return true;
LP = Object.getPrototypeOf(LP) // 沿着原型链重新赋值
}
}
数组去重
数组乱序
继承
原型链继承
function Animal(name) { // 定义一个动物类
this.name = name || 'Animal'; // 属性
this.sleep = function () { // 实例方法
console.log(this.name + '正在睡觉!');
}
}
Animal.prototype.eat = function (food) { // 原型方法
console.log(this.name + '正在吃:' + food);
};
// 核心: 将父类的实例作为子类的原型
function Cat() {}
Cat.prototype = new Animal();
// Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name); // cat
cat.eat('fish') // cat正在吃:fish
cat.sleep() // cat正在睡觉!
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
// 特点:
// 非常纯粹的继承关系, 实例是子类的实例, 也是父类的实例
// 父类新增原型方法 / 原型属性, 子类都能访问到
// 简单, 易于实现
// 缺点:
// 要想为子类新增属性和方法, 必须要在new Animal() 这样的语句之后执行, 不能放到构造器中
// 无法实现多继承
// 来自原型对象的所有属性被所有实例共享( 详细请看附录代码: 示例1)
// 创建子类实例时, 无法向父类构造函数传参
构造函数继承
// 核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Animal(name) { // 定义一个动物类
this.name = name || 'Animal'; // 属性
this.sleep = function () { // 实例方法
console.log(this.name + '正在睡觉!');
}
}
Animal.prototype.eat = function (food) { // 原型方法
console.log(this.name + '正在吃:' + food);
};
function Cat(name) {
Animal.call(this); // this指向cat实例对象,Animal方法调用 给cat实例对象添加方法
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
cat.sleep()
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
// 特点:
// 解决了1中, 子类实例共享父类引用属性的问题
// 创建子类实例时, 可以向父类传递参数
// 可以实现多继承( call多个父类对象)
// 缺点:
// 实例并不是父类的实例, 只是子类的实例
// 只能继承父类的实例属性和方法, 不能继承原型属性 / 方法
// 无法实现函数复用, 每个子类都有父类实例函数的副本, 影响性能
组合继承
// 核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Animal(name) { // 定义一个动物类
this.name = name || 'Animal'; // 属性
this.sleep = function () { // 实例方法
console.log(this.name + '正在睡觉!');
}
}
Animal.prototype.eat = function (food) { // 原型方法
console.log(this.name + '正在吃:' + food);
};
function Cat(name) {
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
// 组合继承也是需要修复构造函数指向的。
Cat.prototype.constructor = Cat;
console.dir(Cat);
// Test Code
var cat = new Cat();
console.log(cat.name);
cat.sleep()
cat.eat('零食')
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
// 特点:
// 弥补了方式2的缺陷, 可以继承实例属性 / 方法, 也可以继承原型属性 / 方法
// 既是子类的实例, 也是父类的实例
// 不存在引用属性共享问题
// 可传参
// 函数可复用
// 缺点:
// 调用了两次父类构造函数, 生成了两份实例( 子类实例将子类原型上的那份屏蔽了)
原型式继承(实例继承)
function Animal(name) { // 定义一个动物类
this.name = name || 'Animal'; // 属性
this.sleep = function () { // 实例方法
console.log(this.name + '正在睡觉!');
}
}
function Cat(name){
var instance = new Animal();
instance.name = name || 'Tom';
return instance;
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false
// 特点:
// 不限制调用方式, 不管是new 子类() 还是子类(), 返回的对象具有相同的效果
// 缺点:
// 实例是父类的实例, 不是子类的实例
// 不支持多继承
拷贝继承
睡眠函数(sleep)
// 1
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
// 2
function sleep(delay) {
var start = (new Date()).getTime();
while ((new Date()).getTime() - start < delay) {
continue;
}
}
// (async function() {
// console.log('Do some thing, ' + new Date());
// await sleep(3000);
// console.log('Do other things, ' + new Date());
// })();
函数柯里化(Currying)
/**
* @description: 将函数柯里化的工具函数
* @param {Function} fn 待柯里化的函数
* @param {array} args 已经接收的参数列表
* @return {Function}
*/
const currying = function(fn, ...args) {
// fn需要的参数个数
const len = fn.length
// 返回一个函数接收剩余参数
return function (...params) {
// 拼接已经接收和新接收的参数列表
let _args = [...args, ...params]
// 如果已经接收的参数个数还不够,继续返回一个新函数接收剩余参数
if (_args.length < len) {
return currying.call(this, fn, ..._args)
}
// 参数全部接收完调用原函数
return fn.apply(this, _args)
}
}
数组扁平化(flat)
let result = [];
let fn = function(ary) {
for(let i = 0; i < ary.length; i++) }{
let item = ary[i];
if (Array.isArray(ary[i])){
fn(item);
} else {
result.push(item);
}
}
}
lazyMan
filter
jsonp
function jsonp (url) {
/*声明一个唯一的回调函数并挂载到全局上
*创建一个script标签地址 指向 请求服务器 将回调函数名作参数带到服务器
*服务器拿到回调名称 并返回前端 该回调的调用 把返回结果当作参数传入
*/
let script = document.createElement('script')
let uniqueName = `jsonpCallback${new Date().getTime()}`
script.src = `url${url.indexOf('?') > -1 ? '&': '?'}callback=${uniqueName}`
document.body.appendChild(script)
window[uniqueName] = (res) => {
cb && cb(res)
document.body.removeChild(script)
delete window[uniqueName]
}
}
// 调用
jsonp('getList', (res) => {
console.log(res)
})
// 服务器端
1. 获取参数, 拿到回调函数名称
2. 返回参数名的前端回调的调用 并 把要返回的参数作为实参调用
/*弊端 - 只支持get请求,并且不安全,需要服务器支持*/
function jsonp ({url, query}) {
let script = document.createElement("script");
let cb = `jsonpCallBack${new Date().getTime()}${Math.floor(Math.random(5)*100000)}`
let params = {...query, cb}
let arr = []
for (let key in params) {
arr.push(`${key}=${params[key]}`)
}
script.src = `${url}?${arr.join("&")}`
document.body.appendChild(script)
return new Promise((resolve, rej) => {
window[cb] = function (res) {
resolve(res)
document.body.removeChild(script)
delete window[cb]
}
})
}
jsonp({
url:'/getList',
query: {name: 'ys',age: 19}
}).then((res) => {
console.log(res)
})
promise
promise.all
发布订阅(EventEmitter)
class EventEmitter {
// 事件容器 用来装事件数组
events = {}
add(eventName, cb) {
// 首先判断events 内有没有eventName事件数组容器,没有则创建一个新数组容器 并将事件存入
!this.events[eventName] && (this.events[eventName] = [])
this.events[eventName].push(cb)
}
emit(eventName, ...params) {
// 事件触发 更改this指向
this.events[eventName] &&
this.events[eventName].forEach(f => f.apply(this, params))
}
remove(eventName, cb) {
// 移除事件
this.events[eventName] &&
(this.events[eventName] = this.events[eventName].filter(f => f != cb))
}
}
// 运用
const event = new EventEmitter()
let fn1 = (...e) => {
console.log(e);
event.remove('on', fn1)
}
let fn2 = e => {
console.log(e);
}
let fn3 = e => {
console.log(3);
}
event.add('on', fn1)
event.add('on', fn2)
event.add('on', fn3)
event.add('on2', fn3)
document.onclick = function () {
event.emit('on', 100, 200)
event.emit('on2')
}