JavaScript中的this
通过谁调用的函数,谁就是this。
JavaScript中的this的值的和其他语言的会更怪(weird)。
由之前从反转链表到执行上下文中我们知道this
是执行上下文的一个属性,其值在执行上下文创建阶段(函数被调用时)时确定。
我们知道,执行上下文无非为global
,function
和eval(不讨论)
这个三个的属性,因此我们来讨论全局上下文和函数上下文的this。
全局上下文
全局上下文中的this
的值是比较容易的,没有那么多条条框框,无论是否处于严格模式,在全局执行环境中(在任何函数体外部)this
都指向全局对象。
console.log(this === window); // => true
let globalVar = "global var";
console.log(this.globalVar); // => "global var"
函数上下文
全局上下文的this
是没什么令人费解的,重头戏是函数上下文,因为JS中除了基本类型都是对象类型,this
的规则会变得乱起来。
作为普通函数
在非严格模式下, this
的值默认指向全局对象。
function f(){
return this;
}
//浏览器
window === f(); // => true
//Node.js
globalThis === f(); // => true
处于严格模式
,this
的值为undefined
。
"use strict";
function f(){
return this;
}
f() === undefined; // => true
作为对象的方法
当函数作为对象里的方法被调用时,this
被设置为调用该函数的对象。
let obj = {
prop: 3.14,
method: function() {
return this.prop;
}
}
obj.method(); // => 3.14
即便对象的方法是在全局作用域添加也不会影响这个默认行为:
let obj = {
PI: 3.1415,
}
function method(){
return this.PI;
}
//添加方法到obj
obj.method = method;
//不会影响这个行为
obj.method(); // => 3.1415
可以看出调用
才是关键。
this
的成员绑定使用“就近原则”:
let obj = {
PI: 3.14,
}
function method(){
return this.PI;
}
obj.innerObj = {inner: method, PI: 3.1415};
//innerObj是最近的对象引用
obj.innerObj.inner(); // => 3.1415
作为构造函数
当一个函数用作构造函数时(使用new
关键字),它的this
被绑定到正在构造的新对象。这是构造函数本身运作原理。
const O = function(){
this.PI = 3.14;
}
let PI = 3.1414;
let obj = new O();
obj.PI; // => 3.14
// 当然构造函数也可以返回对象
const O2 = function(){
this.a = "baz"; //僵尸代码? 但对外界无影响
return {a: "bar"}
}
let obj2 = new O2();
obj2.a; // => "bar"
作为箭头函数
箭头函数是没有单独的this
的。它只会从自己的作用域链的上一层
继承this
。
function Person(){
this.g = "not arrow";
setTimeout(() => {
console.log(this.g);// this的值为继承Person实例的this
},0);
}
let p = new Person(); // => "not arrow"
处于严格模式
时,this
的规则将被忽略:
"use strict";
let baz = () => {return this};
baz(); // => 不是undefined 而是window(或globalThis)
再将MDN上的一个例子摘录(抄。。)过来,能够将混乱的this
理清一点。
// 创建一个含有bar方法的obj对象,
// bar返回一个函数,
// 这个函数返回this,
// 这个返回的函数是以箭头函数创建的,
// 所以它的this被永久绑定到了它外层函数的this。
// bar的值可以在调用中设置,这反过来又设置了返回函数的值。
var obj = {
bar: function() {
var x = (() => this);
return x;
}
};
// 作为obj对象的一个方法来调用bar,把它的this绑定到obj。
// 将返回的函数的引用赋值给fn。
var fn = obj.bar();
// 直接调用fn而不设置this,
// 通常(即不使用箭头函数的情况)默认为全局对象
// 若在严格模式则为undefined
console.log(fn() === obj); // true
// 但是注意,如果你只是引用obj的方法,
// 而没有调用它,再次说明了调用才是关键所在!
var fn2 = obj.bar;
// 那么调用箭头函数后,this指向window,因为它从 bar 继承了this。
console.log(fn2()() == window); // true
"手动"更改this
JavaScript中有三个内置函数Function.prototype.call()
, Function.prototype.apply()
和Function.prototype.bind()
可以更改函数上下文的this
。
Function.prototype.call(thisArg, arg1, arg2, ...)
thisArg
- 必选的。在
func
函数运行时使用的this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为null
或undefined
时会自动替换为指向全局对象,原始值会被包装
(装箱)。
- 必选的。在
记录一些让我感到weird的例子。。:
匿名函数调用
call()
let animals = [ {species: "Cat", name: "Lihua"}, {species: "Dog", name: "Erha"}, ] for(let i = 0; i<animals.length; i++){ (function(i){ console.log('#' + i + ' ' + this.species + ': ' + this.name); }).call(animals[i], i); } /* #0 Cat: Lihua #1 Dog: Erha */
不指定第一个参数的值
非严格模式下,不指定第一个参数的值或者参数为
undefined
和null
,this
的值将会被绑定为全局对象。let sData = 'Wisen'; function display() { console.log('sData value is %s ', this.sData); } display.call(); // sData value is Wisen
严格模式下,
this
的值会为undefined
'use strict'; let sData = 'Wisen'; function display() { console.log('sData value is %s ', this.sData); } display.call(); // Cannot read the property of 'sData' of undefined
Function.prototype.apply(thisArg, [argsArray])
apply()
和call()
这两个方法比较类似,区别就是call()
方法接受的是参数列表,而apply()
方法接受的是一个参数数组。因为MDN文档上的例子非常好,可以自行去阅读。
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])
与上面两个函数不同的是,bind()
方法创建一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
简单用法:
this.x = 123; // = window.x 全局变量
let module = {
x: "local var",
getX: function(){
return this.x;
}
}
module.getX(); // "local var" 作为方法调用
let another = module.getX; // 赋值给全局变量another
another(); // => 123 window.another()..
//使用bind()
another = another.bind(module);
another(); // => "local var"
当返回的绑定函数作为构造函数时,原来提供的 this
就会被忽略,并且返回一个实例
(即和普通的构造函数没啥区别),但是提供的参数列表仍然会插入到构造函数调用时的参数列表之前。
function Cat(name, age){
this.age = age;
this.name = name;
}
//重写toString()
Cat.prototype.toString = function() {
return this.name + ',' + this.age;
}
let liHua = new Cat("狸花", 11);
liHua.toString(); // => "狸花,11"
let emptyObj = {};
let nCat = Cat.bind(emptyObj, "三花"); // 绑定一个空对象作为this,并且预设第一个参数,这个参数不会被覆盖!
let sanHua = new nCat(7); // 只传三花的年龄
sanHua.toString(); // => "三花,7" 可见emptyObj被忽略了,this为新实例的上下文
sanHua instanceof Cat; // => true
sanHua instanceof nCat; // => true
还有一点就是绑定函数
不能继续向后bind()
function foo(){
console.log(this.PI);
}
let pi1 = {PI: 3.14};
let pi2 = {PI: 3.1415926};
let boundFun = foo.bind(pi1);
boundFun(); // => 3.14
//链式bind()始终是第一个this
boundFun == foo.bind(pi1).bind(pi2);
boundFun(); // => 3.14
箭头函数和bind, call和apply
如果将this
传递给call
、bind
、或者apply
来调用箭头函数,它将被忽略。不过你仍然可以为调用添加参数,不过第一个参数(thisArg
)应该设置为null
。
let globalObj = this; // 全局对象
let bar = (() => this);
let obj = {bar: bar};
obj.bar() === globalObj; // => true
// 尝试使用call、bind来设定this
bar.call(obj) === globalObj; // => true
bar.bind(obj) === globalObj; // => true
//...
Reference参考: