call()、apply()和bind()用法、区别和场景使用

先简单了解下:
例一:

var name = "小王";
var age = 17;
var obj = {
    name: "小张",
    objAge: this.age,
    myFun: function() {
        console.log(this.name + "年龄" + this.age);
    }
}
console.log(obj.objAge); // 17
console.log(obj.myFun()); // 小张年龄 undefined

例二:

var fav = "盲僧"
function shows(){
    console.log(this.fav);
}
console.log(shows()); // 盲僧

比较一下这两者 this 的差别,第一个打印里面的 this 指向 obj,第二个全局声明的 shows() 函数 this 是 window ;

  1. call()、apply()、bind() 都是用来重定义 this 这个对象的!
var name = "小王";
var age = 17;
var obj = {
    name: "小张",
    objAge: this.age,
    myFun: function() {
        console.log(this.name + "年龄" + this.age);
    }
}
var db = {
    name: "德玛",
    age: 99
}
console.log(obj.myFun.call(db)); // 德玛年龄 99
console.log(obj.myFun.apply(db)); // 德玛年龄 99
console.log(obj.myFun.bind(db)()); // 德玛年龄 99

以上出了:

  1. 如果call()和apply()的第一个参数是null或者undefined,那么this的指向就是全局变量,在浏览器里就是window对象。
  2. bind 方法后面多了个 () 外 ,结果返回都一致!
    由此得出结论,bind 返回的是一个新的函数,你必须调用它才会被执行。

2,对比call 、bind 、 apply 传参情况下

var name = "小王";
var age = 17;
var obj = {
    name: "小张",
    objAge: this.age,
    myFun: function(fm,t) {
        console.log(this.name + "年龄" + this.age, "来自" + fm + "去往" + t);
    }
}
var db = {
    name: "德玛",
    age: 99
}
obj.myFun.call(db,'成都','上海');     // 德玛 年龄 99  来自 成都去往上海
obj.myFun.apply(db,['成都','上海']);      // 德玛 年龄 99  来自 成都去往上海  
obj.myFun.bind(db,'成都','上海')();       // 德玛 年龄 99  来自 成都去往上海
obj.myFun.bind(db,['成都','上海'])();   // 德玛 年龄 99  来自 成都, 上海去往 undefined

微妙的差距!

从上面四个结果不难看出:

call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就来了:

call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔,直接放到后面 obj.myFun.call(db,'成都', ... ,'string' )。

apply 的所有参数都必须放在一个数组里面传进去 obj.myFun.apply(db,['成都', ..., 'string' ])。

bind 除了返回是函数以外,它 的参数和 call 一样。

当然,三者的参数不限定是 string 类型,允许是各种类型,包括函数 、 object 等等!

  • 总结:
  1. 每个函数都包含两个非继承而来的方法:call()方法和apply()方法。
  2. 相同点:这两个方法的作用是一样的。
    都是在特定的作用域中调用函数,等于设置函数体内this对象的值,以扩充函数赖以运行的作用域。
    一般来说,this总是指向调用某个方法的对象,但是使用call()和apply()方法时,就会改变this的指向。
  3. 不同点:接收参数的方式不同。
    apply()方法 接收两个参数,一个是函数运行的作用域(this),另一个是参数数组。
    语法:apply([thisObj [,argArray] ]);,调用一个对象的一个方法,2另一个对象替换当前对象。

说明:如果argArray不是一个有效数组或不是arguments对象,那么将导致一个
TypeError,如果没有提供argArray和thisObj任何一个参数,那么Global对象将用作thisObj。

call()方法 第一个参数和apply()方法的一样,但是传递给函数的参数必须列举出来。
语法:call([thisObject[,arg1 [,arg2 [,...,argn]]]]);,应用某一对象的一个方法,用另一个对象替换当前对象。

说明: call方法可以用来代替另一个对象调用一个方法,call方法可以将一个函数的对象上下文从初始的上下文改变为thisObj指定的新对象,如果没有提供thisObj参数,那么Global对象被用于thisObj。

call():

call方法既可以调用函数,又可以改变函数内的this指向。

var obj = {
	name: 'andy'
}
function fn(a, b) {
console.log(this);
console.log(a+b)
};
fn(1,2)// 此时的this指向的是window 运行结果为3
fn.call(obj,1,2)//此时的this指向的是对象obj,参数使用逗号隔开,运行结果为3

应用场景: 经常做继承,参考下面这篇讲原型链的末尾部分:https://blog.csdn.net/caipital/article/details/108396438

function f1(a,b){
    console.log(this)  //输出f2
    console.log(a+b)	//输出3
}
function f2 (a,b) {
    console.log(this)
    console.log(a-b)
}
f1.call(f2,1,2)		//打印输出f2 和 3

利用call()判断数据类型(在判断数据类形式使用typeof,一般不是太准确的,我们可以使用Object.prototype.toString.call()方法来判断一个数据的数据类型

console.log(Object.prototype.toString.call("sun"))            // [Object String] 返回值都是字符串类型
console.log(Object.prototype.toString.call(18))              // [object Number]
console.log(Object.prototype.toString.call(false))           // [object Boolean]
console.log(Object.prototype.toString.call(undefined))       // [object Undefined]
console.log(Object.prototype.toString.call(null))            // [object Null]
console.log(Object.prototype.toString.call(function(){}))    // [object Function]
console.log(Object.prototype.toString.call([]))              // [object Array]
console.log(Object.prototype.toString.call({}))              // [object Object]

// 封装
function getType(para){
   var obj = Object.prototype.toString.call(para); //区分对象类型  确定当前的数据的类型
   var sub = obj.substr(8); 
   // stringObject.substr(start,length)  start 要抽取的子符串的起始下标,
   // length 截取的长度,如果不写则表示从start开始截取到最后 ,stringObject表示某一字符串
  var len = sub.length;
  var sub = sub.substr(0,len-1)
  var change =  sub.toLowerCase(sub) //转换成小写
  return change ;
}
 console.log(getType("sun")); //string

翻转字符串

//思路:将字符串转化为数组,借用数组中的reverse,将字符串翻转过来
 var str = "abcdefg";
 console.log(Array.prototype.reverse.call(str)); //此时会报错误,即引用类型错误,就是说只有数组才能使用reverse这个方法;(错误写法)
    //方法一:这种方法内有使用call()
 var arr =  Array.from(str).reverse().join("") //将字符串转化为数组,在进行翻转,然后在进行拼接
 console.log(arr) //gfedcba
 console.log(typeof arr) //string
     //方法二:
var rs = Array.prototype.reverse.call(str.split("")).join(""); 
    //splice(start,length)方法用于把一个字符串分割成字符串数组,start 表示从指定的地方分割字符串    length表示分割的长度。
    //返回一个一个字符串数组 如果把空字符串 ("") 用为参数那么字符串中的每个字符之间都会被分割
console.log(rs); //gfedcba
console.log(typeof arr) //string

apply()

call方法既可以调用函数,又可以改变函数内的this指向。与call()的区别是函数调用时参数是数组。

function fn(a,b){
    console.log(this)  
    console.log(a+b)
}
let obj = {
    name:'zy'
}
fn.apply(obj,[1,2]) //此时的this指向的是对象obj,参数传入了一个数组,运行结果为3

应用场景:

  1. 数组运用到Math的API中,如:Math.max,Math.min等,
  2. Array.prototype.push 可以实现两个数组合并等
    Math的API:
let a = Math.max(9,21,33);
console.log(a)   //33

let arr = [9,21,33];
let res1 = Math.max(...arr)  
let res2 = Math.max.apply(null,arr); 
let res3 = Math.max.apply(Math,arr);   // 推荐  
let res4 = Math.max.apply(Function,arr);  

console.log(res1)    //33
console.log(res2)    //33
console.log(res3)    //33
console.log(res4)    //33

数组合并:

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

bind()

bind()改变函数this指向,但不调用函数,会生成一个新的函数

function fn(a,b){
     console.log(this)  
     console.log(a+b)
 }
 let obj = {
     name:'zy'
 }
 let new_fn1 = fn.bind(obj)
 new_fn1(2,3)    //this指向obj 打印输出5
 let new_fn2 = fn.bind(obj,1,2)
 new_fn2()   //this指向obj 打印输出3

应用场景:当你想改变函数的this指向同时又不想立即执行函数的时候。
例子:要求:给三个按钮绑定点击事件,点击之后按钮变成不可点的状态,1秒之后恢复,变成可点击的状态。
方案一 :let 块级作用域

for(let i =0;i<btns.length;i++) {
    btns[i].onclick = function() {
        //表达式函数this指向绑定事件的对象
        this.disabled = true;
        setTimeout(function(){
            btns[i].disabled = false;
        },1000)
    }
}

方案二:使用自调用函数(闭包思想)
闭包(closure):指有权访问另一个函数作用域中变量的函数。

for (var i = 0; i < btns.length; i++) {
    (function (i) {
        btns[i].onclick = function () {
            //表达式函数this指向绑定事件的对象
            this.disabled = true;
            setTimeout(function () {
                console.log(this)   //Window
                btns[i].disabled = false;
            }, 1000)
        }
    })(i)
}

方案三: var that = this,储存this(闭包)

for(var i =0;i<btns.length;i++) {
    btns[i].onclick = function() {
        //表达式函数this指向绑定事件的对象
        this.disabled = true;
        var that = this;
        setTimeout(function(){
            that.disabled = false;
        },1000)
    }
}

方案四:使用 bind()改变this指向

for(var i =0;i<btns.length;i++) {
    btns[i].onclick = function() {
        //表达式函数this指向绑定事件的对象
        this.disabled = true;
        setTimeout(function(){
            this.disabled = false;
        }.bind(this),1000)
    }
}

参考地址:
https://www.runoob.com/w3cnote/js-call-apply-bind.html
https://www.cnblogs.com/phoebeyue/p/9216514.html
https://blog.csdn.net/caipital/article/details/108437024

砥砺前行
原文地址:https://www.cnblogs.com/lhongsen/p/14849053.html