this 到底是什么
因为
this的取值是函数执行上下文(context)的一部分,每次调用函数,都会产生一个新的执行上下文环境。当代码中使用了this,这个this的值就直接从执行上下文中获取了,而不会从作用域链中搜寻。
this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。当一个函数被调用时,会创建一个活动记录(也称为执行上下文)。这个记录会包含函数在哪里被调用、函数的调用方法、传入的参数等信息。this 就是记录的其中一个属性,会在函数执行的过程中用到。
this 在全局作用域
浏览器环境下 this 在全局作用域指向 window。
Node环境下 this 在全局作用域指向空对象 {},Node 中每一个文件就是一个模型,经过加载、编译,会将代码放到一个函数中,通过 call() 调用该函数。而 call() 传入的第一个参数(用于指定 this )是一个 {}。
this 的绑定规则
默认绑定
独立函数调用,是无法应用其他规则时的默认规则。
声明在全局作用域中的变量就是全局对象的一个同名属性。它们本质上是一个东西,并不是通过赋值得到的。如果函数是直接使用不带任何修饰的函数引用进行调用的,只能使用默认绑定,无法应用其他规则。this 的绑定规则完全取决于调用位置,但是只有函数运行在非严格模式下时,默认绑定才能绑定到全局对象。
隐式绑定
function foo() {
console.log(this.a)
}
var obj = {
a:2,
foo:foo
}
obj.foo() // 2
无论是直接在 obj 中定义还是先定义再添加为引用属性,这个函数严格来说都不属于 obj 对象。调用位置会使用 obj 上下文来引用函数,当 foo 被调用时,它的落脚点指向 obj 对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
}
var obj1 = {
a: 2,
obj2: obj2
}
obj1.obj2.foo(); // 42
显式绑定
允许指定函数调用时的 this 值,这意味着可以将任意函数作为任意对象的方法来调用。即使这个函数实际上并不是该对象的方法。
call() 和 apply() 的第一个参数是一个对象,它们会把这个对象绑定到 this ,接着在调用函数时指定这个 this 。因为可以直接指定 this 绑定的对象,因此称之为显式绑定。
function foo() {
console.log(this.a)
}
var obj = {
a:2
}
foo.call(obj) // 2
通过 foo.call(..) 可以在调用 foo 时强制把它的 this 绑定到 obj 上。
bind() 硬绑定是显式绑定的一个变种。bind() 会返回一个硬编码的新函数,它会把参数设置为 this 的上下文并调用原始函数。
function foo(something) {
console.log( this.a, something )
return this.a + something
}
var obj = {
a:2
}
var bar = foo.bind( obj )
bar( 3 ) // 2 3
注意:
bar()是独立函数调用,为什么this不绑定window?因为bind是显式绑定比隐式绑定优先级高。
new 绑定
使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
- 创建或者说构造一个全新的对象;
- 这个新对象会被执行 [[原型]] 连接;
- 这个新对象会被绑定到函数调用的
this; - 如果函数没有返回其他对象,那么
new表达式中的函数调用自动返回这个新对象。
function foo(a) {
this.a = a
}
var bar = new foo(2)
console.log( bar.a ) // 2
this 绑定的优先级
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定。根据优先级来判断函数在某个调用位置应用哪条规则。可以按照下面顺序进行判断。
-
函数是否在 new 中调用?如果是,this 绑定的是新创建的对象。
var bar = new foo() -
函数是否通过
callapply显式绑定或bind硬绑定调用?如果是,this绑定的是指定的对象。var bar = foo.call(obj) -
函数是否在某个上下文对象中调用(隐式绑定)?如果是,
this绑定的是那个上下文对象。var bar = obj.foo() -
如果以上都不是,使用默认绑定。非严格模式下绑定到全局对象,严格模式下绑定到
undefined。var bar = foo()
箭头函数的 this
箭头函数中的 this 与定义时所处的上下文绑定,且不能被改变。箭头函数 this 指向取决于它外层找到的离它最近的第一个非箭头函数的 this。
显式绑定 this 的方式
call()
function.call(thisArg, arg1, arg2, ...) thisArg 可选的。在 function 函数运行时 this 要指向的那个对象。如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象。
// 将类数组对象转为数组
Array.prototype.slice.call(arguments);
// 取出数组中最小数值
var arr = [1,2,3]
Math.min.call(this,...arr) // 1
apply()
A.apply( B, [ arg1, arg2, ... ] ) B是调用方法的真正对象。apply() 第一个参数也是 this 要指向的那个对象,如果设为 null 或 undefined,则等同于指定全局对象。
第二个参数是一个数组,数组的成员会依次作为参数传入原函数。
// 转换类数组对象
Array.prototype.slice.apply({0: 1,1: 2, length: 2}) //[1,2]
// 求数组元素中最大数
Math.max.apply(null,[1, 2, 3, 4, 5])
// 将一个数组添加到另一个数组尾部
var arr1 = [0,1,2]
var arr2 = [3,4,5]
// arr1调用push方法,参数是arr2提供
Array.prototype.push.apply(arr1, arr2)
console.log(arr1) //[ 0, 1, 2, 3, 4, 5 ]
bind()
如果我们希望一个函数总是显式的绑定到一个对象上,bind() 方法会创建一个新的函数实例,其 this 会被绑定到传给 bind() 的对象。bind() 可以将原始函数和新函数的参数做合并,在原始函数中输出。
function foo(num1,num2,...args) {
console.log(num1, num2,...args)
}
var bar = foo.bind(null,10,20)
bar(30,40) // 10 20 30 40
在 sayColor() 上调用 bind() 并传入对象 o 创建了一个新函数 objectSayColor()。objectSayColor() 中的 this 值被设置为 o,因此直接调用这个函数,即使是在全局作用域中调用,也会返回字符串 'blue'
window.color = 'red'
var o = {
color: 'blue'
}
function sayColor() {
console.log(this.color)
}
let objectSayColor = sayColor.bind(o)
objectSayColor() // blue
注意:
bind()并不会改变原有函数的this指向,而是创建新的函数,改变的是新函数的this