这两天学习了手写call、apply、bind,手写bind思考了很久才实现了MDN的示例的结果,所以记录下来~
因为是第一篇文章,所以可能存在一些错误,希望各位大佬批评指正,不吝赐教。
也欢迎不懂的同学留言评论一起交流~~~
----------------------------------------------------------------------------
目录
解析①:普通函数的调用 + 柯里化的实现
解析②: 通过new操作符调用 + 柯里化的实现
示例①:普通函数的调用 + 柯里化的实现
示例②:通过new操作符调用 + 柯里化的实现
bind与call和apply方法不同,bind返回的是创建的新函数,而不是像call、apply那样立即执行调用它的函数。
基于对bind返回的新函数不同的使用方法,我们在手写bind的时候从实现以下两个方面来入手:
① 普通函数的调用 + 柯里化的实现
② 通过new操作符调用 + 柯里化的实现
对这两个使用方法不熟悉的同学可以先看下后面的示例。
bind的实现会用到call/apply的方法,如果对这两个函数的原理不太懂的同学可以先看一下手写call和apply~【手写call和apply】详解手写call和apply函数JavaScript_Jennifer0204的博客-CSDN博客
Function.prototype.bind2 = function(asThis,...args) {//这里的this就是调用bind2的函数(普通函数this指向调用者)if (typeof this !== "function") {throw new TypeError("Not a Function");}// 这里保存调用bind的函数以及传给bind后面的参数。var origin = this;let args0 = args;function bound(...args) {if(this instanceof bound) //如果是new的形式来使用绑定函数的return new origin( ...args0,...args)else //如果是普通函数的形式来使用绑定函数的(基本上与call、apply无区别,实现柯里化即可)return origin.call(asThis, ...args0,...args);}bound.prototype = origin.prototype;return bound;}
解析①:普通函数的调用 + 柯里化的实现
如果是正常的普通函数的调用方法来调用bind返回的函数,那么直接返回像调用call函数那样返回,第一个参数值就是传入bind的asThis值。注意要将前后传入的参数(两次...arg收集到的)同时收集到call函数的参数中,从而实现柯里化。
解析②: 通过new操作符调用 + 柯里化的实现
如果通过new来调用bind生成的函数(绑定函数)情况又有所不同了
如何判断时通过new来调用的呢?我们知道,通过new 某个构造函数时,构造函数里的this会指向一个新创建的对象,然后执行构造函数中的代码,这个对象的[[prototype]]会指向构造函数的prototype属性,那么此时只需要在绑定函数(bind的返回函数——bound,此时也是作为一个构造函数来使用的)内部判断this是否指向bound的原型即可。
if(this instanceof bound) return new origin( ...args0,...args)
//判断条件也可写bound.prototype.isPrototypeOf(this)
所以在通过new调用绑定函数时, 则应忽略asThis值,相当于直接new一个Origin的实例对象即可,当然参数也应实现柯里化。
还有一点需要注意的是,new调用绑定函数时,绑定函数的prototype属性存在与返回的实例对象的原型链上,将bound的prototype指向origin的prototype属性即可。
bound.prototype = origin.prototype;
以下是代码测试,部分参考了MDN的示例。Function.prototype.bind() - JavaScript | MDN
示例①:普通函数的调用 + 柯里化的实现
//代码测试
//----------普通函数的调用
let classRoom107 = {desk:100,chair:97
}function course(teacherName,studentNumber) {console.log(`This course is taught by ${teacherName},today there are ${studentNumber} students in this class.`);
}//教室和老师的名字都固定了,只是每天上课的人数不同。
//这里'预设的初始参数' 就是'pink'
let course_css = course.bind2(classRoom107,'pink');
let Monday = course_css(9999);
// This course is taught by pink,today there are 9999 students in this class.//只固定了教室,老师的名字和上课的人数不同。
let course_happy = course.bind2(classRoom107);
let Wed = course_happy('马冬梅',9);
// This course is taught by 马冬梅,today there are 9 students in this class.
可以通过这个示例理解一下bind的含义,传入bind一个或多个参数时,绑定函数相当于有了固定的一个或多个参数,比方说course_css就是固定了classRoom和老师,只能指定每天上课的人数。course_happy只指定了上课教室,而上课老师和上课人数都是待定的。
示例②:通过new操作符调用 + 柯里化的实现
function Positon(x,y){this.x = x;this.y = y;
}
Positon.prototype.toAxis = function() {return `(${this.x},${this.y})`;
}let a1 = new Positon(1,3);
console.log(a1.toAxis());//(1,3)let emptyObj = {};
let X_5_Line = Positon.bind2(emptyObj,5);
let a2 = new X_5_Line(8);
console.log(X_5_Line instanceof Positon);//false
console.log(X_5_Line.constructor.name);//"Function"
console.log(a2 instanceof Positon);//true
console.log(a2 instanceof X_5_Line);//true
console.log(a2.constructor.name);//Positon
console.log(a2.toAxis());//(5,8)//也可以作为普通函数调用,但是最好不要这样做。
X_5_Line(8);
console.log(emptyObj);//{ x: 5, y: 8 }
实例②可以参考下面这张图来看,我认为需要关注的几点:
1. 表面上看a2是X_5_Line new出来的,但实际上这个bound构造函数自己返回了非空对象,所以a2的constructor不是X_5_Line而是Position;
2. 不用new操作符调用X_5_Line时,相当于调用origin.call(asThis, ...args0,...args),也就是Position.call(emptyObj, 5,8),执行构造函数的代码this.x = 5 , this.y = 8,所以最后empty不为空。