写在前面
call
、apply
和bind
是强制改变this
指向的几种方法。大家应该看过了很多关于手写call
、apply
和bind
的文章,并且自己手动尝试,已经实现手写源码~所以我写这篇文章的目的是怕大家忘了,及时出来帮大家复习一下????
本篇文章将列举三种方法之间的区别和手写三种方法。下面是一张思维导图:

三种方法的区别
call
和apply
两个方法第一个参数都是要改变为的this
指向,在非严格模式下,如果传递null
、undefined
,则指向window
。第二个参数有所区别,call
是将参数一个一个传递,而apply
是将参数整体放到数组中传递。两个方法都是立即执行函数,call
的性能要好于apply
。bind
方法预先改变this
指向、传递参数,不立即执行函数。
手写三种方法
我们将方法直接挂到函数原型上
call
~ function anonymous(proto){
// call
function call(context = window, ...args) {
// 判断context是否为null,为null时让其指向window
context === null ? context = window : null;
// 保证context是引用类型值
var 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;
// 执行函数赋值给result,删除传入函数,最后将结果返回
var result = context.$fn(...args);
delete context.$fn;
return result;
}
proto.call = call;
}(Function.prototype)
上面就是实现的源码,现在测试一下它是否好用
var obj = {
name: '小红',
fn: function() {
return this.name;
}
}
var obj1 = {
name: '小明'
}
console.log(obj.fn()); // 小红
console.log(obj.fn.call(null)); // 小白
console.log(obj.fn.call(obj1)); // 小明
let func = obj.fn;
console.log(func()); // 小白
console.log(func.call(obj1)); // 小明
apply
上面说了,call
和apply
只是传参形式不一样,所以,我们只需要改一小部分
~ function anonymous(proto){
// apply
function apply(context = window, args) {
// 判断context是否为null,为null时让其指向window
context === null ? context = window : null;
// 保证context是引用类型值
var 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;
// 执行函数赋值给result,删除传入函数,最后将结果返回
var result = context.$fn(...args);
delete context.$fn;
return result;
}
proto.apply = apply;
}(Function.prototype)
测试用例
var obj = {
name: '小红',
fn: function(...args) {
return [this.name, ...args];
}
}
var obj1 = {
name: '小明'
}
console.log(obj.fn()); // ["小红"]
console.log(obj.fn.apply(null, [1, 2, 3, 5])); // ["小白", 1, 2, 3, 5]
console.log(obj.fn.apply(obj1, [2, 3, 4, 6])); // ["小明", 2, 3, 4, 6]
let func = obj.fn;
console.log(func()); // ["小白"]
console.log(func.apply(obj1, [7, 5, 3])); // ["小明", 7, 5, 3]
bind
接下来实现bind
,之前的文章也有手写实现内置bind
,在高级技巧应用的文章里:【JavaScript】几个必须要会的高级编程技巧,它是柯里化的应用
~ function anonymous(proto) {
// es5
function bind(context) {
context = context || window;
// 截取参数
var outArgs = Array.prototype.slice.call(arguments, 1);
var _this = this;
return function() {
var innerArgs = Array.prototype.slice.call(arguments);
var args = innerArgs.concat(outArgs);
return _this.apply(context, args);
}
}
// es6
function bind(context = window, ...args) {
return (...innerArgs) => this.call(context, ...args.concat(innerArgs));
}
proto.bind = bind;
}(Function.prototype)
测试用例:
var obj = {
name: '小红',
fn: function(...args) {
return [this.name, ...args];
}
}
var obj1 = {
name: '小明'
}
console.log(obj.fn()); // ["小红"]
console.log(obj.fn.bind(null, 1, 2, 3, 5)()); // ["小白", 1, 2, 3, 5]
console.log(obj.fn.bind(obj1, 2, 3, 4, 6)()); // ["小明", 2, 3, 4, 6]
let func = obj.fn;
console.log(func()); // ["小白"]
console.log(func.bind(obj1, 7, 5, 3)()); // ["小明", 7, 5, 3]
一道面试题
function fn1() {
console.log(1);
}
function fn2() {
console.log(2);
}
fn1.call(fn2); // 1
fn1.call.call(fn2); // 2
Function.prototype.call(fn1); // 1
Function.prototype.call.call(fn1); // undefined
这道题就不写解答过程了,欢迎大家积极讨论~
最后
综上为call
、apply
、bind
的手写源码,如果觉得帮你复习的还不错????,就给本篇文章点个赞吧~如果有有瑕疵的地方,还请大家指出来,我们共同学习,一起进步~
最后,如果你想更加快速的收到文章推送,欢迎关注我的公众号「web前端日记」~
