JS函数调用的this指向与apply,call,bind调用模式

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对象
  1. obj.say()为对象方法调用this指向obj,所以this.name为Martin
  2. say放在setTimeout方法当中,在定时器中的是作为回调函数执行的,回到执行栈执行时候是在全局执行上下文的环境中执行,所以this.name为Lucy
  3. 为了改变情况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

 总结:三者的区别

  1. 三者都是可以改变函数的this指向
  2. 三者第一个参数都是this指向的对象,如果没有这个参数或者参数为undefined或null,则默认指向全局Window
  3. 三者都是可以传递参数,apply是数组,call、bind是列表。其中apply和call都是一次性传入参数,而bind可以分为多次传入
  4. bind是返回绑定this之后的函数,而apply、call是立即执行。所以bind的this指向是永久改变apply,call则是临时绑定

这里可以结合函数柯里化-CSDN博客⭐这篇文章去理解他们的应用。

4、实现

  • apply函数实现
  1. 判断调用对象是否为函数,即便是定义在函数的原型上面,但是可能出现使用call等方法调用的情况。
  2. 判断传入上下文对象是否存在,如果不存在,设置为window
  3. 将函数作为上下文对象的一个属性
  4. 判断参数值是否传入
  5. 使用上下文对象来调用这个方法,并保存返回结果
  6. 删除刚才新的属性
  7. 返回结果
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函数实现
  1. 判断调用对象是否为函数,即便是定义在函数的原型上面,但是可能出现使用call等方法调用的情况。
  2. 判断传入上下文对象是否存在,如果不存在,设置为window
  3. 处理传入的参数,截取第一个参数后的所有参数
  4. 将函数作为上下文对象的一个属性
  5. 使用上下文对象来调用这个方法,并保存返回结果
  6. 删除刚才新的属性
  7. 返回结果
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函数实现
  1. 判断调用对象是否为函数,即便是定义在函数的原型上面,但是可能出现使用call等方法调用的情况。
  2. 保存当前函数的引用,获取其余传入参数值
  3. 创建一个函数返回
  4. 函数内部使用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)

你可能感兴趣的:(JS,javascript,开发语言,ecmascript)