JS的this指向问题

前端js 专栏收录该内容
4 篇文章 0 订阅

JS的this指向问题

this关键字是javascript中最复杂的知识点之一,是一个特别的关键字,被自动定义在所有函数的作用域中。在写javascript的时候经常用到,但是你真的懂this吗?作为一个合格的码农,绝对不能只会背口诀,还需要懂实现逻辑。在好久之前学习this的时候,我自己做了一些小总结,也照猫画虎地写了一些代码不断地去测试。希望这些学习笔记能帮助到大家。

一 this指向分类

在这里插入图片描述

1.1 默认绑定

默认状态就是无绑定状态,经常在独立函数中用到。

   function test(){
        console.log(this.a);
    }
    var a = 1111;
	test();
//1111

在这里插入图片描述

分析输出结果:

test()前面没有调用它的对象,这种写法就是应用了this的默认绑定,this指向全局对象(非严格模式下),所以输出了1111

扩展

在非严格模式下:test()函数会挂载在window上所有函数中的this指向的是全局对象window。

在严格模式下:禁止了this关键字指向全局对象,this指向undifined,undifined没有全局对象,抛出错误

1.2 隐式绑定

函数的调用是在某个对象上触发的,调用位置上存在上下文对象。典型的的形式为XXX.fun();

function test(){
    console.log(this.a);
}
var obj = {
    a:1111,
    test:test
}
obj.test();
//1111

在这里插入图片描述

分析输出结果

test()函数是在外部声明,严格来说并不属于obj,但是在调用test()时,调用位置会使用obj的上下文来引用函数,隐式绑定会把函数调用中的this即test()中的this绑定到这个上下文对象(obj)中。

  • 永远记住一句话:this永远指向最后调用它的那个对象。对象属性链中只有最后一层会影响调用的位置
function test(){
    console.log(this.a);
}
var obj1 = {
    a:1111,
    test:test
}
var obj2 = {
    a:2222,
    obj1:obj1
}
obj2.obj1.test();
//1111,this指向最后绑定它的obj1

在这里插入图片描述

注意的是,在隐式绑定经常会出现隐式丢失的问题

Q:什么是隐式丢失?

A:被绑定的函数丢失绑定对象。

attention:参数传递其实是一种隐性赋值

function fn(){
    console.log(this.a);
}
var a = 'hello world';
var obj = {
    a:'obj',
    fn:fn
}
var demo = obj.fn;
demo();
//控制台最终打印hello world

在这里插入图片描述

分析输出结果

demo直接指向了fn的引用,调用的时候跟obj没有关系。demo绑定了fn函数的引用,因此demo只是一个函数的调用,应用了默认绑定绑定了全局对象。所以打印出来hello world。

  • 混淆知识点:XXX.fn();fn()前如果什么都没有,就不是隐式绑定。

除了上述这种丢失之外,隐式绑定丢失还经常发生在回调函数中(事件回调也是其中一种)

function test(){
 	console.log(this.a);
}
function gn(fn){
    fn();
}
var obj = {
    a:1111,
    test:test
}
var a = '我是全局对象'
gn(obj.test);
//我是全局对象

在这里插入图片描述

再一次说明了参数传递也是一种隐形赋值

  • 特殊情况:定时器setTimeout
function test(){
    console.log(this.a);
}
var a = 'hello world';
var obj = {
    a:'obj',
    test:test
}		
setTimeout(obj.test,0);
//hello world

在这里插入图片描述

输出结果分析

setTimeout第一个参数是传入回调函数,obj.test被当做一个函数进行绑定,相当于

function setTimeout(test,delay){//delay毫秒后执行
    test();//obj.test
}
1.3 显式绑定

借助apply,call,bind等方法直接指定this绑定对象,call、apply、bind的第一个参数,就是对应函数的this所指向的对象。

Q:apply,call.bind有什么区别?

A:apply,call作用一样,传递参数的方式不一样,都会执行对应的函数,但是bind不一样,它不会执行,需要手动去调用。

//apply绑定
function test(){
    console.log(this.a);
}
var obj = {
    a:1111,
}
test.apply(obj);//hello world

在这里插入图片描述

//call绑定
function test(){
    console.log(this.a);
}
var obj = {
    a:1111,
}
test.call(obj);//hello world

在这里插入图片描述

使用显式绑定是不是不会出现绑定丢失呢?当然不是

  • 在显式绑定也会面临绑定丢失的问题
function sayHi(){
    console.log('hello',this.name);
}
var obj = {
    name:'Gabrielle',
    sayHi:sayHi
}
var name = 'Gaby';
var Hi  = function(fn){
    fn();
}
Hi.call(obj,obj.sayHi);
//hello,Gaby

在这里插入图片描述

输出结果分析

在执行fn的时候,相当于直接调用了sayHi方法(obj.sayHi已经被赋值给fn了,隐式绑定也丢失了),没有指定this的值,对应的是默认绑定。所以输出结果hello Gaby。

Q:如何解决绑定丢失问题?

A:给fn硬绑定this。

function sayHi(){
    console.log('hello',this.name);
}
var obj = {
    name:'Gabrielle',
    sayHi:sayHi
}
var name = 'Gaby';
var Hi  = function(fn){
    fn.call(this)}
Hi.call(obj,obj.sayHi);
//hello,Gabrielle

在这里插入图片描述

输出结果分析

因为obj被绑定到Hi函数中的this上,fn又将这个函数绑定给sayHi的函数。

也可以用bind改写,bind被归类为硬绑定。

function sayHi(){
    console.log('hello',this.name);
}
var obj = {
    name:'Gabrielle',
    sayHi:sayHi
}
var name = 'Gaby';
var Hi  = sayHi.bind(obj);
Hi.call(obj,obj.sayHi);
//hello,Gabrielle

在这里插入图片描述

  • 绑定例外

应用绑定规则:把null,undifined作为绑定对象传入call,apply,bind,这些值往往被忽略

var obj = {
    name:'Gabrielle'
}
var name = 'Gaby';
function Hi() {
    console.log(this.name);
}
Hi.call(null);
//Gaby

在这里插入图片描述

1.4 new绑定

js没有类,通过构造函数来模拟类,使用new来调用函数,会自动执行下面的操作

创建一个对象,这个对象被执行[[Prototype]]连接,这个新对象会绑定到函数调用的this,如果函数没有返回其他对象,那么返回这个新对象,否则返回构造函数返回的对象。

  • 手写new
  //1、创建一个空对象
function _new(fn,...args){
  //2、这个对象的 __proto__ 指向 fn 这个构造函数的原型对象
  var obj = Object.create(fn.prototype);
  //3、改变this指向
  var res = fn.apply(obj,args);
  // 4. 如果构造函数返回的结果是引用数据类型,则返回运行后的结果,否则返回新创建的 obj
  if ((res!==null && typeof res == "object") || typeof res == "function") {
      return res;
  }
  return obj;
}
  • new绑定this
function person(name){
    this.name = name;
}
var per = new person('Gaby');
console.log(per.name);
//Gaby
//per已经被绑定到person调用的this上

在这里插入图片描述
插播一个小片段
在写上述代码的时候,我忘记给person()传入参数了,导致控制台打印出来了undefined。然后我就在思考一个问题,为什么给person()传入参数才可以有正常的输出。这背后的逻辑是什么?在此给大家提个醒,function person()是一个构造函数,this.name要拿到name,但是我的name没有传入进去,new的person对象中没有得到我想要传入构造函数里面的name,因为没有传入参数,所以根本无法赋值。

1.5 箭头函数this绑定

ES6箭头函数需要单独讨论,它不适用上面的四种绑定规则。箭头函数中没有this

箭头函数的this指向取决于外层作用域中的this,外层作用域或函数的this指向谁,箭头函数中的this便指向谁。

function fn(){
    return () =>{
        console.log(this.name);
    }
}
let obj1 = {
    name='Gabrielle'
};
let obj1 = {
    name='Gaby'
}
let bar = fn.call(obj1);//fn this指向obj1
bar.call(obj2);
//Gabrielle

Q1:为什么再一次绑定后没有修改?

A2:cuz箭头函数的特性:一旦箭头函数的this绑定成功,也无法被再次修改。

Q2:真的无法被修改吗?

A2:其实也不是没有解决办法。箭头函数的this会像作用域继承一样从上层作用域找,因此可以修改外层函数this指向达到间接修改箭头函数this的目的。

function fn(){
    return () =>{
        console.log(this.name);
    }
}
let obj1 = {
    name='Gabrielle'
};
let obj1 = {
    name='Gaby'
}
fn.call(obj1)();//fn this指向obj1,箭头函数this也指向obj1
fn.call(obj2)();//fn this指向obj2,箭头函数this也指向obj2

二 keep going

JS深入理解要整理的知识点又多又杂,学好JS一定要理解背后的逻辑和原理,而不是简简单单的知道怎么写代码去运用而已。

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值