Polyfill 实现
new
- 实现步骤
- 首先创建了一个新的空对象
- 设置原型,将对象的原型设置为函数的 prototype 对象。
- 让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
- 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
function myNew(fn, ...args) {
//创建一个空对象并将对象的原型设置为函数的prototype对象
const obj = {};
obj.__proto__ = fn.prototype;
//以上两步可合成一步
//const obj = Object.create(fn.prototype)
//调用构造函数,并且this绑定到obj上
const value = fn.apply(obj, args);
// 如果构造函数有返回值,并且返回的是对象,就返回value ;否则返回obj
return value instanceof Object ? value : obj;
}
instanceof
- 实现步骤
- 获取类型的原型
- 获得对象的原型
- 一直循环判断对象的原型是否等于类型的原型,直到对象原型为
null
,因为原型链最终为null
//判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left), // 获取对象的原型
prototype = right.prototype; // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
typeOf
/**
* @description typeof 可以正确识别:Undefined、Boolean、Number、String、Symbol、Function 等类型的数据,
* 但是对于其他的都会认为是 object,比如 Null、Date 等,
* 所以通过 typeof 来判断数据类型会不准确。但是可以使用 Object.prototype.toString 实现。
*/
function typeOf(obj) {
let type = Object.prototype.toString.call(obj);
return type.slice(8, -1).toLowerCase();
}
console.log(typeOf(new Date()), typeOf(null), typeOf([]));
call、apply、bind
this 指向第一个参数
- call
- 传参方式:多个参数
- 立即执行
- apply
- 传参方式:第二个参数为数组或类数组,用数组接收多个参数
- 立即执行
- bind
- 等待执行
// bind 函数实现
Function.prototype.myBind = function (context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
};
// call函数实现
Function.prototype.myCall = function (context) {
// 判断调用对象
if (typeof this !== "function") {
console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
};
// 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;
// 调用方法
result = arguments[1] ? context.fn(...arguments[1]) : context.fn();
// 将属性删除
delete context.fn;
return result;
};
继承实现方式
- 原型链继承: 让子类构造函数的 prototype 属性指向其父类构造函数的实例
Dog.prototype = new Animal()
- 原理:让子类构造函数的 prototype 属性指向其父类构造函数的实例
- 缺点:
- 引用类型的属性被所有实例共享
- 在创建子类实例时,无法向父类构造函数传参
- 构造函数继承
Animal.call(this)
- 原理:在子类构造函数中调用父类构造函数
- 优点:解决了原型链继承中子类实例共享父类引用类型属性的问题
- 缺点:无法实现父类原型上的属性和方法的复用
- 组合式继承
- 原理:结合了原型链继承和构造函数继承的优点
- 缺点:调用了两次父类构造函数,生成了两份实例
- 寄生组合式继承
- 原理:在组合式继承的基础上,通过 Object.create() 创建一个中间对象,避免了调用两次父类构造函数
- class、extends 继承
- class、super 继承
/**
* @description 继承的五种方式
* 1、原型链继承
* 2、借用构造函数实现继承
* 3、组合继承
* 4、寄生式组合继承
* 5、class实现继承
*/
function Animal() {
this.type = "animal";
}
function Dog() {
this.name = "dog";
}
//1、原型链 - 实例化一个新的函数,子类的原型指向了父类的实例
Dog.prototype = new Animal();
//2、借用构造函数实现继承
//基本思想就是利用 call 或者 apply 把父类中通过 this 指定的属性和方法复制(借用)到子类创建的实例中。
function Dog() {
//3、组合继承 - 1、2的组合
function Cat(name) {
Animal.call(this);
this.name = name || "cat";
}
Cat.prototype = new Animal();
//Cat.prototype.constructor = Cat;
//4、寄生式组合继承
function inheritPrototype(SubType, SuperType) {
var prototype = Object.create(SuperType.prototype);
prototype.constructor = SubType;
SubType.prototype = prototype;
}
//兼容写法
function object(o) {
function W() {}
W.prototype = o;
return new W();
}
function inheritPrototype(SubType, SuperType) {
var prototype;
if (typeof Object.create === "function") {
prototype = Object.create(SuperType.prototype);
} else {
prototype = object(SuperType.prototype);
}
prototype.constructor = SubType;
SubType.prototype = prototype;
}
//5、es6继承
class Animal {
constructor(name) {
this.name = name;
}
eat() {}
}
class Dog extends Animal {
constructor(name) {
//继承父类属性
super(name);
}
eat() {
//继承父类方法
super.eat();
}
}
节流、防抖
频繁去触发一个事件, 不同的处理方式
操作 | 描述 | 场景 |
---|---|---|
防抖 | 只触发最后一次。以最后一次为准 | 1、input 框变化 2、频繁点击按钮提交表单 |
节流 | 只能每隔一段时间触发一次 | 滚动频繁请求列表 |
/**
* debounce函数实现防抖
* @param {Function} fn - 需要执行的函数
* @param {Number} wait - 等待时间
* @param {Boolean} immediate - 是否立即执行
* @returns {Function} - 返回一个函数
*/
function debounce(fn, wait, immediate) {
let timer = null; // 定义一个计时器
return function (...args) { // 返回一个函数
let context = this; // 保存this指向
if (immediate && !timer) { // 如果是立即执行且计时器不存在
fn.apply(context, args); // 立即执行函数
}
if (timer) clearTimeout(timer); // 如果计时器存在,清除计时器
timer = setTimeout(() => { // 设置计时器
fn.apply(context, args); // 执行函数
}, wait);
};
}
/**
* throttle函数实现节流
* @param {Function} fn - 需要执行的函数
* @param {Number} wait - 等待时间
* @returns {Function} - 返回一个函数
*/
function throttle(fn, wait) {
let timer = null; // 定义一个计时器
let previous = 0; // 上一次执行的时间
return function (...args) { // 返回一个函数
let context = this; // 保存this指向
let now = +new Date(); // 获取当前时间
let remaining = wait - (now - previous); // 计算剩余时间
if (remaining <= 0) { // 如果剩余时间小于等于0
if (timer) { // 如果计时器存在
clearTimeout(timer); // 清除计时器
timer = null; // 重置计时器
}
previous = now; // 更新上一次执行的时间
fn.apply(context, args); // 执行函数
} else if (!timer) { // 如果剩余时间大于0且计时器不存在
timer = setTimeout(() => { // 设置计时器
previous = +new Date(); // 更新上一次执行的时间
timer = null; // 重置计时器
fn.apply(context, args); // 执行函数
}, remaining);
}
};
}
/**
* 简写节流函数
* @param {Function} fn - 需要执行的函数
* @param {Number} wait - 等待时间
* @returns {Function} - 返回一个函数
*/
function throttle(fn, wait) {
let timer = null; // 定义一个计时器
return function (...args) { // 返回一个函数
let context = this; // 保存this指向
if (!timer) { // 如果计时器不存在
timer = setTimeout(() => { // 设置计时器
timer = null; // 重置计时器
fn.apply(context, args); // 执行函数
}, wait);
}
};
}
高阶函数(curry、compose)
一个函数接收另一个函数作为参数,这种函数就称之为高阶函数。
像数组的 map
、reduce
、filter
这些都是高阶函数
函数柯里化
函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
好处:
- 参数复用
- 延迟执行
function curry(fn, ...args) {
return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
compose
组合函数将函数串联起来执行,前一个函数的输出值是后一个函数的输入值
简单的 compose 函数
const compose = (a, b) => (c) => a(b(c));
其他:递归实现、reduce 实现
function compose(...fns) {
let len = fns.length;
let res = null;
return function fn(...arg) {
res = fns[len - 1].apply(null, arg); // 每次函数运行的结果
if (len > 1) {
len--;
return fn.call(null, res); // 将结果递归传给下一个函数
} else {
return res; //返回结果
}
};
}
function compose(...fns) {
return function (...arg) {
return fns.reduce((acc, cur) => {
// 第一次acc是函数,之后是结果,所以要加个判断
return typeof acc === "function" ? cur(acc(...arg)) : cur(acc);
});
};
}
数组去重
/**
* @description 数组去重
* es5 fillter实现
* es6 扩展运算符+Set实现
*/
// ES5 实现
function unique(arr) {
return arr.filter(function (item, index, array) {
return array.indexOf(item) === index;
});
}
// es6 实现
let uniqueES6 = (arr) => [...new Set(arr)];
let arr = [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1];
let arr1 = unique(arr);
let arr2 = uniqueES6(arr);
console.log(arr1, arr2);
数组随机打乱
//随机打乱
Array.prototype.shuffle = function () {
for (
var j, x, i = this.length;
i;
j = parseInt(Math.random() * i),
x = this[--i],
this[i] = this[j],
this[j] = x
);
数组扁平化
// 数组扁平化
//递归
let flatArr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
flatten(flatArr); // [1, 2, 3, 4,5]
//flat -reduce
function _flat(arr, depth) {
if (!Array.isArray(arr) || depth <= 0) {
return arr;
}
return arr.reduce((prev, cur) => {
if (Array.isArray(cur)) {
return prev.concat(_flat(cur, depth - 1));
} else {
return prev.concat(cur);
}
}, []);
}
//es6 flat函数
flatArr.flat(Infinity); //如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。