this指向

什么是this

this是JS中定义的一个保留字,被自动的定义在作用域中,每一个作用域中有且只有一个this。

为什么学习this

看个例子

function fun() {
      return this.name.toUpperCase();
   }
   function speak() {
     var greeting = "Hello, I'm " + fun.call( this );
     console.log(greeting)
   } 
   var me ={ name: "Kyle"};
   var you = { name: "Reader"};
   
  fun.call( me );     // KYLE
  fun.call( you );    // READER
  speak.call( me );   // Hello, I'm KYLE
  speak.call( you );  // Hello, I'm READER
  
  // 这两个函数,在不同的上下文环境里(me, you)被重用了!省事儿,不需要写两个重复的函数。
  // 当然,也可以直接传一个上下文对象context给他们
  
  function identify(context) {
	return context.name.toUpperCase();
  }
  
  function speak(context) {
	var greeting = "Hello, I'm " + identify( context );
	console.log( greeting );
  }
  identify( you );  // READER
  speak( me );      // Hello, I'm KYLE

this 提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将 API 设计得更加简洁并且易于复用。随着你的使用模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱,使用 this 则不会这样。

this是运行的时候才被绑定的。它的上下文,取决于据函数是如何被调用的,和函数在哪里定义完全无关。

this绑定规则

调用位置(call-site):就是函数在代码中被调用的位置 。

调用栈(call-stack):到达当前执行位置所调用的全部函数。

一个例子理解上面两个名词

  function foo() {
         //foo函数调用栈   全局 =>  foo  
         //调用位置为全局
         bar();  // bar的调用位置
     }

     function bar () {
         // bar的函数调用栈  全局  => foo => bar
         baz();  // baz的调用位置
     }

    function baz() {
        // baz的函数调用栈 全局 => foo => bar =>baz
        console.log('baz');
    }
    // foo的调用位置 
    foo();
  • 默认绑定

发生在最常见的独立函数调用情况。

  function foo() {
        console.log(this.a);
    }
    var a = 2;
    foo();
    
    // 结果:2。对于浏览器来说,是window对象,对于node来说,是global obeject。
"use strict"   
   function foo() {
        console.log(this.a);
    }
    var a = 2;
    foo();
        
    // 结果:Uncaught TypeError: Cannot read property 'a' of  undefined

没有其它绑定规则时,应用默认规则, 上面的例子在函数调用时,应用了this的默认绑定,因此this指向全局对象。

严格模式下 默认绑定会报错,如果使用严格模式 全局对象将无法使用默认绑定。

  • 隐式绑定

1. 常规隐式

调用位置(call-site)是否有上下文对象,或者是某个对象拥有或包含。 例子:

 function foo() {
     console.log( this.a );
   }
   
   var obj = {
      a: 2,
      foo: foo
   };
   obj.foo(); 
   
   // 结果:2

foo()先被定义,之后作为一个引用属性被加在obj上了。其实obj从来没有拥有过foo()但是因为调用位置(call-site) 用了obj作为上下文来引用foo(),所以可以看成是:foo()被调用的时候,obj拥有foo()的引用。因为foo()被obj引用了,相当于有了上下文,所以this就被绑在了这个对象上。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

对象引用链中只有最后一层会影响调用位置。

2. 隐式丢失

 function foo() { 
   console.log( this.a );
 }
 var obj = { 
   a: 2,
   foo: foo 
 };
 // 函数别名!
 var bar = obj.foo;
 // a 是全局对象的属性 
 var a = "oops, global";
 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 
};
// a 是全局对象的属性 
var a = "oops, global";
doFoo( obj.foo ); 

// 结果:oops, global

参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一 个例子一样。用JS内置的setTimeout()测试下:

function foo() { 
   console.log( this.a); 
}
var obj ={
 a: 2,
 foo: foo 
};
// a 是全局对象的属性 
var a = "oops, global";
setTimeout( obj.foo, 100 );

// 结果:oops, global
  • 显示绑定

通过使用call apply 方法

  function foo() {
    console.log( this.a );
  }
  var obj={
   a:2
  };
  foo.call( obj ); 
 
  // 结果:2

上面这种写法还是能被随意更改this指向,怎么解决this被其他库覆盖改写的问题呢?

  • 硬绑定
function foo() { 
   console.log( this.a );
}
var obj={
 a:2
};
var bar = function() {
 foo.call( obj );
};
bar(); 
setTimeout( bar, 100 ); 
// 硬绑定的 bar 不可能再修改它的 this 
bar.call( window );

// 结果:2
// 结果:2
// 结果:2

在函数bar的内部手动调用 了 foo.call(obj),因此强制把 foo 的 this 绑定到了 obj。无论之后如何调用函数 bar,它 总会手动在 obj 上调用 foo。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。

硬绑定的典型用法:

 function foo(something) { 
   console.log( this.a, something );
   return this.a + something;
}
// 简单的辅助绑定函数 
 function bind(fn, obj) {
    return function() {
       return fn.apply( obj, arguments );
   };
 }
 var obj={
  a:2
 };
 var bar = bind( foo, obj );
 var b = bar(3);    
 console.log( b );
 
 // 结果:2 3
 // 5

由于硬绑定是一种非常常用的模式 以在 ES5 中提供了内置的方法 Function.prototype. bind,它的用法如下:

function foo(something) { 
    console.log( this.a, something );
    return this.a + something;
}
var obj={
 a:2
};
var bar = foo.bind( obj );
var b = bar(3);
console.log( b );  

// 结果:2 3 
// 5
  • new绑定

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

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

2. 这个新对象会被执行[[Prorotype]]连接。

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

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


function Foo(a) { 
 this.a = a;
}
var bar = new Foo(2); 
console.log(bar.a) 

// 结果:2

使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。

优先级

默认绑定是优先级中最低的,我们来比较下 其它的优先级 隐式绑定和显式绑定的优先级,我们来对比下:

 function foo() { 
   console.log(this.a);
 }
 var obj1 = {
  a: 2,
  foo: foo
};
 var obj2 = { 
   a: 3,
  foo: foo
 };
 obj1.foo();
 obj2.foo();
 obj1.foo.call( obj2 );
 obj2.foo.call( obj1 );
 
 // 结果:2
 // 3
 // 3
 // 2

隐式绑定和new 优先级

 function Foo(name){
     this.name = name;
 }
 
 var obj = {
     name : "obj",
     foo:Foo
 }
 var obj2 = new obj.foo("new")
 console.log(obj2)
 
 // 结果:Foo {name: "new"}

new 和 显示 优先级 由于new没法直接与apply和 call 使用,我们用硬绑定的方式来判断 new foo.call(obj) 是不允许的。

function foo(something) { 
  this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 ); 
bar(2);
console.log( obj1.a );
var baz = new bar(3); 
console.log( obj1.a );
console.log( baz.a );

// 结果:2
// 2
// 3

bar被硬绑定到obj1上,但是new bar(3)并没有像我们预计的那样把obj1.a 修改为 3。相反,new 修改了硬绑定(到 obj1 的)调用 bar(..) 中的 this。因为使用了 new 绑定,我们得到了一个名字为 baz 的新对象,并且 baz.a 的值是 3。

总结一下this的判断流程:

从call-site 调用位置 开始,逐一判断下列条件,第一个符合的就是当前有效规则。

1 函数调用前面有没有new?有 this->新建对象

2 有没有call/apply?有 this->绑定指定对象

3 函数有没有上下文?有 this->绑定上下文对象

4 其他情况就是默认绑定,严格模式指向全局报错

例外的this

  • 被忽略的this
  function foo() { 
    console.log( this.a )
  }
  var a = 2;
  foo.call( null ); 
  
  // 结果:2

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

  • 箭头函数

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

function foo() { 
   setTimeout(() => {
       // 这里的 this 在此法上继承自 foo()
       console.log( this.a );
 },100);
}
var obj={ 
  a:2
};
foo.call( obj );

// 结果:2
  • ES6之前
function foo() {
    var self = this; 
    setTimeout( function(){
        //这是一个闭包
        console.log( self.a );
    }, 100 );
}
var obj={
  	a:2
};
foo.call( obj );

// 结果:2
  • 当this遇到return

如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。

 function fn() {
      this.b = "哈哈";
      return {
        b: "嘿嘿"
      };
    };
    var a = new fn();
    console.log(a.b);
    
    // 结果:嘿嘿
   function fn() {
      this.b = "哈哈";
      return 123
    };
    var a = new fn();
    console.log(a.b);

    // 结果:哈哈

测试

var object = {
 myName:"object",
 getName: function() {
    return function() {
        console.log(this)
        console.info(this.myName)
    }
 }
}
 object.getName()()
var o = {
    a:10,
    b:{
        fn:function(){
            console.log(this.a);
        }
    }
}
o.b.fn();
var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a);
            console.log(this); 
        }
    }
}
var j = o.b.fn;
j()
var obj = {
   a: 'obj'
 }
var a = 'window'
var arr = [1, 2, 3]
arr.filter(function (i) {
    console.log(i, this.a)
    return i > 2
}, obj)
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇