个人认为要行先理解了闭包,理解了js函数执行机制,作用域链之后再来理解this的指向更容易。判断es5的this指向应该按这三步:
- 创建时 scope
- 执行时 (作用域链,上下文 )
- 是否被显式,隐式改变
this 是一个指针,一般指向其所在函数的执行环境(作用域链)的第二个环境对象(或者说是指向[[scope]]的第一个环境对象)(作用域链:执行环境(作用域链)= 函数内部环境 + 定义环境[[scope]])(和函数作用域链不同的是,函数是按作用域链层层查找的(从里到外哪里有就返回哪里的),而this指向是唯一的。)
所以,this并不指向自身,也不一定指向包含它的外部函数。
可以先看一下例子:
1 | function baz() { |
运行结果:
1 | baz[object Window] |
我们知道,函数是词法作用域,而不是动态作用域,所以不管在哪里调用,只要this没有发生隐式或显式更改,就按其声明时的scope。
再看这段代码:
1 | function foo(num) { |
执行 foo.count = 0 时,的确向函数对象 foo 添加了一个属性 count。但是函数内部代码
this.count 中的 this 并不是指向那个函数对象,所以虽然属性名相同,根对象却并不相
同。这里的this指向window.
进入正文:
this绑定规则
- 默认绑定:函数声明的位置
1 | var a = 2; |
默认绑定时,this指向全局对象。
注意:严格模式下(‘use strict’)this会默认绑定为undefined
- 隐式绑定:调用位置是否有上下文对象,或是否被某个对象拥有
1 | function foo(){ |
等同于foo.apply(obj),foo被调用时,函数引用有上下文对象,隐式绑定规则会把函数调用中的this绑定到这个对象上。
但是有时候会出现隐式丢失:
1 | function foo(){ |
又比如
1 | function foo() { |
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样。
再比如:
1 | var obj = { |
这里不是像上面那两个例子一样独立出去也不是传参,而是执行obj里面的立即执行函数,虽然fn是被绑定到obj上下文中的,但是f不是,。this没有按预想的绑定到外层函数对象上,而是绑定到了全局对象。这里普遍被认为是JavaScript语言的设计错误,因为没有人想让内部函数中的this指向全局对象。
还有一种常见的丢失
1 | function foo() { |
超时调用的代码都是在全局作用域中执行的,因此函数中this的值在非严格模式下指向window对象
,在严格模式下是undefined
- 显式绑定
(1)利用call 和 apply, bind方法
1 | function foo() { |
引申知识:call 和 apply, bind的区别以及如何手写实现。
例子:
1 | function foo(sth) { |
new绑定
首先看 new一个对象的过程发生了什么:
- 创建(或者说构造)一个全新的对象。
1 | instance = new Object(); |
- 进行原型链的链接
1 | instance.__proto__ = SubType.prototype; |
-
新对象绑定到函数调用的this,再让 SubType中的this指向instance,执行SubType的函数体内的语句(上面语句的内容就是定义两个属性并给其赋值)。
-
若没有return,默认返回该新对象
这就是new 操作符的工作
1 | function person(a,b) { |
因此,在这个过程中,就会有this指向的改变
四种绑定的优先级
new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
验证:
- 隐式绑定和显式绑定:
1 | function foo() { |
显然,显式绑定的优先级比隐式绑定优先级更高
- new绑定和隐式绑定
1 | function foo(something) { |
(这个例子需要用心观察 )
- new绑定和显实绑定:
1 | function foo(something) { |
bar
被硬绑定到 obj1
上,但是 new bar(3)
并没有像我们预计的那样把 obj1.a
修改为 3。相反,new
修改了硬绑定(到 obj1
的)调用 bar(..)
中的 this
。因为使用了
new
绑定,我们得到了一个名字为 baz
的新对象,并且baz.a
的值是 3
。
因此: 对于this的判断
- 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。
var bar = new foo()- 函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是
指定的对象。
var bar = foo.call(obj2)- 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上
下文对象。
var bar = obj1.foo()- 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到
全局对象。
var bar = foo()
ES6箭头函数的this
箭头函数的 this 的指向是不能被更改的。箭头函数完全修复了this的指向,即es6之前,this总是指向词法作用域,也就是外层调用者obj,如果使用箭头函数,以前的hack写法 var that = this就不再需要了。
例如:
1 | var obj = { |
1 | function foo() { |
foo() 内部创建的箭头函数会捕获调用时 foo() 的 this。由于 foo() 的 this 绑定到 obj1,
bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无法被修改。(new 也不
行!)
还有一个之前es5被认为设计错误的例子在es6 箭头函数这里也得到了修正:
1 | var x = 1 |
箭头函数的 this 是没有办法被更改的。 因为无论显示更改还是隐式更改,都只能更改函数自身的 this,而箭头函数的 this 不是自己的,而是像普通变量一样从外部引进来的。
最后,欢迎大家围观指正