草庐IT

call ,apply和bind方法 详解

Summer_dog 2023-04-10 原文

文章目录


前言

call()、apply()和bind()方法 三者作用都是 改变this指向

本文旨在探讨三者之间的区别和作用。

  1. call, apply, bind 三者的区别在哪里
  2. 什么情况下用apply,什么情况下用call
  3. apply的其他巧妙用法(一般在什么情况下可以使用apply)

bind、call、apply都是用来指定一个函数内部的this的值, 先看看bind、call、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

一、 call和apply

1. call() 方法

call()方法接受的语法和作用与apply()方法类似,只有一个区别就是call()接受的是一个参数列表,而apply()方法接受的是一个包含多个参数的数组。

二者都是函数对象Function的方法,且第一个参数都是要绑定对象的上下文
例如:

let obj = {
    a: 1,
    get: function(){
        return 2
    }
}
let g = obj.get
g.call({},1,2,3)
g.apply({},[1,2,3])
  • call方法调用父构造函数
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);
  • call方法调用匿名函数
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调用匿名函数
}
  • call方法调用函数并且指定上下文的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"
  • call方法调用函数并且不指定第一个参数(argument)

在这个例子中,我们没有传递第一个参数,this的值将被绑定为全局对象。

var sData = 'marshall';

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

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

但是在严格模式下,this 的值将会是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() 方法

使用 apply, 我们可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。

apply 与 call() 非常相似,不同之处在于提供参数的方式。apply 使用参数数组而不是一组参数列表。apply 可以使用数组字面量(array literal),如 fun.apply(this, [‘eat’, ‘bananas’]),或数组对象, 如 fun.apply(this, new Array(‘eat’, ‘bananas’))。

  • apply方法调用一个具有给定this值的函数,以及以一个数组的形式提供参数。
var array = ['marshall','eminem'];
var elements = [0,1,2];
array.push.apply(array,elements);
console.log(array);  //['marshall','eminem',0,1,2]
  • 使用apply和内置函数

对于一些需要写循环以遍历数组各项的需求,我们可以用apply完成以避免循环。

//找出数组中最大值和最小值
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);

上边这种调用apply的方法,有超出JavaScript引擎参数长度上限的风险。
如果我们的参数数组非常大,推荐使用下边这种混合策略:将数组切块后循环传入目标方法

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. apply与call的实现

具体代码如下:

// 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

二、bind

1. bind 简介

bind()函数会创建一个新的绑定函数,这个绑定函数包装了原函数的对象。调用绑定函数通常会执行包装函数。
绑定函数内部属性:

  • 包装的函数对象
  • 在调用包装函数时始终作为this传递的值
  • 在对包装函数做任何调用时都会优先用列表元素填充参数列表。

而原函数 retrieveX 中的 this 并没有被改变,依旧指向全局对象 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

bind传递参数问题:
在通过bind改变this指向的时候所传入的参数会拼接在调用返回函数所传参数之前,多余参数不起作用。

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

bind传入的参数和newShowName方法传入的参数会拼接在一起,一齐传给showName方法。

bind无法改变构造函数的this指向

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函数体

可以看出,通过bind改变this指向返回函数的构造器还是最开始的showName函数。

new newShowName()实例化了一个新的方法,这个方法的this也不再指向newThis。

2. bind的实现

通过apply模拟bind源码实现:

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}
};

三、 call ,apply 和bind方法应用

1. 什么情况下用apply,什么情况下用call

在给对象参数的情况下:

如果参数的形式是数组的时候,比如apply示例里面传递了参数arguments,这个参数是数组类型,并且在调用Person的时候参数的列表是对应一致的(也就是Person和Student的参数列表前两位是一致的) 就可以采用 apply。

如果我的Person的参数列表是这样的(age,name),而Student的参数列表是(name,age,grade),这样就可以用call来实现了,也就是直接指定参数列表对应值的位置(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和apply 应用场景

a. 函数之间的相互调用

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. 构造函数之间的调用

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. 多重继承

使用多个call 或者apply 即可。

场景1:找出一个数组的最大值或最小值,数组长度不确定

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

场景2:两数组合并

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

d. 类数组共用数组方法


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);
    }
}

有关call ,apply和bind方法 详解的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  5. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  6. ruby - 在 Ruby 中实现 `call_user_func_array` - 2

    我怎样才能完成http://php.net/manual/en/function.call-user-func-array.php在ruby中?所以我可以这样做:classAppdeffoo(a,b)putsa+benddefbarargs=[1,2]App.send(:foo,args)#doesn'tworkApp.send(:foo,args[0],args[1])#doeswork,butdoesnotscaleendend 最佳答案 尝试分解数组App.send(:foo,*args)

  7. Ruby 方法() 方法 - 2

    我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby​​-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco

  8. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  9. ruby - Highline 询问方法不会使用同一行 - 2

    设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案

  10. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

随机推荐