call
call()方法是使用指定一个this和若干个参数,调用某一个函数或方法
先来举个例子
const foo = {
value: 'foo',
}
function bar() {
console.log(this.value);
}
bar.call(foo); // foo
复制代码
先来分析一下call都干了什么
- call改变了this的指向,将其指向了foo
- bar函数执行
根据这个思路,可以手写一个call方法
Function.prototype._call = function (target) {
// 判断target是否为null
// 如果是null,将执行上下文指向window
const context = target || window;
// 由于arguments是类数组,将形参加入到数组中
// 注意是从第二个参数开始循环,因为第一个参数是target
const args = [];
for (let i = 1; i < arguments.length; i++) {
args.push(`arguments[${i}]`);
}
// 注意this,他其实就是当前调用call的函数
// 将方法添加到上下文中,以便执行
context.fn = this;
// 用eval拼成字符串执行
const result = eval(`context.fn(${args});`);
// 将方法从上下文中移除
delete context.fn;
return result;
}
复制代码
上面我实现了一个简单的call,里面有几点需要解释一下
- context.fn是个临时属性,执行完之后,要将它删除
- fn是可以有返回值的,所以要在最后返回结果
apply
和call一样,唯一的不同点在于apply接受一个数组作为参数,看实现代码
Function.prototype._apply = function (target, params) {
const context = target || window;
context.fn = this;
if(!params) {
return context.fn();
}
const args = [];
for (let i = 0; i < params.length; i++) {
args.push(`params[${i}]`);
}
const result = eval(`context.fn(${args})`);
delete context.fn;
return result;
}
复制代码
和call差不多,不做过多解释
bind
MDN解释
bind()会创建一个新方法,当这个新函数被调用时,bind()的第一个参数将作为它运行时的this,之后的一序列参数将会在传递的实参前传入作为它的参数。
总结出bind的特点:
- 返回一个新方法
- 可以传入参数
按照MDN的说法,举个例子
Function.prototype._bind = function (target) {
const fn = this;
const context = target || window;
const args = Array.prototype.slice._call(arguments, 1);
return function () {
const fnArgs = Array.prototype.slice._call(arguments);
return fn._apply(context, args.concat(fnArgs));
}
}
复制代码
注意:代码中用到了上文实现的**_call**,_apply方法,完成以上功能还不算完。
::重点来了!!::
绑定创造的函数还可以被构造调用,这时bind中指定的this绑定会失效,但参数依然有效
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数
举个例子,调用原生bind之后可以看到this.value的结果是undefined,证明指定的this被忽略了。
const foo = {
value: 'foo',
}
function bar(name, age) {
this.job = "developer";
console.log(this.value); // undefined
console.log(name); // elvis
console.log(age); // 32
}
const fBind = bar.bind(foo, 'elvis');
const obj = new fBind('32');
console.log(obj.job); // developer
复制代码
根据上面的结果,我们可以通过修改返回函数的原型来实现
Function.prototype._bind = function (target) {
const fn = this;
const context = target || window;
const args = Array.prototype.slice._call(arguments, 1);
const fBind = function () {
const fnArgs = Array.prototype.slice._call(arguments);
// 当作为构造函数时,this指向实例
// 当作为普通函数调用时,this指向bind指定的target
fn._apply(this instanceof fBind ? this : context, args.concat(fnArgs));
}
// 修改返回函数的prototype,指向绑定函数的prototype,这样就可以访问绑定函数原型中的值了
fBind.prototype = this.prototype;
return fBind;
}
复制代码
构造调用的优化
在这个写法中,fBind.prototype = this.prototype也会直接修改绑定函数的原型,我们可以通过一个空函数进行中转
Function.prototype._bind = function (target) {
const fn = this;
const context = target || window;
const args = Array.prototype.slice._call(arguments, 1);
const f = function () { };
const fBind = function () {
const fnArgs = Array.prototype.slice._call(arguments);
fn._apply(this instanceof f ? this : context, args.concat(fnArgs));
}
f.prototype = this.prototype;
fBind.prototype = new f();
return fBind;
}
复制代码
到这里为止,大的问题都解决了??
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END