JS 中的 This 别再被问倒了,面试常见问题分析

GitHub地址:https://github.com/SimonZhang...javascript

this的指向问题应该是让每个前端er都头疼的问题,我也同样,曾经遇到甚至都是一顿乱猜。最近在研读一些书籍如《你不知道的JavaScript》和《JavaScript语言精粹与编程实践》,让我对this的问题豁然开朗。故写下此篇文章,分享一下个人心得。前端

隐式绑定

关于this,通常来讲,谁调用了方法,该方法的this就指向谁,如:java

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

var a = 3;

var obj = {
    a: 2,
    foo: foo
};

obj.foo(); // 输出2,由于是obj调用的foo,因此foo的this指向了obj,而obj.a = 2

若是存在屡次调用,对象属性引用链只有上一层或者说最后一层在调用位置中起做用,如:git

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

var obj2 = { 
    a: 42,
    foo: foo
}

var obj1 = {
    a: 2,
    obj2: obj2
}

obj1.obj2.foo(); // 42

隐式丢失

一个最多见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说他回应用默认绑定,从而把this绑定到全局对象或者undefined上,取决因而否是严格模式。github

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

var obj1 = {
    a: 2,
    foo: foo
}

var bar = obj1.foo; // 函数别名!

var a = "oops, global"; // a是全局对象的属性

bar(); // "oops, global"

虽然bar是obj.foo的一个引用,可是实际上,它引用的是foo函数自己,所以此时的bar()实际上是一个不带任何修饰的函数调用,所以应用了默认绑定编程

一个更微妙、更常见而且更出乎意料的状况发生在传入回调函数时app

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

function doFoo( fn ){
    // fn 其实引用的是 foo
    fn(); // <-- 调用位置!
}

var obj = {
    a: 2,
    foo: foo
}

var a = "oops, global"; // a是全局对象的属性

doFoo( obj.foo ); // "oops, global"

参数传递其实就是一种隐式赋值,所以咱们传入函数时也会被隐式赋值,因此结果和上一个例子同样,若是把函数传入语言内置的函数而不是传入本身声明的函数(如setTimeout等),结果也是同样的函数

显式绑定

简单的说,就是指定this,如:call、apply、bind、new绑定等oop

硬绑定

function foo( something ) {
    console.log( this.a, something)
    return this.a + something
}

var obj = {
    a: 2
}

var bar = function() {
    return foo.apply( obj, arguments)
}

var b = bar(3); // 2 3
console.log(b); // 5

这里简单作一下解释:
在bar函数中,foo使用apply函数绑定了obj,也就是说foo中的this将指向obj,与此同时,使用arguments(不限制传入参数的数量)做为参数传入foo函数中;因此在运行bar(3)的时候,首先输出obj.a也就是2和传入的3,而后foo返回了二者的相加值,因此b的值为5测试

一样,本例也可使用bind:

function foo( something ) {
    console.log( this.a, something)
    return this.a + something
}

var obj = {
    a: 2
}

var bar = foo.bind(obj)

var b = bar(3); // 2 3
console.log(b); // 5

new绑定

在传统面向类的语言中,使用new初始化类的时候会调用类中的构造函数,可是JS中new的机制实际上和面向类和语言彻底不一样。

使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操做:

  • 建立(或者说构造)一个全新的对象

  • 这个新对象会被执行[[Prototype]]链接

  • 这个新对象会绑定到函数调用的this

  • 若是函数没有返回其余对象,那么new表达式中的函数会自动返回这个新对象
    如:

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

var bar = new foo(2);
console.log(bar.a); // 2

使用new来调用foo(...)时,咱们会构造一个新对象并把它绑定到foo(...)调用中的this上。new是最后一种能够影响函数调用时this绑定行为的方法,咱们称之为new绑定。

this的优先级

毫无疑问,默认绑定的优先级是四条规则中最低的,因此咱们能够先不考虑它。

隐式绑定和显式绑定哪一个优先级更高?咱们来测试一下:

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

var obj1 = {
    a: 2,
    foo: foo
}

var obj2 = {
    a: 3,
    foo: foo
}

obj1.foo(); // 2
obj2.foo(); // 3

obj1.foo.call(obj2); // 3
obj2.foo.call(obj1); // 2

能够看到,显式绑定优先级更高,也就是说在判断时应当先考虑是否能够存在显式绑定。

如今咱们要搞清楚new绑定隐式绑定的优先级谁高谁低 :

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

var obj1 = {
    foo: foo
}

var obj2 = {}

obj1.foo(2); 
console.log(obj1.a); // 2

obj1.foo.call(obj2,3);
console.log(obj2.a); // 3

var bar = new obj1.foo(4)
console.log(obj1.a); // 2
console.log(bar.a); // 4

能够看到new绑定隐式绑定优先级高。可是new绑定显式绑定谁的优先级更高呢?

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

var obj1 = {}

var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2

var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3

能够看到,new绑定修改了硬绑定中的this,因此new绑定的优先级比显式绑定更高。

之因此要在new中使用硬绑定函数,主要目的是预先设置函数的一些参数,这样在使用new进行初始化时就能够只传入其他的参数。bind(...)的功能之一就是能够把除了第一个参数(第一个参数用于绑定this)以外的其余参数都传给下层的函数(这种技术称为“部分应用”,是“柯里化”的一种)。举例来讲:

function foo(p1,p2){
    this.val = p1 + p2;
}

// 之因此使用null是由于在本例中咱们并不关心硬绑定的this是什么
// 反正使用new时this会被修改
var bar = foo.bind(null,'p1');

var baz = new bar('p2');

baz.val; // p1p2
}

柯里化:在直觉上,柯里化声称“若是你固定某些参数,你将获得接受余下参数的一个函数”。因此对于有两个变量的函数yx,若是固定了 y = 2,则获得有一个变量的函数 2x

This在箭头函数中的应用

箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)做用域来决定this。

咱们来看一下箭头函数的词法做用域:

function foo() {
    // 返回一个箭头函数
    return (a) => {
        // this继承自foo()
        console.log(this.a)
    };
}

var obj1 = {
    a: 2
};

var obj2 = {
    a: 3
};

var bar = foo.call(obj1);
bar.call(obj2); // 2, 不是3!

foo()内部建立的箭头函数会捕获调用时foo()的this。因为foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定没法被修改。(new也不行!)

总结

若是要判断一个运行中的函数的this绑定,就须要找到这个函数的直接调用位置。找到以后就能够顺序应用下面这四条规则来判断this的绑定对象。

  1. 由new调用?绑定到新建立的对象。

  2. 由call或者apply(或者bind)调用?绑定到指定的对象。

  3. 由上下文对象调用?绑定到那个上下文对象。

  4. 默认:在严格模式下绑定到undefined,不然绑定到全局对象。

相关文章
相关标签/搜索