apply、call、bind 简单介绍和从堆栈信息看this的改变

403 阅读3分钟

参考:

[译] JavaScript 中至关重要的 Apply, Call 和 Bind

JavaScript 中 call()、apply()、bind() 的用法

this、apply、call、bind

一、个人对apply,call,bind的认知

  1. 目的:常常用来改变原调用方法中,this的指向;
  2. 可以用apply,call,bind来优雅地处理很多生产使用中的问题,也是很多底层代码中经常被使用的,这篇文章只是基础理解,更深入的使用和介绍可以阅读参考文章,或者其他;

二、基础使用[vscode 中 this指向与浏览器跑的时候不一样]

1. 以下代码中,因为this指向调用方法getAgeobj对象,所以输出为小明,undefined,参考图片中,运行时堆栈信息里this变量的指向;

    var name="小王",age="22"
    var obj={
        name:"小明",
        age:this.age,  //对象声明过程中,这个this指向window
        getAge:function(){
            // 处于函数块中,此时this,指向函数所属的obj这个对象
            console.log(this.name,this.age)
        },
        doFun:function(city,job){
            console.log(this.name,this.age,city,job)
        }
    }

    obj.getAge()
    // output:  小明,undefined

image.png

2. 创建一个新对象newObj,如果想让newObj也能调用原obj的方法,那么此时就可以通过apply/call/bind来轻巧的解决;

    // 为了让newObj调用obj的方法,参数1都是this重新指向的对象
    obj.getAge.apply(newObj)   //typeof obj.getAge.apply(newObj) =>undefined,输出 执行结果,相当于执行了一次函数
    obj.getAge.call(newObj)    //typeof obj.getAge.call(newObj) =>undefined,输出 执行结果,相当于执行了一次函数
    obj.getAge.bind(newObj)()  //typeof obj.getAge.call(bind) =>function,说明仅返回函数声明

    // output:  小林,26

image.png image.png

三、参数传递

  1. 仅apply的第二参数开始以数组形式传递形参,call和bind传递参数,用逗号隔开。
    obj.doFun.apply(newObj,["北京","developer"])
    obj.doFun.call(newObj,"北京","developer")
    obj.doFun.bind(newObj,"北京","developer")()
    // output: 小林 26 北京 developer

image.png

四、部分简单使用扩展

  1. 比如改变函数声明中this(window)的指向(非node环境)。
var colorObj={
    color:"red"
}
var animal="cat"
function getName(){
    console.log(this.animal,animal,this.color)   
}

getName()   //output: cat cat undefined,这里的this指向window 
getName.call(colorObj)  //output: undefined cat red 这里的this指向colorObj
  1. 一些绑定在元素上的事件,比如click等,此时this和全局指向;
<body>
    <div id="domId" onclick="setColor()">domId</div>
    <script>
        var colorObj={
            color:"red",
            getColor:function(){
                return this.color
            }
        }
        function setColor(){
            console.log(this)
            this.style.color="red"
        }
        var ele=document.getElementById("domId")
        // setColor()   // =>error
        setColor.call(ele)  //=>success
    </script>
</body>
  1. 利用apply第二参数为数组的特性,轻巧地实现一些功能。
  • 数组比较大小,常规比较是排序后,取其中的最大数,这里可以借用Math.max等方法;
  • 通过pushapply,来拼接数组;
var nums=[1,240,33,42]
// 这里因为没有用到this,所以可以使用null
// 因为apply接收的第二个参数为数组,当做一系列的实参,实际上相当于Math.max(1,240,33,42)
console.log(Math.max.apply(null,nums))  //output: 240    
var alps=["A","B","C"]
Array.prototype.push.apply(nums,alps)
console.log(nums)   //output: [1, 240, 33, 42, "A", "B", "C"]
  1. 对于类数组借用Array的原型对象上(Array.prototype)的方法。
  • 我理解的类数组,一般指具备数组特征的类似对象 :通过角标调用,如arryLike[0];具有长度属性length;可以通过for循环遍历;
//类数组
var arryLike = {
    0: 1,
    1: 2,
    2: 3,
    length: 3
}
  • 常见的类数组,包括但不限于,函数形参中的arguments,HTML节点结合HTMLCollection等。
function fn(a, b) {
    //  arguments.pop() //=>error,arguments这种类数组无法使用全部Array的方法
    var last = Array.prototype.pop.call(arguments)  //通过这种方式来实现调用Array的一些方法
    console.log(last)  //output: 2
}
fn(1,2)