本节我们继续讲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指向
- 忽略显示绑定
function foo() {
console.log(this);
}
foo.call(null) // window
foo.call(undefined) // window
可以看出这是一个显示绑定,但是在非严格模式下他们输出的是window。
- 间接函数引用
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)()这种写法是当个独立函数在调用。
- 箭头函数 说明:箭头函数不使用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创建的对象,他产生了作用域