你应该了解的this

作者: 7YearsOld | 来源:发表于2017-12-01 10:53 被阅读0次
    • this机制:this在运行时进行绑定,并不是在编写是进行绑定,它的上下文取决于函数调用时的各种条件。this的绑定和和函数的声明位置没有任何关系,只取决于函数的调用方式
    • 当一个函数被调用的时候,会创建一个活动记录(执行上下文),这个记录会包含函数在哪里被调用、函数的调用方式、传入的参数等信息,this就是这个记录的一个属性,会在函数的执行中被找到。

    四种绑定规则

    默认绑定

    我们先来看一段代码:

     var name = 'global'
    
     function person(){
       var name = 'inner'
       console.log(this.name)
     }
    
     person()  // global
    

    这里我们执行person()、输出结果是在全局上的name的值,那么为什么呢,因为这里的this指向全局对象,执行的是默认绑定。
    那么我们怎么知道何时使用默认绑定呢,这里函数 person()在调用的时候没有应用任何的修饰,直接使用的是 person(),因此就只能使用默认绑定,那么使用修饰调用是什么样的、之后会进行详细说明。
    但是也不是默认绑定的所以情况都是指向全局对象,当你使用严格模式编写代码时,
    this指向undefined。

    'use strict'
     var name = 'global'
    
     function person(){
       var name = 'inner'
       console.log(this.name)
     }
    
     person() //Cannot read property 'name' of undefined
    

    隐式绑定

    先上代码:

     var name = 'global'
    
     function person() {
       var name = 'inner'
       console.log(this.name)
     }
    
     var obj = {
       name:'objName',
       person: person
     }
    
    obj.person()  // objName
    

    这里我们遇见第一种带有修饰的调用obj.person() 而非默认绑定中的直接调用person()

    就像我们最开始说的那种,this不是在声明时进行绑定的,而是在调用时进行绑定的。当函数的引用有上下文对象时,隐式绑定会把函数调用中的this绑定到这个上下文对象,这个例子中this绑定到obj对象上,this.name就相当于obj.name,我们最终就会得到objName

    下面这个例子中,说明只有在引用链的最后一层起作用。

     var name = 'global'
    
     function person() {
       var name = 'inner'
       console.log(this.name)
     }
    
     var obj1 = {
       name:'obj1Name',
       person: person
     }
     
     var obj2 = {
      name:'obj2Name',
      obj1: obj1
     }
    
    obj2.obj1.person()  // obj1Name
    

    常见面试题:隐式丢失问题

     var name = 'global'
     
      function person() {
        var name = 'inner'
        console.log(this.name)
      }
     
      var obj1 = {
        name:'obj1Name',
        person: person
      }
      
     var obj2 = obj1.person
     
     obj2()// global
    

    这里声明一个变量 obj2 = obj1.person 之后调用 obj(2) 虽然obj2是对obj1.person的引用,但是在下文的调用时使用的是没有任何修饰的调用,即obj2,这将应用我们上面提到的默认绑定,即非严格模式下this绑定到全局对象,
    严格模式下绑定到undefined,输出golbal也符合我们的预期。

    再看下面的代码:

     var name = 'global'
    
     function person() {
       var name = 'inner'
       console.log(this.name)
     }
    
     function student(fn) {
       // fn=obj1.person
       fn()
     }
    
     var obj1 = {
       name: 'obj1Name',
       person: person
     }
    
     student(obj1.person) //global
    

    这里执行student()是相当于 fn=obj1.person fn是对obj1.person的引用,和上个例子中同样,发生隐式绑定丢失,对this使用默认绑定。

    setTimeout:

     var name = 'global'
    
     function person() {
       var name = 'inner'
       console.log(this.name)
     }
    
     var obj1 = {
       name: 'obj1Name',
       person: person
     }
    
     setTimeout(obj1.person, 1000); // node 环境下 undefined
    

    我们来看下 mdn对this指向错误的解释:

    由setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致,这些代码中包含的 this 关键字在非严格模式会指向 window (或全局)对象,严格模式下为 undefined,这和所期望的this的值是不一样的
    mdn上polyfill实现setTimeout的部分代码

     window.setTimeout = function(vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */ ) {
      var aArgs = Array.prototype.slice.call(arguments, 2);
      return __nativeST__(vCallback instanceof Function ? function() {
        vCallback.apply(null, aArgs);
      } : vCallback, nDelay);
    }
    

    我们简化一下:去掉args

     window.setTimeout = function(vCallback, nDelay ) {
      return __nativeST__(vCallback instanceof Function ? vCallback() : vCallback, nDelay);
    }
    

    我们可以看出和上面一样,vCallback = obj1.person 同样是对obj1.person的引用。

    显示绑定

    call、apply

    一般来说基本上所有的函数都是由Function创建,Function的原型链上有两个方法call,apply

    mdn上关于call和apply的方法接收的参数:
    call: fun.call(thisArg, arg1, arg2, ...)

    apply:fun.apply(thisArg, [argsArray])

    可以看出第一个参数是一样的都是this,不同点在于call接收的是参数列表,apply接收一个数组。

    这种显示的更改this的指向方法,我们称之为显示绑定。

    var name = 'global'
    
     function person() {
       var name = 'inner'
       console.log(this.name)
     }
    
     var obj1 = {
       name: 'obj1Name',
       person: person
     }
    
     person.call(obj1) //obj1Name
    

    这样我们虽然解决了this绑定的值,但是并没有解决之前提到的绑定丢失问题。

    bind:

    变种:我们封装一个函数,当每次调用函数时将this绑定到obj1上。

    var name = 'global'
    
     function person() {
       var name = 'inner'
       console.log(this.name)
     }
    
     var obj1 = {
       name: 'obj1Name',
       person: person
     }
    
     function bind(fn,obj,...args){
       return function(){
         fn.apply(obj,args)
       }
     }
    
    var res = bind(person,obj1)
    res()  //obj1Name
    

    同样,javascript 也为我们提供了这个方法:Function.protoype.bind()

    mdn 上关于bind()的polyfill:

    if (!Function.prototype.bind) {
      Function.prototype.bind = function(oThis) {
        if (typeof this !== 'function') {
          // closest thing possible to the ECMAScript 5
          // internal IsCallable function
          throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
        }
    
        var aArgs   = Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP    = function() {},
            fBound  = function() {
              return fToBind.apply(this instanceof fNOP
                     ? this
                     : oThis,
                     // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                     aArgs.concat(Array.prototype.slice.call(arguments)));
            };
    
        // 维护原型关系
        if (this.prototype) {
          // Function.prototype doesn't have a prototype property
          fNOP.prototype = this.prototype; 
        }
        fBound.prototype = new fNOP();
    
        return fBound;
      };
    }
    

    可以看出都是和我们写的bind()核心是一样的内部调用call、apply。

    new 绑定

    当new 一个函数的时候会执行一下的几步:

    1. 创建一个空对象;
    2. 将空对象的原型指向函数的property
    3. 调用apply、call 方法空对象的this指向函数
    4. 如果函数返回了一个“对象”,那么这个对象会取代整个new出来的结果。如果构造函数没有返回对象,那么new出来的结果为步骤1创建的对象

    之前写了一篇模拟实现 new的文章
    new的模拟实现

    示例:

     function Person(name) {
       this.name = name
     }
    
    
    var student = new Person('lili')
    
    console.log(student.name)  // lili
    

    ES6 箭头函数

    箭头函数本身没有this,而是根据外层(函数或者全局)作用域来决定this.

     var name = 'outer'
    
     function person() {
       setTimeout(() => {
         console.log(this.name)
       }, 100)
     }
    
     var obj = {
       name: 'lili',
       person: person
     }
    
     obj.person() // lili
    

    相关文章

      网友评论

        本文标题:你应该了解的this

        本文链接:https://www.haomeiwen.com/subject/qbvibxtx.html