前言
大家好,我是梁木由,是一个曾经躺平了两年的废柴,目前在当初这家公司曾经工作了近两年半了。没加薪,没升职,没走的起因是因为工作气氛很难受,制度很宽松。这不2023来了,我及时觉悟,要有指标,目前就先为年后跳槽,做些筹备,先温习回顾下一些手写代码,
目录
- 实现new函数
- 实现instanceof
- 实现call函数
- 实现apply函数
- 实现bind函数
- 实现debounce防抖函数
- 实现throttle节流函数
- 实现数组对象去重
- 实现数组扁平化
- 实现深拷贝
1.实现new函数
首先须要理解new做了什么事件:
- 首先创立了一个空对象。
- 将空对象
proto
指向构造函数的原型prototype
。 - 使
this
指向新创建的对象,并执行构造函数。 - 执行后果有返回值并且是一个对象, 返回执行的后果, 否则返回新创建的对象。
// 代码实现
function mu_new(fn,...arg){
// 首先创立空对象
const obj = {};
// 将空对象的原型proto指向构造函数的原型prototype
Object.setPrototypeOf(obj, fn.prototype)
// 将this指向新创建的对象,并且执行构造函数
const result = fn.apply(obj,arg);
// 执行后果有返回值并且是一个对象,返回执行的后果,否侧返回新创建的对象
return result instanceof Object ? result : obj;
}
// 验证mu_new函数
function Dog(name){
this.name = name;
this.say = function(){
console.log('my name is' + this.name);
}
}
const dog = mu_new(Dog, "傻🐶");
dog.say() //my name is傻🐶
2.实现instanceof
优缺点:
- 「长处」:可能辨别Array、Object和Function,适宜用于判断自定义的类实例对象
- 「毛病」:Number,Boolean,String根本数据类型不能判断
实现步骤:
- 传入参数为左侧的实例L,和右侧的构造函数R
- 解决边界,如果要检测对象为根本类型则返回false
- 别离取传入参数的原型
- 判断左侧的原型是否取到了null,如果是null返回false;如果两侧原型相等,返回true,否则持续取左侧原型的原型。
// 传入参数左侧为实例L, 右侧为构造函数R
function mu_instanceof(L,R){
// 解决边界:检测实例类型是否为原始类型
const baseTypes = ['string','number','boolean','symbol','undefined'];
if(baseTypes.includes(typeof L) || L === null) return false;
// 别离取传入参数的原型
let Lp = L.__proto__;
let Rp = R.prototype; // 函数才领有prototype属性
// 判断原型
while(true){
if(Lp === null) return false;
if(Lp === Rp) return true;
Lp = Lp.__proto__;
}
}
// 验证
const isArray = mu_instanceof([],Array);
console.log(isArray); //true
const isDate = mu_instanceof('2023-01-09',Date);
console.log(isDate); // false
3.实现call函数
实现步骤:
-
解决边界:
- 对象不存在,this指向window;
- 将「调用函数」挂载到「this指向的对象」的fn属性上。
- 执行「this指向的对象」上的fn函数,并传入参数,返回后果。
Function.prototype.mu_call = function (context, ...args) {
//obj不存在指向window
if (!context || context === null) {
context = window;
}
// 发明惟一的key值 作为咱们结构的context外部办法名
let fn = Symbol();
//this指向调用call的函数
context[fn] = this;
// 执行函数并返回后果 相当于把本身作为传入的context的办法进行调用了
return context[fn](...args);
};
// 测试
var value = 2;
var obj1 = {
value: 1,
};
function bar(name, age) {
var myObj = {
name: name,
age: age,
value: this.value,
};
console.log(this.value, myObj);
}
bar.mu_call(null); //打印 2 {name: undefined, age: undefined, value: 2}
bar.mu_call(obj1, 'tom', '110'); // 打印 1 {name: "tom", age: "110", value: 1}
4.实现apply函数
实现步骤:
- 与call统一
- 区别于参数的模式
Function.prototype.mu_apply = function (context, args) {
//obj不存在指向window
if (!context || context === null) {
context = Window;
}
// 发明惟一的key值 作为咱们结构的context外部办法名
let fn = Symbol();
//this指向调用call的函数
context[fn] = this;
// 执行函数并返回后果 相当于把本身作为传入的context的办法进行调用了
return context[fn](...args);
};
// 测试
var value = 2;
var obj1 = {
value: 1,
};
function bar(name, age) {
var myObj = {
name: name,
age: age,
value: this.value,
};
console.log(this.value, myObj);
}
bar.mu_apply(obj1, ["tom", "110"]); // 打印 1 {name: "tom", age: "110", value: 1}
5.实现bind函数
Function.prototype.mu_bind = 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);
};
// 一般函数
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 obj = {
objName: '我是obj传进来的name',
objAge: '我是obj传进来的age',
};
// 先测试作为结构函数调用
// let bindFun = Person.mu_bind(obj, '我是参数传进来的name');
// let a = new bindFun('我是参数传进来的age');
// a.say(); //123
// 再测试作为一般函数调用a;
let bindFun = normalFun.mu_bind(obj, '我是参数传进来的name');
bindFun('我是参数传进来的age');
6.实现debounce防抖函数
函数防抖是在事件被触发n秒后再执行回调,如果在「n秒内又被触发」,则「从新计时」
function debounce(fn, wait) {
let timer = null;
return function () {
if (timer != null) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn();
}, wait);
};
}
// 测试
function handle() {
console.log(Math.random());
}
// 窗口大小扭转,触发防抖,执行handle
window.addEventListener('resize', debounce(handle, 1000));
7.实现throttle节流函数
当事件触发时,保障肯定时间段内只调用一次函数。例如页面滚动的时候,每隔一段时间发一次申请
实现步骤:
- 传入参数为执行函数fn,等待时间wait。
- 保留初始工夫now。
- 返回一个函数,如果超过等待时间,执行函数,将now更新为以后工夫。
function throttle(fn, wait, ...args) {
var pre = Date.now();
return function () {
// 函数可能会有入参
var context = this;
var now = Date.now();
if (now - pre >= wait) {
// 将执行函数的this指向以后作用域
fn.apply(context, args);
pre = Date.now();
}
};
}
// 测试
var name = 'mu';
function handle(val) {
console.log(val + this.name);
}
// 滚动鼠标,触发防抖,执行handle
window.addEventListener('scroll', throttle(handle, 1000, '木由'));
8.实现数组对象去重
利用map的键不能反复,去掉某个属性雷同的项
function uniqBy(arr, key) {
return [...new Map(arr.map((item) => [item[key], item])).values()];
}
const list = [
{ id: 1, name: 'tom' },
{ id: 1, name: 'jey' },
{ id: 2, name: 'joy' },
];
console.log(uniqBy(list, 'id')); // [{id:1,name:"jey"},{id:2,name:"joy"}]
9.实现数组扁平化
flat
let arr = [1,2,[3,4],[5,6,[7,8,9]]]
console.log(arr.flat(Infinity))//[1, 2, 3, 4, 5, 6, 7, 8, 9]
join/ split
let arr = [1,2,[3,4],[5,6,[7,8,9]]]
console.log(arr.toString().split(",").map(Number))//[1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(arr.join().split(",").map(Number))//[1, 2, 3, 4, 5, 6, 7, 8, 9]
函数版
let arr = [1,2,[3,4],[5,6,[7,8,9]]]
function flatter(arr) {
if (!arr.length) return;
while (arr.some((item) => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
// console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));
10.实现深拷贝
对象的深拷贝,本质上就是递归办法实现深度克隆:遍历对象、数组直到里边都是根本数据类型,而后再去复制。
简略版
function deepCopy(obj){
//判断是否是简略数据类型,
if(typeof obj == "object"){
//简单数据类型
var result = obj.constructor == Array ? [] : {};
for(let i in obj){
result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
}
}else {
//简略数据类型 间接 == 赋值
var result = obj;
}
return result;
}
进阶版
function deepClone(obj, hash = new WeakMap()) {
// 如果是null或者undefined我就不进行拷贝操作
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;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是以后类自身
let cloneObj = new obj.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);
发表回复