js 中的 this 指向十分重要,在我们的工作和面试中也会时常遇到,倒是对this,总是不太了解,今天我来带大家系统的了解和学习一下。
一、this的指向
this 总是(非严格模式下)指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境
动态绑定的,而非函数被声明时的环境;
this的指向大体分为以下几种情况:
1、全局作用域和普通函数调用,此时 this 指向 window
当函数不作为对象的属性被调用,而是以普通函数的方式,this总是指向全局对象(在浏览器中,通常是Window对象)
//直接打印
console.log(this) //window
//function声明函数
function bar () {console.log(this)}
bar() //window
//function声明函数赋给变量
var bar = function () {console.log(this)}
bar() //window
//自执行函数
(function () {console.log(this)})(); //window
2、方法调用中谁调用 this 指向谁
(1)、构造函数调用, 此时 this 指向 实例对象
当函数作为对象的方法被调用时,this指向该对象
function Person(age, name) {
this.age = age;
this.name = name
console.log(this) // 此处 this 分别指向 Person 的实例对象 p1 p2
}
var p1 = new Person(18, 'zs')//Person {age: 18, name: 'zs'}
var p2 = new Person(18, 'ww')//Person {age: 18, name: 'ww'}
(2)、对象方法调用, 此时 this 指向 该方法所属的对象
当对象内的方法调用时,this指向该对象
var obj = {
fn: function () {
console.log(this); // obj
}
}
obj.fn();//{fn: ƒ}
(3)、通过事件绑定的方法, 此时 this 指向 绑定事件的对象
当时事件绑定方法时,this指向绑定的方法
//事件监听
var btn = document.querySelector("button")
btn.addEventListener('click', function () {
console.log(this) //btn
})
3、构造器调用
//不使用new指向window
function Person(name) {
console.log(this) // window
this.name = name;
}
Person('inwe')
//使用new
function Person(name) {
this.name = name
console.log(this) //people
self = this
}
var people = new Person('iwen')
console.log(self === people) //true
//这里new改变了this指向,将this由window指向Person的实例对象people
//但是,如果显式的返回了一个object对象,那么此次运算结果最终会返回这个对象。
var MyClass = function () {
this.name = 1;
return {
name: 2
}
}
var myClass = new MyClass();
console.log('myClass:', myClass.name);//myClass: 2
4、箭头函数中指向外层作用域的 this
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。
var obj = {
foo() {
console.log(this);
},
bar: () => {
console.log(this);
}
}
obj.foo() // {foo: ƒ, bar: ƒ}
obj.bar() // window
5、call或apply调用
跟普通的函数调用相比,用call和apply可以动态的改变函数的this
var obj1 = {
name: 1,
getName: function (num = '') {
return this.name + num;
}
};
var obj2 = {
name: 2,
};
// 可以理解成在 obj2的作用域下调用了 obj1.getName()函数
console.log(obj1.getName()); // 1
console.log(obj1.getName.call(obj2, 2)); // 2 + 2 = 4
console.log(obj1.getName.apply(obj2, [2])); // 2 + 2 = 4
6、补充
有一点需要注意的是定时器函数, 此时 this 指向 window
setInterval(function () {
console.log(this); // window
}, 1000);
二、call、apply和bind
我们先来看看call和apply方法的例子
var obj1 = {
name: 1,
getName: function (num = '') {
return this.name + num;
}
};
var obj2 = {
name: 2,
};
// 可以理解成在 obj2的作用域下调用了 obj1.getName()函数
console.log(obj1.getName()); // 1
console.log(obj1.getName.call(obj2, 2)); // 2 + 2 = 4
console.log(obj1.getName.apply(obj2, [2])); // 2 + 2 = 4
从中我们可以看出来,call和apply的作用都是改变this的指向,似乎没有什么差距,事实上也的确如此,他们的差距很小,也可以实现切换,但是他们之间还是有一些细小的差距的,接下来我们来看看他们之间的差距
call()和apply()的区别
- apply接受两个参数
- 第一个参数指定了函数体内this对象的指向
- 第二个参数为一个带下标的参数集合(可以是数组或者类数组)
- call接受的参数不固定
- 第一个参数指定了函数体内this对象的指向
- 第二个参数及以后为函数调用的参数
在所有(非箭头)函数中都可以通过arguments对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,它本身就是一个类数组,我们apply在实际使用中更常见一些。
call是包装在apply上面的语法糖,如果我们明确的知道参数数量,并且希望展示它们,可以使用call。
当使用call或者apply的时候,如果我们传入的第一个参数为null,函数体内的this会默认指向宿主对象,在浏览器中则是window。
var obj1 = {
name: 1,
getName: function (num = '',j= '') {
return this.name + num+j;
}
};
var obj2 = {
name: 2,
};
// 可以理解成在 obj2的作用域下调用了 obj1.getName()函数
console.log(obj1.getName()); // 1
console.log(obj1.getName.call(obj2, 2)); // 2 + 2 = 4
console.log(obj1.getName.apply(obj2, [2,3]));// 2 + 2 + 3 = 7
bind()方法
bind()创建的是一个新的函数(称为绑定函数),与被调用函数有相同的函数体,当目标函数被调用时this的值绑定到 bind()的第一个参数上
var obj = {
name: 1
};
var func = function(){
console.log(this.name);
};
func.bind(obj)()//记住一定到调用,否则只是绑定
//bind也可以直接绑定在函数上
var obj = {
name: 1
};
var func = function(){
console.log(this.name);
}.bind(obj);
func(); //1