Javascript:this指向

113 阅读5分钟

this关键字

与其他语言相比,函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。

在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)。

this 不能在执行期间被赋值,并且在每次函数被调用时 this 的值也可能会不同。ES5 引入了 bind 方法来设置函数的 this 值,而不用考虑函数如何被调用的。ES2015 引入了箭头函数,箭头函数不提供自身的 this 绑定(this 的值将保持为闭合词法上下文的值)。

引用取自MDN描述,个人理解:this就是一个指针,它指示的就是当前的一个执行环境,可以用来对当前执行环境进行一些操作。因为它指示的是执行环境,所以在定义这个变量时,其实是不知道它真正的值的,只有运行时才能确定他的值。谁调用函数,函数的this就指向谁

this指向哪里

在 JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变。

  • 在方法中,this 表示调用该方法的对象。
  • 如果单独使用,this 表示全局对象。
  • 在函数中,this 表示全局对象。
  • 在函数中,在严格模式下,this 是未定义的(undefined)。
  • 在事件中,this 表示接收事件的元素。
  • 类似 call() 和 apply() 方法可以将 this 引用到任何对象。

我们来看看如下代码:

    //let,const 声明的变量不会绑定给window对象 而var会
    // let name='我是 window 上的 名字'
    const name='我是 window 上的 名字'
    function getName() {
        'use strict';
        console.log('我的this:',this);
        console.log('我的名字:',this.name);
    }
    /*
    Uncaught TypeError: Cannot read properties of undefined (reading 'name')
    */
    getName() // 报错

该段代码会报错,let和const由于作用域不会给window上增加属性。在该场景下getName调用,没有明确调用者是谁,this失效默认绑定为 window 对象上,又由于我们使用的是 严格模式下 所以this指向的 undefined的,undefined.name 所以会报错

修改后:

    var name='我是 window 上的 名字'
    function getName() {
        console.log('我的this:',this);
        console.log('我的名字:',this.name);
    }
    getName() // window   我是 window 上的 名字

我们来分析下以下的例子:

    var name = '我是 window 上的 名字'
    function getName() {
        console.log('我的名字:', this.name);
    }
    getName()
    var Person = {
        name: '我是Person的名字',
        showName: function () {
            console.log(this)
            console.log('我的名字:', this.name);
        },
        getName
    }
    Person.showName()
    Person.getName()
    var showName = Person.showName
    showName()

执行结果:

image.png

分析:

image.png

函数showName在也是一个引用类型数据,所以在PersonshowName执行的内存地址,Person.getName也是一样的,在全局属性 showName 指向 在PersonshowName执行的内存地址。执行后, Person.showName()调用着是 Person 所以 执行的this是Person,showName 直接执行的时候没有明确调用者是谁,this失效默认绑定为 window 对象上。

会出现 this 的场景:

// 构造函数 和 原型函数
//构造函数中的this,指的是实例对象。
function Obj(name) {
  this.name = name;
};
const obj=new Obj('张三')
obj.name  // 张三
// 原型对象中
var o = {
  f : function(){ 
    return this.a + this.b; 
  }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
// dom 事件处理
var oBox = document.getElementById('box');
oBox.onclick = function () {
    console.log(this)   //oBox
}
// setTimeout 和 setInterval
//默认情况下代码
function Person() {  
    this.age = 0;  
    setTimeout(function() {
        // 此处是window执行
        console.log(this);
    }, 3000);
}
var p = new Person();//3秒后返回 window 对象

this怎么改变

call

函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。

var obj = {};
var f = function () {
  return this;
};
f() === window // true
f.call(obj) === obj // true

手动实现

  // arguments 实参类数组的对象
  Function.prototype.myCall = function () {
        // 将类数组变成数组
        const args=Array.prototype.slice.call(arguments)
        let context = args[0]
        context = context || window
        let args = [], result;
        const argClone =args.slice(1)
        context.fn = this
        result = context.fn(...args)
        delete context.fn
        return result;
  }
  es6实现 
  // ...args 将数组变成数组
  Function.prototype.myCall = function (...args) {
        let context = args[0]
        // 如果不存在第一个参数就指向 window
        context = context || window
        let args = [], result;
        const argClone = args.slice(1)
        argClone.forEach((item, index) => {
            args.push(item)
        })
        //使用谁调用指向谁的原理
        context.fn = this
        result = context.fn(...args)
        delete context.fn
        return result;
  }

apply

apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。

function f(x, y){ console.log(x + y); }
f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2

手动实现:

Function.prototype.myApply = function () {
  let result = null
  // 判断传入的对象是否存在,在浏览器中默认是window, 在node.js中默认是Object
  let context = arguments[0]
  context = context || window
  // 把当前调用的函数赋值给传入对象的
  // context.fn 可以理解为: context.prototype.
  context.fn = this
  if (arguments[1]) {
    result = context.fn([...arguments[1]]) // 调用赋值的函数
  }else{
    result = context.fn()
  }
  delete context.fn
  return result
}

bind

bind()方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。

  function showName() {
        console.log('this.name', this.name);
   }
  const name = showName.bind(Person)
  name()

手动实现:

Function.prototype.myBind = function() {
  // 获取参数
  const context = arguments[0]
  let args = [...arguments].slice(1) // 第一个是对象
  context.fn = this // 调用对象
  return function Fn() {
    return fn.apply(this instanceof Fn ? this : context, args.concat(...arguments))
  }
}

以上三个方法如果context不存在的话就默认指向windos,这个实现逻辑有很多种,这个是我的实现方法。核心点就是,为context绑定当前调用方法,然后执行完毕得到结果返回,删除 context 绑定的该方法

特殊的箭头函数

箭头函数表达式的语法比函数表达式更简洁,并且没有自己的thisargumentssupernew.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。

箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承 this。创建的时候已经指定了this,指向词法作用域,也就是外层调用者,使用者。 通过 call 或 apply 调用,由于 箭头函数没有自己的 this 指针,通过 call() 或 apply() 方法调用一个函数时,只能传递参数,他们的第一个参数会被忽略