【JavaScript由浅入深】this绑定详解(一)
this是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。
- 1.函数在调用时,JavaScript会默认给this绑定一个值;
- 2.this的绑定和定义的位置(编写的位置)没有关系;
- 3.this的绑定和调用方式以及调用的位置有关系;
- 4.this是在运行时被绑定的;
this的绑定方式:
- 绑定一:默认绑定(非严格模式下this指向全局对象, 严格模式下
this
会绑定到undefined
) - 绑定二:隐式绑定(当函数引用有上下文对象时, 如
obj.foo()
的调用方式,foo
内的this
指向obj
) - 绑定三:显示绑定(通过
call()
或者apply()
方法直接指定this
的绑定对象, 如foo.call(obj)
) - 绑定四:new绑定(使用
new
来调用一个函数,会构造一个新对象并把这个新对象绑定到调用函数中的this
)
一、默认绑定
- 什么情况下使用默认绑定呢?独立函数调用。
- 独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用;
- 严格模式下,独立调用的函数中的this指向的是
undefined
// 1.普通的函数被独立的调用
function foo1() {
console.log("foo1", this)
}
foo1()
// 2.函数被定义在对象中,但是被独立调用
var obj1 = {
name: "zhang",
foo2: function() {
console.log("foo2", this)
}
}
var obj2 = obj1.foo2
obj2()
二、隐式绑定
- 另外一种比较常见的调用方式是通过某个对象进行调用的:
- 也就是它的调用位置中,是通过某个对象发起的函数调用。
// 1.通过对象调用
function foo() {
console.log("foo",this)
}
var obj1 = {
name: "obj1",
foo: foo
}
obj1.foo()
var obj2 = {
name: "obj2",
foo: function() {
console.log("obj2",this);
}
}
obj2.foo()
三、显式绑定
- 隐式绑定有一个前提条件:
- 必须在调用的对象内部有一个对函数的引用(比如一个属性);
- 如果没有这样的引用,在进行调用时,会报找不到该函数的错误;
- 正是通过这个引用,间接的将this绑定到了这个对象上;
- 如果我们不希望在对象内部包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?
- 显示绑定顾名思义,通过调用
call
/apply
/bind
方法,强制将函数中的this
绑定到指定的对象,三者区别如下:call()
接受的是多个对象作为参数。apply()
接受的是多个对象组成的数组作为参数。bind()
返回的是一个函数,除此之外使用方法与call()
一样。
3.1 call()
function.call(thisArg, arg1, arg2, ...)
参数:
thisArg
可选的。在function
函数运行时使用的this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为null
或undefined
时会自动替换为指向全局对象,原始值会被包装。arg1, arg2, ...
指定的参数列表。
返回值:
使用调用者提供的 this
值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined
。
function foo(name, age) {
console.log("foo函数被调用",this);
console.log(name, age);
}
foo.call(window)
foo.call("call", "zhang", 16)
foo.call(456)
3.2 apply()
apply()
方法调用一个具有给定this
值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。
apply(thisArg, argsArray)
参数:
thisArg
在function
函数运行时使用的this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为null
或undefined
时会自动替换为指向全局对象,原始值会被包装。argsArray
可选 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给func
函数。如果该参数的值为null
或undefined
,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象
返回值:
- 调用有指定
this
值和参数的函数的结果。
与
call()
根本区别在于,call()
接受一个参数列表,而apply()
接受一个参数的单数组。
function foo(name, age) {
console.log("foo函数被调用",this);
console.log(name, age);
}
foo.apply() // 默认this指向全局对象
foo.apply("apply",["zhang", 20])
3.3 bind()
bind()
方法创建一个新的函数,在bind()
被调用时,这个新函数的this
被指定为bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
- bind() 函数会创建一个新的绑定函数(bound function,BF)。
- 绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语),它包装了原函数对象。
- 调用绑定函数通常会导致执行包装函数。
function.bind(thisArg[, arg1[, arg2[, ...]]])
参数:
thisArg
调用绑定函数时作为this
参数传递给目标函数的值。- 如果使用
new
运算符构造绑定函数,则忽略该值。 - 当使用
bind
在setTimeout
中创建一个函数(作为回调提供)时,作为thisArg
传递的任何原始值都将转换为object
。 - 如果
bind
函数的参数列表为空,或者thisArg
是null
或undefined
,执行作用域的this
将被视为新函数的thisArg
。
- 如果使用
arg1, arg2, ...
当目标函数被调用时,被预置入绑定函数的参数列表中的参数
返回值:
- 返回一个原函数的拷贝,并拥有指定的
this
值和初始参数。
function foo(age, number) {
console.log("foo:", this);
console.log("参数:",age, number);
}
var obj = { name: "zhang" }
// 当调用foo函数时,希望绑定到obj对象身上,但不希望obj对象身上有函数
var bar1 = foo.bind(obj)
bar1()
var bar2 = foo.bind(obj, 19, 001)
bar2()
四、new绑定
- JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用
new
关键字。 - 使用new关键字来调用函数是,会执行如下的操作:
- 创建一个全新的对象;
- 这个新对象会被执行prototype连接;
- 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
- 如果函数没有返回其他对象,表达式会返回这个新对象;
// 创建Student
function Student(name) {
console.log(this);
this.name = name;
}
var stu = new Student("zhang");
console.log("stu", stu);
五、内置函数的绑定
- 有些时候,我们会调用一些JavaScript的内置函数,或者一些第三方库中的内置函数。
- 这些内置函数会要求我们传入另外一个函数;
- 我们自己并不会显示的调用这些函数,而且JavaScript内部或者第三方库内部会帮助我们执行;
- 这些函数中的this又是如何绑定的呢?
// setTimeout
setTimeout(function() {
console.log("setTimeout:",this); //window
}, 1000)
// div的点击
var box = document.querySelector(".box")
box.onclick = function() {
console.log("div:", this); //div
console.log(this === box)
}
// 数组的forEach
var stus = ["jaens", "zhang"]
stus.forEach(function (item) {
console.log("forEach:", this); //window
})
forEach()方法参数中有可选参数thisArg
,当执行回调函数 callbackFn
时,用作 this
的值。
- 如果
thisArg
参数有值,则每次callbackFn
函数被调用时,this
都会指向thisArg
参数。 - 如果省略了
thisArg
参数,或者其值为null
或undefined
,this
则指向全局对象。
var stus = ["jaens", "zhang"]
var obj = {name: "beak"}
stus.forEach(function (item) {
console.log("forEach:", this); //两次obj对象
}, obj)