JavaScript闭包系列之几种基本的this指向分析

180 阅读5分钟

个人笔记

全局上下文thiswindow

全局上下文中的thiswindow(JS严格模式下是undefined);块级上下文中没有自己的this,所用到的this都是所处上级上下文中的this

console.log(this); //window
{
    let n = 12;
    console.log(this); //window  上级上下文是全局上下文
} 

this是函数的执行主体

区分:函数执行主体:谁把函数执行的;函数执行上下文:在哪执行的

注意:只有函数执行的时候才知道函数中的this是什么,根据不同的情况来不同判断,在创建函数的时候不知道this是什么

规律:

  1. 事件绑定:给当前元素的某个事件行为绑定方法,当事件触发、方法执行,方法中的this是当前元素本身的DOM对象
    document.body.onclick = function () {
        console.log(this); //body
    };
    document.body.addEventListener('click', function () {
        console.log(this); //body
    });
    // IE6~8 DOM2事件绑定
    document.body.attachEvent('onclick', function () {
        console.log(this); //window
    }); 
    
    注意:
    • IE6~8 DOM2事件绑定this还是window
    • vue中事件绑定,v-on:click='fn' ,fn方法中的this是vue实例,因为vue在内部做了特殊处理
  2. 普通函数执行
  • 函数执行前面是否有“点”,没有“点”,this就是window(JS严格模式下是undefined

  • 有“点”,“点”前面是谁this就是谁,几个例子如下:

      function fn() {
          console.log(this);
      }
      let obj = {
          name: 'ttt',
          fn: fn
      };
      fn(); //this->window
      obj.fn(); //this->obj 
    
    // 开启JS严格模式  =>注意严格模式和非严格模式的区别
    "use strict";
    function fn() {
        console.log(this);
    }
    let obj = {
        name: 'ttt',
        fn: fn
    };
    fn(); //this->undefined
    obj.fn(); //this->obj 
    
  • 匿名函数(自执行函数/回调函数)如果没有经过特殊的处理,则this一般都是window/undefined,但是如果经过一些特殊处理,一切都以处理后的结果为主

    个人理解:函数在没执行之前,只是存在一块内存里的一段字符串,等执行的时候才变为可执行的代码,到那时候其中的this再根据当时调用的情况再来判断到底是什么,然后看函数执行时的私有上下文的作用域链,看私有变量,等等。例如,同样内存地址的函数,调用时是在全局调用this就是window,对象调用this就是对象

    function fn(callback) {
      callback(); //callback()中的this->window/undefined
    }
    let obj = {
        sum() {
            console.log(this);
        }
    };
    obj.sum(); //this->obj
    // 回调函数:把一个函数作为实参值,传递给另外一个函数
    // => fn(function () {});
    fn(obj.sum); //this->window/undefined
    
    setTimeout(function () {
        console.log(this); //window或者undefined
    }, 1000); 
    
    let obj = {
        name: 'xxx'
    };
    let arr = [10, 20];
    arr.forEach(function (item, index) {
        console.log(this); //window或者undefined
    });
    

    经过特殊处理的例子:

    let obj = {
        name: 'xxx'
    };
    let arr = [10, 20];
    arr.forEach(function (item, index) {
        console.log(this); //obj 因为触发回调函数执行的时候,forEach内部会把回调函数中的this改变为传递的第二个参数值obj “特殊处理”
    }, obj);
    
    document.body.addEventListener('click',function(){console.log(this)})//body 经过特殊处理
    

    特殊:

    let obj = {
          sum() {
              console.log(this);
          }
      };
      obj.sum(); //this->obj
      (obj.sum)(); //this->obj
      // 括号表达式:小括号中包含“多项”(如果只有一项,和不加括号没啥本质区别),其结果是只取最后一项;但是这样处理后,this会发生改变,变为window/undefined
    (10, obj.sum)(); //this->window 
    

关于this的基本情况的面试题

var x = 3,
obj = {x: 5};
obj.fn = (function () {
    this.x *= ++x;
    return function (y) {
        this.x *= (++x)+y;
        console.log(x);
    }
})();
var fn = obj.fn;
obj.fn(6);
fn(4);
console.log(obj.x, x);

/*
13
234
95 234
*/

简单分析

  1. 前两行声明变量,执行到第三行obj.fn = (function () {...})();会立即执行这个函数,这里this.x *= ++x;=>this.x *= this.x *(++x);,也就是全局的x = x*(++x)=>x=3*4=12,所以全局x现在值为12

  2. 接着return一个函数,把这个函数记录一下,所以obj.fn是这个函数。

    function (y) {
          this.x *= (++x)+y;
          console.log(x);
      }
    
  3. 因为var fn = obj.fn;,所以全局的fn也是这个函数,这个函数中,x就是全局的x因为立即执行函数和这个函数没有私有变量,根据作用域链往上寻找,就到了全局的x,但是this.x还不清楚这个this是什么,需要根据执行场景环境的不同,来确定这个this是什么。

  4. 接着执行obj.fn(6),将6带入,作为函数私有上下文中的私有变量y.this。x *= (++x)+y;=>this.x *=this.x * ((++x)+y)这句代码,因为是obj.fn,所以其中this.x就是obj.x,既5,所以obj.x = 5*((12+1)+6)=95,现在obj.x值为95,全局x值为13。所以首先打印13

  5. 接着执行fn(4),还是上面记录下来的函数,此时函数中的thiswindow,所以this.x *= (++x)+y;=>window.x =window.x * ((++window.x)+y)window。x=13*(13+1+4)=234,此时全局x值为234,所以打印234

  6. 最后console.log(obj.x, x)分别打印95 234

var num = 10;
var obj = {
    num: 20
};
obj.fn = (function (num) {
    this.num = num * 3;
    num++;
    return function (n) {
        this.num += n;
        num++;
        console.log(num);
    }
})(obj.num);
var fn = obj.fn;
fn(5);
obj.fn(10);
console.log(num, obj.num); 

/*
22
23
65 30
*/

和上面一样的分析思路

全局上下文基于var/function声明的变量,会给GO(window)设置对应的属性,这样有时候this指向window,就会改变window的对应属性,同样改变声明的变量

初始化this,初始化auguments,形参赋值,变量提升

/*
 * EC(G)
 *   变量提升:-- 
 * ----VO(G)
 *   obj = 0x000
 *   fn = 0x001
 */
let obj = {
    // fn:0x001
    fn: (function () {
        /* 
         * EC(AN)
         *   作用域链:<EC(AN),EC(G)>
         *   初始THIS:window
         *   形参赋值:--
         *   变量提升:--
         */
        return function () {
            console.log(this);
        }; //return 0x001; [[scope]]:EC(AN)
    })()
};
obj.fn(); //this->obj 
let fn = obj.fn;
fn(); //this->window 
var fullName = 'language'; //window.fullName='language'
var obj = { //window.obj=0x000
    fullName: 'javascript',
    prop: {
        getFullName: function () {
            return this.fullName;
        }
    }
};
console.log(obj.prop.getFullName()); //this->obj.prop   ->obj.prop.fullName  ->undefined
var test = obj.prop.getFullName;
console.log(test()); //this->window   ->window.fullName  ->'language'
var name = 'window';
var Tom = {
    name: "Tom",
    show: function () {
        // this->window
        console.log(this.name); //->'window'
    },
    wait: function () {
        // this->Tom
        var fun = this.show; //fun=Tom.show
        fun();
    }
};
Tom.wait();

this的指向跟在哪定义的,在哪执行的都没有关系,fun()直接执行,前面没有.,所以this就是window,有.就是.前面的

window.val = 1; //val是GO的属性,“也可以说”是全局变量「val=100 / window.val=100」
var json = {
    // val是json对象的一个属性「json.val」
    val: 10,
    dbl: function () {
        this.val *= 2;
    }
}
json.dbl();
// this->json
// json.val *= 2  =>  json.val=json.val*2  => json.val=20
var dbl = json.dbl;
dbl();
// this->window
// window.val *= 2  =>  window.val=2
json.dbl.call(window);
// this->window 基于call方法强制改变方法中的this是window
// window.val *= 2  => window.val=4
alert(window.val + json.val); //=>“24” 
 (function () {
    // this->window
    var val = 1;
    var json = {
        val: 10,
        dbl: function () {
            // this->json
            // val不是json.val,是其上级上下文中的val变量
            val *= 2;
        }
    };
    json.dbl();
    alert(json.val + val); //=>“12”
})();