1、函数调用的四种模式与this指向
- 普通函数调用:this指向全局对象
- 对象方法调用:this指向该调用的对象
- 构造函数调用:this指向构造函数new的对象
- call,apply和bind间接调用:显式绑定this,传入的第一个参数绑定的对象
2、this指向例子(⭐⭐⭐⭐⭐)
var name = "lucy";
var obj = {name: "martin",say: function () {console.log(this.name);}
};
//1
obj.say(); // martin,this 指向 obj 对象
//2
setTimeout(obj.say,0); // lucy,this 指向 window 对象
//3
setTimeout(obj.say.bind(obj),0); //martin,this指向obj对象
- obj.say()为对象方法调用,this指向obj,所以this.name为Martin
- say放在setTimeout方法当中,在定时器中的是作为回调函数执行的,回到执行栈执行时候是在全局执行上下文的环境中执行,所以this.name为Lucy
- 为了改变情况2的指向,这时候需要bind函数obj.say,bind(obj)显式绑定this到obj上面
2、apply,call,bind的区别
- apply接收两个参数,第一个参数是this指向,第二个参数是数组。该方法的this指向只临时改变一次,如下:
function fn(...args){console.log(this,args);
}
let obj = {myname:"张三"
}fn.apply(obj,[1,2]); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向window
- call接收多个参数,第一个参数是this指向,后面的是参数列表。也是this指向只临时改变一次
function fn(...args){console.log(this,args);
}
let obj = {myname:"张三"
}fn.call(obj,1,2); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向window
- bind接收多个参数,第一个参数是this指向,后面的是参数列表(但是这个参数列表可以分为多次传入)。改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。
function fn(...args){console.log(this,args);
}
let obj = {myname:"张三"
}const bindFn = fn.bind(obj); // this 也会变成传入的obj ,bind不是立即执行需要执行一次
bindFn(1,2) // this指向obj
fn(1,2) // this指向window
总结:三者的区别
- 三者都是可以改变函数的this指向
- 三者第一个参数都是this指向的对象,如果没有这个参数或者参数为undefined或null,则默认指向全局Window
- 三者都是可以传递参数,apply是数组,call、bind是列表。其中apply和call都是一次性传入参数,而bind可以分为多次传入
- bind是返回绑定this之后的函数,而apply、call是立即执行。所以bind的this指向是永久改变,apply,call则是临时绑定。
这里可以结合函数柯里化-CSDN博客⭐这篇文章去理解他们的应用。
4、实现
- apply函数实现
- 判断调用对象是否为函数,即便是定义在函数的原型上面,但是可能出现使用call等方法调用的情况。
- 判断传入上下文对象是否存在,如果不存在,设置为window
- 将函数作为上下文对象的一个属性
- 判断参数值是否传入
- 使用上下文对象来调用这个方法,并保存返回结果
- 删除刚才新的属性
- 返回结果
Function.prototype.myApply = function(context){//判断调用对象是否为函数if(typeof this!="function"){throw new TypeError("Error");}let result =null;context = context||window;context.fn = this;if(arguments[1]){result = contetx.fn(...arguments[1]);}else{result = context.fn()}delete context.fn;return result;
}
- call函数实现
- 判断调用对象是否为函数,即便是定义在函数的原型上面,但是可能出现使用call等方法调用的情况。
- 判断传入上下文对象是否存在,如果不存在,设置为window
- 处理传入的参数,截取第一个参数后的所有参数
- 将函数作为上下文对象的一个属性
- 使用上下文对象来调用这个方法,并保存返回结果
- 删除刚才新的属性
- 返回结果
Function.prototype.myCall = function(context){//判断调用对象是否为函数if(typeof this!="function"){throw new TypeError("Error");}let args = [...arguments].slice(1),result =null;context = context||window;context.fn = this;result = contetx.fn(...args);delete context.fn;return result;
}
- bind函数实现
- 判断调用对象是否为函数,即便是定义在函数的原型上面,但是可能出现使用call等方法调用的情况。
- 保存当前函数的引用,获取其余传入参数值
- 创建一个函数返回
- 函数内部使用apply来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的this给apply调用,其余情况都传入指定的上下文对象
Function.prototype.myBind = function(context){//判断调用对象是否为函数if(typeof this!="function"){throw new TypeError("Error");}let args = [...arguments].slice(1),fn = this;return function Fn(){return fn.apply(this instanceof Fn ? this : context,args.concat(...arguments))};
}
修改this指向、动态传参
// 方式一:只在bind中传递函数参数
fn.bind(obj,1,2)()// 方式二:在bind中传递函数参数,也在返回函数中传递参数
fn.bind(obj,1)(2)
参考文献
函数 -- JavaScript 标准参考教程(alpha)