call,apply,bind不管在面试中还是在平时撸代码中,出现的频率都很高,为了加深自己的记忆,整理如下:
1,基本介绍
语法:
function.call(thisArg, arg1,arg2,arg3,...);
function.apply(thisArg, [arg1, arg2, arg3,...]);
function.bind(thisArg, arg1,arg2,arg3,...);
功能:用来重新定义或改变调用函数的this指向,故调用call/apply/bind的必须是函数;
既然call/apply/bind能改变函数的指向,那么我们让Math.max来依次调用一下这三个方法;
//未调用前:
Math.max(2,4,10,3,5); // 10
调用后:
// 使用apply(thisArg, [arg1,arg2,arg3])
Math.max.apply(null, [2,4,10,3,5]); // 10
// 使用call(thisArg, arg1,arg2,arg3,...)
Math.max.call(null, 2,4,10,3,5); // 10
// 使用bind
// 写法一:
const Max = Math.max.bind(null,2,4,10,3,5); // 使用bind不会立即执行,再调用才会执行,现在明白了
Max(); // 10
// 写法二:
Math.max.bind(null,2,4,10,3,5)(); // 10
备注:在浏览器中,这里的null指向全局window,或用undefined,可以自行验证一下;
2,call,apply,bind的区别
call与apply的不用点:call方法接受的是个参数列表,apply接受的是一个包含多个参数的数组;
那么bind与call、apply的区别呢?
call、apply在调用函数之后会立即执行,而bind在调用函数之后会返回一个新的函数,需要再调用之后才会执行;现在明白了在react的constructor中要用bind改变this指向,而不是使用call和apply。
3,手写一下call,apply,bind的Polyfill
那么什么是Polyfill呢?Polyfill是前端中的“黑话”,准确意思为:用于实现浏览器并不支持的原生API的代码。
call的Polyfill
Function.prototype.myCall = function(thisArg, ...args){
if (typeof thisArg === null || typeof thisArg === 'undefined') {
thisArg = window;
}
let mySymbol = Symbol();
thisArg[mySymbol] = this;
let func = thisArg[mySymbol](...args);
delete thisArg[mySymbol];
return func;
}
apply的Polyfill
Function.prototype.myApply = function(thisArg, args){
if (typeof thisArg === null || typeof thisArg === 'undefined') {
thisArg = window;
}
let mySymbol = Symbol()
thisArg[mySymbol] = this;
let func = thisArg[mySymbol](...args);
return func;
}
bind的polyfill
// 方法一:
Function.prototype.myBind = function (thisArg,...list) {
let self = this; // 目标函数
let Bind = function(...args){
// 如果这个函数作为构造函数,那么目标函数的this,应该执行的是实例对象;
// 如果这个函数不是作为构造函数,目标函数中的this还指向thisArg;
let thisArgSelf = this instanceof Bind ? this : thisArg;
self.apply(thisArg, [...list, ...args])
};
// 原型继承
// Object.create用来创建以某一个对象作为原型的对象的
Bind.prototype = Object.create(self.prototype);
Bind.prototype.constructor = self;
// 返回新函数
return Bind;
}
// 方法二:来源bind MDN (https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)
if (!Function.prototype.bind) (function(){
var slice = Array.prototype.slice;
Function.prototype.bind = function(){
var self = this, thatArg = arguments[0];
var args = slice.call(arguments, 1);
if (typeof self !== 'function') {
throw new TypeError('this is not callable')
}
return function () {
var funcArgs = args.concat(slice.call(arguments));
return self.apply(thatArg, funcArgs);
}
}
})()
4,例子
call相关例子
1,例题
function sum(a,b){
return a+b;
};
function minus(a, b){
return a - b;
};
sum.call(minus, 5, 3); // 8
(个人所得结论:指向minus下的a和b,那么sum得到a = 5,b =3,但不改变调用函数的执行方法,即改变this指向无非就是改变作用域);
2,将类数组转换为数组
(function(){
let arr = Array.prototype.slice.call(arguments, 0);
// Array.prototype.slice.call(arguments, start, end);
console.log('arr', arr); // [11,22,33]
})(11,22,33)
加料(类数组转换为数组)整理:
// 1, Array.prototype.slice.call(arrayLike);
// 2, Array.from(arrayLike);// 3, [...arrayLike]注意:第三种扩展运算的方法,只能用于定义了遍历器(Iterator)接口的对象;
apply相关例子
1,获取数组中的最大值
/* 找出数组中最大/小的数字 */
var nums = [51, 62, 21, 32, 72];
/* 基本等同于 Math.max(nums[0], ...) 或 Math.max(51, 62, ..) */
var max = Math.max.apply(null, nums);
var min = Math.min.apply(null, nums);
2,用apply将数组添加到另一个数组:
var arr = [3,4,5];
var otherArr = ['a','b','c'];
arr.push.apply(arr, otherArr);
console.log(arr); // [3,4,5,'a','b','c']
bind相关例子
1,创建绑定函数
this.name = '千钧'; // 浏览器中,this指向全局的window对象
var obj = {
name: 'Lucy',
getName: function(){
console.log(this);
return this.name;
}
};
obj.getName(); // 'Lucy'; // this指向obj
var otherName = obj.getName; // this指向window,因为otherName = window.obj.getName;
otherName(); // '千钧'
var bindName = otherName.bind(obj);
bindName(); // 'Lucy'
注:如果getName由普通函数变为箭头函数,作用域会发生变化;那么执行结果如下:
this.name = '千钧'; // 浏览器中,this指向全局的window对象
var obj = {
name: 'Lucy',
getName: () => {
console.log(this); // this为obj所在的window全局环境中
return this.name;
}
};
obj.getName(); // '千钧';var otherName = obj.getName; // obj为window.obj
otherName(); // '千钧'
var bindName = otherName.bind(obj); // obj为window.obj
bindName(); // '千钧'
2,给函数一个预设的初始参数
var addArguments = function(arg1, arg2){
return arg1 + arg2;
};
// 创建一个函数,让它拥有预设的第一个参数
var addInit = addArguments.bind(null, 20);
addInit(30); // 50
addInit(40,50); // 60 = 20 + 40,第二个参数会被忽略
小结
1,call,apply,bind都是改变this指向,call和apply会立即执行,而bind则是创建新的函数,需要调用后才会执行;
2,call,apply,bind经常出现在源码中,如果不熟悉这三种方法,那么去解读一些源码库,就会显得很吃力;同时,这也是面试题经常出现的问题。
ps:本文还有不足之处,后续会持续优化更新;