this
值为当前执行上下文(global、functio或eval)的一个属性,在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。
描述
全局上下文
无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this
都指向全局对象。
// 在浏览器中, window 对象同时也是全局对象:
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "MDN";
console.log(window.b) // "MDN"
console.log(b) // "MDN"
函数上下文
在函数内部,this
的值取决于函数被调用的方式。
function f1(){
return this;
}
//在浏览器中:
f1() === window; //在浏览器中,全局对象是window
//在Node中:
f1() === globalThis;
然而,在严格模式下,如果进入执行环境时没有设置 this
的值,this
会保持为 undefined
,如下:
function f2(){
"use strict"; // 这里是严格模式
return this;
}
f2() === undefined; // true
注:
- 箭头函数比较特殊没有自己的 this,也不可以改变 this 的绑定,其 this 指向函数所在的作用域 。
类上下文
this
在 类 中的表现与在函数中类似,因为类本质上也是函数,但也有一些区别和注意事项。
在类的构造函数中,this
是一个常规对象。类中所有非静态的方法都会被添加到 this
的原型中:
class Example {
constructor() {
const proto = Object.getPrototypeOf(this);
console.log(Object.getOwnPropertyNames(proto));
}
first(){}
second(){}
static third(){}
}
new Example(); // ['constructor', 'first', 'second']
派生类
不像基类的构造函数,派生类的构造函数没有初始的 this
绑定。在构造函数中调用 super()
会生成一个 this
绑定,并相当于执行如下代码,Base为基类
this = new Base();
派生类不能在调用 super()
之前返回,除非其构造函数返回的是一个对象,或者根本没有构造函数。
class Base {}
class Good extends Base {}
class AlsoGood extends Base {
constructor() {
return {a: 5};
}
}
class Bad extends Base {
constructor() {}
}
new Good();
new AlsoGood();
new Bad(); // ReferenceError
例题
1.1
var a = 1
function foo () {
var a = 2
console.log(this)
console.log(this.a)
}
foo()
// Window
// 1
1.2
var a = 1
function foo () {
var a = 2
function inner () {
console.log(this.a)
}
inner()
}
foo()
// 1
2、隐式绑定的丢失问题
隐式丢失其实就是被隐式绑定的函数在特定的情况下会丢失绑定对象。
有两种情况容易发生隐式丢失问题:
- 使用另一个变量来给函数取别名
- 将函数作为参数传递时会被隐式赋值,回调函数丢失this绑定
2.1 使用另一个变量来给函数取别名
function foo () {
console.log(this.a)
};
var obj = { a: 1, foo };
var a = 2;
var foo2 = obj.foo;
obj.foo();
foo2();
// 1
// 2
var obj = { a: 1, foo };
就相当于是var obj = { foo: foo }
,函数foo()
虽然是定义在window
下,但是我在obj
对象中引用了它,并将它重新赋值到obj.foo
上。且调用它的是obj
对象,因此打印出来的this.a
应该是obj
中的a
。
这是因为虽然foo2
指向的是obj.foo
函数,不过调用它的却是window
对象,所以它里面this
的指向是为window
。
2.2 将函数作为参数传递
function foo () {
console.log(this.a)
}
function doFoo (fn) {
console.log(this)
fn()
}
var obj = { a: 1, foo }
var a = 2
doFoo(obj.foo)
// Window
// 2
function foo () {
console.log(this.a)
}
function doFoo (fn) {
console.log(this)
fn()
}
var obj = { a: 1, foo }
var a = 2
var obj2 = { a: 3, doFoo }
obj2.doFoo(obj.foo)
// { a:3, doFoo: f }
// 2
var obj1 = {
a: 1
}
var obj2 = {
a: 2,
foo1: function () {
console.log(this.a)
},
foo2: function () {
setTimeout(function () {
console.log(this)
console.log(this.a)
}, 0)
}
}
var a = 3
obj2.foo1()
obj2.foo2()
// 2
// Window
// 3
对于setTimeout
中的函数,这里存在隐式绑定的隐式丢失,也就是当我们将函数作为参数传递时会被隐式赋值,回调函数丢失this
绑定,因此这时候setTimeout
中的函数内的this
是指向window
的。
3、显示绑定call、apply、bind
如果call、apply、bind
接收到的第一个参数是空或者null、undefined
的话,则会忽略这个参数。
3.1
function foo () {
console.log(this.a)
}
var obj = { a: 1 }
var a = 2
foo()
foo.call(obj)
foo.apply(obj)
foo.bind(obj)
// 2
// 1
// 1
foo.call()
foo.call(null)
foo.call(undefined)
// 2
// 2
// 2
var obj1 = {
a: 1
}
var obj2 = {
a: 2,
foo1: function () {
console.log(this.a)
},
foo2: function () {
setTimeout(function () {
console.log(this)
console.log(this.a)
}.call(obj1), 0)
}
}
var a = 3
obj2.foo1()
obj2.foo2()
// 2
// { a: 1 }
// 1
3.2
function foo () {
console.log(this.a)
}
var obj = { a: 1 }
var a = 2
foo()
foo.call(obj)
foo().call(obj)
// 2
// 1
// 2
// Uncaught TypeError: Cannot read property 'call' of undefined
foo()
会正常打印出window
下的a
,也就是2
foo.call(obj)
由于显式绑定了this
,所以会打印出obj
下的a
,也就是1
foo().call(obj)
开始会执行foo()
函数,打印出2
,但是会对foo()
函数的返回值执行.call(obj)
操作,可是我们可以看到foo()
函数的返回值是undefined
,因此就会报错了。
所以我们可以看到foo.call()
和foo().call()
的区别了,一个是针对于函数,一个是针对于函数的返回值。
加上返回值看一下:
function foo () {
console.log(this.a)
return function () {
console.log(this.a)
}
}
var obj = { a: 1 }
var a = 2
foo()
foo.call(obj)
foo().call(obj)
// 2
// 1
// 2
// 1
4、箭头函数
var name = 'window'
var obj1 = {
name: 'obj1',
foo: function () {
console.log(this.name)
}
}
var obj2 = {
name: 'obj2',
foo: () => {
console.log(this.name)
}
}
obj1.foo()
obj2.foo()
// 'obj1'
// 'window'
- 不使用箭头函数的
obj1.foo()
是由obj1
调用的,所以this.name
为obj1
。 - 使用箭头函数的
obj2.foo()
的外层作用域是window
,所以this.name
为window
。
var name = 'window'
var obj1 = {
name: 'obj1',
foo: function () {
console.log(this.name)
return function () {
console.log(this.name)
}
}
}
var obj2 = {
name: 'obj2',
foo: function () {
console.log(this.name)
return () => {
console.log(this.name)
}
}
}
var obj3 = {
name: 'obj3',
foo: () => {
console.log(this.name)
return function () {
console.log(this.name)
}
}
}
var obj4 = {
name: 'obj4',
foo: () => {
console.log(this.name)
return () => {
console.log(this.name)
}
}
}
obj1.foo()() // obj1 window
obj2.foo()() // obj2 obj2
obj3.foo()() // window window
obj4.foo()() // window window
obj1.foo()()
两层都是普通函数,外层函数的调用者 obj1,外层函数的调用者为 window,分别打印出obj1
和window
。obj2.foo()()
外层为普通函数,内层为箭头函数,外层函数的调用者为 obj2,内层箭头函数的 this 由外层作用域决定,即为 obj2,分别打印出 obj2 、obj2obj3.foo()()
外层为箭头函数,内层为普通函数,箭头函数的this
由外层作用域决定,因此为window
,内层普通函数由调用者决定,调用它的是window
,因此也为window
。obj4.foo()()
两层都是箭头函数,第一个箭头函数的this
由外层作用域决定,因此为window
,第二个箭头函数的this
也由外层作用域决定,它的外层作用域是第一个箭头函数,而第一个箭头函数的this
是window
,因此内层的this
也是window
。
call、apply、bind
call
call()
方法使用一个指定的 this
值和单独给出的一个或多个参数来调用一个函数。
注意:该方法的语法和作用与
apply()
方法类似,只有一个区别,就是call()
方法接受的是一个参数列表,而apply()
方法接受的是一个包含多个参数的数组。
语法
function.call(thisArg, arg1, arg2, ...)
参数
-
thisArg
可选的。在
function
函数运行时使用的this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则不指定或指定为null
或undefined
时会自动替换为指向全局对象,原始值会被包装。 -
arg1, arg2, ...
指定的参数列表。
返回值
使用调用者提供的 this
值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined
。
示例
-
使用 call 方法调用父构造函数
function Product(name, price) { this.name = name; this.price = price; } function Food(name, price) { Product.call(this, name, price); this.category = 'food'; } function Toy(name, price) { Product.call(this, name, price); this.category = 'toy'; } var cheese = new Food('feta', 5); var fun = new Toy('robot', 40);
-
使用 call 方法调用匿名函数
var animals = [ { species: 'Lion', name: 'King' }, { species: 'Whale', name: 'Fail' } ]; for (var i = 0; i < animals.length; i++) { (function(i) { this.print = function() { console.log('#' + i + ' ' + this.species + ': ' + this.name); } this.print(); }).call(animals[i], i); }
-
使用 call 方法调用函数并且不指定第一个参数
如果没有传递第一个参数,非严格模式下,this的值将会被绑定为全局对象‘;严格模式下,this 的值将会是 undefined。
var sData = 'Wisen'; function display() { console.log('sData value is %s ', this.sData); } display.call(); // sData value is Wisen
apply
apply()
方法调用一个具有给定this
值的函数,以及以一个数组(或类数组)的形式提供的参数。
类数组:
- 拥有length属性,其它属性(索引)为非负数
- 不具有数组所具有的方法,可以通过Function.call或者Function.apply方法改变this指向,可以间接在类数组中使用数组的方法。
- javascript中常见的类数组有
arguments
对象和 DOM方法的返回结果。比如document.getElementsByTagName()
。
参数
-
thisArg
必选的。在
func
函数运行时使用的this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为null
或undefined
时会自动替换为指向全局对象,原始值会被包装。 -
argsArray
可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给
func
函数。如果该参数的值为null
或undefined
,则表示不需要传入任何参数。
返回值
调用有指定**this**
值和参数的函数的结果。
bind
**bind()
**方法创建一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
function.bind(thisArg[, arg1[, arg2[, ...]]])
参数:
-
thisArg
调用绑定函数时作为
this
参数传递给目标函数的值。 -
arg1, arg2, ...
当目标函数被调用时,被预置入绑定函数的参数列表中的参数。
返回值:
返回一个原函数的拷贝,并拥有指定的this
值和初始参数。
示例
-
偏函数
只需要将预设的初始参数作为
bind()
的参数写在this
后面。当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置。function list() { return Array.prototype.slice.call(arguments); } function addArguments(arg1, arg2) { return arg1 + arg2 } var list1 = list(1, 2, 3); // [1, 2, 3] var result1 = addArguments(1, 2); // 3 // 创建一个函数,它拥有预设参数列表。 var leadingThirtysevenList = list.bind(null, 37); // 创建一个函数,它拥有预设的第一个参数 var addThirtySeven = addArguments.bind(null, 37); var list2 = leadingThirtysevenList(); // [37] var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3] var result2 = addThirtySeven(5); // 37 + 5 = 42 var result3 = addThirtySeven(5, 10); // 37 + 5 = 42 ,第二个参数被忽略
区别
- call 和 apply 会调用函数,并且改变函数内部 this 指向。
- cal 和 apply 传递的参数不一样, call 传递参数以 aru1, aru2…形式, apply 必须以数组形式 [ arg ]。
- bind 不会调用函数,可以改变函数内部 this 指向。