Detailed explanation of call, apply and bind methods


foreword

The three functions of call(), apply() and bind() methods are to change the point of this .

This article aims to explore the differences and roles among the three.

  1. What is the difference between call, apply, and bind?
  2. When to use apply and when to use call
  3. Other ingenious uses of apply (generally under what circumstances apply can be used)

bind, call, and apply are all used to specify the value of this inside a function, first look at the usage of bind, call, and apply

var year = 2021
function getDate(month, day) {
    
    
  return this.year + '-' + month + '-' + day
}

let obj = {
    
    year: 2022}
getDate.call(null, 3, 8)    //2021-3-8
getDate.call(obj, 3, 8)     //2022-3-8
getDate.apply(obj, [6, 8])  //2022-6-8
getDate.bind(obj)(3, 8)     //2022-3-8

1. call and apply

1. call() method

The syntax and functions accepted by the call() method are similar to those of the apply() method. The only difference is that call() accepts a list of parameters, while the apply() method accepts an array containing multiple parameters.

Both are methods of the function object Function, and the first parameter is the context of the object to be bound.
For example:

let obj = {
    
    
    a: 1,
    get: function(){
    
    
        return 2
    }
}
let g = obj.get
g.call({
    
    },1,2,3)
g.apply({
    
    },[1,2,3])
  • The call method calls the parent constructor
function Product(name, price){
    
    
    this.name = name;
    this.food = food;
}

// 调用父构造函数的call方法来实现继承
function Food(name, price){
    
    
    Product.call(this.name, toy);
    this.category = 'food';
}

function Toy(name, price){
    
    
    Product.call(this, name, price);
    this.category = 'toy';
}

var cheese = new Food('feta', 5);
var fun = new Toy('robot', 40);
  • The call method calls an anonymous function
var animals = [
    {
    
    species: 'Lion', name: 'King'},
    {
    
    species: 'Whale', name: 'Fail'}
];

for(var i = 0; i < animals.length; i++){
    
    
    (function(i){
    
    
        this.print = function(){
    
    
            console.log('#' + i + ' ' + this.species + ': ' + this.name);
        }
        this.print();
    }).call(animals[i], i); //call调用匿名函数
}
  • The call method calls the function and specifies the context of this
var obj = {
    
    
    animal: 'cats', sleepDuration: '12 and 16 hours'
};

function greet(){
    
    
    var reply = [this.animal, 'typically sleep between', this.sleepDuration].join(' ');
    console.log(reply);
}

greet.call(obj);  //"cats typically sleep between 12 and 16 hours"
  • The call method calls a function without specifying the first argument (argument)

In this example, we don't pass the first parameter, the value of this will be bound as the global object.

var sData = 'marshall';

function display(){
    
    
    console.log("sData's value is %s",this.sData);
}

display.call();  // sData value is marshall

But in strict mode, the value of this will be undefined

var sData = 'marshall';

function display(){
    
    
    console.log("sData's value is %s",this.sData);
}

display.call();  // Cannot read the property of 'sData' of undefined

2. apply() method

Using apply, we can write this method once and then inherit it in another object, without having to rewrite the method in the new object.

apply is very similar to call(), the difference lies in the way the arguments are supplied. apply takes an array of arguments rather than a set of argument lists. apply can use an array literal, such as fun.apply(this, ['eat', 'bananas']), or an array object, such as fun.apply(this, new Array('eat', 'bananas' )).

  • The apply method invokes a function with a given this value and provides arguments as an array.
var array = ['marshall','eminem'];
var elements = [0,1,2];
array.push.apply(array,elements);
console.log(array);  //['marshall','eminem',0,1,2]
  • Using apply with built-in functions

For some requirements that need to write loops to traverse the array items, we can use apply to avoid loops.

//找出数组中最大值和最小值
var numbers = [5, 6, 2, 3, 7];
//使用Math.min和Math.max以及apply函数时的代码
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);

The above method of calling apply has the risk of exceeding the upper limit of the JavaScript engine parameter length.
If our parameter array is very large, it is recommended to use the following mixed strategy: cut the array into pieces and then loop into the target method

function minOfArray(arr) {
    
    
    var min = Infinity;
    var QUANTUM = 32768;
  
    for (var i = 0, len = arr.length; i < len; i += QUANTUM) {
    
    
      var submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len)));
      min = Math.min(submin, min);
    }
  
    return min;
  }
  
  var min = minOfArray([5, 6, 2, 3, 7]);

3. Implementation of apply and call

The specific code is as follows:

// call和apply实现方式类似,只是传参的区别
// 基本思想是把fn.call(obj,args)中的fn赋值为obj的属性,然后调用obj.fn即可实现fn中this指向的改变
Function.prototype.myCall = function(context = window){
    
     //myCall函数的参数,没有传参默认是指向window
  context.fn = this //为对象添加方法(this指向调用myCall的函数)
  let args = [...arguments].slice(1) // 剩余的参数
  let res = context.fn(...args)  // 调用该方法,该方法this指向context
  delete context.fn //删除添加的方法
  return res
}

Function.prototype.myApply = function(context = window){
    
     //myCall函数的参数,没有传参默认是指向window
  context.fn = this //为对象添加方法(this指向调用myCall的函数)
  let res
  if(arguments[1]){
    
     //判断是否有第二个参数
    res = context.fn(...arguments[1])// 调用该方法,该方法this指向context
  }else{
    
    
    res = context.fn()// 调用该方法,该方法this指向context
  }
  delete context.fn //删除添加的方法
  return res
}

// 验证
function sayName(name= 'wwx',age= 18){
    
    
  this.name = name
  this.age = age
  console.log(this.name)
  return this.age
}
var obj = {
    
    
  name : 'zcf',
  age:24
}
var age = sayName.myCall(obj,"wxxka",19) // 19
var age1 = sayName.myApply(obj,["wwxSSS",20]) //20

Two, bind

1. Introduction to bind

The bind() function creates a new bound function that wraps the object of the original function. Calling a bound function usually executes the wrapper function.
Binding function internal properties:

  • wrapped function object
  • The value that is always passed as this when calling the wrapper function
  • The argument list is populated with list elements prior to any call to the wrapper function.

However, this in the original function retrieveX has not been changed, and still points to the global object window.

this.x = 9; //this指向全局的window对象
var module = {
    
    
    x: 81,
    getX: function(){
    
    return this.x;}
};

console.log(module.getX()); //81

var retrieveX = module.getX;
console.log(retrieveX()); //9,因为函数是在全局作用域中调用的

// 创建一个新函数,把this绑定到module对象
// 不要将全局变量 x 与 module 的属性 x 混淆
var boundGetX = retrieveX.bind(module);
console.log(boundGetX()); //81

The problem of passing parameters in bind:
when changing the this point through bind, the parameters passed in will be spliced ​​before the parameters passed in the call return function, and the redundant parameters will not work.

var newShowName = showName.bind(newThis, 'hello');
//在通过bind改变this指向的时候只传了“hello”一个参数,
//在调用newShowName这个返回参数的时候,bind传参拼接在其前
newShowName('world'); //输出:newThis hello world
var newShowName = showName.bind(newThis, 'hello');
//在通过bind改变this指向的时候只传了“hello”一个参数,
//在调用newShowName这个返回参数的时候,bind传参拼接在其前,
//这时newShowName的参数为“hello”,“a”,“world”
//而该函数只需要两个参数,则第三个参数被忽略
 newShowName('a','world'); //输出:newThis hello a

The parameters passed in by bind and the parameters passed in by the newShowName method will be spliced ​​together and passed to the showName method together.

bind cannot change the this point of the constructor

var name = 'window';
var newThis = {
    
     name: 'newThis' };
function showName(info1, info2) {
    
    
    console.log(this.name, info1, info2);
}
showName('a', 'b'); //输出:window a b

// 通过bind改变this指向
var newShowName = showName.bind(newThis, 'hello','1','2');
newShowName('a','world'); //输出:newThis hello world

console.log(new newShowName().constructor); //输出:showName函数体

It can be seen that the constructor that changes this to the return function through bind is still the original showName function.

new newShowName() instantiates a new method, and the this of this method no longer points to newThis.

2. The implementation of bind

Realize by simulating the bind source code through apply:

Function.prototype.myBind = function(context = window){
    
    
  let fn = this // 调用bind的函数
  let args = [...arguments].slice(1) // myBind的参数
  let bind = function(){
    
    
    let args1 = [...arguments].slice() // bind的参数
    return fn.apply(context,args.concat(args1))
  }
return bind
}

// 测试
var obj = {
    
    
  name : 'zcf',
  age:24
}
function sayName(name= 'wwx',age= 18){
    
    
  this.name = name
  this.age = age
  console.log(this.name)
  return this.age
}
var mb = sayName.myBind(obj)
mb() // obj = {name:"wwx",age:18}
mb("acfwwx",1819) // obj = {name:"acfwwx",age:1819}
};

Three, call, apply and bind method application

1. When to use apply and when to use call

In the case of object parameters:

If the form of the parameter is an array, such as the parameter arguments passed in the apply example, this parameter is an array type, and the list of parameters is consistent when calling Person (that is, the first two digits of the parameter list of Person and Student are Consistent) can use apply.

If the parameter list of my Person is like this (age, name), and the parameter list of Student is (name, age, grade), then it can be realized by calling, that is, directly specify the position of the corresponding value of the parameter list ( Person. call(this,age,name,grade));

  • call方法:call(obj,x,y,z,…)
  • apply方法:apply(obj,[x,y,z])
<script type="text/javascript">  
    /*定义一个人类*/  
    function Person(name,age)  
    {
    
      
        this.name=name;  
        this.age=age;  
    }  
    /*定义一个学生类*/  
    functionStudent(name,age,grade)  
    {
    
      
        Person.apply(this,arguments);  //Person.call(this,name,age);
        this.grade=grade;  
    }  
    //创建一个学生类  
    var student=new Student("zhangsan",21,"一年级");  
    //测试  
    alert("name:"+student.name+"\n"+"age:"+student.age+"\n"+"grade:"+student.grade);  
    //大家可以看到测试结果name:zhangsan age:21  grade:一年级  
    //学生类里面我没有给name和age属性赋值啊,为什么又存在这两个属性的值呢,这个就是apply的神奇之处.  
</script>  

2. call and apply application scenarios

a. Mutual calls between functions

function add(a,b){
    
    
      alert(a+b);
}
function sub(a,b){
    
    
    alert(a-b);
}
add.call(sub,5,6); 
add.apply(sub,[5,6]); 
//弹出11,对象替换,等等这不是函数吗??  其实函数名是Function对象的引用。

b. Calls between constructors

function Person(){
    
    
     this.age = 50;
     this.showAge= function(){
    
    
        alert(this.age);
    }
}
function Son(){
    
    
    this.age  = 20;
}

// 让Son也具有Person的方法
// function Son(){
    
    
//     this.age  = 20;
//     Person.call(this);
//    //Person.apply(this)
// }

var father  = new Person();
var xiaoming = new Son();

father.showAge.apply(xiaoming)  //立即执行显示20
father.showAge.call(xiaoming)  //立即执行显示20
xiaoming.showAge();  //报错,showAge() is not a function

c. Multiple inheritance

Just use multiple calls or apply.

Scenario 1: Find the maximum or minimum value of an array with an uncertain length

var arr  = [1,2,3,.......n]
Math.min.apply(this,arr) // this可随便换,但需是一个对象

Scenario 2: Merge of two arrays

var arr1=new Array("1","2","3");  
var arr2=new Array("4","5","6");  
  
Array.prototype.push.apply(arr1,arr2); 

d. Array-like shared array method


function add() {
    
    
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);
 
    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var _adder = function() {
    
    
        _args.push(...arguments);
        return _adder;
}

function add() {
    
    
        // 第一次执行时,定义一个数组专门用来存储所有的参数
        var _args = [].slice.call(arguments);
        // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值,执行时已经收集所有参数为数组
        var adder = function () {
    
    
            var _adder = function () {
    
    
                // 执行收集动作,每次传入的参数都累加到原参数
                [].push.apply(_args, [].slice.call(arguments));
                return _adder;
            };
            // 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
            _adder.toString = function () {
    
    
                return _args.reduce(function (a, b) {
    
    
                    return a + b;
                });
            }
            return _adder;
        }
        return adder(_args);
    }
}

Guess you like

Origin blog.csdn.net/qq_43000315/article/details/125360096