函数原型链中的 apply,call 和 bind 方法是 JavaScript 中相当重要的概念,它们都是用来改变函数中的this指向。那关于它们的用法、区别和内部实现,你都知道吗?
call
函数基于原型链找到Function.prototype.call方法,并且把它执行,在方法执行时完成了一些功能:
- 让当前函数执行;
- 把函数中的this指向改为第一个传递的实参;
- 把传递给call其余的实参当作参数传递给当前函数;
如果一个参数都没有,非严格模式下是让函数中的this指向window,严格模式下指向的是Undefined.
window.name = 'window';
let obj = {name:'张三'};
let fn = function(){
console.log(this.name);
}
fn(); //=> this:window 输出'window' 严格模式=> undefined
//obj.fn(); //报错 obj.fn is not a function => obj中没有fn属性
需求:如何让fn执行时this指向obj呢?
obj.fn = fn;
obj.fn();
delete obj.fn;
//先给obj增加fn属性,然后再删除
fn.call(obj); //this:obj 输出'张三'
fn.call(); //this:window 严格:undefined
fn.call(null); //this:window 严格:undefined
因此,如果第一个参数传递的是null / undefined,或者不传,非严格模式下this指向window,严格模式下传递的是谁this就是谁,不传this是undefined.
如果给fn加参数后,传递参数是什么意思?
function fn(a,b){
...
}
fn.call(obj); //a和b均为undefined
fn.call(obj,1,2); //this:obj a:1 b:2
fn.call(1,2); //this:1 a:2 b:undefined
那么如何实现一个内置call方法呢?
/*
* @params:
* context:最后要改变的函数中的this指向
* args:传递给函数的实参信息
*/
function call(context,...args){
//context为null或者undefined为window,否则传了什么就是什么
context = context == null ? window : context;
//context必须为对象
let contextType = typeof context;
if(!/^(function|object)$/i.test(contextType)){
context = Object(context);
}
let result,
key = Symbol('key');
context[key] = this; //把函数作为对象的某个成员值(成员名唯一,防止修改原始对象的值)
result = context[key](...args); //基于对象[成员]方式把函数执行,此时函数中的this就是对象
delete context[key]; //设置的成员用完后删除掉
return result; //把函数的返回值作为call方法执行的结果返回
}
apply
和call方法一样,都是把函数执行,并且改变里面的this关键字,唯一的区别就是传递给函数的参数方式不同;
call和apply的区别:
- call是一个个传参;
- apply是按照数组传参;
通过实例来对比一下区别:
let obj = {name:'张三'};
let fn = function(a,b){
console.log(this.name);
}
//让fn方法执行,且方法中this指向obj,并且传递参数10和20
//使用call
fn.call(obj,10,20);
//使用apply
fn.apply(obj,[10,20]);
bind
和call / apply 一样,也是用来改变函数中的this关键字的,只不过基于bind改变this,当前方法并没有被执行,类似于预先改变this。
let obj = {name:'张三'};
let fn = function(a,b){
console.log(this.name);
}
document.body.onclick=fn; //事件触发时,fn中的this->body
如果希望点击body时,让fn中的this指向obj应该怎样去实现?
document.body.onclick = fn.call(obj);
是不是很多人觉得应该用call来改变指向,这么写是错误的,错误的,错误的!!!这样处理,不是把fn绑定给事件,而是把fn执行后的结果绑定给了事件。
正确方法:
//给事件绑定了匿名函数
document.body.onclick = function(){
//this -> body
fn.call(obj);
}
//那么如何绑定fn呢?
//使用bind
document.body.onclick = fn.bind(obj);
由此可见,bind的优势在于:预先把fn中的this修改为obj,此时fn并没有执行,当点击事件触发才会执行fn,而call和apply都是在改变this的同时立即把方法执行。
注:在IE6~8中不支持bind方法
既然存在兼容性,那么就来自己动手实现一个bind方法吧!
/*
* @params:
* context:最后要改变的函数中的this指向
* args:传递给函数的实参信息
*/
function bind(context,...params){
let _this = this;
return function anonymous(...args){
//args -> 可能传递的事件对象等信息
_this.call(context,...params.concat(args));
}
}
call & apply & bind区别
call / apply :立即执行函数并且修改里面的this;
bind:利用柯理化思想,预先把 “需要处理的函数/改变的this/传递的参数” 等信息存储在闭包中,后期达到条件(事件触发/定时器等),先执行返回的匿名函数,在执行匿名函数的过程中再去改变this等 => this和参数的预处理