目 录CONTENT

文章目录

Javascript中的this,call,apply,bind

Hello!你好!我是村望~!
2023-10-22 / 0 评论 / 0 点赞 / 164 阅读 / 2,873 字
温馨提示:
我不想探寻任何东西的意义,我只享受当下思考的快乐~

Javascript中的this,call,apply,bind

(以下的示例代码全都在浏览器环境下运行)

this的指向

【在es5规范的用法中!可以大声的说!谁最终调用我,我最终就归谁!】(记住这个最终!)

function codeMethod(){
  console.log(this);
}

const obj = {
  name:'codeHope',
  age:23,
  codeMethod,
}

codeMethod();
obj.codeMethod();

image-20210324174712930

codeMethod();是在全局的环境下调用的(浏览器下window),而第二个obj.codeMethod();是obj对象调用的,谁调用这个函数,那么里面的那个this就指向那个对象

下面我们对上面那些变形一下,看看有没有什么坑!

1 第一个

window.obj = obj;
window.obj.codeMethod();//{name: "codeHope", age: 23, codeMethod: ƒ}

其实还是指向Obj因为还是obj,最终调用的这个方法!

2 第二个

function codeMethod(){
  console.log(this);
}

const obj = {
  name:'codeHope',
  age:23,
  codeMethod,
}

const f = obj.codeMethod;
f(); //Window

不管你如何变化,因为codeMethod函数对象,是引用类型,你所有的变量( obj.codeMethodf)存储的都只是他的内存地址,永远指向全局中定义的那个codeMethod,所以你这样子最终还是在全局中调用的这个方法!那么this指向的就是window.

3 第三个

function method(){
  console.log(this);
  innerMethod();
  function innerMethod() {
    console.log(this);
  }
}
const obj = {
  name:"codeHopeObj",
  myMethod:method,
}

method();
obj.myMethod();

method(); 在浏览器环境下是window对象调用,所以第一个里面的this 打印的是windowinnerMethod,在内部这个方法没有被任何对象调用,所以他找不到this,默认指向window

obj.myMethod(); obj.myMethod指向的全局的method方法,等于是Obj调用method,所以第一个this打印的是 obj对象,第二个和上面同理,innerMethod,在内部这个方法没有被任何对象调用,所以他找不到this,默认指向window

所以打印结果如下:

image-20210325110225594

img

填一填,我们拍拍土巩固一下啦!

var name = 'Nicolas';
function Person(){
    this.name = 'Smiley';
    this.sayName=function(){
        console.log(this); 
        console.log(this.name); 
    };
    setTimeout(this.sayName, 0);   
}

var person = new Person();
person.sayName();
点击查看答案

Person {name: "Smiley", sayName: ƒ}

Smiley

Window {window: Window, self: Window, document: document, name: "Nic..}

Nicolas

person.sayName();这个调用的打印结果,很好解释,因为是person这个对象调用的,所以this指向的是person对象,this.name也是Smiley,setTimeout(this.sayName, 0);这个虽然是在构造函数里面,但是他的调用者其实是window,因为你也找不到其他的调用了是不是,其实他写全了是window.setTimeout(this.sayName, 0);所以this指向window对象,指向name是Nicolas

怎么改变 this 的指向

比如下面这个对象中的一个方法,我们需要在一段时间后调用对象中的另一个方法

var name = "windowsName";

var a = {
    name : "Cherry",

    func1: function () {
        console.log(this.name)     
    },

    func2: function () {
        setTimeout(  function () {
            this.func1()
        },100);
    }

};

a.func2()

func2被执行的时候,其实里面的setTimeout定时器最终是window对象调用的,而全局中没有func1这个函数所以直接报错了

Uncaught TypeError: this.func1 is not a function at index.js:12

如果需要完成我们的需求的话,就需要我们去改变this指向,让这个this,指向我们对象的内部!

1 使用箭头函数

箭头函数的 this 始终指向函数定义时的 this,而非执行时。箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”

var name = "windowsName";

var a = {
    name : "Cherry",

    func1: function () {
        console.log(this.name)     
    },

    func2: function () {
        setTimeout(()=> {
          //这个函数,因为是在箭头函数中调用,本身内部不存在this,他就向上在func2中找this,
          //func2是被a对象调用的,所以func2中的this,指向a对象,然后调用a对象的func1的方法,就可以顺利执行了
            this.func1(); 
        },100);
    }

};

a.func2();// Cherry

2 在函数内部使用 _this = this

var name = "windowsName";

var a = {
  name: "Cherry",

  func1: function () {
    console.log(this.name);
  },

  func2: function () {
    //将func2中的this对象指向的a对象地址存入一个变量,然后再其他能访问的这个变量的地方都可以调用里面的方法!
    const _this = this; 
    setTimeout(function () {
      _this.func1();
    }, 100);
  },
};

a.func2();// Cherry

3 使用 apply、call、bind

  • 使用 apply

    var name = "windowsName";
    
    var a = {
      name: "Cherry",
    
      func1: function () {
        console.log(this.name);
      },
    
      func2: function () {
        setTimeout(function () {
          this.func1();
        }.apply(a), 100);
      },
    };
    
    a.func2();//Cherry
    
    
  • 使用 call

    var name = "windowsName";
    
    var a = {
      name: "Cherry",
    
      func1: function () {
        console.log(this.name);
      },
    
      func2: function () {
        setTimeout(function () {
          this.func1();
        }.call(a), 100);
      },
    };
    
    a.func2();//Cherry
    
  • 使用 bind

    var name = "windowsName";
    
    var a = {
      name: "Cherry",
    
      func1: function () {
        console.log(this.name);
      },
    
      func2: function () {
        setTimeout(function () {
          this.func1();
        }.bind(a), 100);
      },
    };
    
    a.func2();//Cherry
    

    【看上面的用法很相似,而且都可以达到改变 this 指向的目的,那么这三个函数有什么不同呢?】

call

call() 方法使用一个指定的this 值和单独给出的一个或多个参数来调用一个函数。(多个参数用逗号","隔开)

现在有一个codeHope对象

const codeHope = {
  nickname:"codeHope",
  age:23,
  introduce(hobby,job){
    console.log(
      `my name is ${this.nickname}, i'm ${this.age} years old. my hobby is ${hobby},my job is ${job}`
      );
  }
}

codeHope.introduce("play guitar","coder")
//my name is codeHope, i'm 23 years old. my hobby is play guitar,my job is coder

然后一个Marry来了

const Marry = {
  nickname:"Marry",
  age:20,
}

她没有codeHope里面的方法,于是想借用一下,来介绍自己!

codeHope.introduce.call(Marry,"swimming","Animal Helper")
//my name is Marry, i'm 20 years old. my hobby is swimming,my job is Animal Helper

这就通过call()来改变了codeHope里面方法的this,而且还可以做到传参。

apply

其实 apply 和 call 基本类似,他们的区别只是传入的参数不同。apply 接收的是一个包含多个参数的数组。

codeHope.introduce.apply(Marry,["swimming","Animal Helper"])
//my name is Marry, i'm 20 years old. my hobby is swimming,my job is Animal Helper

bind

bind()方法创建一个新的函数,并不会帮我调用,它只是帮我们返回了一个更换了this指向的函数,我们还要自己去调用,传参的

const merryIntroduce = codeHope.introduce.bind(Marry);

merryIntroduce("swimming","Animal Helper");
//my name is Marry, i'm 20 years old. my hobby is swimming,my job is Animal Helper 

回首掏之总结一下apply,call,bind

1 callapply 的共同点

【call 和 apply 它们的共同点是,都能够改变函数执行时的上下文,将一个对象的方法交给另一个对象来执行,并且是立即执行的。】

为何要改变执行上下文?举一个生活中的小例子:

平时没时间做饭的我,周末想给自己炖个砂锅麻辣烫尝尝。但是家里没有砂锅,而我又不想出去买。所以就问邻居借了一个砂锅来用,这样既达到了目的,又节省了开支,一举两得。
【改变执行上下文也是一样的,A 对象有一个方法,而 B 对象因为某种原因,也需要用到同样的方法,那么这时候我们是单独为 B 对象扩展一个方法呢,还是借用一下 A 对象的方法呢?当然是借用 A 对象的啦,既完成了需求,又减少了内存的占用。】

2 call apply的区别

它们的区别,主要体现在参数的写法上。先来看一下它们各自的具体写法。

  • call

    • 调用 call 的对象,必须是个函数 Function。
    • call 的第一个参数,是一个对象。 Function 的调用者,将会指向这个对象。如果不传,则默认为全局对象 window。
    • 第二个参数开始,可以接收任意个参数。每个参数会映射到相应位置的 Function 的参数上
    Function.call(obj,params1,params2,...,paramsN)
    
  • apply

    • 它的调用者必须是函数 Function,并且只接收两个参数,第一个参数的规则与 call 一致。
    • 第二个参数,必须是数组或者类数组,它们会被转换成类数组,传入 Function 中,并且会被映射到 Function 对应的参数上。这也是 call 和 apply 之间,很重要的一个区别。
    Function.apply(obj,[params1,params2,...,paramsN])
    

3 bind

最后来说说 bind。在 MDN 上的解释是:bind() 方法创建一个新的函数,在调用时设置 this 关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。

【bind 方法 与 apply 和 call 比较类似,也能改变函数体内的 this 指向。不同的是,bind 方法的返回值是函数,并且需要稍后调用,才会执行。而 apply 和 call 则是立即调用。】

//定义一个累数组
const obj = {
    1:1,
    2:2,
    3:3,
    length:4
}

const t = Array.prototype.filter.bind(obj);

console.log(typeof t);//function

const newObj = t(item=>{
    return item ==3;
})

console.log(newObj);//[ 3 ]

【如果 bind,apply,call 的第一个参数是 null 或者 undefined,this 就指向全局对象 window】

【注意,在浏览器环境下,只有通过var定义的变量才会被挂载到window对象中】

var nickname1 = "windowsName1"
const nickname2 = "windowsName2"
let nickname3 = "windowsName3"
const obj = {
    nickname1:"codeHope1",
    nickname2:"codeHope2",
    nickname3:"codeHope3",
    logName(){
        console.log(this);
        console.log(this.nickname1);
        console.log(this.nickname2);
        console.log(this.nickname3);
    }
}
obj.logName();

/*
{nickname1: "codeHope1", nickname2: "codeHope2", nickname3: "codeHope3"..}
codeHope1
codeHope2
codeHope3
*/
obj.logName.apply(null)
obj.logName.call(null)
obj.logName.bind(null)();
/*
Window {window: Window, self: Window, document: document, name: "" …}
windowsName1
undefined
undefined

Window {window: Window, self: Window, document: document , name: ""…}
windowsName1
undefined
undefined

Window {window: Window, self: Window, document: document , name: ""…}
windowsName1
undefined
undefined
*/

apply,call,bind的使用场景实例分析

  • 使用apply实现继承

    function Father(name,age){
        this.name = name;
        this.age =age;
    }
    
    
    function Son(name,age,grade){
        Father.call(this,name,age)
        this.grade = grade;
    }
    
    const son = new Son('codeHope"s',2,1)
    
    console.log(son);
    //Son { name: 'codeHope"s', age: 2, grade: 1 }
    
  • 使用apply实现多重继承

  • 借用方法

    let domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
    

    这样,domNodes 就可以应用 Array 下的所有方法了。

  • Math.max|.min。用它来获取数组中 最大|最小 的一项。

    let max = Math.max.apply(null, array);
    let min = Math.min.apply(null, array);
    
  • 实现两个数组合并

    let arr1 = [1, 2, 3];
    let arr2 = [4, 5, 6];
    
    Array.prototype.push.apply(arr1, arr2);
    //arr2相当于是数组参数!
    
    console.log(arr1); // [1, 2, 3, 4, 5, 6]
    

    最后,分享一个在知乎上看到的,关于 call 和 apply 的便捷记忆法:

    猫吃鱼,狗吃肉,奥特曼打小怪兽。

    有天狗想吃鱼了

    猫.吃鱼.call(狗,鱼)

    狗就吃到鱼了

    猫成精了,想打怪兽

    奥特曼.打小怪兽.call(猫,小怪兽)

    猫也可以打小怪兽了

0

评论区