【JS】手撕JS(可能持续更新···)
关于实现js中一些常见的方法属于面试中的常问问题,可能刚开始接触的时候会一筹莫展。知道和理解其中的原理能够在日常开发中更如鱼得水,面对面试也不成问题。另外,学会以目的(实现的功能)为导向一层一层反推,总结出实现的思路就能按照步骤直接实现或者曲线实现(整理不易记得点赞
哈)。
一、call的实现
call()
方法:让call()中的对象调用当前对象所拥有的function。例如:test.call(obj,arg1,arg2,···) 等价于 obj.test(arg1,arg2,···)
;在手写实现call()
方法前我们先进行分析,test
调用call
方法可以看作将test
方法作为obj
的一个属性(方法)调用,等obj.test()
执行完毕后,再从obj
属性上删除test
方法:
- 1、将函数设置为对象的属性;
- 2、处理传入的参数;
- 3、执行对象上设置的函数;
- 4、删除对象上第一步设置的函数;
myCall:
function test(a, b) {console.log(a);
console.log(b);
console.log(this.c);
}
let obj = {
c: "hello",
};
//myCall
Function.prototype.myCall = function () {
//声明传入上下文为传入的第一个参数,如果没有传参默认为global(node环境),如果是浏览器环境则为 window;
let context = arguments[0] || global;
//将调用myCall方法函数(this)设置为 声明的传入上下文中的fn函数;
context.fn = this;
//对函数参数进行处理
var args = [];
for (let index = 0; index < arguments.length; index++) {
index > 0 && args.push(arguments[index]);
}
//执行fn,也就是调用myCall方法的函数
context.fn(...args);
//执行完毕后删除传入上下文的fn,不改变原来的对象
delete context.fn;
};
test.myCall(obj, "a", 123);
console.log(obj)
打印的结果:
a123
hello
{ c: 'hello' }
从结果可以看出:test
中this.c
输出为hello
,说明this
为obj
;最后输出的obj
也没有改变。
二、apply的实现
apply()
方法作用和call()
完全一样,只是apply
的参数第一个为需要指向的对象,第二个参数以数组形式传入。例如:test.apply(obj,[arg1,arg2,···]) 等价于 obj.test(arg1,arg2,···)
;
myApply:
//myApplyFunction.prototype.myApply = function(){
let context = arguments[0] || global;
context.fn = this;
var args = arguments.length > 1 ? arguments[1] : [];
context.fn(...args);
delete context.fn;
}
test.myApply(obj, ["world", 123]);
console.log(obj)
打印的结果:
world123
hello
{ c: 'hello' }
三、bind的实现
bind
方法:创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。例如:let fn = test.bind(obj,arg1,arg2,···); fn() 等价于 let fn = obj.test; fn(arg1,arg2,···)
;实现思路为:
- 1、将函数设置为对象的属性;
- 2、处理传入的参数;
- 3、返回函数的定义/引用;
- 4、外层执行接收的函数;
- 5、删除对象上第一步设置的函数;
myBind:
Function.prototype.myBind = function(){//1.1、声明传入上下文为传入的第一个参数,如果没有传参默认为global(node环境),如果是浏览器环境则为 window;
let context = arguments[0] || global;
//1.2、将调用myBind方法函数(this)设置为 声明的传入上下文中的fn函数;
context.fn = this;
//2.1、对调用myBind的函数参数进行处理
var args = [];
for (let index = 0; index < arguments.length; index++) {
index > 0 && args.push(arguments[index]);
}
//3、声明和定义函数变量F,用于返回给外层
let F = function (){
//2.2、对再次传入的参数进行处理,追加到
for (let index = 0; index < arguments.length; index++) {
args.push(arguments[index]);
}
//4.2、执行实际的调用myBind方法函数
context.fn(...args);
//5、执行完毕后删除传入上下文的fn,不改变原来的对象
delete context.fn;
}
return F;
}
var f = test.myBind(obj, "a")
//4.1、执行返回的函数
f(9527);
打印的结果:
a9527
hello
{ c: 'hello' }
四、Promise的实现
1、分析Promise使用
MDN中关Promise
的定义如下:
new Promise((resolve, reject) => {//异步操作
//···
//执行完后调用resolve和reject输出两种不同结果
if (true) {
resolve("res");
} else {
reject("err");
}
})
.then((res) => { //then接受resolve中的结果
console.log(res);
})
.catch((err) => { //catch接受reject中的结果
console.log(err);
});
Promise的使用分为三步:
- 1、新建Promise实例,即通过new实现,同时接受一个函数参数,函数参数中接受resolve和reject两个形参(实质上也是函数);
- 2、新建的Promise实例接受的函数参数中就是要执行的异步代码,并且用resolve和reject对异步结果进行调用输出;
- 3、新建的Promise实例可以调用then和catch方法对异步结果进行接受和处理;
上述新建实例代码可以转化为:
function fn(resolve, reject) {//异步操作
//···
//执行完后调用resolve和reject输出两种不同结果
if (true) {
resolve("res");
} else {
reject("err");
}
}
let p = new Promise(fn);
p.then((res) => { //then接受resolve中的结果
console.log(res);
})
p.catch((err) => { //catch接受reject中的结果
console.log(err);
});
上述中的使用者就是then和catch
,结合代码中的使用方式,简单来说就是Promise中执行异步操作,then和catch只会在异步执行完后才会接到返回结果继续执行!
2、手撕Promise
了解了Promise的定义和使用步骤后,接下来直接手撕Promise的实现,直接上实现Promise的代码(内涵大量注释,基本一句一解释,但是逻辑还是得第三部分来讲
):
// 定义promise中的三种状态const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";
// 定义promise的类
class myPromise {
//class的构造函数,接受新建实例时的参数:executor在promise中是一个函数
constructor(executor) {
//初始化该class中的初始状态
this.status = STATUS_PENDING;
//定义class中成功(res)和失败(err)时的变量值
this.res = "";
this.err = "";
//promis异步中最重要的异步,定义成功和错误函数存储的数组,存放异步时还没有执行的操作
this.onResCallbacks = [];
this.onErrCallbacks = [];
//定义该构造函数constructor定义域中的变量resolve
let resolve = (res) => {
// 首先判断该class中的状态,只有状态为pending时才能转化class转态为fulfilled或者rejected
if (this.status === STATUS_PENDING) {
//修改class的转态为fulfilled,也就表示不会转进行其他转态的转化了
this.status = STATUS_FULFILLED;
//将成功(resolve)状态下的值赋给class的成功返回res
this.res = res;
//此时状态由pending转为fulfilled,执行之前在then中存放的需要执行的异步操作,promise的then中参数res接受结果
this.onResCallbacks.forEach((fn) => {
fn();
});
}
};
//定义该构造函数constructor定义域中的变量reject
let reject = (err) => {
// 首先判断该class中的状态,只有状态为pending时才能转化class转态为fulfilled或者rejected
if (this.status === STATUS_PENDING) {
//修改class的转态为rejected,也就表示不会转进行其他转态的转化了
this.status = STATUS_REJECTED;
//将失败(reject)状态下的值赋给class的失败返回err
this.err = err;
//此时状态由pending转为rejected,执行之前在catch中存放的需要执行的异步操作,promise的catch中参数err接受结果
this.onErrCallbacks.forEach((fn) => {
fn();
});
}
};
//按照promise中的逻辑,在调用时就立即执行了,所以在手写的myPromise创建构造函数constructor时就执行executor
try {
//执行参入的函数,并将上述定义的resolve和reject作为参数传入
executor(resolve, reject);
} catch (err) {
//报错时调用失败的状态函数
reject(err);
}
}
//在class中定义promise的成功状态接收函数then,按照promise逻辑,then中传入的一般都是一个函数
then(onRes = () => {}) {
//如果是异步的,此时在constructor中status的状态还没变成fulfilled,所以会跳过onRes调用,没有返回
if (this.status === STATUS_FULFILLED) {
onRes(this.res);
}
//但是我们将此时的异步放入数组存放
if (this.status === STATUS_PENDING) {
this.onResCallbacks.push(() => onRes(this.res));
}
//这步操作保证了then和catch能够在同级一起"."调起,当then上述操作完后,返回class实例,便可以接在后面继续调用catch
return this;
}
//在class中定义promise的失败状态接收函数catch,按照promise逻辑,catch中传入的一般都是一个函数
catch(onErr = () => {}) {
//如果是异步的,此时在constructor中status的状态还没变成rejected,所以会跳过onErr调用,没有返回
if (this.status === STATUS_REJECTED) {
onErr(this.err);
}
//但是我们将此时的异步放入数组存放
if (this.status === STATUS_PENDING) {
this.onErrCallbacks.push(() => onErr(this.err));
}
//这步操作保证了then和catch能够在同级一起"."调起,当catch上述操作完后,返回class实例,便可以接在后面继续调用then
return this;
}
}
//调用自己手写的promise
new myPromise((resolve, reject) => {
console.log("进入了手写的promise");
//用setTimeOut模拟异步操作
setTimeout(() => {
if (false) {
resolve("输出成功结果resolve");
} else {
reject("输出失败结果reject");
}
}, 2000); //按照js的特性,此时不会等待异步完成,直接调用then或者catch
})
.then((res) => {
console.log("then:", res);
})
.catch((err) => { //return this具体作用体现在这里
console.log("catch:", err);
});
以上是 【JS】手撕JS(可能持续更新···) 的全部内容, 来源链接: utcz.com/a/100336.html