JS专题 : this

定义

  1. 需要明确的是,事实上JavaScript并不具有动态作用域。它只有词法作用域,简单明了。但是this机制某种程度上很像动态作用域

  2. 在运行时才被确定下来的, 他们只关心从何处调用, 也就是动态作用域是基于调用栈, 而不是书写代码时产生的作用域嵌套, 只关注在何处调用

this在普通函数中function xx (){}

  1. this在任何情况下都不指向函数的词法作用域

  2. 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, applybind
  1. 硬绑定: 显式的强制绑定
  • 举例说明:
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 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

  1. 创建(或者说构造)一个全新的对象。
  2. 这个新对象会被执行 [[ 原型 ]] 连接。
  3. 这个新对象会绑定到函数调用的 this。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。

优先级

new绑定>显式绑定> 隐式绑定> 默认绑定

this 在箭头函数中 ()=>{}

  • 单来说,箭头函数在涉及this绑定时的行为和普通函数的行为完全不一致。它放弃了所有普通this绑定的规则,取而代之的是用当前的词法作用域覆盖了this本来的值。

  • 箭头函数的绑定无法被修改(new 也不行)

  • 箭头函数最常用于回调函数中, 如事件处理或者定时器

总结:

如果要判断一个运行中函数的 this 绑定,就需要找到这个函数直接调用位置。找到之后 就可以顺序应用下面这四条规则来判断 this 的绑定对象。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值