牢牢记住 this 的 五种情况

826 阅读3分钟

前言

在全局上下文中,严格默认 this 为 undefined,非严格模式下,this 为 window。块级作用域的 this 是继承所在上下文中的 this 。在函数的私有上下文中,this 情况多种多样。

注意 this 并不是执行上下文,EC 是执行上下文,this 是执行主体。

1. 元素的事件

给元素的某个事件行为绑定方法,事件触发,方法执行,此时方法中的 this 一般都是当前元素本身。

// DOM0
 btn.onclick = function anonymous() {
    console.log(this); // this指向元素本省
 }
 // DOM2
 btn.addEventListener('click', function anonymous() {
     console.log(this); // this指向元素本省
 }, false);
 // DOM2-ie中
 btn.attachEvent('onclick', function anonymous() {  
    // <= IE8
     console.log(this); // this -> window 特殊情况
 })

2. 普通函数执行

普通函数执行,它里面的 this 取决于执行方法前面是否有“点”,有“点”,“点”前面是谁,this 就是谁,前面没有“点”,非严格模式 this 是 window,严格模式是 undefined。

3. 构造函数执行

构造函数执行,this 执行当前类的实例。

function Func() {
    this.name = "F";
    console.log(this); //=>构造函数体中的THIS在“构造函数执行”的模式下,是当前类的一个实例,并且THIS.XXX=XXX是给当前实例设置的私有属性
}
Func.prototype.getNum = function getNum() {
    // 而原型上的方法中的THIS不一定都是实例,主要看执行的时候,“点”前面的内容
    console.log(this);
};
let f = new Func;
f.getNum();
f.__proto__.getNum();
Func.prototype.getNum(); 

4. 箭头函数

箭头函数中没自身的this,所用的 this 都是上下文中的 this 。

  • 箭头函数没有this
  • 箭头函数没有prototype
  • 箭头函数没有constructor
  • 箭头函数不能被new执行
  • 箭头函数没有arguments,如果想用实参集合只能使用...args。

普通函数执行:

  • 形成私有上下文 (和AO)
    • 初始化作用域链
    • 初始化THIS
    • 初始化ARGUMENTS
    • 形参赋值
    • 变量提升
    • 代码执行

箭头函数执行:

  • 形成私有上下文 (和AO)
    • 形参赋值
    • 代码执行
    • 代码执行的时候遇到 this 直接找上级上下文中的 this。
let obj = {
    i: 0,
    // func:function(){}
    func() {
        // THIS:OBJ
        let _this = this;
        setTimeout(function () {
            // THIS:WINDOW 回调函数中的THIS一般都是WINDOW(但是有特殊情况)
            _this.i++;
            console.log(_this);
        }, 1000);
    }
};
obj.func();

5. call/apply/bind

call/apply

- 第一个参数就是改变 this 的指向,写谁就是谁,在非严格模式下,null/undefined 指向的是 window- call/apply 的唯一区别就是,传递参数不一样,apply 第二个参数是数组,call的参数是一个一个传递。
- call 的性能要比 apply 好一些(尤其是传递给函数的参数超过三个的时候)

bind

-  call/apply都是改变this的同时就把函数执行了,但是bind不是立即执行函数,属于预先改变this和传递一些内容,利用的是柯理化的思想。

这里提到这个 call/apply/bind 在面试中也常也会被问到关于如何自己实现这三个方法,其实不难,只要理解这三个方法就可以手写他们了。

apply源码-手写apply源码

~(function(proto){
	function apply(content, args) {
  	if (content === undefined || contetn === null) {
    	content = window;
    } else {
    	content = Object(content);
    }
    content.$fn = this;
    var res = content.$fn(...args);
    delete content.$fn;
    return res;
  }
  proto.apply = apply;
})(Function.prototype)

call源码-手写call源码

~(function(proto){
	function call(content, args) {
  	if (content === undefined || contetn === null) {
    	content = window;
    } else {
    	content = Object(content);
    }
    content.$fn = this;
    var res = content.$fn(...args);
    delete content.$fn;
    return res;
  }
  proto.call = call;
})(Function.prototype)

bind源码-手写bind源码

~(function(proto) {
    function bind(content) {
        if(content === undefined || content === null) {
            content = window;
        }
        // 获取实参集合
        var args = [].slice.call(arguments, 1);
        // 最后要执行的函数
        var _this = this;
        return function anonymous() {
            var amArgs = [].slice.call(arguments, 0);
            _this.apply(content, args.concat(amArgs));
        }
    }
    proto.bind = bind;
})(Function.prototype);

~(function(proto) {
    function bind(content = window, ...args) {
        if (content === null) {
            content = window;
        }
        return (...amArgs) => {
            // 经测试apply的性能在三个参数以上是没有call好的
            this.call(content, ...args.content(amArgs));
        }
    }
    proto.bind = bind;
})(Function.prototype);