JS中 this 的五种情况

# 事件绑定

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

<html>
  <body>
    <button id="btn">点我</button>
    <script>
      // DOM0事件绑定
      btn.onclick=function() {
        console.log(this) // <button id="btn">点我</button>
      }
      // DOM2事件绑定
      btn.addEventListener('click', function() {
        console.log(this) // <button id="btn">点我</button>
      })

      btn.onclick = function() {
        handleClick();
      };

      function handleClick() {
        // 这里的 this 指向 window
        console.log(this); // window
      }
  </script>
  </body>
</html>

我们可以通过 call、apply、bind 来改变this的指向。

btn.onclick = function() {
  fn();
};

function fn() {
  console.log(this); // window
}

btn.onclick = fn.bind(window); // fn.bind(window) 首先会先返回一个匿名函数,将这个匿名函数绑定给事件,点击按钮时触发这个匿名函数执行,这个函数中的 this 指的是元素,但是会在匿名函数中执行 fn,fn 中的 this 预先指定为 window了。

对于 Internet Explorer 来说,在IE 9之前的浏览器不支持DOM2事件绑定,必须使用 attachEvent来绑定事件,此时的this指向window

el.attachEvent('onclick', function() {
  console.log(this) // window
});

# 普通函数执行

普通函数执行时,它里面的this是谁,取决于被谁调用,说白了就是看方法执行前面是否有点,有的话点前面是谁 this 就是谁没有的点的话 this 指向window,严格模式下指向undefined 。

function fn() {
  console.log(this)
}
let obj = {
  name: 'f',
  fn: fn
}
fn() // window
obj.fn() // {name: "f", fn: ƒ}

// hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)
console.log(obj.hasOwnProperty('name')) // true

// 如果指定的属性在指定的对象或其原型链中,则in 运算符返回true
console.log('name' in obj); //  true

console.log(obj.__proto__.hasOwnProperty('name')) //false
console.log(Object.prototype.hasOwnProperty.call(obj, 'name')); // true

hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)

如果指定的属性在指定的对象或其原型链中,则in 运算符返回true。

自执行函数执行,其中的 this 一般都是 window.

# 构造函数执行

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

function F() {
  console.log(this)
}

let f = new F

# 箭头函数

箭头函数中没有 this,箭头函数中所用到的 this 是其上下文中的 this。

这里需要先明白执行上下文大概分为三种,分别为:全局执行上下文、函数执行上下文、eval执行上下文。

下面的代码 fn 使用的是箭头函数,它的执行上下文是全局上下文 ,所以 this 指向 window 。

const obj = {
  fn: () => {
    console.log(this) // window
  }
}
obj.fn()

下面代码中定时器中使用箭头函数后,它的执行上下文指的是函数 fn,函数 fn 中的 this 指的是对象 obj, 所以使用箭头函数的定时器中的 this 指的是 obj,而没有使用箭头函数的的定时器中的 this 指的是 window,因为定时器方法实际是 window 对象调用的:window.setTimeout(fn, delay)

const obj1 = {
  name: 'f',
  fn() {
    setTimeout(function() {
      console.log(this); // window
    }, 0);

    setTimeout(() => {
      console.log(this) // {name: "f", fn: ƒ}
    }, 0);
  }
}

obj1.fn();

箭头函数中没有 prototype,不能当做构造函数用所以不能被 new 执行。

const FN = _ => {}
new FN(); // FN is not a constructor

箭头函数中还没有 arguments 实参集合。

const fn = _ => {
  console.log(arguments);
};

fn() // arguments is not defined

如果箭头函数中想用实参,只能通过 ...args 剩余参数获取 。

const fn = (...args) => console.log(args); // [1, 2, 3]

fn(1, 2, 3);

# call、apply、bind

call、apply、bind 可以改变函数中 this 的指向。call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。

const obj = {
  fn(){
    console.log(this)
  },
  fn1() {
    'use strict'
    console.log('dd',this)
  }
}
obj.fn() // obj

 // 如果call没有传参,this默认指向 window,如果传 null、undefined 默认指向 window,严格模式下指向undefined,
obj.fn.call(); // window
obj.fn.call(null); //  window
obj.fn.call(undefined); //  window

// 严格模式下
obj.fn1.call(); // undefined
obj.fn1.call(null); // null
obj.fn1.call(undefined); // undefined

obj.fn.call(12); // Number {12}

// 严格模式下输出 12
obj.fn1.call(12); // 12

call、apply都是改变this的同时,直接把函数执行了。而bind不是立即执行,属于预先改变 this 和传递一些内容并返回一个函数。

function fn() {
  console.log(this)
}

console.log(fn.call(window)); // undefined
console.log(fn.apply(window)); // undefined
console.log(fn.bind(window)); // ƒ fn

因为箭头函数没有 this 所以通过 call、apply、bind 改变 this 是没有意义的。

const obj = {
  fn() {
    return _ => console.log(this);
    
    // return function() {
    //   console.log(this);
    // };
  }
}

obj.fn().call(window); // {fn: ƒ}
obj.fn().apply(window); // {fn: ƒ}
obj.fn().bind(window)(); // {fn: ƒ}

箭头函数也是 Function 的一个实例,箭头函数也有__proto__,所以可以调用call、apply、bind等方法。

Function 的原型通过原型链可以找到 Object。

# 手写 bind

# 手写 call

# 手写 apply

题目:

function fn1() {console.log(1)}
function fn2() {console.log(2)}
fn1.call(fn2) // 1
fn1.call.call(fn2) // 2
/* 
第一次call执行
this => fn1.call
context => fn2
fn2.$fn() => fn1.call()
call第二次执行
this => fn2
context => undefined
context.fn() => fn2()

如果调用多个call执行的是fn2
*/

Function.prototype.call(fn1) // 没有输出
Function.prototype.call.call(fn1) //  1
/* 
this => Function.prototype.call
context => fn1
fn1.$fn() = Function.prototype.call()
call第二次执行
this => fn1
context => undefined
undefined.$fn() =>  fn1()
*/

函数的原型 Function.prototype 是一个匿名函数