JavaScript 中的
this
指向题目有许多博客在诠释,依然有许多人问。上周我们的开辟团队一连两个人碰到相干题目,所以我不得不将关于前端构建手艺的交流会延长了半个时刻议论this
的题目。
与我们罕见的许多言语差别,JavaScript 函数中的 this
指向并不是在函数定义的时刻肯定的,而是在挪用的时刻肯定的。换句话说,函数的挪用体式格局决议了 this
指向。
JavaScript 中,一般的函数挪用体式格局有三种:直接挪用、要领挪用和 new
挪用。除此之外,另有一些迥殊的挪用体式格局,比方经由过程 bind()
将函数绑定到对象以后再举行挪用、经由过程 call()
、apply()
举行挪用等。而 es6 引入了箭头函数以后,箭头函数挪用时,其 this
指向又有所差别。下面就来剖析这些状况下的 this
指向。
直接挪用
直接挪用,就是经由过程 函数名(...)
这类体式格局挪用。这时刻,函数内部的 this
指向全局对象,在浏览器中全局对象是 window
,在 NodeJs 中全局对象是 global
。
来看一个例子:
// 简朴兼容浏览器和 NodeJs 的全局对象
const _global = typeof window === "undefined" ? global : window;
function test() {
console.log(this === _global); // true
}
test(); // 直接挪用
这里须要注重的一点是,直接挪用并不是指在全局作用域下举行挪用,在任何作用域下,直接经由过程 函数名(...)
来对函数举行挪用的体式格局,都称为直接挪用。比方下面这个例子也是直接挪用
(function(_global) {
// 经由过程 IIFE 限制作用域
function test() {
console.log(this === _global); // true
}
test(); // 非全局作用域下的直接挪用
})(typeof window === "undefined" ? global : window);
bind() 对直接挪用的影响
另有一点须要注重的是 bind()
的影响。Function.prototype.bind()
的作用是将当前函数与指定的对象绑定,并返回一个新函数,这个新函数不论以什么样的体式格局挪用,其 this
一直指向绑定的对象。照样来看例子:
const obj = {};
function test() {
console.log(this === obj);
}
const testObj = test.bind(obj);
test(); // false
testObj(); // true
那末 bind()
干了啥?无妨模仿一个 bind()
来相识它是怎样做到对 this
发生影响的。
const obj = {};
function test() {
console.log(this === obj);
}
// 自定义的函数,模仿 bind() 对 this 的影响
function myBind(func, target) {
return function() {
return func.apply(target, arguments);
};
}
const testObj = myBind(test, obj);
test(); // false
testObj(); // true
从上面的示例能够看到,起首,经由过程闭包,坚持了 target
,即绑定的对象;然后在挪用函数的时刻,对原函数运用了 apply
要领来指定函数的 this
。固然原生的 bind()
完成能够会差别,而且更高效。但这个示例说清晰明了 bind()
的可行性。
call 和 apply 对 this 的影响
上面的示例顶用到了 Function.prototype.apply(),与之类似的另有 Function.prototype.call()。这两要领的用法请人人自身经由过程链接去看文档。不过,它们的第一个参数都是指定函数运转时个中的 this
指向。
不过运用 apply
和 call
的时刻依然须要注重,假如目次函数自身是一个绑定了 this
对象的函数,那 apply
和 call
不会像预期那样实行,比方
const obj = {};
function test() {
console.log(this === obj);
}
// 绑定到一个新对象,而不是 obj
const testObj = test.bind({});
test.apply(obj); // true
// 希冀 this 是 obj,即输出 true
// 然则因为 testObj 绑定了不是 obj 的对象,所以会输出 false
testObj.apply(obj); // false
因而可知,bind()
对函数的影响是深远的,慎用!
要领挪用
要领挪用是指经由过程对象来挪用其要领函数,它是 对象.要领函数(...)
如许的挪用情势。这类状况下,函数中的 this
指向挪用该要领的对象。然则,一样须要注重 bind()
的影响。
const obj = {
// 第一种体式格局,定义对象的时刻定义其要领
test() {
console.log(this === obj);
}
};
// 第二种体式格局,对象定义好以后为其附加一个要领(函数表达式)
obj.test2 = function() {
console.log(this === obj);
};
// 第三种体式格局和第二种体式格局道理雷同
// 是对象定义好以后为其附加一个要领(函数定义)
function t() {
console.log(this === obj);
}
obj.test3 = t;
// 这也是为对象附加一个要领函数
// 然则这个函数绑定了一个不是 obj 的别的对象
obj.test4 = (function() {
console.log(this === obj);
}).bind({});
obj.test(); // true
obj.test2(); // true
obj.test3(); // true
// 受 bind() 影响,test4 中的 this 指向不是 obj
obj.test4(); // false
这里须要注重的是,后三种体式格局都是预定定义函数,再将其附加给 obj
对象作为其要领。再次强调,函数内部的 this
指向与定义无关,受挪用体式格局的影响。
要领中 this 指向全局对象的状况
注重这里说的是要领中而不是要领挪用中。要领中的 this
指向全局对象,假如不是因为 bind()
,那就肯定是因为不是用的要领挪用体式格局,比方
const obj = {
test() {
console.log(this === obj);
}
};
const t = obj.test;
t(); // false
t
就是 obj
的 test
要领,然则 t()
挪用时,个中的 this
指向了全局。
之所以要迥殊提出这类状况,重要是因为常常将一个对象要领作为回调传递给某个函数以后,却发明运转结果与预期不符——因为疏忽了挪用体式格局对 this
的影响。比方下面的例子是在页面中对某些事变举行封装以后迥殊轻易碰到的题目:
class Handlers {
// 这里 $button 假定是一个指向某个按钮的 jQuery 对象
constructor(data, $button) {
this.data = data;
$button.on("click", this.onButtonClick);
}
onButtonClick(e) {
console.log(this.data);
}
}
const handlers = new Handlers("string data", $("#someButton"));
// 对 #someButton 举行点击操纵以后
// 输出 undefined
// 但预期是输出 string data
this.onButtonClick
作为一个参数传入 on()
以后,事宜触发时,理论上是对这个函数举行的直接挪用,而不是要领挪用,所以个中的 this
会指向全局对象 —— 但实际上因为挪用事宜处置惩罚函数的时刻,this
指向会绑定到触发事宜的 DOM 元素上,所以这里的 this
是指向触发事宜的的 DOM 元素(注重:this
并不是 jQuery 对象),即 $button.get(0)
(注重代码前解释中的假定)。
要处理这个题目有许多种要领:
// 这是在 es5 中的处理办法之一
var _this = this;
$button.on("click", function() {
_this.onButtonClick();
});
// 也能够经由过程 bind() 来处理
$button.on("click", this.onButtonClick.bind(this));
// es6 中能够经由过程箭头函数来处置惩罚,在 jQuery 中慎用
$button.on("click", e => this.onButtonClick(e));
不过请注重,将箭头函数用作 jQuery 的回调时形成要警惕函数内对 this
的运用。jQuery 大多数回调函数(非箭头函数)中的 this
都是示意挪用目的,所以能够写 $(this).text()
如许的语句,但 jQuery 没法转变箭头函数的 this
指向,一样的语句语义完整差别。
new 挪用
在 es6 之前,每个函数都能够看成是组织函数,经由过程 new
挪用来发生新的对象(函数内无特定返回值的状况下)。而 es6 转变了这类状况,虽然 class
定义的类用 typeof
运算符获得的依然是 "function"
,但它不能像一般函数一样直接挪用;同时,class
中定义的要领函数,也不能看成组织函数用 new
来挪用。
而在 es5 中,用 new
挪用一个组织函数,会建立一个新对象,而个中的 this
就指向这个新对象。这没有什么牵挂,因为 new
自身就是设想来建立新对象的。
var data = "Hi"; // 全局变量
function AClass(data) {
this.data = data;
}
var a = new AClass("Hello World");
console.log(a.data); // Hello World
console.log(data); // Hi
var b = new AClass("Hello World");
console.log(a === b); // false
箭头函数中的 this
先来看看 MDN 上对箭头函数的申明
An arrow function expression has a shorter syntax than a function expression and does not bind its own
this
,arguments
,super
, ornew.target
. Arrow functions are always anonymous. These function expressions are best suited for non-method functions, and they cannot be used as constructors.
这里已清晰了说清晰明了,箭头函数没有自身的 this
绑定。箭头函数中运用的 this
,实际上是直接包括它的谁人函数或函数表达式中的 this
。比方
const obj = {
test() {
const arrow = () => {
// 这里的 this 是 test() 中的 this,
// 由 test() 的挪用体式格局决议
console.log(this === obj);
};
arrow();
},
getArrow() {
return () => {
// 这里的 this 是 getArrow() 中的 this,
// 由 getArrow() 的挪用体式格局决议
console.log(this === obj);
};
}
};
obj.test(); // true
const arrow = obj.getArrow();
arrow(); // true
示例中的两个 this
都是由箭头函数的直接外层函数(要领)决议的,而要领函数中的 this
是由其挪用体式格局决议的。上例的挪用体式格局都是要领挪用,所以 this
都指向要领挪用的对象,即 obj
。
箭头函数让人人在运用闭包的时刻不须要太纠结 this
,不须要经由过程像 _this
如许的局部变量来暂时援用 this
给闭包函数运用。来看一段 Babel 对箭头函数的转译能够能加深明白:
// ES6
const obj = {
getArrow() {
return () => {
console.log(this === obj);
};
}
}
// ES5,由 Babel 转译
var obj = {
getArrow: function getArrow() {
var _this = this;
return function () {
console.log(_this === obj);
};
}
};
别的须要注重的是,箭头函数不能用 new
挪用,不能 bind()
到某个对象(虽然 bind()
要领挪用没题目,然则不会发生预期结果)。不论在什么状况下运用箭头函数,它自身是没有绑定 this
的,它用的是直接外层函数(即包括它的近来的一层函数或函数表达式)绑定的 this
。