带着问题出发
- this的定义是什么?
- this在不同的情况下调用,指向分别如何?
- 普通函数和es6中箭头函数中的this有什么区别?
理解
this代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。
这里注意的是 运行时,这说明this关键词只与函数的执行环境有关。
this会在执行上下文中绑定一个对象,但是是根据什么条件绑定的呢?在不同的执行条件下会绑定不同的对象,这也是让人捉摸不定的地方。
this绑定规则
0. 全局作用域下-this默认指向window
在浏览器中测试可知,全局作用下this指向是指向window
console.log(this) // window
var name = "guava"
console.log(this.name,window.name) // "guava","guava"
开发中很少直接在全局作用域下使用this,通常都是在函数中使用。
- 直接调用普通函数-this指向window
对于直接调用普通函数,this 的指向一定是 window
function foo(){
console.log(this.a)
}
var a = 1;
//相等于window.foo()
foo()
所有函数被调用时,都会创建一个执行上下文:
- 这个上下文中记录着函数的调用栈、函数的调用方式、传入的参数信息等
- this也是其中的一个属性
这里看一个例子:
function foo(func) {
func()
}
var obj = {
name: "guava",
bar: function() {
console.log(this); // window
}
}
foo(obj.bar);
这里打印的结果时window,猜对了吗?真正调用函数的位置,是foo函数,foo函数没有进行任何的对象绑定,foo函数的this指向当然是window,传入的bar函数被当成了普通函数执行,所以也是指向window。
- 通过对象调用函数-谁调用函数,this指向谁
一般情况下,谁调用了函数,this就指向谁。
const obj = {
a: 2,
foo: function (){
console.log(this.a)
}
}
// obj调用了foo函数,所以foo里面的this指向的是obj对象
obj.foo()
// 2
那么思考一下,下面的案例,this指向哪里?
function foo(){
console.log(this)
}
var obj1 = {
name:"obj1",
foo:foo
}
var obj2 = {
name:"obj2",
obj1:obj1
}
obj2.obj1.foo()
很简单,最终foo函数还是通过obj调用的,所以还是指向了obj1
- 显式绑定-bind、call、apply
显式绑定后,this会明确的指向绑定对象
function foo(){
console.log(this)
}
foo.call(window); // window
foo.call({name: "guava"}); // {name: "guava"}
foo.call(123); // Number {123}
foo.apply(window); // window
foo.apply({name: "guava"}); // {name: "guava"}
foo.apply(123); // Number {123}
var bar1 = foo.bind(window)
var bar2 = foo.bind({name: "guava"})
var bar3 = foo.bind(123)
bar1() // window
bar2() // {name: "guava"}
bar3() // Number {123}
- 调用new 关键词创建的对象实例的函数
构造函数或Class类,可以通过new 关键词创建的对象实例,这时 this 永远的指向了实例化的对象上面
function Foo() {
this.a = 3
}
let f = new Foo()
console.log(f.a) // 3
当函数中有return关键词:如果函数中返回值是一个对象(null除外),则实例化对象时this指向就是这个对象,否则指向该函数的实例。
function Foo1(){
this.name = "guava"
return null
//return 1
//return false
//return undefined
//return "guava"
}
let person1 = new Foo1()
console.log(person1.name) // "guava"
function Foo2(){
this.name = "guava"
return {name:"st"}
}
let person2 = new Foo2()
console.log(person2.name) // "st"
- 规则优先级
前四条规则中,多条规则存在时,谁的优先级更高呢?也就是多条规则存在应用那一条规则呢?
结论是:new关键词 > 显式绑定 > 对象调用 > 普通函数调用
ES6的箭头函数
ES6中箭头函数(()=>{})没有 this、arguments、super 等,不受上边的规则限制,这些只依赖包含箭头函数最接近的函数中的规则,也就是外层作用域来决定this。
var obj1 = {
name:"guava",
getName:function(){
setTimeout(()=>{
console.log(this.name)
},1000)
}
}
obj1.getName() // "guava"
var name = 'st'
var obj2 = {
name:"guava",
getName:()=>{
setTimeout(()=>{
console.log(this.name)
},1000)
}
}
obj2.getName() // "st"
代码中,obj1中的setTimeout函数使用了箭头函数,所以this获取的是getName函数的this,所以指向了调用函数的对象obj1;obj2中的setTimeout和getName函数都是箭头函数,所以继续向外找,找到了全局作用域window
this面试题
- 面试题1
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss();
person.sayName();
(person.sayName)();
(b = person.sayName)();
}
sayName();
这题很简单,相信你已经知道答案了
function sayName() {
var sss = person.sayName;
sss(); // window
person.sayName(); // person
(person.sayName)(); // person
(b = person.sayName)(); // window 全局声名 变量 b ,然后调用
}
- 面试题2
var name = 'window'
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name)
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
return () => {
console.log(this.name)
}
}
}
var person2 = { name: 'person2' }
person1.foo1();
person1.foo1.call(person2);
person1.foo2();
person1.foo2.call(person2);
person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);
person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);
答案和解析如下:
person1.foo1(); // person1
person1.foo1.call(person2); // person2
person1.foo2();// window
person1.foo2.call(person2);// window
//相当于返回的函数再在window下调用
person1.foo3()();// window
//同上
person1.foo3.call(person2)();// window
//显式调用
person1.foo3().call(person2);// person2
//箭头函数往上一层作用域为person1
person1.foo4()();// person1
//foo4的作用域显式的绑定为person2,所以箭头函数往上一层作用域为person2
person1.foo4.call(person2)();// person2
//返回的是箭头函数,只看上一层作用域,所以还是person1
person1.foo4().call(person2);//person1
- 面试3
var name = 'window'
function Person (name) {
this.name = name
this.foo1 = function () {
console.log(this.name)
},
this.foo2 = () => console.log(this.name),
this.foo3 = function () {
return function () {
console.log(this.name)
}
},
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.foo1()
person1.foo1.call(person2)
person1.foo2()
person1.foo2.call(person2)
person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)
person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)
答案和解析如下:
//对象调用
person1.foo1() // person1
//显式调用
person1.foo1.call(person2) //person2
//箭头函数,则往上一层作用域
person1.foo2() // person1
//同上
person1.foo2.call(person2) // person1
//相当于返回的函数再在window下调用
person1.foo3()() // window
//同上
person1.foo3.call(person2)() // window
//返回的函数 显式调用
person1.foo3().call(person2) // person2
//箭头函数,则往上一层作用域
person1.foo4()() // person1
//同上
person1.foo4.call(person2)() // person2
//箭头函数使用显式绑定无效,只会找上一层作用域
person1.foo4().call(person2) // person1
- 面试题4
var name = 'window'
function Person (name) {
this.name = name
this.obj = {
name: 'obj',
foo1: function () {
return function () {
console.log(this.name)
}
},
foo2: function () {
return () => {
console.log(this.name)
}
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2
person1.obj.foo2()() // obj
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj
是不是已经驾轻就熟,一眼就看出来了
person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2
person1.obj.foo2()() // obj
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj
总结
通过上面的学习,当面试官问到我们可以从容的回答了:
当函数运行时,函数内部的执行上下文会创建一个this的对象属性,在不同的情况下this绑定的对象也有不同的指向,一般地遵循四条规则:
- 调用普通函数时,this默认指向到window
- 对象调用函数时,this指向到调用的对象,也就是谁调用,this指向谁
- 使用call、apply、bind函数可以显式的绑定this指向
- new关键词创建的实例,this默认指向到实例化对象
多条规则同时存在的情况下,其优先级是new关键词>显式绑定>对象调用函数>默认绑定
ES6中箭头函数没有this对象属性,只会找上级作用域的this。