JS 专题之: this
定义
-
需要明确的是,事实上JavaScript并不具有动态作用域。它只有词法作用域,简单明了。但是
this
机制某种程度上很像动态作用域 - 在运行时才被确定下来的, 他们只关心
从何处调用
, 也就是动态作用域是基于调用栈
, 而不是书写代码时产生的
作用域嵌套, 只关注在何处调用
this在普通函数中function xx (){}
-
this
在任何情况下都不指向函数的词法作用域 -
this
的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在函数执行的过程中用到。
调用位置
理解动态作用域
或this
, 无法逃避理解调用位置
, 最重要的是要分析调用栈
(就是为了到达当前执行位置所调用的所有函数)。我们关心的调用位置
就在当前正在执行的函数的前一个调用中
例子:
function baz(){
// 当前的调用栈是: baz,相当于 window->baz
// 因此当前的调用位置是全局作用域
bar();
}
function bar() {
//当前调用栈是window->baz->bar , 调用位置 永远是栈中的第二个元素
//因此,当前调用位置在baz中console.log("bar");
foo();//<--foo的调用位置
}
function foo(){
//当前调用栈是window->baz->bar->foo
//因此,当前调用位置在bar中
console.log("foo");
}
baz();//<--baz的调用位置
绑定规则
个人理解, 如果通过现象看本质, 其实要判断 this 到底指的的是谁, 无非就是判断, 我当前调用的这个 包含 this 的方法的指针
是在哪个作用域下面, 记住这句话, 无论在面试中遇到怎么样的迷惑你的 this, 你都能分辨出来
默认绑定
例子:
function foo(){
console.log(this.a);
}
var a =2;
foo(); //2
调用
foo()
时,this.a
被解析成了全局变量a
。为什么?因为在本例中,函数调用时应用了this
的默认绑定,因此this
指向全局对象. 为什么这里用到了默认绑定, 通过调用位置分析,foo()
是直接使用不带任何修饰的函数引用进行调用.因此只能是默认调用
隐式绑定
function foo(){
console.log(this.a);
}
var obj = {
a:2,
foo: foo
}
obj.foo(); // 2
当函数引用有上下文对象时,隐式绑定规则会把
函数调用中的this
绑定到这个上下文对象。因为调用foo()时this被绑定到obj,因此this.a和obj.a是一样的
对象属性引用链中只有最顶层或者说最后一层会影响
调用
位置 (最好的理解就是看那个调用的指针
是在哪个作用域下)
隐式绑定 所带来的问题 : 隐式丢失
我们经常被 this 搞糊涂是因为我们往往被 xx.xx
这种给搞混, 但是如果我们用指针去理解, 从而脱去他花里胡哨的’外衣’, 我们就能够理解 隐式丢失 .
例子 a:
function foo(){
console.log(this.a);
}
var obj ={
a:2,
foo:foo
};
var bar = obj.foo // 函数别名, 这个是一个全局的 bar
var a ='ops! global' // 给全局添加一个 a
bar()// 'ops global'
从这个例子可以看出,
bar
引用了obj.foo
(object.foo
方法的 指针, 我们起个名字叫做指针*a
, 不但指向了obj.foo
, 也指向了bar
), 但是当我们调用bar()
的时候, 其实是window.bar()
(相当于告诉js引擎,window 全局下的那个 *a
), 因此 this 就默认指向了 window, 跟obj.foo
没有关系. tips: 隐式丢失最好的理解方式,就是从指针
角度去理解
我们在来看一段代码,尝试用指针
的角度去分析
例子b:
function foo(){
console.log(this.a)
}
var obj = {
a:2,
foo:foo
}
var a = 'oops, global' // a 为全局对象属性
setTimeout(obj.foo, 100) // 'oops, global'
这是你不知道的 js
一书中的一段代码, 书上对这段代码只说明了结果, 并没有给出为什么会这么回事, 当时看到这里我就很难受了,不理解是我难受的点, 然后我就尝试用 指针的方式去分析了, 结果有点茅塞顿开(如果有错误分析,请指出):
在例子 b 中,我们明明看到
setTimeout
调用了obj.foo
, 咦? 那不对啊, 这不就是obj.foo
, 根据我学到的this
指向他最近的调用, 拿不就应该是this
指向了foo
吗? 为啥书中告诉是global
中的a
而不是obj
中的a
? 重点来了!!! 我们被obj.foo
给蒙骗了, 其实obj.foo
相当于一个衣服, 谁的衣服? 是指针的衣服. 哪个指针? 一个指向foo()
的指针. ok, 现在我们给这个指针不叫obj.foo
, 我们可以起名叫*a
, 那例子 b 中的setTimeout
那段代码就相当于是setTimeout(*a, 100)
, 这下你知道为什么了是window
下的a
了吧? 谁调用了这个指针*a
???setTimeout
作用域呗, 所以*a
这个指针方法里面的this
就指向了 setTimeout 的作用域
显示绑定
我认为可以是 告诉 js, 我要把这个带有 this 的
function
指针*a
, 必须要在某个
作用域中去调用, 也就是我们指定了调用这个function
的 指针*a
的
显示绑定: call
, apply
和 bind
- 硬绑定: 显式的强制绑定
- 举例说明:
function foo(){
console.log(this.a);
}
var obj = {
a:2
}
foo.call(obj); //2
个人理解: 就是告诉 foo, 我把你对应的函数指针,给强制绑定到了 obj 的作用域下了
注意: 如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作 this 的绑定对 象,这个原始值会被转换成它的对象形式(也就是 new String(…)、new Boolean(…) 或者 new Number(…))。这通常被称为“装箱”。(你不知道的 js
)
- 硬绑定的典型应用场景
function foo(something){
console.log(this.a, something);
return this.a + something;
}
var obj = {
a:2
}
var bar = function (){
return foo.apply(obj,arguments);
}
var b = bar(3); //2 3;
conosole.log(b); // 5
- 一些内置函数可以提供绑定对象, 例如 forEach(fn, thisArg ), thisArg(当执行回调函数 callback 时,用作 this 的值。)
new
绑定
使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行 [[ 原型 ]] 连接。
- 这个新对象会绑定到函数调用的 this。
- 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
优先级
new
绑定>显式绑定> 隐式绑定> 默认绑定
this 在箭头函数中 ()=>{}
-
单来说,箭头函数在涉及this绑定时的行为和普通函数的行为完全不一致。它放弃了所有普通this绑定的规则,取而代之的是用当前的
词法作用域
覆盖了this本来的值。 -
箭头函数的绑定无法被修改(new 也不行)
-
箭头函数最常用于回调函数中, 如事件处理或者定时器
总结:
如果要判断一个运行中函数的 this 绑定,就需要找到
这个函数
的直接调用
位置。找到之后 就可以顺序应用下面这四条规则来判断 this 的绑定对象。