前端面试题之[this的原理以及用法]

199 阅读4分钟

this是什么?

this 是 JavaScript 中的一个关键字。this指代当前函数的运行环境。它通常被运用于函数体内,依赖于函数调用的上下文条件,与函数被调用的方式有关。它指向谁,则完全是由函数被调用的调用点来决定的。

所以,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象

let a=10;
let obj={
    a:1,
    foo:function(){
        console.log(this.a)
    }
}
let foo=obj.foo;
obj.foo()// 1 这里的this是在obj的环境中 所以指向obj
foo()//10   这里的this则在全局环境中运行 指向windows

this的原理

理解 this 的原理,有助于帮我们更好地理解它的用法。JavaScript 语言之所以有 this 的设计,跟内存里面的数据结构有关系。 举个例子:

    var obj = { foo:  5 };

上面的代码将一个对象赋值给变量obj。JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 },然后把这个对象的内存地址赋值给变量obj。

也就是说,变量obj是一个地址(reference)。后面如果要读取obj.foo,引擎先从obj拿到内存地址,然后再从该地址读出原始的对象,返回它的foo属性。

原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的foo属性,实际上是以下面的形式保存的。

这样的结构很清晰,但如果属性的值是一个函数,又会怎么样呢?比如这样:

var obj = { foo: function () {} };

这时,JavaScript 引擎会将函数单独保存在内存中,然后再将函数的地址赋值给 foo 属性的 value 属性。

可以看到,函数是一个单独的值(以地址形式赋值),所以才可以在不同的环境中执行。

又因为,JavaScript 允许在函数体内部,引用当前环境的其他变量。所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。

this的用法

函数中的用法

简单调用

这是函数的最通常用法,属于全局性调用,因此 this 就代表全局对象 window。

function f1(){
  return this;
}
//在浏览器中:
f1() === window;   //在浏览器中,全局对象是window

严格模式

在严格模式下,this将保持他进入执行环境时的值,所以下面的this将会默认为undefined。

function f2(){
  "use strict"; // 这里是严格模式
  return this;
}

f2() === undefined; // true

call或apply方法

要想把 this 的值从一个环境传到另一个,就要用 call 或者apply 方法。

// 将一个对象作为call和apply的第一个参数,this会被绑定到这个对象。
var obj = {a: 'Custom'};

// 这个属性是在global对象定义的。
var a = 'Global';

function whatsThis(arg) {
  return this.a;  // this的值取决于函数的调用方式
}

whatsThis();          // 'Global'
whatsThis.call(obj);  // 'Custom'
whatsThis.apply(obj); // 'Custom'

bind方法

调用f.bind(someObject)会创建一个与f具有相同函数体和作用域的函数,但是在这个新函数中,this将永久地被绑定到了bind的第一个参数,无论这个函数是如何被调用的。

function f(){
  return this.a;
}

var g = f.bind({a:"azerty"});
console.log(g()); // azerty

var h = g.bind({a:'yoo'}); // bind只生效一次!
console.log(h()); // azerty

var o = {a:37, f:f, g:g, h:h};
console.log(o.a, o.f(), o.g(), o.h()); // 37, 37, azerty, azerty

箭头函数

箭头函数没有自己的this,arguments,**因此箭头函数的this是继承父执行上下文里面的this **

var i=1000;
var obj2 = {
  i: 10,
  b: () => console.log(this.i, this),
  c: function() {
    let fn=()=>{
        console.log(this.i, this)
    }
    fn()
  }
}
obj2.b(); //1000  Window

obj2.c(); //obj2  10

都是通过obj调用匿名函数为何打印出来的this不同呢?

因为this是继承自父执行上下文!!中的this,obj.b的箭头函数本身所在的对象为obj,obj的父执行上下文为Window,因此obj.b的this指向Window

注意:简单对象(非函数)是没有执行上下文的!

obj2.c的父执行上下文为obj,所以obj2.c中this指向obj2

作为对象中的方法

当函数作为对象里的方法被调用时,它们的 this 是调用该函数的对象

var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //12
        }
    }
}
o.b.fn();//12

this 的绑定只受最靠近的成员引用的影响。因此fn中的this指向b

作为构造函数

当一个函数用作构造函数时(使用new关键字),它的this被绑定到正在构造的新对象。

function C(){
  this.a = 37;
}

var o = new C();
console.log(o.a); // logs 37  this指向 o

function C2(){
  this.a = 37;
  return {a:38};
}

o = new C2();
console.log(o.a); // logs 38

在刚刚的例子中(C2),因为在调用构造函数的过程中,手动的设置了返回对象,与this绑定的默认对象被丢弃了。