this使用指北

写在前面

  如果要问JavaScript中哪个知识点容易混淆,this指向绝对名列前茅,今天我们就来聊聊JavaScript的一大山脉——this指向。
  JavaScript的this总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。即this指向哪里由函数在哪里调用以及如何被调用决定。

默认绑定

  在不能使用其他绑定规则时执行的规则,一般作为普通函数调用。默认绑定给全局, 浏览器为window, node为global

  1. 全局环境中,this默认绑定到window

    console.log(this === window);//true
    
  2. 函数独立调用时,this默认绑定到window

    function foo(){
      console.log(this === window);
    }
    foo(); //true
    
  3. 被嵌套的函数独立调用时,this默认绑定到window
    虽然test()函数被嵌套在obj.foo()函数中,但test()函数是独立调用,而不是方法调用。所以this默认绑定到window

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

【IIFE】
  IIFE立即执行函数实际上是函数声明后直接调用执行

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

  上面的代码等价于下面:

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

【闭包】

  类似地,test()函数是独立调用,而不是方法调用,所以this默认绑定到window

var a = 0; 
function foo(){ 
  function test(){
    console.log(this.a);
  } 
  return test;
}; 
var obj = {
  a : 2,
  foo:foo
}
obj.foo()();//0

  由于闭包的this默认绑定到window对象,但又常常需要访问嵌套函数的this,所以常常在嵌套函数中使用var that = this,然后在闭包中使用that替代this,使用作用域查找的方法来找到嵌套函数的this值,如下所示:

var a = 0; 
function foo(){ 
  var that = this; 
  function test(){
    console.log(that.a);
   } 
  return test;
}; 
var obj = {
  a : 2,
  foo:foo
}
obj.foo()();//2

隐式绑定

  函数调用是在某个对象身上触发的, 即调用位置存在执行上下文,一般作为对象的方法调用, 如obj.fun(), 此时this会绑定给调用者

  特殊情况: 虽然为对象调用, 但是__proto__上并没有fun函数

function foo(){
  console.log(this.a);
}; 
var obj1 = {
  a:1,
  foo:foo,
  obj2:{
    a:2,
    foo:foo
  }
} 
//foo()函数的直接对象是obj1,this隐式绑定到obj1
obj1.foo();//1
//foo()函数的直接对象是obj2,this隐式绑定到obj2
obj1.obj2.foo();//2
obj.__proto__.fun() //error,因为__proto__上无fun方法,所以会报错

隐式丢失

  对象内函数被传递赋值给其他变量时, 通过其他变量调用会丢失this,因为传递是函数本身, 在调用时会绑定给其调用者

【函数别名】
  下面只是把obj.foo赋予别名bar,造成了隐式丢失,因为只是把foo()函数赋给了bar,而bar与obj对象则毫无关系。

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

  上面的代码等价于下面:

var a = 0; 
var bar = function foo(){
  console.log(this.a);
}
bar();//0

【参数传递】
  把obj.foo当作参数传递给bar函数时,有隐式的函数赋值fn=obj.foo。与上例类似,只是把foo函数赋给了fn,而fn与obj对象则毫无关系

var a = 0; 
function foo(){
  console.log(this.a);
}; 
function bar(fn){
  fn();
} 
var obj = {
  a : 2,
  foo:foo
} 
bar(obj.foo);//0

  上面的代码等价于下面:

var a = 0; 
function bar(fn){
  fn();
}
bar(function foo(){
  console.log(this.a);
});

【内置函数】

  内置函数与上例类似,也会造成隐式丢失

var a = 0; 
function foo(){
  console.log(this.a);
}; 
var obj = {
  a : 2,
  foo:foo
}
setTimeout(obj.foo,100);//0

  上面的代码等价于下面:

var a = 0;
setTimeout(function foo(){
  console.log(this.a);
},100);//0

【间接引用】

  函数的"间接引用"一般都在无意间创建,最容易在赋值时发生,会造成隐式丢失
  下面将o.foo函数赋值给p.foo函数,然后立即执行。相当于仅仅是foo()函数的立即执行

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

  与下面不同, 将o.foo函数赋值给p.foo函数,之后p.foo函数再执行,是属于p对象的foo函数的执行

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;
p.foo();//4

【其他情况】

  在javascript引擎内部,obj和obj.foo储存在两个内存地址,简称为M1和M2。只有obj.foo()这样调用时,是从M1调用M2,因此this指向obj。但是,下面三种情况,都是直接取出M2进行运算,然后就在全局环境执行运算结果(还是M2),因此this指向全局环境

var a = 0; var obj = {
  a : 2,
  foo:foo
}; 
function foo() {
  console.log( this.a );
};
(obj.foo = obj.foo)();//0
(false || obj.foo)();//0
(1, obj.foo)();//0

显式绑定

  显式绑定即手动的给函数传递this, 常用的有三种方法, call()、apply()、bind();核心理念是借用方法, 即借助其他对象的方法;绑定在函数原型上的方法, 只能由函数调用

var a = 0; 
function foo(){
  console.log(this.a);
} 
var obj1 = {
  a:1 
}; 
var obj2 = {
  a:2
};
foo.call(obj1);//1
foo.call(obj2);//2

【硬绑定】

  硬绑定是显式绑定的一个变种,使this不能再被修改
  直接在bar函数内部手动调用foo.call(obj)。因此,无论之后如何调用函数bar,它总会手动在obj上调用foo

var a = 0; 
function foo(){
  console.log(this.a);
} 
var obj = {
  a:2 
}; 
var bar= function(){
  foo.call(obj);
} 
bar();//2
setTimeout(bar,100);//2
bar.call(window);//2

【API】

  javascript中新增了许多内置函数,具有显式绑定的功能,如数组的5个迭代方法:map()、forEach()、filter()、some()、every()

var id = 'window'; function foo(el){
  console.log(el,this.id);
} 
var obj = {
  id: 'fn' 
};
[1,2,3].forEach(foo);//1 "window" 2 "window" 3 "window"
[1,2,3].forEach(foo,obj);//1 "fn" 2 "fn" 3 "fn"

new绑定

  如果函数或者方法调用之前带有关键字new,它就构成构造函数调用。对于this绑定来说,称为new绑定

  【1】构造函数通常不使用return关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值

function fn(){ this.a = 2;
} var test = new fn();
console.log(test);//{a:2}

  【2】如果构造函数使用return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果

function fn(){ this.a = 2; return;
} var test = new fn();
console.log(test);//{a:2}

  【3】如果构造函数显式地使用return语句返回一个对象,那么调用表达式的值就是这个对象

var obj = {a:1}; function fn(){ this.a = 2; return obj;
} var test = new fn();
console.log(test);//{a:1}

  [注意] 尽管有时候构造函数看起来像一个方法调用,它依然会使用这个新对象作为this。也就是说,在表达式new o.m()中,this并不是o

var o = {
  m: function(){ return this;
  }
}
var obj = new o.m();
console.log(obj,obj === o);//{} false
console.log(obj.constructor === o.m);//true

严格模式

  【1】严格模式下,独立调用的函数的this指向undefined

function fn(){ 'use strict';
  console.log(this);//undefined
}
fn(); 
function fn(){
  console.log(this);//window
}
fn();

  【2】在非严格模式下,使用函数的call()或apply()方法时,null或undefined值会被转换为全局对象。而在严格模式下,函数的this值始终是指定的值

var color = 'red'; function displayColor(){
  console.log(this.color);
}
displayColor.call(null);//red
var color = 'red'; 
function displayColor(){ 
  'use strict';
  console.log(this.color);
}
displayColor.call(null);//TypeError: Cannot read property 'color' of null

最后

  this的四种绑定规则:默认绑定、隐式绑定、显式绑定和new绑定,分别对应函数的四种调用方式:独立调用、方法调用、间接调用和构造函数调用。
  分清这四种绑定规则不算难,比较麻烦的是需要练就火眼金睛,识别出隐式丢失的情况。说到底,javascript如此复杂的原因是因为函数过于强大。因为,函数是对象,原型链比较复杂;因为函数可以作为值被传递,执行环境栈比较复杂;同样地,因为函数具有多种调用方式,this的绑定规则也比较复杂。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值