之前有一篇文章写到了this,后来在面试的时候面试官说我写的太浅了,我又回去看了一下,确实哈哈哈哈哈哈,那么今天就从原理层面理解一下吧。

首先学习this的第一步是明白this既不指向函数自身,也不指向函数的词法作用域,this实际上是在函数被调用是发生的绑定,它指向是什么完全取决于在哪里被调用。

下面先简单介绍一下this的绑定规则:

1.默认绑定

首先先来看一段代码:

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

var a = 2;

foo();     //2

可能不熟悉javascript的朋友对这里有点困惑,因为在java等其他语言里面,this指向的是该对象的属性作用域,而javascript中在独立函数调用的时候,因为是全局调用的该函数,所以this指向的是全局,这里由于没有其他绑定方式,所以直接是默认绑定。

2.隐式绑定

考虑下面的代码:

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

obj.foo();   // 2 

与默认绑定类似,隐式绑定规则也是会把调用者的this传入函数对象,这里是把obj作为this传入了foo中,最后得以输出2。

3.显式绑定

之前的绑定都是应用规则来绑定,必须间接引用才可以完成功能,如果不想在对象内包含函数引用的话,而又需求在某个对象上强制调用函数,这时候就需要显示绑定了,下面介绍两种显示绑定的方法,顺便介绍call,apply,bind这几个javascript内置函数的使用方法以及区别。

首先是call与apply,这两个函数的主要作用都是将this绑定到某个对象上,例如上述代码中加入foo.call(obj),foo中的this就会显式指向obj,this.a就是obj.a,返回的是foo执行的结果,其区别在于call之后的参数数量是按照原函数的参数+1传入的,例如foo.call(obj,1,2,3)而apply是以参数数组的方式传入的,如foo.apply(obj,[1,2,3])

3.1硬绑定

接下来是bind,bind可以理解为辅助绑定函数,该函数返回的是一个函数,考虑如下代码:

function bind(fn, obj) {
    return function() {
        return fn.apply(obj, arguments);
    }
}

虽然说bind函数实现起来没有这么简单,但是该代码已经能很好地表达硬绑定的思想了,返回了一个绑定了该对象的函数,相比之前的call与apply可以重复执行,如下:

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

let obj1 = {a: '3'};
let obj2 = {a: '7'};

const foo1 = foo.bind(obj1);
const foo2 = foo.bind(obj2);

foo1(); //3
foo2(); //7

3.2new绑定

这里需要明确的是JavaScript中的new机制和其他面向类的语言完全不同,js中的构造函数只是被new操作符调用时的函数,其并不属于某个类,也不会实例化一个类,从某种意义上来说,其只是被new操作符调用的普通函数而已。总结一下,就是不存在所谓的构造函数,而是只存在对于函数的构造调用。考虑如下代码:

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

const foo1 = new foo(2);

console.log(foo1.a) \\2

可以看到这里的new操作符构造了一个全新的对象,并且将新对象绑定到该函数调用的this,如果函数没有返回其他对象的话,new表达式会将该对象本身返回。与前面的bind对比来看,关于this的部分就是创建了一个全新的obj来供new出来的函数使用。

4.软绑定

这里的软绑定是修改了的默认绑定+硬绑定的产物,简单来说,就是在该软绑定之后,将遇到默认绑定的规则的时候,不再绑定到全局对象或者undefined,而是软绑定指定的默认对象,出现其他硬绑定场景时再将该绑定绑定带其他对象,相比之下这种绑定方式更加灵活。

5.箭头函数

这里再提到箭头函数,结合上文中描述的this指向,箭头函数的this就很好理解了,箭头函数直接继承的是外层函数或者全局的作用局来决定的,当有时需要使用_this = this来控制父级到子集的this指向时,箭头函数是一个不错的选择,但是尽量不要将箭头函数与bind也就是this风格混用,这样会导致代码结构混乱。

文章目录
2人点赞