在JS中,当一个函数执行时,都会建立一个执行上下文用来确认当前函数的执行环境,执行上下文分为 全局执行上下文
和 函数执行上下文
。而 this
就是指向这个执行上下文的对象。因此,this是在运行时决定的,能够简单的理解为 谁调用,this指向谁。javascript
分四种状况来看:java
当函数做为函数独立调用的时候,则是在全局环境中运行,this
则指向全局对象 window
数据结构
一个简单的例子app
function demo() { console.log(this); // window } demo();
demo
函数独立调用,因此 this
指向全局对象 window
函数
接着this
function outer() { function inner() { console.log(this); // window } inner(); } outer();
虽然在 outer
函数内部声明了一个 inner
函数,但实际上 inner
函数是独立调用的,因此依然是在全局环境,this
仍然是指向了 window
。prototype
function demo(func) { func(); } demo(function () { console.log(this); // window });
给demo
函数传入一个匿名函数,执行匿名函数func
的时候,依然是做为函数独立调用,因此this
仍然指向window
。设计
理解一下什么是做为函数独立调用:
当定义一个函数,例如var demo = function () {}
等号右边的函数是独立放在内存
中的,而后赋予demo变量的指向为函数所在的内存地址
,当直接调用 demo()
,至关于直接找到函数自己执行,因此函数内部建立的上下文为全局上下文,this
则指向了全局对象 window
。code
当调用一个对象方法时,this
表明了对象自己。对象
let obj = { name: 'invoker', getName: function () { console.log(this); // obj console.log(this.name); // "invoker" } } obj.getName();
定义了一个 obj
对象,调用其内部的getName
,this
则指向了 obj
对象。
稍微修改一下
var name = 'windowName'; let obj = { name: 'invoker', getName: function () { console.log(this); // window console.log(this.name); // windowName } } var getName = obj.getName; getName();
当用一个变量 getName
接收 obj
对象的 getName
方法, 再执行 getName
,发现 this
指向了 window
,由于此时变量 getName
直接指向了函数自己,而不是经过 obj
去调用,此时就变成了函数独立调用的状况了。
再看个例子
let obj = { test: function() { function fn() { console.log(this); // window } fn(); }, test1: function (fn) { fn() } } obj.test(); obj.test1(function () { console.log(this) // window });
虽然在 obj
对象的 test
方法内定义了 fn
,但执行时一样属于函数独立调用,因此 this
指向 window
。
将函数做为参数传入 obj
的 test1
方法,也属于函数独立调用,this
一样指向 window
。
使用 new
关键字调用函数,则是构造函数调用,this
指向了该构造函数新建立的对象。
function person(name) { this.name = name } let p = new person('invoker') console.log(p.name) // 'invoker'
回顾一下 new
关键词的过程:
obj
obj
的 __proto__
指向 构造函数的原型对象constructor
,改变this
的指向为 obj
function myNew(Fn) { let obj = {} obj.__proto__ = Fn.prototype const res = Fn.prototype.constructor.call(obj) if (typeof res === 'object') { obj = res } return obj }
this
指向的是 call
、 apply
、bind
调用时传递的第一个参数。
let obj = { name: 'invoker' } function demo() { console.log(this.name) // 'invoker' } demo.call(obj) demo.apply(obj) demo.bind(obj)()
箭头函数在执行时并不会建立自身的上下文,它的 this
取决于自身被定义的所在执行上下文。
例子:
let obj = { fn: () => { console.log(this) // window } } obj.fn()
obj
的 fn
指向一个箭头函数,因为只有函数能够建立执行上下文,而箭头函数外部并无包裹函数,因此箭头函数所在的执行上下文为全局的执行上下文,this
指向 window
包裹一个函数看看呗?
let obj = { fn: function () { console.log('箭头函数所在执行上下文', this) // '箭头函数所在执行上下文' obj var arrow = () => { console.log(this) //obj } arrow() } } obj.fn()
箭头函数 arrow
被定义在 obj.fn
内,因此 fn
中的 this
就是 arrow
中的 this
箭头函数一次绑定上下文后便不可更改:
let obj = { name: 'invoker' } var demo = () => { console.log(this) // window } demo.call(obj)
虽然使用了 call
函数间接修改 this
的指向,但并不起做用。
javascript中存在 this
的设计,跟其内存中的数据结构有关系。
假设定义 let obj = { name: 'invoker' }
{ name: 'invoker' }
并放在内存当中{ name: 'invoker }
所在的内存地址赋予 obj
因此 obj
其实就是个指向某个对象的地址,若是要读取 obj.name
,则先要找到 obj
所在地址,而后从地址中拿到原始对象,读取 name
属性。
对象中每一个属性都有一个属性描述对象:可经过 Object.getOwnPropertyDescriptor(obj, key)
来读取。
也就是说上面所说的 obj
的 name
属性实际是下面这样的
{ name: { [[value]]: 'invoker', [[configurable]]: true, [[enumerable]]: true, [[writable]]: true } }
value
就是得到的值。
如今假设对象的属性是一个函数:
let name = 'windowName' let obj = { name: 'invoker', sayHello: function () { console.log('my name is ' + this.name) } } let descriptor = Object.getOwnPropertyDescriptor(obj, 'sayHello') console.log(descriptor) //这个sayHello的属性描述对象为: sayHello: { [[value]]: ƒ (), [[configurable]]: true, [[enumerable]]: true, [[writable]]: true }
sayHello
的 value
值是一个函数,这个时候,引擎会单独将这个函数放在 内存
当中,而后将函数的内存地址赋予 value
。
所以能够得知,这个函数在 内存
中是单独的,并不被谁拥有,因此它能够在不一样的上下文执行。
因为函数能够在不一样上下文执行,因此须要一种机制去获取当前函数内部的执行上下文。因此,就有了 this
,它指向了当前函数执行的上下文。
// 接着上面代码 obj.sayHello() // my name is invoker let sayHello = obj.sayHello sayHello() // my name is windowName
obj.sayHello()
是经过 obj
找到 sayHello
,也就是对象方法调用,因此就是在 obj
环境执行。
当 let sayHello = obj.sayHello
,变量 sayHello
就直接指向函数自己,因此 sayHello()
也就是函数独立调用,因此是全局环境执行。
this
的出现,跟JS引擎内存中的数据结构有关系。
当发现一个函数被执行时,经过上面的多种状况。
call、apply
等间接调用这样就能很好的确认 this
的指向。