编程技术文章分享与教程

网站首页 > 技术文章 正文

高级前端进阶,为什么要使用call、apply、bind?

hmc789 2024-11-21 15:54:53 技术文章 1 ℃

前言:

callapplybind这3个方法的用处都是更改this指向,在学习callapplybind之前,需要先了解this,所以本文会先对this进行讲解。

通过本文可以了解:

  1. this是什么
  2. callapplybind是如何实现的
  3. callapplybind的用处

什么是this

定义:

当前执行上下文(globalfunctioneval)的一个属性,在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。

说白了,this就是一个变量,他指向一个对象或者一个参数,我们通常需要使用this来设置或获取变量、对象、方法,最重要的就是能够分辨出当前使用的this的指向。接下来我们来看看不同情况下this的指向。

重点:this的指向是==调用时==决定的。

1. 全局上下文

  • 在全局上下文中,this指向的是window
console.log(this==window)
//true

2.函数调用

  • 直接调用this指向window
function test(){
 console.log(this)
}
test();
//window

function test(){
 test1()
 function test1(){
  console.log(this)
 }
}
test();
//window 

3.通过对象调用

  • this指向调用方法的对象
const obj = {
 a:1,
 test:function(){
  console.log(this)
 }
}
console.log(obj.test())
//obj

4.箭头函数

  • this与所在上下文this指向相同
const fun1 = ()=>{
 console.log(this)
}
console.log(fun1())
//window

function fn1(){
 console.log(this)
 return ()=>{
  console.log(this)
 }
}
console.log(fn1()())
//window
//window

 

5.构造函数

  • this指向构造出的对象
function Test(){
 this.name = 'test';
}
let t = new Test();
console.log(t.name)
//test

call、apply、bind原生实现解析

1.call

function.call(thisArg, arg1, arg2, ...)。 第一个参数为要更改的对象,arg1,arg2...为传入参数

原生实现

//call函数原生模拟实现
//给context一个默认值,如果没有传入,默认指向window,用...来将传入参数转为数组
Function.prototype.call = function(context = window,...args){
 //将调用call的函数挂在传入的对象上,起到更改this的目的
 context.fun = this;
 //执行方法,并传入参数
 const result = context.fun(...args);
 //执行完毕删除刚创建的自定义方法,防止污染
 delete context.fun;
 //返回结果
 return result
}
let obj = {
 a:1
}
function test(){
 console.log(this.a)
}
test.call(obj);
//输出
//1

2.apply

func.apply(thisArg, [argsArray])。 第一个参数为要更改的对象,第二个参数为func执行时传入的数组参数

//apply函数原生模拟实现
//给context一个默认值,如果没有传入,默认指向window,用...来将传入参数转为数组
Function.prototype.apply = function(content=window,args){
 //将调用call的函数挂在传入的对象上,起到更改this的目的
 content.fn = this;
 //执行方法,并传入参数
 let result = content.fn(...args);
 //执行完毕删除刚创建的自定义方法,防止污染
 delete content.fn;
 //返回结果
 return result;
}
let obj = {
 a:1
}
function test(){
 console.log(this.a)
}
test.apply(obj);

call/apply方法基本一样,唯一的区别在于call传入的是多个参数,apply传入的为数组

3.bind

function.bind(thisArg[, arg1[, arg2[, ...]]])。第一个参数为要更改的对象,arg1, arg2, ...为传入的参数

  • callapply的不同在于,callapply调用后会立即执行,返回值依赖于调用他们的函数。而bind,调用后会返回原函数,拥有指定的this以及初始参数。
//bind函数原生模拟实现
//给context一个默认值,如果没有传入,默认指向window,用...来将传入参数转为数组
Function.prototype.bind = function(oThis) {
 //校验调用者是否为方法
 if (typeof this !== "function") {
  throw new TypeError("error");
 }
 //截取入参内的参数,并转为数组
 const aArgs = Array.prototype.slice.call(arguments, 1);
 //临时存储调用bind的对象
 const fToBind = this;
 //中间方法
 const fNOP = function() {};
 //返回的新函数
 const fBound = function() {
  //这里判断this指向
  //如果this指向中间方法,则说明返回的函数fBound通过new调用了,则这里的this不更改指向,否则指向bind时传入的对象
  //执行方法并将bind时传入的参数与新函数调用时传入的参数合并,传入执行方法。
  //例:如果通过let t = new test1()
  //此时 这里的this指向t,通过t的原型链可以找到fNOP的原型对象,这时候不能更改this为其他值
  return fToBind.apply(this instanceof fNOP && oThis ?
   this :
   oThis,
   aArgs.concat(Array.prototype.slice.call(arguments)));
 };
 //中间方法的原型指向调用bind的原型对象
 fNOP.prototype = this.prototype;
 //将返回函数的原型对象的隐式原型指向中间方法的原型对象
 fBound.prototype = new fNOP();
 return fBound;
};
let obj = {
 a: 1
}
function test() {
 console.log(this.a)
}
let test1 = test.bind(obj);
test1()
//输出
//1

到这里,callapplybind的实现方式已经介绍完毕。

为什么要更改this指向

接下来用几个示例说明为什么要使用callapplybind更改this指向

例1:

var obj = {
 fn1: function() {
 },
 fn2: function() {
 },
 fn3: function(cb) {
     //这里调用的this.fn3传入的function,由于是直接调用,所以this指向的是window
  cb();
 },
 list: function() {
  this.fn3(function() {
   console.log(this)
   this.fn1();
   this.fn2();
  });
 }
};
obj.list();

执行这段代码,控制台会报个错误,this.fn1 is not a function,我们查看this.fun3中的this,可以知道,这里的this指向的是window,去window中查询fn1方法,的确是查不到。

这时候我们就需要更改方法中的this指向了。

1、使用call
var obj = {
 fn1: function() {
  console.log('fn1')
 },
 fn2: function() {
  console.log('fn2')
 },
 fn3: function(cb) {
  cb.call(this);
  console.log('fn3')
 },
 list: function() {
  this.fn3(function() {
   this.fn1();
   this.fn2();
  });
 }
};
obj.list();
//输出
//fn1
//fn2
//fn3

2、使用bind
var obj = {
 fn1: function() {
  console.log('fn1')
 },
 fn2: function() {
  console.log('fn2')
 },
 fn3: function(cb) {
  cb();
  console.log('fn3')
 },
 list: function() {
  this.fn3(function() {
   this.fn1();
   this.fn2();
  }.bind(this));
 }
};
obj.list();
//输出
//fn1
//fn2
//fn3

例2:

function test(){
 console.log(typeof arguments)
 //通过输出可以看到,arguments是类数组对象
 //如果我们想要把传入的参数转为数组,就需要借用call,将slice方法挂到arguments上,从而实现我们要的功能
 let args = Array.prototype.slice.call(arguments,0)
 console.log(args);
}
test(1,2,3);
//输出
//object
//[1,2,3]

通过call方法可以将不存在当前对象的方法挂在到自己对象上,从而实现我们需要的功能。

例3:

const arr = [1,2,3,4];
//通过apply方法,将min方法挂到window上,然后将arr转为字符串组传入min方法
const min = Math.min.apply(null,arr);
console.log(min)
//输出
//1

通过这3个示例,可以看到callapplybind的使用场景。

结尾:

callapply都是立即执行,call传入的是多个参数,apply传入的是数组。

bind是返回一个新函数,拥有this以及初始参数。

在此,对callapplybind的介绍就结束了,希望本篇文章对大家有所启示,谢谢观看~

标签列表
最新留言