本来不想写this的东西,因为实在是头晕啊,讲不清楚,JavaScript中的this真是让人抓狂,好在我们调试的时候发现错误,修改就可以了。但是一位美女程序员给我推荐了一些文章,我就在这里总结一下吧。
所有的知识来自于这两个牛人的博客和MDN
由于两人都讲的很好,但是又都有一些是对方没讲到的,所以在这里汇总一下。如果能答对下面题目的,可以略过此文了。
测试题目
有时候面试的时候就会有人问到很讨厌的情况,我很想骂一声,还能不能好好写代码。
测试题目1
请问下面打印出什么?
var obj = {
x: 10,
fn: function() {
function f() {
console.log(this);
console.log(this.x);
}
f();
}
};
obj.fn();
答案是window。
fn里面的函数f才是打印this的,所以fn中this还是obj,但是f在准备打印this的时候,this不是obj。提供一个写解析器的思路:如果是全局环境,就是window,如果是对象调用,就是obj,如果不知道上下文环境,还是window吧。哈哈,不知道这样说,大家觉得直观不。这一个case我觉得还是很奇怪的,只有调试出错了才好想起来。
测试题目2
下面打印什么?
var fullname = 'John Doe';
var obj = {
fullname: 'Colin Ihrig',
prop: {
fullname: 'Aurelio De Rosa',
getFullname: function() {
return this.fullname;
}
}
};
var test = obj.prop.getFullname;
console.log(test());
答案是打印'John Doe'。
这个明显可以套用万能公式。因为test()
的函数在全局环境中。console.log(obj.prop.getFullname());
打印的是Aurelio De Rosa
测试题目3
下面的this是什么?
<button onclick="alert((function(){return this}}()));"> Show inner this</button>
<button onclick="alert(this.tagName.toLowerCase());"> Show this</button>
这一题在下面也有答案。两个不同哦。
测试题目4
下面打印什么?
function a(xx) {
this.x = xx;
return this
};
var x = a(5);
var y = a(6);
console.log(x.x);
console.log(y.x);
很多人会说6......呵呵,x名称在window下面被覆盖了,x是6,但是x.x是undefined。答案是undefined,6。
this知识的总结
在JavaScript中,函数的this关键字的行为与其他语言相比有很多不同。在JavaScript的严格模式和非严格模式下也略有区别。
在绝大多数情况下,函数的调用方式决定了this的值。可以简单理解为谁调用该函数,this就是谁;或者被调用时,函数上下文是谁,谁就是this(这两句总结是我瞎掰的,对不对我们看看下面的例子)
全局上下文中的this
在全局运行上下文中(在任何函数体外部),this 指代全局对象,无论是否在严格模式下。
console.log(this.document === document); // true
// 在浏览器中,全局对象为 window 对象:
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
万能公式:谁调用,谁是this,全局中调用,全局对象是this。
函数上下文
javascript除了全局作用域,就是函数作用域了(新的标准会有块作用域let),但是作用域和执行上下文又不一样,这个可以换一篇文章来说,this和执行上下文有关系吗?当然有关系,但是执行上下文是啥玩意啊,有一篇文章写的特别好,推荐一下简述【执行上下文】,简单描述一下,当函数准备运行的时候,准备下面这些数据:
- 变量、函数表达式变量“提升”声明,默认赋值为undefined;
- this赋值;
- 函数声明“提升”;
- 参数赋值
- arguments 赋值
- 自由变量(非本地变量)的取值作用域赋值,这里参见闭包或者作用域的知识。
数据的准备情况我们称之为“执行上下文”或者“执行上下文环境”。this的值就是在做这些准备工作的时候创建的。但是this赋值的规则是什么呢,我们看看下面。
构造函数中的this
所谓构造函数就是用来new对象的函数。其实严格来说,所有的函数都可以new一个对象,但是有些函数的定义是为了new一个对象,而有些函数则不是。另外注意,构造函数的函数名第一个字母大写(规则约定)。例如:Object、Array、Function等。
function Foo(){
this.name = '王福民';
this.year = 1988;
console.log(this);
}
var f1 = new Foo();
那么我们看看构造函数怎么工作的
- 当一个函数被作为一个构造函数来使用(使用new关键字),它的this与即将被创建的新对象绑定。
- 没有返回值的时候,返回的是this,有返回值的时候,就是返回的对象。所以默认情况下,构造函数就是返回this的。
//构造函数是这么工作的:
function MyConstructor(){
// Actual function body code goes here.
// Create properties on |this| as
// desired by assigning to them. E.g.,
this.fum = "nom";
// et cetera...
// If the function has a return statement that
// returns an object, that object will be the
// result of the |new| expression. Otherwise,
// the result of the expression is the object
// currently bound to |this|
// (i.e., the common case most usually seen).
}
上面的代码我们可以这样使用
f1.name;
f1.year;
对于这个规则,是不是很难记住啊。那就套用万能公式:
谁调用函数,函数中的this就是谁:
new来调用构造函数,返回的this是创建的对象本身(如果你没有指定返回值),内部的this就是正在被创建的那个对象。
直接调用函数中的this
注意,以上仅限new Foo()的情况,即Foo函数作为构造函数的情况。如果直接调用Foo函数,而不是new Foo(),情况就大不一样了。
-
非严格模式this就是全局对象
-
严格模式this是undefined
万能公式:谁调用函数,函数this就是谁:
直接调用函数,this就是window了(非浏览器环境就是全局对象)。严格模式就是undefined
对象“方法”中的this
当以对象调用函数时,this 是调用该函数的对象.
下面的例子中,当 o.f() 被调用时,函数内的this将绑定到o对象。
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // logs 37
注意,不在对象中定义函数也没关系,因为this是在准备执行上下文中指定的(动态),和你在哪定义这种静态的代码位置没有关系。
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // logs 37
this 的绑定只受最靠近的成员引用的影响。
o.b = {
g: independent,
prop: 42
};
console.log(o.b.g()); // logs 42
原型链中的 this也是一样。
var o = {
f : function(){
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
getter 与 setter 中的 this也一样
function modulus(){
return Math.sqrt(this.re * this.re + this.im * this.im);
}
var o = {
re: 1,
im: -1,
get phase(){
return Math.atan2(this.im, this.re);
}
};
Object.defineProperty(o, 'modulus', {
get: modulus, enumerable:true, configurable:true});
console.log(o.phase, o.modulus); // logs -0.78 1.4142
这么多,怎么记呢。万能公式:谁调用函数,this就是谁。对象调用函数,对象就是this
call 和 apply
当一个函数的函数体中使用了this关键字时,通过所有函数都从Function对象的原型中继承的call()方法和apply()方法调用时,它的值可以绑定到一个指定的对象上。
当一个函数的函数体中使用了this关键字时,通过所有函数都从Function对象的原型中继承的call()方法和apply()方法调用时,它的值可以绑定到一个指定的对象上。
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(7); // [object Number]
王炸:bind 方法
ECMAScript 5 引入了 Function.prototype.bind。调用f.bind(someObject)会创建一个与f具有相同函数体和作用域的函数,但是在这个新函数中,this将永久地被绑定到了bind的第一个参数,无论这个函数是如何被调用的。
function f(){
return this.a;
}
var g = f.bind({a:"azerty"});
console.log(g()); // azerty
var o = {a:37, f:f, g:g};
console.log(o.f(), o.g()); // 37, azerty
DOM事件处理函数中的 this
当函数被用作事件处理函数时,它的this指向触发事件的元素
// 被调用时,将关联的元素变成蓝色
function bluify(e){
console.log(this === e.currentTarget); // 总是 true
// 当 currentTarget 和 target 是同一个对象是为 true
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
// 获取文档中的所有元素的列表
var elements = document.getElementsByTagName('*');
// 将bluify作为元素的点击监听函数,当元素被点击时,就会变成蓝色
for(var i=0 ; i<elements.length ; i++){
elements[i].addEventListener('click', bluify, false);
}
内联事件处理函数中的 this
当代码被内联处理函数调用时,它的this指向监听器所在的DOM元素:
<button onclick="alert(this.tagName.toLowerCase());"> Show this</button>
上面的alert会显示button。注意只有外层代码中的this是这样设置的:
<button onclick="alert((function(){return this}}()));"> Show inner this</button>
在这种情况下,没有设置内部函数的 this
,所以它指向 global/window
对象(即非严格模式下调用的函数未设置 this 时指向的默认对象)。
总结
上面例举了很多情况,我们看看万能公式还能用否。谁调用函数,谁就是this。下面的的谁调用,也可以理解为所有者,或者环境。
|谁调用|this是谁|this是否是调用者|
|---|---|
|全局上下文调用函数或者使用this|window或者全局对象|是|
|new 构造函数|构造的对象|是|
|直接调用函数|window或者undefied|除了strict模式,都是|
|对象方法中的this|对象|是|
|call或者apply|第一个参数|不是|
|bind方法|第一个参数|不是|
|DOM中的this|触发操作的元素|外层this是监听器的元素,算是,内层不是|