彻底搞懂this指向

彻底搞懂this指向

在 JavaScript 中,普通函数内部的 this 默认情况下会指向全局对象(非严格模式下),或者是 undefined(在严格模式下),而不会自动继承外部函数的 this

一、为什么使用this

使用this有什么意义呢?

javascript 复制代码
var obj = {
  name: "why",
  running: function() {
    console.log(obj.name + " running");
  },
  eating: function() {
    console.log(obj.name + " eating");
  },
  studying: function() {
    console.log(obj.name + " studying");
  }
}

如果没有this:

  • 在该方法中,为了能够获取name名称,必须通过obj的引用(变量名称)来获取
  • 这样有一个很大的弊端:如果将obj的名称换成info,那么所有的方法中obj都需要换成info

在实际开发中,便需要使用this来进行优化

javascript 复制代码
var obj = {
  name: "why",
  running: function() {
    console.log(this.name + " running");
  },
  eating: function() {
    console.log(this.name + " eating");
  },
  studying: function() {
    console.log(this.name + " studying");
  }
}

二、 this 绑定规则

this无非就是在函数调用时被绑定的一个对象,我们只需知道它在不同场景下的绑定规则即可

2.1 默认绑定

什么情况下使用默认绑定呢?

独立函数调用。我们可以理解成没有被绑定到某个对象上进行调用

如:

  • 普通函数调用
  • 函数调用链(一个函数又调用另外一个函数)
    所有的函数调用都没有被绑定到某个对象上
scss 复制代码
// 2.案例二:
function test1() {
  console.log(this); 
  test2();
}

function test2() {
  console.log(this); 
  test3()
}

function test3() {
  console.log(this);
}
test1();
  • 将函数作为参数,传入到另一个函数中
scss 复制代码
function foo(func) {
  func()
}

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

foo(bar);

2.2 隐式绑定

隐式绑定是通过某个对象进行调用的;也就是它的调用位置中,是通过某个对象发起的函数调用。

2.3.2 bind函数

bind可以将一个函数一直显式的绑定到一个对象上

scss 复制代码
function foo() {
  console.log(this);
}

var obj = {
  name: "why"
}

var bar = foo.bind(obj);

bar(); // obj对象
bar(); // obj对象
bar(); // obj对象
2.3.3 内置函数

一些内置函数会要求我们传入另外一个函数,JavaScript内部会帮助我们执行;

  • setTimeout
    setTimeout中会传入一个函数,这个函数中的this通常是window。
javascript 复制代码
setTimeout(function() {
  console.log(this); // window
}, 1000);

setTimeout内部是通过apply进行绑定的this对象,并且绑定的是全局对象

  • 数组的forEach
    默认也是window,因为默认情况下传入的函数是自动调用函数(默认绑定)
    我们也可以改变该函数的this指向,forEach函数的第二个参数可以改变指向
ini 复制代码
var names = ["abc", "cba", "nba"];
var obj = {name: "why"};
names.forEach(function(item) {
  console.log(this); // 三次obj对象
}, obj);

拓展:forEach我们知道是遍历,那么这个遍历可以停下来吗?怎么让它停下来

答:不可以,我们可以通过try...catch的方法捕获错误让它停下来,但是不能使用return终止它,因为forEach可以看作是一个一个的函数,你只return一个,让一个停下来,这无济于事

2.4 new 绑定

JavaScript中的函数可以当作一个类的构造函数来使用,也就是使用new关键字;

使用new关键字来调用函数时,会执行如下的操作:

  1. 创建一个全新的对象
  1. 这个新对象会被执行Prototype连接
  1. 这个新对象会绑定到函数调用的this上
javascript 复制代码
// 创建Person
function Person(name) {
  console.log(this); // Person {}
  this.name = name; // Person {name: "why"}
}

var p = new Person("why");
console.log(p);

2.5 规则优先级

new绑定 > 显示绑定(bind)> 隐式绑定 > 默认绑定

2.6 箭头函数

箭头函数捕获最近的非箭头函数父级作用域的 this 值

当谈到箭头函数的 this 时,它与传统的 JavaScript 函数行为有所不同。在箭头函数中,this 的值是在创建函数时确定的,而不是在调用函数时确定的。具体来说,箭头函数的 this 值是从封闭(定义)它时所在的词法作用域中继承而来的,而不是动态绑定的。

这意味着,无论在什么上下文中调用箭头函数,this 的值都保持不变,始终与箭头函数定义时所在的作用域中的 this 值相同。

也就是说:箭头函数的 this 值是由定义箭头函数时的词法作用域决定的,而不是由调用时的对象决定的

javascript 复制代码
    const obj1 = {
      fn: () => {
        return this
      }
    }
    const obj2 = {
      fn: function(){
        return this
      }
    }
    
    console.log(obj1.fn());
    console.log(obj2.fn());

输出结果:

  1. window || undefined
  2. obj2

原因:在箭头函数 fn 中的 this 关键字指向的是定义该函数的上下文,而不是调用该函数的对象。因此,当 obj1.fn() 被调用时,由于箭头函数没有它自己的this,当你调用fn()函数时,会捕获最近的非箭头函数父级作用域的 this 值,因此箭头函数中的 this 指向的是全局对象(在浏览器环境下通常是 window 对象),因此返回的是 undefined。

面试题一

ini 复制代码
var name = "window";
var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  }
};
function sayName() {
  var sss = person.sayName;
  sss(); 
  person.sayName(); 
  (person.sayName)(); 
  (b = person.sayName)(); 
}
sayName();

这道面试题非常简单,无非就是绕一下,希望把面试者绕晕:

scss 复制代码
function sayName() {
  var sss = person.sayName;
  // 独立函数调用,没有和任何对象关联
  sss(); // window
  // 关联
  person.sayName(); // person
  (person.sayName)(); // person
  (b = person.sayName)(); // window
}

面试题二

javascript 复制代码
var name = 'window'
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

person1.foo1(); 
person1.foo1.call(person2); 

person1.foo2();
person1.foo2.call(person2);

person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);

person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);

下面是代码解析:

scss 复制代码
// 隐式绑定,肯定是person1
person1.foo1(); // person1
// 隐式绑定和显示绑定的结合,显示绑定生效,所以是person2
person1.foo1.call(person2); // person2

// foo2()是一个箭头函数,不适用所有的规则
person1.foo2() // window
// foo2依然是箭头函数,不适用于显示绑定的规则
person1.foo2.call(person2) // window


person1.foo3()() // window
// foo3显示绑定到person2中
// 但是拿到的返回函数依然是在全局下调用,所以依然是window
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数,通过显示绑定到person2中,所以是person2
person1.foo3().call(person2) // person2

// foo4()的函数返回的是一个箭头函数
// 箭头函数的执行找上层作用域,是person1
person1.foo4()() // person1
// foo4()显示绑定到person2中,并且返回一个箭头函数
// 箭头函数找上层作用域,是person2
person1.foo4.call(person2)() // person2
// foo4返回的是箭头函数,箭头函数只看上层作用域
person1.foo4().call(person2) // person1

person1.foo3()()这个调用中,foo3函数返回了一个新的函数,然后我们立即调用了这个新的函数。这个新的函数并没有被一个对象调用,而是直接被调用的,所以它的this值默认指向全局对象(在浏览器中是window)。这就是为什么person1.foo3()()打印的是"window"。

面试题三

javascript 复制代码
var name = 'window'
function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  },
  this.foo2 = () => console.log(this.name),
  this.foo3 = function () {
    return function () {
      console.log(this.name)
    }
  },
  this.foo4 = function () {
    return () => {
      console.log(this.name)
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1()
person1.foo1.call(person2)

person1.foo2()
person1.foo2.call(person2)

person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)

person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)

下面是代码解析:

scss 复制代码
// 隐式绑定
person1.foo1() // peron1
// 显示绑定优先级大于隐式绑定
person1.foo1.call(person2) // person2

// foo是一个箭头函数,会找上层作用域中的this,那么就是person1
person1.foo2() // person1
// foo是一个箭头函数,使用call调用不会影响this的绑定,和上面一样向上层查找
person1.foo2.call(person2) // person1

// 调用位置是全局直接调用,所以依然是window(默认绑定)
person1.foo3()() // window
// 最终还是拿到了foo3返回的函数,在全局直接调用(默认绑定)
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数后,通过call绑定到person2中进行了调用
person1.foo3().call(person2) // person2

// foo4返回了箭头函数,和自身绑定没有关系,上层找到person1
person1.foo4()() // person1
// foo4调用时绑定了person2,返回的函数是箭头函数,调用时,找到了上层绑定的person2
person1.foo4.call(person2)() // person2
// foo4调用返回的箭头函数,和call调用没有关系,找到上层的person1
person1.foo4().call(person2) // person1
相关推荐
喵叔哟6 分钟前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特1 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解1 小时前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
Mr.咕咕1 小时前
Django 搭建数据管理web——商品管理
前端·python·django