阅读 442
This ! 到底指向何方 ?

This ! 到底指向何方 ?

thisjavaScript 中是个基础,也容易搞不懂的知识点。有的小伙伴总是搞不懂 this 在什么时候调用指向哪里,这篇文章带着大家梳理一下所有会出现 this 的情况,归类总结并拿些面试题来看看。
有两点前置知识(箭头函数特性,new修饰符的工作原理),我放到了最后,需要了解的可以先去看看。
我们进入正题:

this是什么

this是对象内部的关键词。在箭头函数出现前,this的指向是在调用时产生的,也可以说是谁调用就指向于谁,通过 this 可以访问指向对象中的属性。说说this有哪几种绑定方式吧:

  1. 默认绑定
  2. 隐式绑定
  3. 显示绑定
  4. 强制绑定
  5. new实例化绑定

默认绑定

没有绑定规则时,就用到默认绑定。也是比较常见的。

var a = 1;
function fn(){
    console.log(this.a);
}
fn() // 1
复制代码

this 默认指向调用他的对象,那fn在执行时,其实是window在调用。回头可以聊聊在js脚本中初始化的GO和在调用栈运行中的AO及预编译,就会明白为什么在window调用了。
注意:1.如果用 let const 声明的变量,由于它没有挂在 window 上,所以无法用 this 访问。2.当使用严格模式时,thisundefined

function fn(){
    "use strict"
    console.log(this);
}
fn() // undefined
复制代码

隐式绑定

如果函数的调用是在某个对象上发生的,那么this也就指向这个对象。

var a = 2;
var obj = { 
    a: 3,
    foo() { 
        console.log( this.a );
    }
};

obj.foo(); // 3
复制代码

当使用obj调用foo方法时,foo 的调用在obj的上下文中,this进行了隐式绑定,this 绑定到 obj 中,this.a为3。
如果再嵌套,多层调用呢?

function foo() { 
    console.log( this.a );
}

var a = 2;

var obj1 = { 
    a: 4,
    foo: foo 
};

var obj2 = { 
    a: 3,
    obj1: obj1
};

obj2.obj1.foo(); //4
复制代码

obj2.obj1 调用 obj1=>obj1.foo 调用 foo,foo 执行,this 还是指向最终调用的对象obj上。

function fn() { 
    console.log( this.a );
}

var a = 2;

var obj1 = { 
    a: 4,
    foo: fn
};


let bar = obj1.foo(); 
bar() //2
复制代码

但要注意赋值过程,由于fn是引用赋值给obj.foo。内存地址指向的同一个地方,所以也相当与使用 window 去调用 bar 这时 this.a 为2。

显示绑定

显示绑定是改变函数内部的 prototype 关联对象来更改this上下文,由两个函数 call()apply() 来实现,call()apply() 只是内部传参不同,关于call()apply()bind()可以拿一篇文章探讨。网上也有许多相关文章,不展开解释

function foo() { 
    console.log( this.a );
}

var a = 2;

var obj1 = { 
    a: 3,
};

var obj2 = { 
    a: 4,
};
foo.call( obj1 ); // 3
foo.call( obj2 ); // 4
复制代码

可以理解成吧 foo 放在 obj1 ,由 obj1 调用 foo 执行,这时this指向也就在 obj 上下文中。

强制绑定

如果一个函数开发者不希望其他人员对 this 进行更改,也可以再封装一层函数进行调用。

function foo() { 
    console.log( this.a );
}

var a = 2;

var obj1 = { 
    a: 3,
};

var obj2 = { 
    a: 4,
};

var bar = function(){
    foo.call( obj1 );
}

bar.call( obj2 ); // 3
复制代码

bar()其实已经将 this 指向了 obj2 的上下文了,可内部函数绑定的是 obj1 的上下文,所以函数输出还是3。

new 实例化绑定

new 修饰符最终会生成一个对象,把函数中的 this 指向新的对象上,不会受全局变量的影响。

function Foo(a) { 
    this.a = a;
}

var a = 2;

var bar = new Foo(3);
console.log(bar.a); // 3
复制代码

但如果该函数有返回值,那么 this 会指向函数的返回值。

function Foo(a) { 
    this.a = a;
    return {
        b:a
    }
}

var a = 2;

var bar = new Foo(3);
console.log(bar.a); // undefined
console.log(bar.b); // 3
复制代码

以上绑定方式优先级依次递增:

  1. 是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。
  2. 是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是 指定的对象。
  3. 是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。
  4. 都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到 全局对象。

前置知识:

  1. 箭头函数特性。
  2. new修饰符的工作原理。

这里会简单介绍。毕竟不是讨论这个的专题。

箭头函数特性

箭头函数跟普通函数的区别:

  1. 书写方式,简化return。
  2. 不能作为构造函数,不能使用new。因为箭头函数没有ptototype
  3. 箭头函数没有arguments,可以用rest参数接收。
  4. 箭头函数不绑定this,而是从上下文捕获this作为函数内部的this。
  5. 箭头函数通过 call() apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。
  6. 箭头函数不能当做Generator函数,不能使用yield关键字。

// 一、简化return
let fn = (value)=> {
    return value
}
// 等价于
let fn = (value)=> value

// 二、不能使用new
let FunConstructor = () => {
    console.log('lll');
}
let fc = new FunConstructor(); //Uncaught TpyeError 报错

复制代码

new修饰符的工作原理

关键字new在调用构造函数的时候实际上进行了如下的几个步骤:

  1. 创建一个新的对象
  2. 将构造函数的作用域赋值给这个新的对象(因此this指向了这个新的对象)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 返回新对象
// 一个简单的new实现
function myNew() { 
    const obj = {},
        Constructor = [].shift.call(arguments) 
    obj.__proto__ = Constructor.prototype
    const res = Constructor.apply(obj, arguments); 
    return typeof res === 'object' ? res : obj; 
}
复制代码

内部创建空对象,将构造函数的 prototype 赋值给新对象的__proto__,将this指向新创建的对象。如果构造函数有返回非空的对象,则返回该对象,否则返回第一步中创建的对象。


如果此文章对您有帮助或启发,那便是我的荣幸
文章分类
前端
文章标签