this

67 阅读4分钟

this 是执行上下文中的一个属性,指向最后一次调用这个方法的 对象。在实际开发中,this 的指向可以通过四种调用模式来判断。

第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象,在严格模式下是undefined,如果调用的是let/const声明的变量是undefined。

    var a = 'absc'    let b = 'bsc'    const c = 'csc'    function initThis() {        console.log(this.a)//absc        console.log(this.b)//undefined        console.log(this.c)//undefined    }    initThis()

思考:为什么全局声明的let/conts声明的变量会是undefined?

这是因为用var声明的变量是挂载到全局window上面的,而let和const声明的变量是挂载到script上的。

第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时, this 指向这个对象。

 const obj = {        e:'eee',        f:function() {            console.log(this.e)//'eee'        },        g:()=>{            console.log(this.e)//undefined        }    }    obj.f()    obj.g()

思考:为什么箭头函数中打印e是undefined?

箭头函数没有自己的this,它指向的this是它当前(外层)作用域下的this(但是当前作用域下必须是函数作用域(这个函数作用域必须是普通函数),不能是普通对象作用域,也就是对象,如果是在对象下定义的箭头函数,this会一直往外找,找到最近的函数作用域,如果没找到,则this指向window)

JS作用域分为全局作用域、函数作用域、let,const块级作用域,并没有对象作用域,所以这里说的this指向为当前作用域下的this的,即为函数作用域或者全局作用域。

第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行 前会新创建一个对象,this 指向这个新创建的对象。

function ThisClass (a) {  this.a = a }let thisClass = new ThisClass('1')console.log(thisClass.a)//1

第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。

//bind()方法会创建一个函数,该函数的this指向了传入的第一个参数,当bind()的参数为空时this指向全局对象。
var hello = function(){
        console.log(this.name);
    };
    
    var demo = {
        name: 'demo'
    };
    
    var h = hello.bind(demo);
    h(); // out 'demo'

第五种:在事件中this指向事件源。

第六种:在定时器中如果不使用箭头函数this指向全局。

这六种方式,使用构造器调用模式的优先级最高,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。

apply 、 call 和 bind?

func.call(thisArg, param1, param2, ...)

func.apply(thisArg, [param1,param2,...])

func.bind(thisArg, param1, param2, ...)

apply:一个是 this 绑定的对象,一个是参数数组。

call:方法接收的参数, 第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。

bind:通过传入一个对象,返回一个this绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。

共同能改变目标this的指向,不同点是call、apply是在改变了目标this执行之后立马执行,而bind不是马上执行。

let a = {
  name: 'jack',
  getName: function(msg) {
    return msg + this.name;
  } 
}
let b = {
  name: 'lily'
}
console.log(a.getName('hello~'));  // hello~jack
console.log(a.getName.call(b, 'hi~'));  // hi~lily
console.log(a.getName.apply(b, ['hi~']))  // hi~lily
let name = a.getName.bind(b, 'hello~');
console.log(name());  // hello~lily

使用场景

获取数组的最大/最小值

let arr = [13, 6, 10, 11, 16];

const max = Math.max.apply(Math, arr); 

const min = Math.min.apply(Math, arr);

 

console.log(max);  // 16

console.log(min);  // 6

继承

  function Parent3 () {

    this.name = 'parent3';

    this.play = [1, 2, 3];

  }



  Parent3.prototype.getName = function () {

    return this.name;

  }

  function Child3() {

    Parent3.call(this);

    this.type = 'child3';

  }



  Child3.prototype = new Parent3();

  Child3.prototype.constructor = Child3;

  var s3 = new Child3();

  console.log(s3.getName());  // 'parent3'

类数组借用方法

var arrayLike = { 
  0: 'java',
  1: 'script',
  length: 2
} 
Array.prototype.push.call(arrayLike, 'jack', 'lily'); 
console.log(typeof arrayLike); // 'object'
console.log(arrayLike);
// {0: "java", 1: "script", 2: "jack", 3: "lily", length: 4}

数据类型判断

function getType(obj){

  let type  = typeof obj;

  if (type !== "object") {

    return type;

  }

  return Object.prototype.toString.call(obj).replace(/^$/, '$1');

}

apply和call的实现

Function.prototype.call = function (context, ...args) {

  var context = context || window;

  context.fn = this;

  var result = eval('context.fn(...args)');

  delete context.fn

  return result;

}

Function.prototype.apply = function (context, args) {

  let context = context || window;

  context.fn = this;

  let result = eval('context.fn(...args)');

  delete context.fn

  return result;

}

bind的实现

Function.prototype.bind = function (context, ...args) {

    if (typeof this !== "function") {

      throw new Error("this must be a function");

    }

    var self = this;

    var fbound = function () {

        self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));

    }

    if(this.prototype) {

      fbound.prototype = Object.create(this.prototype);

    }

    return fbound;

}