上一篇《JavaScript系列之做用域和做用域链》中,了解到了执行上下文建立阶段的做用域链(Scope chain),在这篇文章里,咱们将讨论跟执行上下文直接相关的更多细节。javascript
再来回顾一下关于执行上下文的三个阶段生命周期:java
本章将专门介绍与执行上下文建立阶段直接相关的最后一个细节——this是什么?以及它的指向究竟是什么。git
也许你在其余面向对象的编程语言曾经看过this
,也知道它会指向某个构造器(constructor)所创建的对象。但事实上在JavaScript里面,this
所表明的不只仅是那个被创建的对象。github
先来看看ECMAScript 标准规范对this 的定义:express
「The this keyword evaluates to the value of the ThisBinding of the current execution context.」 「this 这个关键字表明的值为当前执行上下文的ThisBinding。」编程
而后再来看看MDN 对this 的定义:数组
「In most cases, the value of this is determined by how a function is called.」 「在大多数的状况下,this 其值取决于函数的调用方式。」浏览器
好,若是上面两行就看得懂的话那么就不用再往下看了,Congratulations!安全
...... 我想应该不会,至少我光看这两行仍是不懂。app
先来看个例子吧:
var getGender = function() {
return people1.gender;
};
var people1 = {
gender: 'female',
getGender: getGender
};
var people2 = {
gender: 'male',
getGender: getGender
};
console.log(people1.getGender()); // female
console.log(people2.getGender()); // female
复制代码
what?怎么people2
变性了呢,这不是我想要的结果啊,为何呢?
由于getGender()
返回(return)写死了people1.gender
的关系,结果天然是'female'。
那么,若是咱们把getGender
稍改一下:
var getGender = function() {
return this.gender;
};
复制代码
这个时候,你应该会分别获得female
与male
两种结果。
因此回到前面讲的重点,从这个例子能够看出,即使people1
与people2
的getGender
方法参照的都是同一个getGender function
,但因为调用的对象不一样,因此执行的结果也会不一样。
如今咱们知道了第一个重点,this
其实是在函数被调用时发生的绑定,它指向什么彻底取决于函数的调用方式。如何的区分this
呢?
看完上面的例子,仍是有点似懂非懂吧?那接下来咱们来看看不一样的调用方式对 this
值的影响。
在全局环境中,this
指向全局对象,在浏览器中,它就是 window
对象。下面的示例中,不管是不是在严格模式下,this
都是指向全局对象。
var x = 1
console.log(this.x) // 1
console.log(this.x === x) // true
console.log(this === window) // true
复制代码
若是普通函数是在全局环境中被调用,在非严格模式下,普通函数中 this
也指向全局对象;若是是在严格模式下,this
将会是 undefined
。ES5 为了使 JavaScript 运行在更有限制性的环境而添加了严格模式,严格模式为了消除安全隐患,禁止了 this
关键字指向全局对象。
var x = 1
function fn() {
console.log(this); // Window 全局对象
console.log(this.x); // 1
}
fn();
复制代码
使用严格模式后:
"use strict" // 使用严格模式
var x = 1
function fn() {
console.log(this); // undefined
console.log(this.x); // 报错 "Cannot read property 'x' of undefined",由于此时 this 是 undefined
}
fn();
复制代码
咱们知道,在对象里的值若是是原生值(primitive type;例如,字符串、数值、布尔值),咱们会把这个新创建的东西称为「属性(property)」;若是对象里面的值是函数(function)的话,咱们则会把这个新创建的东西称为「方法(method)」。
若是函数做为对象的一个方法时,而且做为对象的一个方法被调用时,函数中的this
指向这个上一级对象。
var x = 1
var obj = {
x: 2,
fn: function() {
console.log(this);
console.log(this.x);
}
}
obj.fn()
// obj.fn()结果打印出;
// Object {x: 2, fn: function}
// 2
var a = obj.fn
a()
// a()结果打印出:
// Window 全局对象
// 1
复制代码
在上面的例子中,直接运行 obj.fn()
,调用该函数的上一级对象是 obj
,因此 this
指向 obj
,获得 this.x
的值是 2;以后咱们将 fn
方法首先赋值给变量 a
,a
运行在全局环境中,因此此时 this
指向全局对象Window
,获得 this.x
为 1。
咱们再来看一个例子,若是函数被多个对象嵌套调用,this 会指向什么。
var x = 1
var obj = {
x: 2,
y: {
x: 3,
fn: function() {
console.log(this); // Object {x: 3, fn: function}
console.log(this.x); // 3
}
}
}
obj.y.fn();
复制代码
为何结果不是 2 呢,由于在这种状况下记住一句话:this
始终会指向直接调用函数的上一级对象,即 y
,上面例子实际执行的是下面的代码。
var y = {
x: 3,
fn: function() {
console.log(this); // Object {x: 3, fn: function}
console.log(this.x); // 3
}
}
var x = 1
var obj = {
x: 2,
y: y
}
obj.y.fn();
复制代码
对象能够嵌套,函数也能够,若是函数嵌套,this
会有变化吗?咱们经过下面代码来探讨一下。
var obj = {
y: function() {
console.log(this === obj); // true
console.log(this); // Object {y: function}
fn();
function fn() {
console.log(this === obj); // false
console.log(this); // Window 全局对象
}
}
}
obj.y();
复制代码
在函数 y
中,this
指向了调用它的上一级对象 obj
,这是没有问题的。可是在嵌套函数 fn
中,this
并不指向 obj
。嵌套的函数不会从调用它的函数中继承 this
,当嵌套函数做为函数调用时,其 this
值在非严格模式下指向全局对象,在严格模式是 undefined
,因此上面例子实际执行的是下面的代码。
function fn() {
console.log(this === obj); // false
console.log(this); // Window 全局对象
}
var obj = {
y: function() {
console.log(this === obj); // true
console.log(this); // Object {y: function}
fn();
}
}
obj.y();
复制代码
咱们可使用 new
关键字,经过构造函数生成一个实例对象。此时,this
便指向这个新对象。
var x = 1;
function Fn() {
  this.x = 2;
console.log(this); // Fn {x: 2}
}
var obj = new Fn(); // obj和Fn(..)调用中的this进行绑定
console.log(obj.x) // 2
复制代码
使用new
来调用Fn(..)
时,会构造一个新对象并把它(obj
)绑定到Fn(..)
调用中的this
。还有值得一提的是,若是构造函数返回了非引用类型(string
,number
,boolean
,null
,undefined
),this
仍然指向实例化的新对象。
var x = 1
function Fn() {
this.x = 2
return {
x: 3
}
}
var a = new Fn()
console.log(a.x) // 3
复制代码
由于Fn()
返回(return)的是一个对象(引用类型),this
会指向这个return的对象。若是return的是一个非引用类型的值呢?
var x = 1
function Fn() {
this.x = 2
return 3
}
var a = new Fn()
console.log(a.x) // 2
复制代码
若是你想改变 this
的指向,可使用 call
或 apply
方法。它们的第一个参数都是指定函数运行时其中的this
指向。若是第一个参数不传(参数为空)或者传 null
、undefined
,默认 this
指向全局对象(非严格模式)或 undefined
(严格模式)。
var x = 1;
var obj = {
x: 2
}
function fn() {
console.log(this);
console.log(this.x);
}
fn.call(obj)
// Object {x: 2}
// 2
fn.apply(obj)
// Object {x: 2}
// 2
fn.call()
// Window 全局对象
// 1
fn.apply(null)
// Window 全局对象
// 1
fn.call(undefined)
// Window 全局对象
// 1
复制代码
使用 call
和 apply
时,若是给 this
传的不是对象,JavaScript 会使用相关构造函数将其转化为对象,好比传 number
类型,会进行new Number()
操做,如传 string
类型,会进行new String()
操做,如传 boolean
类型,会进行new Boolean()
操做。
function fn() {
console.log(Object.prototype.toString.call(this))
}
fn.call('love') // [object String]
fn.apply(1) // [object Number]
fn.call(true) // [object Boolean]
复制代码
call
和 apply
的区别在于,call
的第二个及后续参数是一个参数列表,apply
的第二个参数是数组。参数列表和参数数组都将做为函数的参数进行执行。
var x = 1
var obj = {
x: 2
}
function Sum(y, z) {
console.log(this.x + y + z)
}
Sum.call(obj, 3, 4) // 9
Sum.apply(obj, [3, 4]) // 9
复制代码
调用 f.bind(someObject)
会建立一个与 f
具备相同函数体和做用域的函数,可是在这个新函数中,新函数的 this
会永久的指向 bind
传入的第一个参数,不管这个函数是如何被调用的。
var x = 1
var obj1 = {
x: 2
};
var obj2 = {
x: 3
};
function fn() {
console.log(this);
console.log(this.x);
};
var a = fn.bind(obj1);
var b = a.bind(obj2);
fn();
// Window 全局对象
// 1
a();
// Object {x: 2}
// 2
b();
// Object {x: 2}
// 2
a.call(obj2);
// Object {x: 2}
// 2
复制代码
在上面的例子中,虽然咱们尝试给函数 a
从新指定 this
的指向,可是它依旧指向第一次 bind
传入的对象,即便是使用 call
或 apply
方法也不能改变这一事实,即永久的指向 bind
传入的第一次参数。
值得一提的是,从ES6 开始新增了箭头函数,先来看看MDN 上对箭头函数的说明:
An arrow function expression has a shorter syntax than a function expression and does notbind 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
。在前面状况二中函数嵌套函数的例子中,被嵌套的函数不会继承上层函数的 this
,若是使用箭头函数,会发生什么变化呢?
var obj = {
y: function() {
console.log(this === obj); // true
console.log(this); // Object {y: function}
var fn = () => {
console.log(this === obj); // true
console.log(this); // Object {y: function}
}
fn();
}
}
obj.y()
复制代码
和普通函数不同,箭头函数中的 this
指向了 obj
,这是由于它从上一层的函数中继承了 this
,你能够理解为箭头函数修正了 this
的指向。因此箭头函数的this
不是调用的时候决定的,而是在定义的时候处在的对象就是它的this
。
换句话说,箭头函数的this
看外层的是否有函数,若是有,外层函数的this
就是内部箭头函数的this
,若是没有,则this
是Window
。
var obj = {
y: () => {
console.log(this === obj); // false
console.log(this); // Window 全局对象
var fn = () => {
console.log(this === obj); // false
console.log(this); // Window 全局对象
}
fn();
}
}
obj.y()
复制代码
上例中,虽然存在两个箭头函数,其实this
取决于最外层的箭头函数,因为obj
是个对象而非函数,因此this
指向为Window
全局对象。
同 bind 同样,箭头函数也很“顽固”,咱们没法经过 call
和 apply
来改变 this
的指向,即传入的第一个参数被忽略。
var x = 1
var obj = {
x: 2
}
var a = () => {
console.log(this.x)
console.log(this)
}
a.call(obj)
// 1
// Window 全局对象
a.apply(obj)
// 1
// Window 全局对象
复制代码
本篇文章介绍了 this
指向的几种状况,不一样的运行环境和调用方式都会对 this
产生影响。总的来讲,函数 this
的指向取决于当前调用该函数的对象,也就是执行时的对象。在这一节中,你须要掌握:
this
指向全局对象的状况;this
的区别;this
指向的几种状况;this
的指向,以及是否 return
的区别;call
和 apply
改变调用函数的对象;bind
建立的函数中 this
的指向;this
指向。若是以为文章对你有些许帮助,欢迎在个人GitHub博客点赞和关注,感激涕零!