JavaScript进阶讲解四—>this的指向二

110 阅读6分钟

本节我们继续讲js中this的绑定问题


一、系统API中的this

setTimeout(function() {
    console.log(this); // window
}, 100);
[1,2].forEach(function(item) {
    console.log(this); // window
});
[1,2].forEach(function(item) {
    console.log(this); // a
}, 'a');
[1,2].map(function(item) {
    console.log(this); // window
});
[1,2].map(function(item) {
    console.log(this); // a
}, 'a');

二、规则之外的this指向

  1. 忽略显示绑定
function foo() {
    console.log(this);
}
foo.call(null) // window
foo.call(undefined) // window

可以看出这是一个显示绑定,但是在非严格模式下他们输出的是window。

  1. 间接函数引用
var obj = {
    name: 'obj',
    foo: function() {
        console.log(this);
    }
}

var obj1 = {
    name: 'obj1'
};

// obj1.bar = obj.foo
// obj1.bar() // obj1

(obj1.bar = obj.foo)() // window

看这段代码, 注意点一:我在var obj1 这个对象后面加了个分号,是因为不加分号会报错(这是词法分析的问题) 注意点二:看注释的那两行 最后的结果是obj1,这个就是个隐式绑定。但是最后一行,是个window,说明我们(obj1.bar = obj.foo)()这种写法是当个独立函数在调用。

  1. 箭头函数 说明:箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this。
var name = 'window'
var obj1 = {
    name: 'obj1',
    foo: () => {
        console.log(this.name);
    }
};

obj1.foo() // window

我们可以看到上面这段代码,结果是window而不是obj1,这是因为foo的上级作用域是最外层(大家可千万不要以为上层作用域是obj1哈,定义对象是不会产生作用域的哈),我们在来看一个

var name = 'window'
var obj1 = {
    name: 'obj1',
    foo: function() {
        return () => {
            console.log(this.name);
        }
    }
};

obj1.foo()() // obj1

这个时候就是obj1,因为现在箭头函数的this其实就是foo中的this,看下面这个代码大家就明白了

var name = 'window'
var obj1 = {
    name: 'obj1',
    foo: function() {
    	console.log(this.name);// obj1
        return () => {
            console.log(this.name);// obj1
        }
    }
};

obj1.foo()() // obj1

这样大家是否明白了呢? 因为箭头函数不绑定this,他的this是上层作用域中的this也就是foo的this。而我们在调用foo时时通过obj1.foo()调用的,他的this就指向obj1。 我们在把上面这个代码调整下呢?

var name = 'window'
var obj1 = {
    name: 'obj1',
    foo: () => {
        return () => {
            console.log(this.name);
        }
    }
};

这个就给大家自己分析了。答案是window

三、常见面试题

为了让大家对this的指向,更加清晰,这里找了几个面试题,来给大家讲解。

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

默认调用:this指向window,所以答案是10

let a = 10
const b = 20

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

答案: undefined undefined undefined 我们还没讲过let 与const,先提前告诉大家let 或者 const,变量是不会被绑定到window上的,但是他还是默认调用,this还是指向window的。

var a = 1
function foo () {
  var a = 2
  function bar() { 
    console.log(this.a)
  }
  bar()
}

foo()

大家不要被闭包迷惑,这里的bar还是默认调用,this还是指向window的。所以答案是1。

var a = 1
function foo () {
  var a = 2
  function bar() { 
    console.log(this.a)
  }
  bar.call({a: 3})
}

foo()

这个bar很明显是显示调用,所以答案是3.

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

这个就是隐式调用,this指向obj,所以答案是1

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

obj.foo();
foo2();

答案1,2 首先obj.foo()很明显是隐式调用,this指向obj。foo2()这个很明显是独立函数调用,也就是默认绑定,this指向window。

var obj1 = {
  a: 1
}
var obj2 = {
  a: 2,
  foo1: function () {
    console.log(this.a)
  },
  foo2: function () {
    setTimeout(function () {
      console.log(this.a)
    }, 0)
  }
}
var a = 3

obj2.foo1()
obj2.foo2()

答案2,3。obj2.foo1()隐式绑定所以是2,这个不在多说。obj2.foo2()这里面是在setTimeout中的打印,而setTimeout是系统内置的api,他的this是window(具体为什么这个就得看源码了,我也没看过,但我猜测setTimeout是把我们传过去的函数,进行了直接调用,所以相当于默认绑定)

var name = 'window'
var obj = {
    name: 'obj',
    foo: function() {
        console.log(this.name)
    }
}
function foo() {
    var aaa = obj.foo
    aaa() // window
    obj.foo(); // obj 这里要加分号,不然下面语句会报错(和js解析机制有关)
    (obj.foo)(); // obj 这里要加分号,不然下面语句会报错(和js解析机制有关)
    (b = obj.foo)() // window
}
foo()

aaa() 默认绑定 obj.foo()隐式绑定

(obj.foo)() 其实这个与obj.foo()是一样的,也是隐式绑定

(b = obj.foo)() 这个就不一样了哟,他相当于b() 所以是 默认绑定

var name = 'window'

var person1 = {
  name: 'person1',
  foo1: () => console.log(this.name),
  foo2: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo3: function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

person1.foo1(); // window
person1.foo1.call(person2); // window

person1.foo2()(); // window
person1.foo2.call(person2)(); // window
person1.foo2().call(person2); // person2

person1.foo3()(); // person1
person1.foo3.call(person2)(); // person2
person1.foo3().call(person2); // person1

我们一个个的来看

person1.foo1() 与 person1.foo1.call(person2) 这里的foo1是箭头函数他是不绑定this的,所以不论你是隐式还是显示绑定都是没用的,他的this始终是上层作用域,而这里的上层作用域是全局。

person1.foo2()() 相当于 var a = person1.foo2() a() 很明显是默认绑定。

person1.foo2.call(person2)() 相当于 var a = person1.call(person2) a()很明显还是默认绑定。

person1.foo2().call(person2) 相当于 var a =person1.foo2() a.call(person2) 很明显是显示绑定。

person1.foo3()() 与person1.foo3.call(person2)()都相当于相当于var a = () => {} a() 这里的a是箭头函数,所以这里的this是上层作用域中的this。

person1.foo3()() 他上一层的this是person1.foo3()中的this也就是person1。

person1.foo3.call(person2)() 他上一层的this是person1.foo3.call(person2)中的this也就是person2。

person1.foo3().call(person2) 相当于var a =() => {} a.call(person2) a是个箭头函数用call是没有用的,这个已经说过多次,找的还是上层作用域的this,而上层就是person1.foo3()所以this是person1。

var name = 'window'

function Person (name) {
  this.name = name
  this.foo1 = () => console.log(this.name),
  this.foo2 = function () {
    return function () {
      console.log(this.name)
    }
  },
  this.foo3 = function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1() // person1 
person1.foo1.call(person2) // person1 (上层作用域中的this是person1)

person1.foo2()() // window
person1.foo2.call(person2)() // window
person1.foo2().call(person2) // person2

person1.foo3()() // person1
person1.foo3.call(person2)() // person2
person1.foo3().call(person2) // person1

注意哟,这个题可与第9题不一样了哟,这里的person1和person2不是字面量对象了哟,而是new出来的对象哟。两者的区别字面量对象是没得作用域的,而new是会产生作用域的(因为你new的是个函数,函数肯定石油作用域的)

其实这里的分析方法和第9题类似,要注意的就是作用域的问题。

我们一个个的来看

person1.foo1() 与 person1.foo1.call(person2) 这里的foo1是箭头函数他是不绑定this的,所以不论你是隐式还是显示绑定都是没用的,他的this始终是上层作用域,而这里的上层作用域是person1。(看到没这里的作用域不再是全局了,因为var person1 = new Person('person1')这句话已经确定了作用域)。

person1.foo2()() 相当于 var a = person1.foo2() a() 很明显是默认绑定。

person1.foo2.call(person2)() 相当于 var a = person1.call(person2) a()很明显还是默认绑定。

person1.foo2().call(person2) 相当于 var a =person1.foo2() a.call(person2) 很明显是显示绑定。

person1.foo3()() 与person1.foo3.call(person2)()都相当于相当于var a = () => {} a() 这里的a是箭头函数,所以这里的this是上层作用域中的this。

person1.foo3()() 他上一层的this是person1.foo3()中的this也就是person1。

person1.foo3.call(person2)() 他上一层的this是person1.foo3.call(person2)中的this也就是person2。

person1.foo3().call(person2) 相当于var a =() => {} a.call(person2) a是个箭头函数用call是没有用的,这个已经说过多次,找的还是上层作用域的this,而上层就是person1.foo3()所以this是person1。

可以看出除了在执行foo1有不同外,其他的都是相同的,不同的原因就是这里是通过new创建的对象,他产生了作用域