在 JavaScript 中并无 OOP 编程的概念,咱们谈到的 this 不一样于一些 OOP 编程里指向的实例化对象,它指的是运行时的上下文。所谓上下文,就是运行时所在的环境对象,好比你在公司,可能你的领导是你的部门经理,在家就是你媳妇儿同样,不一样的场合上下文是不同的。编程
在 JavaScript 中函数具备定义时上下文、运行时上下文以及上下文可改变的特色,也就是说函数中的 this 在不一样的场合对应不一样的值。在变量对象与做用域链一文中咱们谈到 this 的肯定是在执行环境的建立阶段完成的,也就是说 this 在运行时是基于执行环境绑定的,在全局执行函数,this 就指向全局(浏览器中为 window),若是函数做为一个对象的方法调用时,this 就指向这个对象。数组
全局调用浏览器
来看下面的的例子。bash
例 1:闭包
以下,函数 getName 在全局调用,this 指向全局对象app
var name = 'lily';
function getName() {
var name = 'lucy';
console.log('name:', this.name);
};
//非严格模式下等同于window.getName()
getName();
=> name: lily
复制代码
例 2:函数
以下,尽管函数 getNameFunc 为 boy 对象的方法,但因其在全局调用,this 一样指向全局对象。post
var name = 'lily';
var boy = {
name: 'lucy',
getName: function() {
console.log('name:', this.name);
},
};
var getNameFunc = boy.getName;
getNameFunc();
=> name: lily
复制代码
例 3:this
以下,boy.getName()返回一个匿名函数,假如这个匿名函数叫作 f,则(boy.getName())()
等同于f()
,等同于在全局中调用,所以 this 一样指向全局对象。spa
var name = 'lily';
var boy = {
name: 'lucy',
getName: function() {
var name = 'snow';
return function() {
console.log('name:', this.name);
}
},
};
(boy.getName())();
=> name: lily
复制代码
为了保持 this 能够经过闭包实现,以下,执行 boy.getName()时,this 指向当前执行环境 boy,所以 that 指向 boy,属性 name 为 lily,匿名函数执行console.log('name:', that.name);
时,因为做用域链的关系,能够访问到上级做用域的 that 对象,指向 boy,所以 that.name 为 lucy
var name = 'lily';
var boy = {
name: 'lucy',
getName: function() {
var name = 'snow';
var that = this;
return function() {
console.log('name:', that.name);
}
}
};
(boy.getName())();
=> name: lucy
复制代码
对象调用
以下,对象 boy 调用本身的方法 getName,this 则指向 boy。
var boy = {
name: 'lucy',
getName: function() {
console.log('name:', this.name);
}
};
boy.getName();
=> name: lucy
复制代码
构造函数调用
想要知道调用构造函数 this 如何指向,须要知道 new 操做符究竟作了什么。 以下为 new 的模拟实现:
function mockNew(f) {
// 1.
var newObj, returnObj, proto;
// 2.
proto = Object(f.prototype) === f.prototype ? f.prototype : Object.prototype;
// 3.
newObj = Object.create(proto);
// 4.
/*
arguments为类数组对象须要经过Array.prototype.slice.call将其转换为数组;
Array.prototype.slice.call(arguments, 1)中的1是为了去掉arguments的第一个参数(函数f),而没有从0开始;
经过f.apply调用,则将this指向了newObj。
*/
returnObj = f.apply(newObj, Array.prototype.slice.call(arguments, 1));
// 5.
// 检查returnObj是否为Object类型
if (Object(returnObj) === returnObj) {
return returnObj;
}
return newObj;
}
复制代码
经过 new 操做符调用构造函数(利用内置[[Construct]]
方法),会经历如下几个阶段:
给内置属性[[prototype]]
(proto)赋值
若是 f 的prototype为原始的 Object 类型,则将构造函数 f 的 prototype 赋值给 proto,不然将 Object 的 prototype 赋值给 proto。
建立继承自 proto 的对象
经过 Object.create 建立对象 newObj,newObj 可经过原型链继承 proto 的属性。
绑定 this,将其值设置为第三步生成的对象
4.1. 经过 f.apply 调用 f,等同于 newObj.f(), 在构造函数 f 中执行 this.xxx = xxx;等同于执行 newObj.xxx = xxx,至关于 this 绑定了 newObj。
4.2. 调用构造函数,可能返回一个对象 returnObj。
返回新生成的对象
若是第 4 步中的 returnObj 值为 Object 类型,则 new 操做最终返回这个对象 returnObj,不然返回第 4 步中绑定this的的 newObj。
来看下面的例子:
经过 mockNew 函数构造对象的过程当中,会调用上述第 4 步 f.apply(newObj, Array.prototype.slice.call(arguments, 1)),等同于调用 newObj.f(...arguments),则 this.name = 'lily'等同于 newObj.name = 'lily',mockNew 返回 newObj 时,p 就等于 newObj,所以 p 可以访问到 person 的 name 属性。
function person() {
this.name = 'lily';
}
//这里,能够认为mockNew(person)等同于new person()
var p = mockNew(person);
p.name // lily
复制代码
来看另外一个例子:
var human = {
name: 'lucy',
}
//返回了一个对象,则new操做符最终返回这个对象
function person() {
this.name = 'lily';
return human;
}
var p = mockNew(person);
p.name // lucy
复制代码
由此,经过 new 操做符调用构造函数时,this 的最终指向为 new 返回的对象,即新建立的对象 newObj 或构造函数中返回的对象 returnObj(上例中的 human)。
func.call 和 func.apply
func.call 和 func.apply 的做用同样都是改变执行上下文,只是接收参数的形式不一样。 func.apply 方法传入两个参数,第一个参数是想要指定的上下文,为空则指向全局对象,第二个参数是函数参数组成的数组。 func.call 方法传入两个参数,第一个参数是想要指定的上下文,第二个参数是传入的是一个参数列表,而不是单个数组。
/*
thisArg: 想要指定的环境
argsArray: 参数数组
*/
func.apply(thisArg, argsArray)
/*
thisArg: 想要指定的环境
arg一、arg2...: 参数列表
*/
func.call(thisArg, arg1, arg2, ...)
复制代码
以下 boy 并无 getName 方法,可是经过 apply/call 改变 this 的指向达到了在 boy 中调用 girl 的 getName 方法。
function getName(firstName, lastName) {
console.log(`${firstName}.${this.name}.${lastName}`)
};
const girl = {
name: 'lucy',
getName,
};
const boy = {
name: 'Jeffrey'
};
//至关于boy.getName(['Michael', 'Jordan'])
girl.getName.apply(boy, ['Michael', 'Jordan']);
girl.getName.call(boy, 'Michael', 'Jordan');
=> Michael.Jeffrey.Jordan
复制代码
bind 函数
bind 方法不会当即执行,而是返回一个改变了上下文 this 后的函数。
const newGetName = girl.getName.bind(boy);
newGetName('Michael', 'Jordan')
=> Michael.Jeffrey.Jordan
复制代码
综上,this 的指向由其具体的执行环境决定,同时也能够经过函数的原型方法 apply、call 以及 bind 来显式地改变 this 的指向。不过,箭头函数的this,老是指向定义时所在的对象,而不是运行时所在的对象,apply、call也没法更改。