在JavaScript中,函数内的this关键字的行为非常依赖于函数是如何被调用的。在user, user2, 和 user3的例子中,sayHi函数被定义了三种不同的方式,这导致this的行为也存在差异。

user对象

这里使用了一个名为sayHi的普通函数表达式:

let user = {
    name: "Tom",
    sayHi: function () {
        console.log('Hi, I am ' + this.name);
    }
}

在这个例子中,sayHi是一个普通的函数属性。当它作为方法(即通过对象引用调用时,例如user.sayHi())被调用时,this指的是调用它的对象,即user。

user2对象

这里使用了一个ES6中的方法简写:

let user2 = {
    name: "Jack",
    sayHi() {
        console.log('Hi, I am ' + this.name);
    }
}

user2例子中的sayHi定义和user的很相似,唯一的区别是它使用了ES6的简化语法来创建方法。这并不改变this的行为:作为user2对象的方法被调用时,this同样指向user2。

user3对象

这里使用了箭头函数:

let user3 = {
    name: "Ben",
    sayHi: () => {
        console.log('Hi, I am ' + this.name);
    }
}

user3.sayHi使用了一个箭头函数,箭头函数不绑定this,相反,它们捕获定义时上下文的this值。在全局代码中定义的箭头函数中的this指向全局对象,在浏览器中通常是window。因此,无论你何时何地调用user3.sayHi,它打印出来的this.name总会引用定义时捕获的this的name属性,而不是user3的name属性。如果user3.sayHi在全局范围内定义,如在浏览器中使用<script>直接定义,则this.name很可能是undefined。

关于setTimeout
当你直接传递方法给setTimeout,如setTimeout(user.sayHi, 1000),你传递的是函数本身,而不是函数的调用。结果是,当setTimeout执行这个函数时,它没有了对象上下文,因此this不再引用原始的对象,除非像user和user2示例中一样使用.bind()方法明确地绑定this。
总结下三者间的区别:
user和user2展示了在对象内部定义方法的两种方式,都可以使用sayHi作为方法调用,其中this指向调用的对象。
user3的箭头函数导致this在定义时就固定下来,对箭头函数使用bind是无效的,因为箭头函数不绑定this。
使用.bind()可以修正setTimeout调用中this的指向,确保当方法在将来某个时间点执行时,this指向正确的对象。

call,apply和bind都是JavaScript中的函数方法,它们都可以改变函数调用时this的指向。不过它们之间各有其特定的使用场景和差异。
call: call()方法接受一个参数列表,这个方法无需改变函数的调用时机,可立即执行函数。常常用来借用别的对象的方法。

func.call(thisArg, arg1, arg2, ...)

apply: apply()方法接受一个包含多个参数的数组,与call()类似,apply()也会立即执行函数。当你不知道要传递给函数多少个参数,或者参数是作为一个数组存在时,apply()非常有用。

func.apply(thisArg, [argsArray])

bind: bind()方法返回一个新的函数,使得这个新函数将this设置为bind()的第一个参数。bind()不会立即执行函数,而是返回一个绑定了正确this值的新函数,这点是bind()和call()、apply()的主要区别。bind()非常适合用在事件监听上。

newFunc = func.bind(thisArg[, arg1[, arg2[, ...]]])

总结一下:
call和apply都是为了解决不同对象之间方法的借用,包括他们的参数列表形式不同之处。
bind是将函数体内this对象绑定为指定对象,返回一个新函数,供日后调用。
apply和call都会立即执行函数,而bind不会。

案例:

        // // 数组连接(concatenate): 使用apply将一个数组添加到另一个数组的末尾。
        let array1 = [1, 2, 3]
        let array2 = [4, 5, 6]
        Array.prototype.push.apply(array1, array2)
        console.log(array1)  // 输出: [1, 2, 3, 4, 5, 6]
        // // 这个例子中,apply方法改变了push方法的上下文并执行它,新的上下文是array1,参数是array2。

        // // // 找到数组中的最大值: 使用apply或者call找到数组中的最大值
        let array = [1, 2, 3, 4, 5]
        console.log(Math.max.apply(null, array)); //5
        // // 在这个例子中,apply方法更改了Math.max的上下文并执行它,通过传递数组作为参数。同样,call也可帮助我们达到同样的效果:
        console.log(Math.max.call(null, ...array)); //5
        // // 这里...array是ES6的语法特性,它将数组转为参数序列。

        // 函数柯里化: bind经常用于函数柯里化。它允许你部分地应用函数,将其余的参数留到函数被实际调用时指定。
        function add(x) {
            return function (y) {
                return x + y
            }
        }
        let add5 = add(5).bind(this);
        console.log(add5(3));
        // 在这个例子中,bind方法创建了一个新的add5函数,它是add(5)的一个固定版本,现在它总是将5作为第一个参数加到其余的参数上。