深入JavaScript系列02--this关键字

684 阅读4分钟

“You know nothing Jon Snow”

this关键字

“你不知道的JavaScript学习笔记02”

概念

  • 定义:当一个函数被调用时,会创建一个执行上下文。这个执行上下文会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到
  • 所以可以明确一点:this既不指向函数自身也不指向函数的词法作用域,this指代的是当前函数被调用时的执行上下文
  • this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用

绑定规则

默认绑定

  • 独立函数调用,this默认绑定全局对象(在严格模式下默认绑定不会指定上下文对象)
    function foo() {
    console.log(this.a)
    }
   var a = "fuck";
   foo();//fuck

隐式绑定

  • 当函数作为对象的属性被调用时,会触发隐式绑定
      var obj1 = {
          a:1,
          foo:function() {
              console.log(this.a);
          }
      }
      var obj2 = {
          b:2,
          bar:function() {
              consolelog(this.b);
          }
      }
      
      obj1.foo();//1
      obj2.bar();//2
  • 当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
  • 注意:对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。
      function foo() {
        console.log(this.a);
      }
      
      var obj1 = {
          a:1,
          foo:foo
      }
      
      var obj2 = {
          a:2,
          obj1
      }
      
      var obj3 = {
          a:3,
          obj2
      }
      
      obj3.obj2.obj1.foo(); //1;

隐式丢失

      var obj1 = {
          a:1,
          foo:function() {
              console.log(this.a);
          }
      }
      var a = "shit";
      
      var bar = obj1.foo;
      bar(); //shit

在js中,函数属于Object的之类,在这块代码块中的RHS实际上是传递函数的引用,而this是由调用宿主和执行上下文决定的,函数执行时执行的是默认绑定的规则

显式绑定

  • 概念:直接指定函数执行时的上下文对象
  • 内置函数(对象)Function上的三个方法:call()、apply()、bind()
 var obj1 = {
          a:1
      }
 function foo() {
              console.log(this.a);
          }
      var a = 2;
    foo.call(obj1); // 1
 foo.apply(obj1); //1
 var bar = foo.bind(obj1);
 bar(); //1

new声明绑定

  • new关键字

    avaScript中的“构造函数”有别于其他语言,在JavaScript中,在函数调用前用new关键字进行声明,该函数就成了“构造函数”,函数本身并无区别。 使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。1.创建(或者说构造)一个全新的对象。2.这个新对象会被执行[[Prototype]]连接。3.这个新对象会绑定到函数调用的this。4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

//eg
function myNew() {

  // 第一个参数是构造函数
  var Constructor = [].shift.call(arguments);

   // 创建一个继承对象
  var obj = Object.create(Constructor.prototype);

  // 执行构造函数,this执行obj
  var result = Constructor.apply(obj, arguments);

  // 构造函数返回是对象,优先使用返回的对象,否则是obj
  return result instanceof Object ? result : obj;

  // 执行构造函数,this执行obj
  var result = Constructor.apply(obj, arguments);

  // 构造函数返回是对象,优先使用返回的对象,否则是obj
  return result instanceof Object ? result : obj;
}

var a = 'fuck';

function foo(a) {
 this.a = a;
    this.saya = function() {
        console.log(this.a)
    }
}
var b = "shit";
var obj = myNew(foo,b); // new foo(b)
obj.saya; //shit;

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

优先级

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

可以按照下面的顺序来进行判断:

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

如果是的话this绑定的是新创建的对象。

var obj = new foo(); //绑定obj

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

如果是的话,this绑定的是指定的对象。

var bar = new foo.call(obj); //绑定obj

3.函数是否在某个上下文对象中调用(隐式绑定)?

如果是的话,this绑定的是那个上下文对象。

var bar = obj.foo(); //绑定obj

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

this
this

箭头函数

箭头函数并不是使用function关键字定义的,而是使用被称为“胖箭头”的操作符=>定义的。箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。

function foo() {
    return (a) => {
        console.log(this.a)
    }
}
var obj1 = {
    a:1
}
var obj2 = {
    a:2
}

var bar = foo.call(obj1);
bar.call(obj2); //1,不是2

foo内部的箭头函数会捕获调用foo时的this;箭头函数的this查询规则遵从词法;外层函数绑定的this会间接传递给内部的箭头函数,箭头函数的绑定无法被修改。

function foo() {
    var _this = this;
    setTimeout(function(){
        console.log(_this.a)
    },100)
}
function bar() {
     setTimeout(()=>{
        console.log(this.a)
    },100)
}
function baz() {
    setTimeout(function(){
        console.log(this.a)
    },100)
}
var obj = {
    a: 1
}
var a = 2;
foo.call(obj); //1
bar.call(obj); //1
baz.call(obj); //2, 天惹,回调函数的this执行了默认绑定

参考文献: 《你不知道的JavaScript(上卷)》

本文使用 mdnice 排版