this的理解
我们先来看一下MDN文档中对this的解释
与其他语言相比,函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。
在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)。this 不能在执行期间被赋值,并且在每次函数被调用时 this 的值也可能会不同。ES5 引入了 bind 方法来设置函数的 this 值,而不用考虑函数如何被调用的。ES2015 引入了箭头函数,箭头函数不提供自身的 this 绑定(this 的值将保持为闭合词法上下文的值)。
看完MDN的解释,很多人还是不懂this的具体作用,下面我用自己的话阐述一下对this的理解,相比MDN的解释,更容易理解一点
根据函数的调用方式不同,this会指向不同的对象
1. 以函数的形式调用时,this永远都是window
2. 以方法的形式调用时,this就是调用方法的那个对象
3.以构造函数的形式调用时,this就是新建的对象
4.使用call和apply调用时,this就是指那个对象
5.在全局作用域中this代表window
this在全局作用下指向什么?
在浏览器中测试就是指向window
// 全局下的this
console.log(this);//window
console.log(window);//window
但是,开发中很少直接在全局作用域下去使用this,通常都是在函数中使用this
所有的函数在被调用时,都会创建一个执行上下文
这个上下文中记录这函数的调用栈、AO对象等
this也是其中一条记录
总结
this指向什么,跟函数所在位置没有关系;
跟函数调用的方式有关系,就是谁调用的这个函数,this就会指向谁
this的绑定规则有四种
- 默认绑定
- 隐式绑定
- 显示绑定
- new绑定
第一种:默认绑定
默认绑定案例一
// 默认绑定,独立函数调用
function foo() {
console.log(this);
}
foo()//window
默认绑定案例二
// 也是独立调用
function foo1() {
console.log(this);//Window
}
function foo2() {
console.log(this);//Window
foo1()
}
function foo3() {
console.log(this);//Window
foo2()
}
foo3()
默认绑定案例三
var obj = {
name: "why",
foo: function () {
console.log(this);
}
}
var bar = obj.foo
// 为什么这里this指向window,this指向取决于调用者,这里是独立调用所以this就是window
bar()//window
以上这三个案例就是默认绑定的情况,属于独立调用,没有调用者去调用函数,最终this都指向了window
第二种:隐式绑定
隐式调用就是通过某个对象进行调用;
也就是它的调用位置中,是通过某个对象发起的函数调用
隐式绑定案例一
function foo() {
console.log(this);
}
let obj = {
name: 'why',
// 把外面的foo函数传递给foo
foo: foo
}
//这里this指向obj对象
obj.foo()
隐式绑定案例二
let obj = {
name: 'why',
eating: function () {
console.log(this.name + '在吃东西');
},
running: function () {
console.log(this.name + '在跑步中');
}
}
//这里函数是obj在调用,this指向obj
obj.eating() //why在吃东西
obj.running()//why在跑步中
隐式绑定案例三
let obj1 = {
name: "obj1",
foo: function () {
console.log(this);//obj2
}
}
let obj2 = {
name: 'obj2',
// 这里把obj1.foo传递给bar
bar: obj1.foo
}
//这里打印的结果是obj2对象,obj2调用的函数,所以this指向obj2
obj2.bar()
第三种:显示绑定
隐式绑定有一个前提条件:
- 必须在调用的对象内部有一个对函数的引用(比如一个属性)
- 如果没有这样的引用,在进行调用时,会报找不到该函数的错误
- 正是通过这个引用,间接的将this绑定到了这个对象上
显示绑定案例一
function foo() {
console.log('函数被调用了', this);
}
// foo直接调用
// foo直接调用和call/apply调用的不同在于this绑定的不同
// foo直接调用指向的是全局对象(window)
// foo()//window
let obj = { name: 'obj' }
// call和apply是可以指定this的绑定对象
foo.call(obj) //指向obj对象
foo.apply(obj) //指向obj对象
foo.apply('abcd')//指向字符串对象
补充:call()和apply()的区别?
call/apply用来改变函数的执行上下文(this),它们的第一个参数thisArg是个对象,即作为函数内的this。
不同点:传参的类别不同
call 参数依次排列在后面
apply 接收参数形式是数组,把参数放在数组里面
// call和apply有什么区别?
function sum(num1, num2) {
console.log(num1 + num2, this);
}
sum.call('call', 20, 30)
sum.apply('apply', [50, 100])
call和apply在执行函数时,是可以明确的绑定this,这个绑定规则称之为显示绑定
显示绑定案例二
// 显示绑定bind
function foo() {
console.log(this);
}
let newFoo = foo.bind('aaa')
newFoo()//String对象
第四种:new绑定
JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。
使用new关键字来调用函数是会执行如下操作:
- 创建一个全新的对象
- 这个新对象会被执行prototype连接
- 这个新对象会绑定到哈数调用的this上(this的绑定在这个步骤完成)
- 如果函数没有返回其他对象,表达式会返回这个新对象
function Person(name, age) {
this.name = name
this.age = age
}
let p1 = new Person("why", 18)
console.log(p1.name, p1.age); //why 18
let p2 = new Person('lidi', 25) //lidi 25
console.log(p2.name, p2.age);
我们通过一个new关键字调用一个函数时(构造器),这个时候this是在调用这个构造器时创建出来的对象
this=创建出来的对象
这个绑定过程就是new绑定