这一节来探讨this。 在 javascript 中 this 也是一个神的存在,相对于 java 等语言在编译阶段肯定,而在 javascript 中, this 是动态绑定,也就是在运行期绑定的。这致使了 javascript 中 this 的灵活性,并且对识别对象不一样的调用场景下 this 指向带来了一些困扰。javascript
在全局环境中this
指向window
,即this === window
。this 的灵活性主要体如今函数环境中,容易断定出错的也是函数环境下的 this 指向.java
this 是函数内部的可见属性之一(另外一个是 arguments), 在函数内部咱们能够直接使用this
访问指定对象
的属性。那么指定对象
是如何肯定的?node
下面就围绕 this 的指向对象
来梳理app
首先 this 是在函数被调用时肯定的, 也就是在进入函数后,函数内表达式、语句执行前的变量对象
建立阶段肯定的。函数
而它指向什么取决于函数在哪里被调用(在什么对象上被调用).this
通常情形须要关注函数在哪里被调用、被怎么调用.编码
下面主要分析函数在不一样调用场景下this
的指向.prototype
把标题展开描述就是: 当函数调用时是被某一个对象所拥有,函数内的
this
将绑定到该对象上。若是函数是独立调用的,则函数内部的 this 在严格模式下为 undefind, 在非严格模式下 this 会指向 window(node.js中指向global)。
根据上面的原则,咱们首要判断的是函数被谁
所拥有,举几个栗子更好理解:code
栗子1:对象
let a = 1 function foo () { console.log(this.a) } foo() // 1
foo() 是在全局环境下独立调用的,此时函数 foo 被全局对象拥有(this 指向 window),因此this.a
获取的是 window 全局对象下面的 a.
栗子2:
var a = 1 var foo = function (){ console.log(this.a) } var too = { a: 2, b: foo } var bar = too.b foo() // 1 too.b() // 2 bar() // 1
函数执行时肯定 this 指向的大体逻辑:
foo()
:
too.b()
:
too.b()
执行时,b 是被对象 too调用的,此时内部的 this 指向 对象 too;this.a
获取的是too.a
,输出2;bar()
:
bar()
此时在全局对下 window 下调用,因此输出1。栗子3:
var a = 1 var foo = function () { console.log(this.a) } var too = function (fn) { var a = 2 fn() } too(foo) // 1
too(foo)
:这里函数 foo 做为参数被传递到函数 too的内部执行, fn()
执行时并无被其余对象显示拥有,因此咱们隐式的断定fn()
是在全局对象 window 下面执行的,因此输出 1 。
这个栗子很容易搞错(我本身感受每过一段时间再看仍是会错o(︶︿︶)o),第一印象输出的应该是2,那是应为把 this 与做用域链弄混淆了。始终要记住做用域链是在源代码编码阶段就肯定了,而 this 是在函数运行阶段才肯定,属于执行上下文
的概念,是在运行阶段依据this
所在函数被谁调用来肯定的。
咱们再来把上面的栗子稍微修改一下
栗子4:
var a = 1 var foo = function () { console.log(this.a) // 输出 1 console.log(a) // 区别在这里, 输出 1 } var too = function (fn) { var a = 2 fn() } too(foo) // 1, 1
表达式 | - | - | 值 |
---|---|---|---|
console.log(this.a) |
基于上下文 this | 表达式所属 foo 函数在 too 函数内调用时 this 指向 window | 1 |
console.log(a) |
基于做用域链 | 全局上下文中的变量 a 在 foo 函数做用域链上 | 1 |
不知道你理解了没有,这个栗子也体现了上下文
与做用域
在进行变量/属性查找时的区别
栗子5:
let c = 1 let foo = { c: 2 } let too = function () { console.log(this.c) } foo.a = { b: too, c: 3 } foo.a.b() // 3
this
的绑定只受最靠近的成员引用的影响。foo.a.b()
函数b
做为对象foo.a
的方法被调用,因此此时的this
绑定到foo.a
。b
与对象foo
的包含成员没有多大关系,最靠近的对象才决定this
的绑定。
最后console.log(this.c)
取到的是foo.a
里c
的值 3 .
栗子5:
let a = 1 let foo = { a: 2, msg: function () { console.log(`hi, ${this.a}`) } } let too = Object.create(foo) too.msg() // hi, 2
上面用对象foo
做为原型建立新对象too
, 因此对象 too 继承对象 foo 的全部属性、方法。too.msg()
执行时,msg 函数被 too 调用,此时this
就指向对象too
, 因此console.log(
hi, ${this.a})
访问的是从对象foo
继承来的 a.
因此对于在对象原型链上某处定义的方法,一样的概念也适用。若是该方法存在于一个对象的原型链上,那么对象实例的this
指向的是调用这个方法的对象,经过this
能够访问到原形链上的方法。
经过上面的几个栗子验证了咱们的总结:
当函数做为对象方法调用时 this 指向该对象,做为函数独立调用时 this 指向全局对象 window (严格模式下 this 为 undefind )。
大部分时候依据上面的原则来判断 this 的指向是没有问题,可是 this 还有如下几种特殊场景须要注意。
函数也是对象
栗子:
function Foo (a) { this.a = a // 实例化后 this 指向 too } let too = new Foo(1)
咱们知道函数 this 在运行期肯定,而构造函数实例化时在内部实际上是为咱们建立了一个新的对象,经过一系列的操做后将 this 指向了这个新对象。
new
操做符执行时的逻辑推导以下:
返回的新对象就是咱们实例化的对象。即, new 操做符调用构造函数时,this 是指向内部建立的新对象,最后会将新建立的对象返回给实例变量。
因此当函数做为构造函数调用,则函数内部的 this 绑定到该函数上。在经过构造函数实例化对象时,对象内部的 this 也一样指向该实例对象。
在 javascript 里函数也是对象。 全部函数都继承于 Function 构造函数,而 call、apply 是 Function.prototype 原形的方法,因此函数都从原形的原形里继承了 call、apply 方法。
call、apply 用于向函数注入 this 对象和变量(call 与 apply 的区别在于传递的参数不同,其余没有区别)。
let a = 1 let too = { a: 2 } function foo () { console.log(this.a) } foo.call(too) // 2
函数 foo 里面的 this 此时指向call
传递进来的对象 too,因此this.a
打印的是 2.
若是传递给 this
的值不是一个对象,JavaScript 会尝试使用内部 ToObject
操做将其转换为对象。所以,若是传递的值是一个原始值好比 7
或 'foo'
,那么就会使用相关构造函数将它转换为对象,因此原始值 7
会被转换为对象: new Number(7)
,而字符串 'foo'
转化成 new String('foo')
。
function bar() { console.log(this, Object.prototype.toString.call(this)); } //原始值 7 被隐式转换为对象 bar.call(7); // Number {[[PrimitiveValue]]: 7}__proto__: Number[[PrimitiveValue]]: 7 "[object Number]"
ECMAScript 5 引入了 Function.prototype.bind
。当函数调用f.bind(someObject)
会建立一个与f
具备相同函数体和做用域的函数,可是在这个新函数中,this
将永久地被绑定到了bind
对象someObject
。
栗子:
let a = 1 let too1 = { a: 2 } function foo () { console.log(this.a) } let bar = foo.bind(too1) let bar2 = { a: 4, b: bar } bar() // 2 bar.call({a: 3}) // 2 bar2.b() // 2
当foo
经过bind
建立一个新函数时,新函数的this
强制绑定到了传入的对象too1
, 后续执行中bar
即便是做为对象方法调用仍是使用 call、apply 都没法替换使用 bind 绑定的 this。
栗子:
function foo () { let that = this let too = () => { console.log(this === that) // true } too() } foo()
too 为箭头函数,内部的 this 被指向为他建立时的上下文,即 foo 的 this 。反过来讲就是箭头函数没有本身的上下文,他共享的是封闭词法上下文。
注意这里提到的 “this 是动态绑定,在运行期绑定的” 主要是指进入函数后,函数运行前的上下文建立阶段(预处理
),此时函数内的表达式、语句并无执行。但这里都统称为函数运行期,详细请关注变量对象
一节的描述(^.^)。