首先看call和apply,第一个参数就是改变的this指向,写谁就是谁,如果是非严格模式下,传递null或undefined指向的也是window,二者唯一的区别是执行函数时,传递的参数方式不同,call是一个个的传递,apply是把需要传递的参数放到数组中整体传递。
·func.call([context], x, y)
·func.apply([context], [x, y])
再看bind,它和call和apply都是改变this并且传递一些参数,不同于call和apply在改变this的同时直接把函数就执行了,bind不会立即执行函数。
let obj = { fn(x, y) { console.log(this, x, y); } }; obj.fn.call({}, 10, 20); // {}, 10, 20 obj.fn.apply(window, [10, 20]); //window, 10, 20 setTimeout(obj.fn.bind(30, 10, 20), 1000); //Number(30), 10, 20
先试着重写一下bind:
从参数看,首先是传递一个this指向并需要做一下处理,后续还有若干个参数
function bind(context) { //context可能是null或undefined,需要处理一下 if (context == undefined) { context = window; } //借用数组的slice方法结合arguments获取传递的指定this之后的参数集合 var args = [].slice.call(arguments, 1); }
bind函数中的this是指最终要执行的函数,而且执行bind的时候会返回一个新的匿名函数,并且在这个新的函数中执行最终要执行的函数也就是this,并且改变其this指向:
function bind(context) { //context可能是null或undefined,需要处理一下 if (context == undefined) { context = window; } //借用数组的slice方法结合arguments获取传递的指定this之后的参数集合 var args = [].slice.call(arguments, 1); //需要最终执行的函数 var _this = this; return function anonymous() { _this.apply(context, args); }; }
这个bind函数大体算是写完,但还是有些问题,比如给元素进行事件绑定,div.onclick = obj.fn.bind(window, 10, 20),元素进行点击的时候,会有ev事件对象,相当于在执行bind函数返回的那个匿名函数中也需要传递参数,而且参数个数不确定,当然,最后还需要改写一下原型上的方法:
function bind(context) { //context可能是null或undefined,需要处理一下 if (context == undefined) { context = window; } //借用数组的slice方法结合arguments获取传递的指定this之后的参数集合 var args = [].slice.call(arguments, 1); //需要最终执行的函数 var _this = this; return function anonymous() { var amArg = [].slice.call(arguments, 0); _this.apply(context, args.concat(amArg)); }; } Function.prototype.bind = bind;
这样,这个bind函数的重写算是完成了。
我们用es6改写一下:
function bind(context = window, ...args) { return (...amArg) => this.call(context, ...args.concat(amArg)); }
代码看上去确实精简不少,当然也可以用apply,但经测试,性能不如call。
接下来看看重写call:
它会有若干个参数,第一个是要指向的this,直接用es6写法:
function call(context = window, ...args) { }
函数中的this就是要调用call方法的函数,想让该函数执行并且其内部this指向传递进来的context,那么形如context.函数可以做到:
function call(context = window, ...args) { //给context增加一个$fn属性,把当前函数赋给这个属性 context.$fn = this; //让context.$fn这个方法执行,就是之前this函数执行,并且this指向的是context let result = context.$fn(...args); //增加完方法应该删除 delete context.$fn; return result; } Function.prototype.call = call;
apply也就出来了:
function apply(context = window, args) { context.$fn = this; let result = context.$fn(...args); delete context.$fn; return result; } Function.prototype.apply = apply;
这里边还是有两个问题,一是$fn属性没有删除,目前还没想到解决办法,一个就是传进来的context必须是引用类型,但其实可以是基础类型:
function call(context = window, ...args) { context === null ? context = window : null; let type = typeof context; if (type !== "object" && type !== "function" && type !== "symbol") { //=>基本类型值 switch (type) { case 'number': context = new Number(context); break; case 'string': context = new String(context); break; case 'boolean': context = new Boolean(context); break; } } context.$fn = this; let result = context.$fn(...args); delete context.$fn; return result; }
apply的判断就不写了,但基本都实现了重写,当然,这几个方法毕竟是js的内置写法,我们只是想大致实现它们的实现原理。
来源:https://www.cnblogs.com/ronaldo9ph/p/12362638.html