前言
在很多面向对象的语言当中,都出现了 this 这个关键词,它在JS中的表现与其他语言略有区别[1],也是很多人入门前端的一块绊脚石,弄懂了 this 也就在很大程度上弄懂了JS面向对象编程的内涵(如果此前没有接触过其它面向对象语言的话)。
在读这篇总结之前,建议确认了解了以下概念:
-
JS中的严格模式和非严格模式
-
ES6语法中的箭头函数
-
执行上下文(作用域)
-
原型和构造函数
-
简单的DOM操作
那就先从最简单的开始吧!
全局上下文
无论是否为严格模式,全局上下文中的 this 都代指全局对象。 在浏览器环境内就是 window 对象;在 Node 中就是 global 对象。
console.log(this === window);//true
var a = 'test';
console.log(window.a);//'test'
console.log(this.a);//'test'
函数上下文
简单来说就是一句话: this 的值取决于函数被调用的方式,或者说 this 指的是调用函数的那个对象。
简单调用
在非严格模式下, this 的值不是通过调用设置的,所以默认指向全局对象。
function f1(){
return this;
}
f1() === window;//true
在严格模式下, this 将保持它进入执行上下文时的值,如果未在执行的上下文中定义,将会默认为 undefined ,如下代码所示。
"use strict";
function f2(){
return this;
}
f2() === undefined;//true
作为对象的方法
作为对象方法调用函数时,它们的 this 是调用该函数的对象。
var o = {
a: 11,
f: function(){
return this.a;
}
}
o.f();//11
这和定义函数的位置无关!!! 这和定义函数的位置无关!!! 这和定义函数的位置无关!!! 比如下面这段代码,在全局上下文定义一个函数,并把它赋值到对象内,同样可以实现跟上面相同的结果:
var a = 13;
function f1(){
return this.a;
}
var o = {
a: 11,
}
o.f = f1;//不带括号的函数名表示函数体本身,带括号则表示函数运行后返回的结果
o.f();//11
f1();//13
window.f1();//13
看看最后一行,是不是就能明白为什么说 this 指向的是调用函数的对象了?其实在简单调用那一节中也可以用这种方法去理解记忆,在全局上下文中声明的变量是全局对象的属性,声明的函数其实就是全局对象的方法,也就是 window.func()。
那对象内部对象的方法中 this 又指向什么呢?当然是距离它最近的对象啦,毕竟调用这个函数的就是最近的对象。
o.b = {
g: f1,
a: 14
}
o.b.g();//14
同样的规则,也适用在定义在对象原型链上的方法,如果一个方法存在于一个对象的原型链上,那么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 中调用的函数,用作 getter/setter 的函数都会把this绑定到设置或获取属性的对象。
作为构造函数调用
当一个函数用作构造函数时,他的this被绑定到正在构造的新对象上。
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); // logs 37
function C2(){
this.a = 37;
return {a:38};
}
o = new C2();
console.log(o.a); // logs 38
箭头函数
与封闭词法上下文的this保持一致,在全局代码中被设置为全局对象。
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
// 作为对象的一个方法调用,与上文所提的不同
var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true
// 尝试使用call来设定this
console.log(foo.call(obj) === globalObject); // true
// 尝试使用bind来设定this
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
即使将this传递call、bind、apply,也将被忽略,但调用添加参数仍然就可以,第一个参数应设置为null。 也就是说,无论如何,箭头函数的this被设置为它被创建时的上下文,这同样适用于在其他函数内创建的箭头函数,它们的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
在上面的例子中,一个赋值给了 obj.bar的函数(称为匿名函数 A),返回了另一个箭头函数(称为匿名函数 B)。因此,在 A 调用时,函数B的this被永久设置为obj.bar(函数A)的this。当返回的函数(函数B)被调用时,它this始终是最初设置的。在上面的代码示例中,函数B的this被设置为函数A的this,即obj,所以即使被调用的方式通常将其设置为 undefined 或全局对象(或者如前面示例中的其他全局执行上下文中的方法),它的 this 也仍然是 obj 。
作为DOM事件处理函数
这时的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指向监听器所在DOM元素(只有外层代码),如果内部是一个函数的话,它将指向window/global(非严格模式下的默认指向)
<button onclick="alert(this.tagName.toLowerCase())">显示button</button>
<button onclick="alert(function () {this.tagName.toLowerCase()})">显示button</button>
相关内容
在JS中,定义时上下文、运行时上下文和上下文是可以改变的三个概念,组成了this的复杂使用。其实这三者简单来说,就是正常你想运行的函数也好,方法也好后面加上一个.bind(),然后这个原本正常的函数和方法就好像被放到了一个特定的上下文(作用域)中,比如说你.方法名.call(window)之后,就好像把这个方法/函数放到了最外层中执行。
apply/call方法
首先来说,他们的功能没有任何区别,只在使用时,apply要接收一个参数数组,而call要把参数逐个列出来。
function cat () {
}
cat.prototype = {
food: 'fish',
say: function () {
alert('I love' + this.food)
}
}
var blackCat = new cat(),whiteDog;
blackCat.say();
blcakCat.say.call(whiteDog)
用的比较多的,通过document.getElementsByTagName选择的dom 节点是一种类似array的array。它不能应用Array下的push,pop等方法。我们可以通过:
var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
这样domNodes就可以应用Array下的所有方法了。 ——来源
那如何利用这两个方法polyfill一个bind方法呢?
Function.prototype.bind = Function.prototype.bind || function(context){
var self = this;
return function(){
return self.apply(context, arguments);
};
}
使用apply将一个argument数组打印出来:
function log(){
var args = Array.prototype.slice.call(arguments);
args.unshift('(app)');//改造数组,段首前加一个字符串
console.log.apply(console, args);
};
bind方法
bind()方法会创建一个新函数,称为绑定函数,调用绑定函数时,绑定函数会以创建它时传入bind()方法的第一个参数作为this,传入bind()方法的第二个以及以后的参数加上绑定函数运行时本身的参数,按照顺序作为原函数的参数调用原函数。
this 妙用
-
检查是否位于严格模式:严格模式中,调用函数(不是方法)的
this
是undefined
,非严格模式为全局对象。var hasStrictMode = (function(){return this === undefined}())
More
Last modified on 2018-02-06