1、防抖
<button id="debounce">点我防抖!</button>;
// 防抖
var myDebounce = document.getElementById("debounce");
myDebounce.addEventListener("click", debounce(sayDebounce));
function debounce(fn) {
let timer = null;
return function () {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.call(this, arguments);
}, 1000);
};
}
function sayDebounce() {
console.log("防抖成功!");
}
2、节流
<button id="throttle">点我节流!</button>;
// 节流
var myThrottle = document.getElementById("throttle");
myThrottle.addEventListener("click", throttle(sayThrottle));
function throttle(fn) {
let timer = true;
return function () {
if (!timer) {
return;
}
timer = false;
setTimeout(() => {
fn.call(this, arguments);
timer = true;
}, 500);
};
}
function sayThrottle() {
console.log("节流成功!");
}
3、柯里化函数
function add() {
const args = [...arguments]
function fn() {
args.push(...arguments)
return fn
}
fn.toString = function () {
return args.reduce((sun, i) => {
return sun + i
})
}
return fn
}
let a = Number(add(1)(1, 1, 1,)(1)(1)(1))
console.log(a); // 7
4、Promise.all()
promise.all方法:哪个先执行就先显示在数组里面,然后全部返回成功了再全部展示。- 如果有一个失败就返回第一个失败的信息。全部不进行了
let promise1 = new Promise((resolve, rejected) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
let promise2 = new Promise((resolve, rejected) => {
setTimeout(() => {
resolve(2);
}, 2000);
});
let promise3 = new Promise((resolve, rejected) => {
setTimeout(() => {
resolve(3);
}, 3000);
});
Promsie.all([promise1, promise2, promise3]).then((res) => {
console.log(res);
});
5、Promise.race()
promise.race方法:哪个先执行先展示哪个的信息,只展示一个。
let promise1 = new Promise((resolve, rejected) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
let promise2 = new Promise((resolve, rejected) => {
setTimeout(() => {
resolve(2);
}, 2000);
});
let promise3 = new Promise((resolve, rejected) => {
setTimeout(() => {
resolve(3);
}, 3000);
});
Promsie.race([promise1, promise2, promise3]).then((res) => {
console.log(res);
});
6、浅拷贝
浅拷贝:以赋值的方式进行拷贝对象,仍指向同一个地址,修改时,原对象也会变化。
赋值的方式 =Object.assign()解构赋值 ...
let obj = {
name: "小三",
arr: [1, 2],
person: {
a: 1,
b: 2,
},
fun: function () {},
};
//
// 1. 赋值的方式 =
function deep(obj) {
let newobj = {};
for (let key in obj) {
if (obj.hasOwnProperty(obj)) {
newobj[key] = obj[key];
}
}
return newobj;
}
let obj1 = deep(obj);
//
// 2. Object.assign()
let obj1 = Object.assign({}, obj);
// 3. 解构赋值的方式
let obj1 = [...obj];
7、深拷贝
深拷贝:新拷贝一个对象,修改时原对象不会改变
JSON.parse(JSON.stringify(Obj))递归循环拷贝
let obj = {
name: "小三",
unfind: undefined,
fun: function () {},
symbol: Symbol("唯一值"),
};
//
let obj1 = JSON.parse(JSON.stringify(obj));
// 缺点就是 undfined,Symbol,function不会进行拷贝,所以要用递归循环
//
function deepOjb(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 newobj = new obj.constructor(); //把拷贝对象的构造函数指向此创建的对象
hash.set(obj, newobj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 进行递归拷贝
newobj[key] = deepObj(obj[key], hash);
}
}
return newobj
}
8、动态路由
<!-- 动态路由定义,怎么获取参数 -->
<!-- 一、路由定义法 -->
<!-- App.vue -->
<router-link :to=" '.user' + userID" replace>xxxxx</router-llink>
<!-- 路由定义 -->
{
path: '/user/:userID'
component:user
}
<!-- 1. -->
<router-link :to="name:'/user', param:{uname:user}"></router-link>
<!-- 2. -->
this.$router.push({name:'/user', param:{uname:userID}})
<!-- 3. -->
this.$router.push('/user' + userID)
<!-- 获取参数的方法 -->
this.$router.userID
9、async 和 catch 可以捕获异常
async function fn() {
try {
let a = await Promise.reject("异常");
} catch (error) {
consolo.log(error);
}
// }
fn() //异常
10、原型和原型链
console.log(Object.__proto__ === Function.prototype); //true
console.log(Function.__proto__.__proto__ === Object.prototype); //true
console.log(Function.prototype.__proto__ === Object.prototype); //true
console.log(Function.__proto__ === Function.prototype); //true
console.log(Object.prototype.__proto__); //null 原型链的尽头
// 原型链继承
function Person() {
this.name = "Zaxlct"; //Person.prototype.name = "Zaxlct";
}
// 一样的写法
Person.prototype.sayName = function () {
alert(this.name);
};
let person1 = new Person();
let person2 = new Person();
person1.name = 666;
console.log(person1.name); //666
console.log(person2.name); //Zaxlct
// 原型继承:将子类的原型对象指向父类的实例
// 优点:继承了父类的模板和父类的原型对象
// 缺点:无法实现多继承,无法传参。
function Parent() {
this.name = "小三";
}
function Child() {
this.age = 21;
}
Child.prototype = new Parent();
let obj1 = new Child();
let obj2 = new Parent();
obj1.name = "小舞";
console.log(obj1); // Child {age: 21, name: '小舞'}
console.log(obj2); // Parent {name: '小三'}
// 构造函数继承:在子类构造函数内用 apply、call 来改变父类构造函数的 this 指向。
// 优点:可以实现多继承,可以继承了父类的实例的属性和方法,也可以传参。
// 缺点: 不能继承父类的原型属性和方法。
function Parent(name, age) {
this.name = name;
this.age = age;
this.aaa = 666;
}
function Child(name, age) {
Parent.call(this, name, age);
}
let obj = new Child("小三", 21);
console.log(obj);
console.log(obj.name, obj.age, obj.aaa); // 小三 21 666
// 组合继承:将原型链继承和构造函数函数继承组合在一起。
// 原型链继承是为了保证子类能够继承父类的原型属性和方法
// 构造函数继承是为了保证子类能够继承父类的实例实行和方法
function Parent(name) {
this.name = name;
this.color = [1, 2, 3];
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
var child1 = new Child("小三", 21);
var child2 = new Child("小舞", 22);
child1.color.push(4);
console.log(child1); // Child {name: '小三', color: Array(4), age: 21}
console.log(child2); // Child {name: '小三', color: Array(3), age: 21}
// 寄生组合继承
// 使用超类型的原型副本作为子类型的原型,避免了创建过多的属性
// 手写 寄生组合继承
function Parent(name) {
this.name = name
this.say = function () {
console.log('say');
}
}
Parent.prototype.play = function () {
console.log('play');
}
function Children(name) {
Parent.call(this)
this.name = name
}
Children.prototype = Object.create(Parent.prototype)
Children.prototype.constructor = Children
let child = new Children('小王')
console.log(child.name); // 小王
child.say() // say
child.play() // palay
11、手写 new
// 手写 new
function mynew(fn, ...args) {
let obj = Object.create(fn.prototype)
let res = fn.call(obj, ...args)
if (res && (typeof res === 'object' || typeof res === 'function')) {
return res
}
return obj
}
// 使用
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.play = function () {
console.log(this.age);
}
let p1 = mynew(Person, '小王', 21)
console.log(p1); // {name: "小王", age: 21}
console.log(p1.name); //小王
p1.play() // 21
12、实现一个简单的 Promise
// 实现一个简单的 Promise
let promise = new Promise((resolve, reject) => {
resolve('小三')
})
promise.then(res => {
console.log(res); // 小三
})
13、手写 柯里化函数
手写 柯里化函数
function add() {
const args = [...arguments]
function fn() {
args.push(...arguments)
return fn
}
fn.toString = function () {
return args.reduce((sum, i) => {
return sum + i
})
}
return fn
}
console.log(add(1)(2)(3)(4)) // 10
14、手写 Call
call 函数的实现步骤:
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 处理传入的参数,截取第一个参数后的所有参数。
- 将函数作为上下文对象的一个属性。
- 使用上下文对象来调用这个方法,并保存返回结果。
- 删除刚才新增的属性。
- 返回结果。
// call函数实现
Function.prototype.myCall = function(context) {
// 判断调用对象
if (typeof this !== "function") {
console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
let result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
};
15、手写 Apply
apply 函数的实现步骤:
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 将函数作为上下文对象的一个属性。
- 判断参数值是否传入
- 使用上下文对象来调用这个方法,并保存返回结果。
- 删除刚才新增的属性
- 返回结果
// apply 函数实现
Function.prototype.myApply = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判断 context 是否存在,如果未传入则为 window
context = context || window;
// 将函数设为对象的方法
context.fn = this;
// 调用方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 将属性删除
delete context.fn;
return result;
};
function parent(name, age) {
this.name = name
this.age = age
this.args = arguments
}
let p1 = new parent('小三', 21)
// console.log(p1);
let arr = p1.args
console.log(arr);
console.log(arr[1]); // 21
console.log([...arr].slice(1)); // [21]
寄生组合继承
// 手写 寄生组合继承
function parent(name) {
this.name = name
this.say = function () {
console.log(this.name);
}
}
parent.prototype.play = function () {
console.log(this.name);
}
function Children(name) {
parent.call(this)
this.name = name
}
Children.prototype = Object.create(parent.prototype)
Children.prototype.constructor = Children
let child = new Children('小三')
console.log(child.name); // 小三
child.say() //小三
child.play() //小三
16、 class 继承
// class 继承
class Parent {
constructor(name, age) {
this.name = name
this.age = age
}
say() {
console.log(this.name);
}
}
let p1 = new Parent('小三', 21)
console.log(p1) // Parent {name: "小三", age: 21}
class Children extends Parent {
constructor(name, age, sex) {
super(name, age)
this.sex = sex
}
play() {
super.say()
}
}
let p2 = new Children('小王', 22, '男')
console.log(p2) // Children {name: "小王", age: 22, sex: "男"}
p2.play() // 小王
手写 bind
//bind实现要复杂一点 因为他考虑的情况比较多 还要涉及到参数合并(类似函数柯里化)
Function.prototype.myBind = function (context, ...args) {
if (!context || context === null) {
context = window;
}
// 创造唯一的key值 作为我们构造的context内部方法名
let fn = Symbol();
context[fn] = this;
let _this = this;
// bind情况要复杂一点
const result = function (...innerArgs) {
// 第一种情况 :若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符使用,则不绑定传入的 this,而是将 this 指向实例化出来的对象
// 此时由于new操作符作用 this指向result实例对象 而result又继承自传入的_this 根据原型链知识可得出以下结论
// this.__proto__ === result.prototype //this instanceof result =>true
// this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
if (this instanceof _this === true) {
// 此时this指向指向result的实例 这时候不需要改变this指向
this[fn] = _this;
this[fn](...[...args, ...innerArgs]); //这里使用es6的方法让bind支持参数合并
delete this[fn];
} else {
// 如果只是作为普通函数调用 那就很简单了 直接改变this指向为传入的context
context[fn](...[...args, ...innerArgs]);
delete context[fn];
}
};
// 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法
// 实现继承的方式: 使用Object.create
result.prototype = Object.create(this.prototype);
return result;
};
//用法如下
// function Person(name, age) {
// console.log(name); //'我是参数传进来的name'
// console.log(age); //'我是参数传进来的age'
// console.log(this); //构造函数this指向实例对象
// }
// // 构造函数原型的方法
// Person.prototype.say = function() {
// console.log(123);
// }
// let obj = {
// objName: '我是obj传进来的name',
// objAge: '我是obj传进来的age'
// }
// // 普通函数
// function normalFun(name, age) {
// console.log(name); //'我是参数传进来的name'
// console.log(age); //'我是参数传进来的age'
// console.log(this); //普通函数this指向绑定bind的第一个参数 也就是例子中的obj
// console.log(this.objName); //'我是obj传进来的name'
// console.log(this.objAge); //'我是obj传进来的age'
// }
// 先测试作为构造函数调用
// let bindFun = Person.myBind(obj, '我是参数传进来的name')
// let a = new bindFun('我是参数传进来的age')
// a.say() //123
// 再测试作为普通函数调用
// let bindFun = normalFun.myBind(obj, '我是参数传进来的name')
// bindFun('我是参数传进来的age')
手写-发布订阅模式(字节)
class EventEmitter {
constructor() {
this.events = {};
}
// 实现订阅
on(type, callBack) {
if (!this.events[type]) {
this.events[type] = [callBack];
} else {
this.events[type].push(callBack);
}
}
// 删除订阅
off(type, callBack) {
if (!this.events[type]) return;
this.events[type] = this.events[type].filter((item) => {
return item !== callBack;
});
}
// 只执行一次订阅事件
once(type, callBack) {
function fn() {
callBack();
this.off(type, fn);
}
this.on(type, fn);
}
// 触发事件
emit(type, ...rest) {
this.events[type] &&
this.events[type].forEach((fn) => fn.apply(this, rest));
}
}
// 使用如下
// const event = new EventEmitter();
// const handle = (...rest) => {
// console.log(rest);
// };
// event.on("click", handle);
// event.emit("click", 1, 2, 3, 4);
// event.off("click", handle);
// event.emit("click", 1, 2);
// event.once("dbClick", () => {
// console.log(123456);
// });
// event.emit("dbClick");
// event.emit("dbClick");