首先,它是函数的一个方法,咱们须要将其--
Function.prototype.mybind =... //这样,全部继承自Function的函数就可以使用.操做符来访问mybind了! //PS:由于JS原型式继承
而后,让咱们先看看原生JS的bind方法有哪些行为--
让调用该方法的函数的this指向传入的第一个参数
咱们能够借助apply方法实现javascript
Function.prototype.mybind = function (context) { this.apply(context); }; let obj = { name: "Crushdada", }; let fn = function (params) { console.log(this.name); }; fn.mybind(obj); //Crushdada
注意两点:
与此同时,因为匿名函数中的this指向window/global,咱们须要使用箭头函数或者手动保存一下指向mybind中指向调用者fn的thisjava
Function.prototype.mybind = function (context) { return () => this.apply(context); }; let obj = { name: "Crushdada", }; let fn = function (params) { console.log(this.name); }; fn.mybind(obj)(); //Crushdada
我的理解:相比“容许传入参数”这种说法,形容为“传递参数”更贴切,bind方法做为一个中间方法,会代收参数后再传递给它返回的匿名绑定函数,其返回一个匿名函数这一点,自然支持柯里化(多是ES6引入它的初衷之一),由于这样就容许咱们在调用bind时传入一部分参数,在调用其绑定函数时再传入剩下的参数。而后它会在接收完第二次传参后再apply执行调用bind的那个方法segmentfault
实现柯里化的逻辑很简单,仅仅须要在mybind中接收一次参数,而后在绑定函数中接收一次参数,并将两者拼接后一块儿传给mybind的调用方法使用便可数组
下面,实现传参&柯里化!
若使用的是普通函数,要处理参数,因为arguments为类数组,slice为Array方法,故先在原型链上调用而后call一下app
使用箭头函数能极大简化代码
下面咱们改亿点点细节!
不得不说ES6引入的rest运算符、扩展运算符在处理参数这一点上提供了极大的便利函数
Function.prototype.mybind = function (context, ...args) { return (...bindArgs) => { //拼接柯里化的两次传参 let all_args = [...args, ...bindArgs]; //执行调用bind方法的那个函数 let call_fn = this.apply(context, all_args); return call_fn; }; }; let person = { name: "Crushdada", }; let getInfo = function (like, fav) { let info = `${this.name} likes ${like},but his favorite is ${fav}`; return info; }; //anonymous_bind:mybind返回的那个匿名的绑定函数 let anonymous_bind = getInfo.mybind(person, "南瓜子豆腐"); let info = anonymous_bind("皂角仁甜菜"); //执行绑定函数 console.log(info); //Crushdada likes 南瓜子豆腐,but his favorite is 皂角仁甜菜
写到支持柯里化这一步,bind方法仍是可使用箭头函数实现的,并且比普通函数更加简洁post
可是想要继续完善它的的行为,就不能用继续用Arrow Function了,由于箭头函数不能被new!,要是尝试去new它会报错:性能
anonymous_bind is not a constructor
笔者也是写到这才想起箭头函数这个机制的。那么下面咱们须要用普通函数重写mybind
不过也很简单,只须要手动保存一下this便可。就再也不贴出改动后的代码了。直接看下一步
bind的一个隐式行为:ui
且new调用时传入的参数照常被传递给调用函数。this
逻辑
实现这一步的逻辑也较为简单,咱们类比一下和通常调用new时的区别--
主要须要咱们写的逻辑有:
让getInfo函数中的this指向--new中建立的实例对象obj
判断getInfo函数是否返回一个对象,如果,则返回该对象,不然返回new生成的obj
至于为何这么写,就须要你先弄懂new关键字实现的机制了,个人笔记连接附在文末
下面,实现它!
Function.prototype.mybind= function (context, ...args) { let self = this; return function (...bindArgs) { //拼接柯里化的两次传参 let all_args = [...args, ...bindArgs]; // new.target 用来检测是不是被 new 调用 if (new.target !== undefined) { // 让调用mybind的那个函数的this指向new中建立的空对象 var result = self.apply(this, all_args); // 判断调用mybind方法的那个实际的构造函数是否返回对象,没有返回对象就返回new生成的实例对象obj return result instanceof Object ? result : this; } //若是不是 new 就原来的逻辑 //执行调用bind方法的那个函数 let call_fn = self.apply(context, all_args); return call_fn; }; }; let person = { name: "Crushdada", }; let getInfo = function (like, fav) { this.dear = "Bravetata"; let info = `${this.name} likes ${like},but his favorite is ${fav}`; return info; }; //anonymous_bind:mybind返回的那个匿名的绑定函数 let anonymous_bind = getInfo.mybind(person, "南瓜子豆腐"); let obj = new anonymous_bind("皂角仁甜菜"); //执行绑定函数 console.log(obj); //{ dear: 'Bravetata' } console.log(obj.name); //undefined
解释一下以上代码:
第一个逻辑
第二个逻辑
接下来咱们直接判断一下调用的结果,即它是否return一个对象,而后return给new作最终的return便可
此外:能够看到,当new mybind返回的绑定函数时,obj没有获取到person.name属性,为undefined。也就是说--
看个栗子,这样清楚一点
var value = 2; var foo = { value: 1 }; function bar(name, age) { this.habit = 'shopping'; console.log(this.value); console.log(name); console.log(age); } bar.prototype.friend = 'kevin'; var bindFoo = bar.bind(foo, 'daisy'); var obj = new bindFoo('18'); // undefined // daisy // 18 console.log(obj.habit); console.log(obj.friend); // shopping // kevin
尽管在全局和 foo 中都声明了 value 值,最后依然返回了 undefind,说明绑定的this 失效了,
这是为何呢?
若是你们了解 new 的模拟实现,就会知道了--
new是JS模拟面向对象的一个关键字,它的目的之一是实现继承,它要去继承构造函数(类)之中的属性,那么new关键字是怎样去实现的呢?它在内部应用了相似这样一条语句:
Con.apply(obj, args) //Con是new 的那个构造函数
new 关键字会先声明一个空对象obj,而后将构造函数的this指向这个对象
这样作会发生什么--
详见:《JS中new操做符作了什么?》--Crushdada's Notes
让咱们回到为何this会失效这一问题上
了解完new关键字的相关实现,咱们已经获得答案了--
new完绑定函数后,绑定函数内部的this 已经指向了 obj,而obj中没有value这个属性,固然就返回undefined了
实际上这一步是对绑定函数内重写new方法的一个补充--
由于new方法原本就支持原型链继承
逻辑
那么咱们只须要--
让new的实例对象obj的原型指向实际构造器getInfo的prototype便可
Object.setPrototypeOf(this, self.prototype);
规范化/严谨性
能够为mybind方法加上一个判断,调用者必须是一个函数,不然抛出TypeError--
if (typeof this !== 'function' || Object.prototype.toString.call(this) !== '[object Function]') { throw new TypeError(this + ' must be a function'); }
一个疑问?
咱们模拟实现bind方法,终归是经过apply实现的。而它源码是如何实现的,对于我来讲就像一个黑盒。也就是说:不用apply,它是如何实现?
百度的各类版本大都借助apply实现的,不过很幸运在思否找到了答案--JS bind方法如何实现?
答者给出的替代apply的方法很简单:
那么咱们只须要--
将caller做为一个对象方法挂载到context上:context.callerFn = caller
这样,当执行该句时,至关于context调用了caller函数,那么caller函数中的this天然就指向其调用者context了。
以上,就替代了apply在本例中的核心功能--调用函数同时改变this指向
此外,为提升代码性能,用完callerFn后就删掉它
context.__INTERNAL_SECRETS = func try { return context.__INTERNAL_SECRETS(...args) } finally { delete context.__INTERNAL_SECRETS }
将apply替换为以上代码,就获得最终版了
FFunction.prototype.mybind = function (context, ...args) { if ( typeof this !== "function" || Object.prototype.toString.call(this) !== "[object Function]" ) { throw new TypeError(this + " must be a function"); } let self = this; //这里的this和self即:调用mybind的方法--fn() context.caller2 = self; return function (...bindArgs) { let all_args = [...args, ...bindArgs]; //new调用时,this被换成new方法最后要返回的实例对象obj if (new.target !== undefined) { try { this.caller = self; var result = this.caller(...all_args); } finally { delete this.caller; } Object.setPrototypeOf(this, self.prototype); return result instanceof Object ? result : this; } //当不是new调用时,this指向global/window(由于匿名函数返回后由全局调用) try { var final_res = context.caller2(...all_args); } finally { delete context.caller2; } return final_res; //调用mybind的那个函数[可能]有返回 }; };
该方法可以将有length属性的对象或字符串转换为数组
所以像是arguments对象这样拥有length属性的类数组就可使用该方法转换为真正的数组
JS中,只有String和Array拥有.slice方法,对象没有。
let slice = (arrlike) => Array.prototype.slice.call(arrlike); var b = "123456"; let arr = slice(b); console.log(arr); // ["1", "2", "3", "4", "5", "6"]
返回一个新的数组对象,这一对象是一个由begin
和end
决定的原数组的浅拷贝(包括begin
,不包括end
)。
begin、end
是数组indexconst animals = ['ant', 'bison', 'camel', 'duck', 'elephant']; console.log(animals.slice(2)); // expected output: Array ["camel", "duck", "elephant"] console.log(animals.slice(2, 4)); // expected output: Array ["camel", "duck"]
a || b :
若true,返回前面的值
当一个函数拥有形参,但调用时没有传实参时,形参是undefined,会被按false处理
function name(params) { console.log(params); //undefined } name(); console.log(undefined == false); //false console.log(undefined || "undefined was reated as false"); //undefined was reated as false
会被逻辑或运算符当作false处理的总共6个--
0、null、""、false、undefined 或者 NaN
参考: