JavaScript中this的使用规则
在绝大多数情况下,函数的调用方式决定了this
的值。this
不能在执行期间被赋值,在每次函数被调用时this
的值也可能会不同。在函数中this
到底取何值,是在函数真正被调用执行的时候确定下来的,函数定义的时候确定不了。
因为
this
的取值是函数执行上下文(context)的一部分,每次调用函数,都会产生一个新的执行上下文环境。当代码中使用了this
,这个this
的值就直接从执行的上下文中获取了,而不会从作用域链中搜寻。
这篇文章介绍了JavaScript中this
的一些规则,这些规则很简单。最重要的规则是,this
决定调用一个函数时的回调是什么。下面简单的罗列一些这方面的规则。
有关于
this
有一篇文章非常的不错,详细介绍了JavaScript中的this
怎么使用。
全局中的this
在全局运行上下文中(任何函数体外部),this
指代全局对象,无论是否在严格模式下:
console.log(this.document === document); // true
// 在浏览器中,全局对象为 window 对象:
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
构造函数中的this
如果调用一个函数时使用了new
关键词,函数内的this
绑定一个新对象,即:它的this
与即将被创建的新对象绑定。
function ConstructorExample() {
console.log(this); // => {}
this.value = 10;
console.log(this); // => {value: 10}
}
new ConstructorExample();
**注意:**当构造器返回的默认值是一个this
引用的对象时,可以手动设置返回其他的对象,如果返回值不是一个对象,返回this
。
function C() {
console.log(this); // => {}
this.a = 37;
console.log(this); // => {a: 37}
}
var o = new C();
console.log(o.a); // => 37
function C2() {
console.log(this); // => {}
this.a = 37;
console.log(this); // => {a: 37}
return {a: 38};
}
o = new C2();
console.log(o.a); // => 38
在最后的例子中C2
,因为在调用构造函数的过程中,手动的设置了返回对象,与this
绑定的默认对象被取消(本质上这使得语句this.a = 37
成了“僵尸”代码,实际上并不是真正的“僵尸”,这条语句执行了但是对于外部没有任何影响,因此完全可以忽略它)。
关于JavaScript中有关于new
更多的介绍,可以阅读下面的文章:
- What is the 'new' keyword in JavaScript?
- 你真的弄明白new了吗
- JavaScript的实例化与继承:请停止使用new关键字
- Why I don’t use the javascript “new” keyword
- Javascript’s “new” Keyword Explained as Simply as Possible
- The new keyword in JavaScript
构造函数prototype
属性时,this
的指向:
function foo() {
console.log(this); // => {}
this.x = 10;
}
foo.prototype.getX = function () {
console.log(this); // => {x: 10}
console.log(this.x); // => 10
}
var bar = new foo();
bar.getX();
call
和apply
中的this
当一个函数的函数体中使用了this
时,通过所有函数都从Function
对象的原型中继承的call()
和apply()
方法调用时,它的值可以绑定到一个指定的对象上。
function sum (c, d) {
console.log(this); // => {a: 1, b: 3}
return this.a + this.b + c + d;
}
var o = {
a: 1,
b: 3
}
sum.call(o, 5, 7); // => 16
sum.apply(o, [10, 20]); // => 34
使用call()
和apply()
函数的时候要注意,如果传递的this
值不是一个对象,JavaScript将会尝试使用内部ToObject
操作将其转换为对象。因此,如果传递的值是一个原始值,比如7
或'foo'
,那么就会使用相关构造函数将它转换为对象,所以原始值7
通过new Number(7)
被转换为对象,而字符串'foo'
使用new String('foo')
转化为对象,例如:
function bar() {
console.log(this);
console.log(Object.prototype.toString.call(this));
console.log(this);
}
bar.call(7);
bar.call('foo');
bar.apply(7);
bar.call('foo');
bind
中的this
ECMAScript5中引入了Function.prototype.bind
。调用f.bind(someObject)
会创建一个与f
具有相同函数体和作用域的函数,但是在这个新函数中,this
将永久地被绑定到了bind
的第一个参数,无论这个函数是如何被调用的。
function f(){
console.log(this);
return this.a;
}
var g = f.bind({a: 'azerty'});
console.log(g());
var o = {
a: 37,
f: f,
g: g
}
console.log(o.f(), o.g());
对象方法中的this
当以对象里的方法的方式调用函数时,它们的this
是调用该函数的对象。下面的例子中,当o.f()
被调用时,函数内的this
将绑定到o
对象。
var o = {
prop: 37,
f: function() {
console.log(this); // => {prop: 37, f: function}
return this.prop;
}
}
console.log(o.f()); // => 37
**注意:**在何处或者如何定义调用函数完全不会影响到this
的行为。在上一个例子中,我们在定义o
的时候为其成员f
定义了一个匿名函数。但是,我们也可以首先定义函数然后再将其附属到o.f
。这样做this
的行为也一致:
var obj = {
prop: 37
}
function fn(){
console.log(this); // => {prop: 37, f: function}
return this.prop;
}
obj.f = fn; // => 37
console.log(o.f());
这说明this
的值只与函数f
作为o
的成员被调用有关系。
类似的,this
的绑定只受最近的成员引用的影响。在下面的这个例子中,我们把一个方法g
当作对象o.b
的函数调用。在这次执行期间,函数中的this
将指向o.b
。事实上,这与对象本身的成员没有多大关系,最靠近的引用是最重要的。
var o = {
prop: 37
}
function independent() {
console.log(this); // => {prop: 42, g: function}
return this.prop;
}
o.b {
g: independent,
prop: 42
}
console.log(o.b.g()); // =>42
相同的概念在定义在原型链中的方法也是一致的。如果该方法存在于一个对象的原型链上,那么this
指向的是调用这个方法的对象,表现得好像是这个方法就存在于这个对象上一样。
var obj = {
fn: function() {
console.log(this); // => {a: 1, b: 4}
return this.a + this.b
}
}
var prop = Object.create(obj);
prop.a = 1;
prop.b = 4;
console.log(prop.fn()); // => 5
在这个例子中,对象prop
没有属于它自己的fn
属性,它的fn
属性继承自它的原型。但是这对于最终在obj
中找到fn
属性的查找过程来说没有关系;查找过程首先从prop.fn
的引用开始,所以函数中的this
指向prop
。也就是说,因为fn
是作为prop
的方法调用的,所以它的this
指各了prop
。这是JavaScript的原型继承中的一个有趣的特性。
再次,相同的概念也适用于函数作为一个getter
或者一个setter
调用。作为getter
或setter
函数都会绑定this
到从设置属性或得到属性的那个对象。
function modules() {
console.log(this); // => {re: 1, im: -1}
return Math.sqrt(this.re * this.re + this.im * this.im)
}
var obj = {
re: 1,
im: -1,
get phase() {
console.log(this); // => {re: 1, im: -1}
return Math.atan2(this.im, this.re);
}
}
Object.defineProperty(obj, 'modules', {
get: modules,
enumerable: true,
configurable: true
})
console.log(obj.phase, obj.modules); // => -0.7853981633974483 1.4142135623730951
直接调用
因为下面的代码不是在严格模式下执行,并且this
的值不会在函数执行时被设置,此时的this
的值会默认设置为全局对象。
function f1(){
console.log(this);
return this;
}
f1(); // => Window {stop: ƒ, open: ƒ, alert: ƒ, ...}
然而,在严格模式下,this
将保持他进入执行环境的值,所以下面的this
将会默认为undefined
:
function f2(){
'use strict'; // 这里是严格模式
console.log(this); // => undefined
return this;
}
f2(); // => undefined
在严格模式下,如果this
未被执行的上下文环境定义,那么它将会默认为undefined
。
**注意:**这个规则和对象方法中的this
有点类似,不同之处在于。函数声明为一个方法而不是自动成为全局对象window
。因此这是一个隐式方法调用。当我们调用fn()
,它被解释为window.fn()
,也就是window
。
function fn() {
console.log(this); // {stop: f, open: f, alert: f, confirm: f, prompt: f...}
return this;
}
fn();
console.log(fn === window.fn); // => true
DOM事件处理函数中的this
在一个HTML DOM事件处理程序里,this
始终指向这个处理程序所绑定的HTML DOM节点:
function Listener(){
document.getElementById('foo').addEventListener('click', this.handleClick); //这里的 this 指向 Listener 这个对象。不是强调的是这里的 this
}
Listener.prototype.handleClick = function (event) {
console.log(this); // => <div id="foo"></div>
}
var listener = new Listener();
document.getElementById('foo').click();
这个很好理解,就相当于是给函数传参数,使handleClick
运行时上下文改变了,相当于下面这样的代码:
var obj = {
x: 10,
fn: function() {
console.log(this); // => Window
console.log(this.x); // => undefined
}
};
function foo(fn) {
fn();
}
foo(obj.fn);
你也可以通过bind
切换上下文:
function Listener(){
document.getElementById('foo').addEventListener('click',this.handleClick.bind(this));
}
Listener.prototype.handleClick = function (event) {
console.log(this); //Listener {}
}
var listener = new Listener();
document.getElementById('foo').click();
**注意:**当函数被用作事件处理函数时,虽然它的this
指向触发事件的元素,但一些浏览器在使用addEventListener
的函数动态添加监听函数时不遵守这个约定。
当代码被内联处理函数调用时,它的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
是词法作用域,由下下文确定。
var obj = {
x: 10,
foo: function() {
var fn = () => {
return () => {
return () => {
console.log(this); // => {x: 10, foo: function}
console.log(this.x); // => 10
}
}
}
fn()()();
}
}
obj.foo();
现在,箭头函数完全修复了 this
的指向,this
总是指向词法作用域,也就是外层调用者 obj
。
如果使用箭头函数,以前的这种 hack 写法:
var self = this;
就不再需要了。
var obj = {
x: 10,
foo: function() {
var fn = () => {
return () => {
return () => {
console.log(this); // => {x: 10, foo: function}
console.log(this.x); // => 10
}
}
}
fn.bind({x: 14})()()();
fn.call({x: 14})()();
}
}
obj.foo();
由于 this
在箭头函数中已经按照词法作用域绑定了,所以,用 call()
或者 apply()
调用箭头函数时,无法对 this
进行绑定,即传入的第一个参数被忽略。
总结
这篇文章罗列了JavaScript中this
在各种场景下使用的规则。上面展示的都是单一的场景,但有的时候会有多个场景的出现。对于这方面自己还不能理解。理解的同学可以指点指点。虽然看了书,自己也亲自撸码了,总感觉JavaScript中的this
是那么的神秘。自己依旧是云里来雾里去。
扩展阅读
- JavaScript中‘this’关键词的优雅解释
- 浅析Javascript 中的 this 的七种使用场景
- 理解并掌握 JavaScript 中 this 的用法
- The Simple Rules to ‘this’ in Javascript
- this
- Learn ES6 The Dope Way Part II: Arrow functions and the ‘this’ keyword
- Welcome to Javascript and “this” keyword
- How to Understand ‘this’ and Binding in Javascript
- JavaScript Keywords: What’s up with “this”?
- What the heck is “this” ? Understanding “this” in javascript
- The ‘this’ keyword in Javascript
- Understand JavaScript’s “this” With Clarity, and Master It
- Understanding the “this” keyword in JavaScript
如需转载,烦请注明出处:https://www.fedev.cn/javascript/the-simple-rules-to-this-in-javascript.htmlAir Jordan XIII Low