likes
comments
collection
share

详解this 指向问题

作者站长头像
站长
· 阅读数 18

详解this 指向问题

人的精神思想方面的优势越大,给无聊留下的空间就越小。 --人生的智慧-叔本华

前言

介绍

对于 普通函数, 可以理解为 this 永远指向 调用 包含 自己(this本身) 的 函数 对应的 对象

对于 箭头函数 ,箭头函数体内的 this 对象,就是定义 该函数时所在的作用域指向的对象 ,而不是使用时所在的作用域指向的对象。

this 的指向,一般来说:this 永远指向最后调用它的那个对象

this被调用时的五种情况:

详解this 指向问题

1.作为对象方法被调用

指向的是当前调用设个普通函数的对象,如下,this指向obj

function sayHi() {
  console.log("Hello,", this.name);
}

let person2 = {
  name: "小万",
  sayHi: sayHi
};

let person1 = {
  name: "张三",
  sayHi: sayHi
};

person1.sayHi();//Hello, 张三
person2.sayHi();//Hello, 小万

Tips:那如果有链式调用的情况呢?this会绑定到哪个对象上?

function sayHi() {
  console.log('Hello,', this.name)
}

var person2 = {
  name: '小万',
  sayHi: sayHi
}

var person1 = {
  name: '张三',
  friend: person2
}
//如果person2里面没有name属性也不会调用person1里面的name,而是输出undefined
person1.friend.sayHi() // Hello, 小万

这里也印证了开头的那句话:this 永远指向最后调用它的那个对象

2. 作为普通函数被调用时

this指向全局对象window

没有明显的调用者的时候,其实是Window在调用Window.fn() 等价于fn()

严格模式下 this被规定不会指向全局 则是undefined

window.name = 'window'

function sayHi() {
  console.log("Hello,", this.window,this);
}

sayHi()

可以看到。打印出的结果:this就是Window对象

详解this 指向问题

赋值后调用的情况

var name = "1999";
var obj = {
  name: "are you ok",
  show : function() {
    return function(){
      console.log(name,this.name,this===window);
    }
  }
};
var foo1 = obj.show();
foo1();  //1999 1999 true

这里调用foo1的是Window,这里的 obj.show() 返回的是一个匿名函数

所以 最后执行的 foo1()== window.foo1(), 函数foo1里面的 this.name 指向 window.name,所以最终输出 China。

3.作为构造函数被调用时

****原型对象里面的this指向实例对象

关于构造函数中this作用,构造函数原来初始化对象, new在构造函数中的作用:

  1. 在内存中创建一个新的对象
  2. 让this指向这个对象
  3. 在构造函数里面的这个代码,给这个新对象添加属性和方法
  4. 返回这个新对象,所以构造函数一般都是不用return返回的

构造函数里的this指向 new创建的实例化对象(没有return的情况下),如果构造函数内出现了return,并且是一个object对象,那么最终的运算结果返回这个对象,只要构造函数不返回数据或者返回基本数据类型 this仍然指向实例

function Person() {
  this.name = "张三";
  return {
    name: "李四"
  };
}
//因为使用了return,这里new出来的是对象{name: "李四"},所以this指向这个对象person.name等价于this.name,也即李四
let person = new Person();
console.log(person.name);//李四

4.箭头函数里面的this

箭头函数的 this 不会被改变,所以当前函数是箭头函数,那么就不用再看其他规则了。箭头函数的 this 是在创建它时外层 this 的指向。他只会从自己的作用域链上一层继承this,箭头函数的 this 是在创建它时外层 this 的指向,他没有自己的this 。这里的重点有两个:

这里的重点有两个:

  1. 创建箭头函数时,就已经确定了它的 this 指向。
  2. 箭头函数内的 this 指向外层的 this

注:箭头函数不能当做构造函数,所以不能与 new 一起执行

this.age = 20;
var obj = {
  age: 18,
  fn: () => {
    console.log(this.age);//20
  }
}
obj.fn()

在这里,箭头函数继承了obj的this,当箭头函数被定义时,它会继承外层作用域的 this 值。在这个例子中,箭头函数 fn 是在 obj 对象中定义的,所以它应该继承 obj 对象作为外层作用域,并且 this 指向 obj 对象。

然而,由于箭头函数的特性,它不会改变 this 的指向,而是继承外层作用域的 this 值。在浏览器环境中,默认的全局对象是 window,因此当访问 this.age 时,它实际上是在访问 window.age。如果全局作用域中存在 age 属性,它的值将被打印出来。

所以,虽然箭头函数的外层作用域应该是 obj 对象,但由于浏览器中默认的全局对象是 window,所以当执行代码时,它会显示 window.age 的值。这可能是导致混淆的原因。

剪头函数的this不会被修改

func = () => {
  // 这里 this 指向取决于外层 this
  console.log(this)
}

func.bind(1)() // Window,口诀 1 优先

如何让箭头函数里this不等于Window,答案是给他加一层函数

var name = 'window'; 

var A = {
   name: 'A',
   sayHello: function(){
      var s = () => console.log(this.name)
      return s//返回箭头函数s,这里return是因为sayHello是普通函数,需要return
   }
}

var sayHello = A.sayHello();
sayHello();// 输出A 

var B = {
   name: 'B';
}

sayHello.call(B); //还是A
sayHello.call(); //还是A

这里使用sayHello函数包裹了一下箭头函数,箭头函数定义时候的环境改变了,从对象A变为了sayHello,所以thisWindow变为了对象A

5.call&& apply&&bind

使用call,apply,bind并不会改变箭头函数中的this指向。
  • 当对箭头函数使用call或apply方法时,只会传入参数并调用函数,并不会改变箭头函数中this的指向。
  • 当对箭头函数使用bind方法时,只会返回一个预设参数的新函数,并不会改变这个新函数的this指向。
window.name = "这是window_name";

let f1 = function () {
  return this.name;
};
let f2 = () => this.name;

let obj = { name: "这是obj_name" };

console.log(f1.call(obj));  //这是obj_name
console.log(f2.call(obj));  // 这是window_name
console.log(f1.apply(obj)); // 这是obj_name
console.log(f2.apply(obj)); // 这是window_name
console.log(f1.bind(obj)());  // 这是obj_name
console.log(f2.bind(obj)());  // 这是window_name
  1. 都是把函数立即执行改变函数中的this指向(第一个参数是谁this就是谁),如果不传参数,默认指向WindowA.sayHello.call();//不传参数指向全局window对象,输出window.name也就是window
  2. apply同理 唯一的区别就是传参的格式不一样,apply把传递给函数的形参以数组的形式管理起来,最终的效果和call一样,也是把数组中的每一项作为实参,一个个的传递给函数
  3. 真实项目中建议大家使用call,因为其性能好一些(三个及以上参数,call的性能明显比apply好一些)
  4. this存储的值 null.undefined.对象
  5. 一个也不写,非严格模式下是window 严格模式下是undefined
  6. null 严格模式下是null非严格模式下是window
  7. 多次bind只认第一次bind的值
function func() {
  console.log(this)
}

func.bind(1).bind(2)() // 1
function fn(x, y) {
  console.log('加油');
  console.log(this);//this指向window
  console.log(x + y);
  console.log('-=-=-=-=--=-=-=-=-=-=-=-=-=');
}
var o = {
  name: 'andy'
}
fn.call()//call 呼叫 可以调用函数
fn.call(o, 1, 2)//第一个值是this指向, 后边实参
fn.apply(o, [10, 20])//apply传递数组
fn.call(10, 20)//this-->new Numbe(10) x-->20 y-->undefined 

执行结果

详解this 指向问题

增加点难度

function Animal(){
    this.name = 'animal';
}

function Cat(){
    Animal.call(this);
}

var cat = new Cat();
console.log(cat.name);// 'animal'

这里要善于理解this 永远指向 调用 包含 自己(this本身) 的 函数 对应的对象。这句话,

首先从cat看起,cat是一个实例化的对象,构造函数的this指向他实例化的对象,而构造函数CatAnimal.call(this);里面传入的this,就是当前的实例化的对象cat,又由于是call调用,给传入的this赋值,也就是给实例化对象cat赋值,所以cat.name就是'animal'

其他示例

立即执行函数指向Window

所谓立即执行函数,就是定义后立刻调用的匿名函数(参见下面这道例题里 hello 方法的函数体里这种写法)。

var name = 'BigBear'

var me = {
  name: 'xiaohong',
  // 声明位置
  sayHello: function() {
    console.log(`你好,我是${this.name}`)
  },
  hello: function() {
    (function(cb) {
      // 调用位置
      cb()
    })(this.sayHello)
  }
}

me.hello() // 这里输出的this指向Window,而不是me,因为

即便不考虑立即执行的匿名函数这种所谓的“特殊情况”, 按照上面指向原则来分析,结果也是一样。 立即执行函数作为一个匿名函数,在被调用的时候,我们往往就是直接调用,而不会(也无法)通过属性访问器( 即 xx.xxx) 这样的形式来给它指定一个所在对象,所以它的 this 是非常确定的,就是默认的全局对象 window。

setTimeout 和 setInterval 中传入的函数

setTimeout 和 setInterval 中函数的 this 指向机制其实是一样的

var name = 'BigBear'

var me = {
  name: 'xiaohong',
  hello: function() {
    setTimeout(function() {
      console.log(`你好,我是${this.name}`) 
    })
  }
}

me.hello() // 你好,我是BigBear

延时效果(setTimeout)和定时效果(setInterval),都是在全局作用域下实现的。无论是 setTimeout 还是 setInterval 里传入的函数,都会首先被交付到全局对象手上。因此,函数中 this 的值,会被自动指向 window。

引用

关于this指向问题及改变this指向的方法

this指向问题汇总

ES6箭头函数的this指向详解

转载自:https://juejin.cn/post/7278509192508637218
评论
请登录