this的指向

一:什么是this

this 其实是在函数被调用时发生的绑定,它指向什么彻底取决于函数在哪里被调用
app

当一个函数被调用时,会建立一个活动记录(有时候也称为执行上下文)。这个记录会包 含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的 其中一个属性,会在函数执行的过程当中用到
函数

二:this指向的2种误区

1:指向自身oop

例如:咱们想要记录一下函数 foo 被调用的次数,思考如下代码this


console.log 语句产生了 4 条输出,证实 foo(..) 确实被调用了 4 次,可是 foo.count 仍然 是 0。显然从字面意思来理解 this 是错误的。 cdn

执行 foo.count = 0 时,的确向函数对象 foo 添加了一个属性 count。可是函数内部代码 this.count 中的 this 并非指向那个函数对象,因此虽然属性名相同,根对象却并不相 同。
对象

解决:强制this指向foo对象
blog

2:它的做用域
继承

第二种常见的误解是,this 指向函数的做用域。这个问题有点复杂,由于在某种状况下它 是正确的,可是在其余状况下它倒是错误的。 ip

须要明确的是,this 在任何状况下都不指向函数的词法做用域。在 JavaScript 内部,做用 域确实和对象相似,可见的标识符都是它的属性。可是做用域“对象”没法经过 JavaScript 代码访问,它存在于 JavaScript 引擎内部。作用域



这段代码中的错误不止一个。虽然这段代码看起来好像是咱们故意写出来的例子,可是实 际上它出自一个公共社区中互助论坛的精华代码。这段代码很是完美(同时也使人伤感) 地展现了 this 多么容易误导人。

 首先,这段代码试图经过 this.bar() 来引用 bar() 函数。这是绝对不可能成功的,咱们之 后会解释缘由。调用 bar() 最天然的方法是省略前面的 this,直接使用词法引用标识符。

此外,编写这段代码的开发者还试图使用 this 联通 foo() 和 bar() 的词法做用域,从而让 bar() 能够访问 foo() 做用域里的变量 a。这是不可能实现的,你不能使用 this 来引用一 个词法做用域内部的东西.

每当你想要把 this 和词法做用域的查找混合使用时,必定要提醒本身,这是没法实现的。

三:判断this指向的几种绑定规则

首先要明确this的调用位置

最重要的是要分析调用栈(就是为了到达当前执行位置所调用的全部函数)。咱们关心的 调用位置就在当前正在执行的函数的前一个调用中。

下面咱们来看看到底什么是调用栈和调用位置: 

function baz() { 

   // 当前调用栈是:baz 

   // 所以,当前调用位置是全局做用域 

   console.log( "baz" ); 

   bar(); // <-- bar 的调用位置 

}

 function bar() {

      // 当前调用栈是 baz -> bar // 所以,当前调用位置在 baz 中 

     console.log( "bar" ); 

     foo();    // <-- foo 的调用位置 

}

function foo() {

       // 当前调用栈是 baz -> bar -> foo

      // 所以,当前调用位置在 bar 中 

      console.log( "foo" );

 } 

baz(); // <-- baz 的调用位置

1:默认绑定

首先要介绍的是最经常使用的函数调用类型:独立函数调用。能够把这条规则看做是没法应用 其余规则时的默认规则。 

思考一下下面的代码: 

function foo() { 

 console.log( this.a ); 

}

var a = 2; 

foo(); // 2

在代码中,foo() 是直接使用不带任何修饰的函数引用进行调用的,所以只能使用 默认绑定,没法应用其余规则,所以 this 指向全局对象。

注:若是使用严格模式(strict mode),那么全局对象将没法使用默认绑定,所以 this 会绑定 到 undefined

2:隐式绑定

隐式绑定主要是考虑 调用位置是否有上下文对象,或者说是否被某个对象拥有或者包 含,不过这种说法可能会形成一些误导。 

思考下面的代码: 

function foo() { 

    console.log( this.a ); 

var obj = { a: 2, foo: foo };

 obj.foo(); // 2

当 foo() 被调用时,它的落脚点确实指向 obj 对象。当函数引 用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。由于调 用 foo() 时 this 被绑定到 obj,所以 this.a 和 obj.a 是同样的。

注:对象属性引用链中只有最顶层或者说最后一层会影响调用位置。

举例来讲: 

function foo() { 

 console.log( this.a ); 

var obj2 = { a: 42, foo: foo };

 var obj1 = { a: 2, obj2: obj2 }; 

obj1.obj2.foo(); // 42

隐式丢失问题

一个最多见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上,取决因而否是严格模式。

思考下面的代码: 

function foo() { 

 console.log( this.a );

 } 

var obj = { a: 2, foo: foo }; 

var bar = obj.foo; // 函数别名! 

var a = "oops, global"; // a 是全局对象的属性

 bar(); // "oops, global" 

虽然 bar 是 obj.foo 的一个引用,,可是实际上,它引用的是 foo 函数自己,所以此时的 bar() 实际上是一个不带任何修饰的函数调用,所以应用了默认绑定。

一种更微妙、更常见而且更出乎意料的状况发生在传入回调函数时: 

function foo() { 

 console.log( this.a ); 

}

 function doFoo(fn) { 

 // fn 其实引用的是 foo 

 fn(); // <-- 调用位置! } 

var obj = { a: 2, foo: foo };

 var a = "oops, global"; // a 是全局对象的属性 

doFoo( obj.foo ); // "oops, global" 

参数传递其实就是一种隐式赋值,所以咱们传入函数时也会被隐式赋值,因此结果和上一 个例子同样。


 若是把函数传入语言内置的函数而不是传入你本身声明的函数,会发生什么呢?结果是一 样的,没有区别: 

function foo() { 

 console.log( this.a );

var obj = { a: 2, foo: foo }; 

var a = "oops, global"; // a 是全局对象的属性 

setTimeout( obj.foo, 100 ); // "oops, global"

JavaScript 环境中内置的 setTimeout() 函数实现和下面的伪代码相似: 

function setTimeout(fn,delay) {

 // 等待 delay 毫秒 

 fn(); // <-- 调用位置! 

}

3:显式绑定

使用call() apply() bind()强行改变this的指向

4:new绑定

使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操做

1. 建立(或者说构造)一个全新的对象。

 2. 这个新对象会被执行 [[ 原型 ]] 链接。

 3. 这个新对象会绑定到函数调用的 this。

 4. 若是函数没有返回其余对象,那么 new 表达式中的函数调用会自动返回这个新对象。

思考下面的代码:

 function foo(a) { 

 this.a = a;

 }

 var bar = new foo(2); 

console.log( bar.a ); // 2 

使用 new 来调用 foo(..) 时,咱们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。new 是最后一种能够影响函数调用时 this 绑定行为的方法,咱们称之为 new 绑定。

总结:

如今咱们能够根据优先级来判断函数在某个调用位置应用的是哪条规则。

能够按照下面的 顺序来进行判断: 

1. 函数是否在 new 中调用(new 绑定)?

      若是是的话 this 绑定的是新建立的对象。 

      var bar = new foo() 

2. 函数是否经过 call、apply(显式绑定)或者硬绑定调用?

若是是的话,this 绑定的是 指定的对象。

var bar = foo.call(obj2)

3. 函数是否在某个上下文对象中调用(隐式绑定)?若是是的话,this 绑定的是那个上 下文对象。 

var bar = obj1.foo()

4. 若是都不是的话,使用默认绑定。若是在严格模式下,就绑定到 undefined,不然绑定到 全局对象。 

var bar = foo()

就是这样。对于正常的函数调用来讲,理解了这些知识你就能够明白 this 的绑定原理了。 不过……凡事总有例外

例外:

1:被忽略的this

若是你把 null 或者 undefined 做为 this 的绑定对象传入 call、apply 或者 bind,这些值 在调用时会被忽略,实际应用的是默认绑定规则:

 function foo() {

 console.log( this.a ); 

var a = 2; 

foo.call( null ); // 2

2:间接引用

另外一个须要注意的是,你有可能(有意或者无心地)建立一个函数的“间接引用”,在这 种状况下,调用这个函数会应用默认绑定规则。 间接引用最容易在赋值时发生: 

function foo() { 

 console.log( this.a ); 

}

var a = 2; 

var o = { a: 3, foo: foo }; 

var p = { a: 4 }; 

o.foo(); // 3

 (p.foo = o.foo)(); // 2

赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,所以调用位置是 foo() 而不是 p.foo() 或者 o.foo()。根据咱们以前说过的,这里会应用默认绑定。 

3:箭头函数

咱们以前介绍的四条规则已经能够包含全部正常的函数。

可是 ES6 中介绍了一种没法使用 这些规则的特殊函数类型:箭头函数。 

箭头函数并非使用 function 关键字定义的,而是使用被称为“胖箭头”的操做符 => 定 义的。

箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)做用域来决 定 this。

function foo() {

 // 返回一个箭头函数

 return (a) => { 

 //this 继承自 foo() 

 console.log( this.a ); 

 }; 

var obj1 = { a:2 };

 var obj2 = { a:3}

var bar = foo.call( obj1 ); 

bar.call( obj2 ); // 2, 不是 3!

foo() 内部建立的箭头函数会捕获调用时 foo() 的 this。

因为 foo() 的 this 绑定到 obj1, bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定没法被修改。(new 也不 行!)



摘自 : 你不知道的JavaScript(上卷)

相关文章
相关标签/搜索