在函数执行时,this
总是指向调用该函数的对象。要判断 this
的指向,其实就是判断this
所在的函数是谁在调用。至于函数在哪里定义,定义的方式(是声明方式还是表达式方式),都无关紧要。
- 有对象就指向调用对象
- 没调用对象就指向全局对象
- 用new构造就指向新对象
- 通过
apply
或call
或bind
来改变this
的所指。
this
指向调用的对象
- 在何处或者如何定义调用函数完全不会影响到
this
的行为。谁调用函数才是关键。 - 当对象嵌套时,
this
指向的是调用函数的那一个对象。至于外面嵌套的,以及里面被嵌套的,都不会去查找。
比如下面的例子:
b
通过属性g()
调用函数independent
时,this
就指向b
。这时候b
只管自己,一看没找到指定属性prop
,就输出undefined
。他不管外面的o
或者里面的c
有没有要找的属性prop
;他不会去找的。
function independent() {
return this.prop; // 函数全局定义,但是this要调用的时候才能确定,现在不知道
}
var o = {
prop: 37,
f: independent,
b: {
g: independent,
c: {
prop: 50,
h: independent
}
}
};
// 这里的调用者是o,所以this指向o
console.log(o.f()); // logs: 37
// 这里的调用者是b,所以this指向b;对于外面的o,以及里面嵌套的c,都不可见
console.log(o.b.g()); // logs: undefined
// 这里的调用者是c,所以this指向c
console.log(o.b.c.h()); // logs: 50
- 原型链中的 this指向的也是调用函数的对象,至于函数是在子类中定义的还是在父类中定义的,无关紧要。
比如下面的例子:
子类p
中的函数f
是从父类o
中继承来的。当p
调用函数f的时候,this
就指向p
var o = {
f : function(){
return this.a + this.b;
}
};
// 原型链:p --> o --> Object.prototype --> null
var p = Object.create(o);
p.a = 1;
p.b = 4;
// 调用者是p,this指向p
console.log(p.f()); // 5
// 调用者是o,this指向o;o没有a和b,所以输出NaN
console.log(o.f()); // NaN
this
指向全局对象
- 在全局运行上下文中(在任何函数体外部),
this
指代全局对象
console.log(this.document === document); // true
// 在浏览器中,全局对象为 window 对象:
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
- 在函数中使用
this
,不过调用函数的地方是全局的,此时this
就是指向全局的。
function f1() {
return this;
}
// 全局调用,this指向全局,在浏览器下是window
f1() === window; // true
- 如果找不到调用函数的具体对象,(不是函数的作用域),那么默认就是全局对象。
function fn1() {
var a = 2;
fn2();
}
function fn2() {
console.log( this.a );
}
// 调用fn2的是函数fn1,找不到具体的对象,所以fn2中的this指向全局对象window
fn1(); // log: undefined
this
应该指向一个具体的对象,或者全局对象,比如window
,跟函数的作用域{}
一点关系没有。另外,this的确定是在运行时,而不是在定义的时候。所以哪个对象调用了函数才是确定this到底指向哪个对象的关键点。
下面代码对this的理解和使用都是错误的,应该避免这种用法
function fn1() {
var a = 2;
this.fn2(); // 以为this引用的是fn1的词法作用域是错误的,这里的this要删除,没有作用。
// 另外这里是函数定义,this的具体指向不确定。比如本例中,下面代码调用fn1的是全局对象window,所以this指向了全局对象window
}
function fn2() {
console.log( this.a );
}
// 调用fn2的是函数fn1,找不到具体的对象,所以fn2中的this指向全局对象window
// 调用fn1的是全局对象window,所以fn1中的this指向全局对象window
fn1(); // log: undefined
- 失去隐式绑定的情况: 如果找不到具体的调用对象,就会指向默认的全局对象。下面这种情况也是需要注意区分的:
function fn() {
console.log( this.a );
}
var a = "全局"; // 定义全局变量
var obj = {
a: "obj内部", // 定义内部变量
fn: fn
};
var bar = obj.fn; // 函数引用传递; 其实就是bar = fn; ==> bar和obj.fn都指向了fn
// 相当于fn(); 而不是obj.fn(); 调用者是全局,所以this指向全局
bar(); // "全局"
// 调用者是obj,所以this指向obj
obj.fn(); // "obj内部"
构造函数中的this
- 当一个函数被作为一个构造函数来使用(使用
new
关键字),它的this
与即将被创建的新对象绑定。
function C(){
this.a = 37; // 这里只是函数定义,还没有运行,this指谁还不确定
}
// 构造过程就是构造函数的执行,已经是运行时,(函数调用也是运行时了),this可以确定。这里和o绑定,this指向o
var o = new C();
console.log(o.a); // logs 37
- 在
js
中,构造函数、普通函数、对象方法、闭包,这四者没有明确界线。如果使用不当,构造函数就会改变其原来的意义。
比如下面这种用法就强烈不推荐使用:
function C2(){
this.a = 37;
// 这里手动返回了对象,默认的this被取消;
// new出来的对象和返回的这个对象绑定,而不是跟this。
// 这就导致this.a = 37;成了“僵尸”代码。执行了,但是外界根本拿不到
return {a:38};
}
// 这里o和对象{a:38}绑定,而不是this
o = new C2();
console.log(o.a); // logs 38
- 构造函数功能和其他语言中的类差不多,
ES6
引入的class
关键字,就可以看做是构造函数改头换面而来的。
在构造函数的使用上,需要遵循一定的规范,充分发挥其本职功能。到ES6
切换到class
关键字,也就水到渠成。
(1)定义用声明形式。不要用表达式形式。
表达式格式的函数定义方式是为了让函数成为变量,可以当做参数,返回值等使用,这给函数式编程带来了很大的方便。这个适合普通函数,对于构造函数,强烈不推荐。
(2)构造函数的命名采用大驼峰式,和类的命名习惯一致
(3)构造函数不要有返回值
(4)函数或者共享变量,定义在构造函数的原型上,节省内存;定义的方式应该在构造函数的外面。构造函数名.prototype = ...;
通过 apply
或 call
或 bind
来改变 this
的所指
function add(c, d){
return this.a + this.b + c + d;
}
var o = {a:1, b:3};
// The first parameter is the object to use as 'this', subsequent parameters are passed as
// arguments in the function call
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
// The first parameter is the object to use as 'this', the second is an array whose
// members are used as the arguments in the function call
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
- 使用
call
和apply
函数的时候要注意,如果传递的this
值不是一个对象,JavaScript
将会尝试使用内部ToObject
操作将其转换为对象。
因此,如果传递的值是一个原始值比如7
或 'foo' ,那么就会使用相关构造函数将它转换为对象,所以原始值7
通过new Number(7)
被转换为对象,而字符串'foo'
使用new String('foo')
转化为对象,例如:
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call("foo"); // [object String]
bar.call(7); // [object Number]
-
ECMAScript 5
引入了 Function.prototype.bind
。调用f.bind(someObject)
会创建一个与f具有相同函数体和作用域的函数,但是在这个新函数中,this
将永久地被绑定到了bind
的第一个参数,无论这个函数是如何被调用的。
function f(){
return this.a;
}
// function g 与对象{a:"azerty"}绑定,并且不变
var g = f.bind({a:"azerty"});
// 全局调用g,this仍然指向{a:"azerty"}
console.log(g()); // azerty
// 对象o调用f,this指向o;对象o调用g,this仍然指向{a:"azerty"}
var o = {a:37, f:f, g:g};
console.log(o.f(), o.g()); // 37, azerty
内部函数
- 在内部函数中,
this
没有按预想的绑定到外层函数对象上,而是绑定到了全局对象。这里普遍被认为是JavaScript
语言的设计错误,因为没有人想让内部函数中的this
指向全局对象。
var name = "global";
var person = {
name : "person",
hello : function(sth) {
var sayhello = function(sth) {
console.log(this.name + " says " + sth);
};
sayhello(sth);
}
}
person.hello("hello world"); // global says hello world
- 一般的处理方式是将
this
作为变量保存下来,一般约定为that
或者self
:
var name = "global";
var person = {
name : "person",
hello : function(sth) {
var that = this;
var sayhello = function(sth) {
console.log(that.name + " says " + sth);
};
sayhello(sth);
}
}
person.hello("hello world"); // person says hello world
参考文章
this
这篇文章的例子挺不错的
javascript中this的四种用法
开头总结的几句话不错,方便理解
详解 JavaScript 中的 this
内部函数的例子来自这里,不过格式错误,要稍微改一下
别再为了this发愁了:JS中的this机制
这篇文章中,那个this
不是指函数作用域{}
的例子很好。
“this
跟函数在哪里定义没有半毛钱关系,函数在哪里调用才决定了this
到底引用的是啥。”这句话也讲得非常到位。