什么是this
JavaScript中的this是什么?
定义:this是包含它的函数作为方法被调用时所属的对象。
function fn1(){
this.name = "halo";
}
fn1();
- 我们将定义拆分一下
- 包含它的函数:包含this的函数是fn1。
- 作为方法被调用:fn1(); 此处fn1函数被调用。
- 所属的对象:函数式调用函数默认所属的对象是window。
通过上面三点分析,很容易知道fn1函数里的this指向的是window。
那么如果是更复杂的场景我们如何判断this的指向呢?
在es6箭头函数之前,想要判断一个函数内部this指向谁,就根据以下四种方式来决定的。
函数的4种调用方式
一、函数式调用
var age = 18;
var p = {
age:15
say:function(){
console.log(this.age);
}
}
var s1 = p.say;
s1(); //18
这个栗子可以很清晰的看到,包含 this的函数是say,函数执行时,函数默认所属的对象是window,那么这里的this就是window,输出18。所以通过函数式调用,函数内的this指向window。
注:一个构造函数如果不用new去调用的话,那么构造函数内部的this也不是指向构造函数的实例,而是指向window。
二、方法调用
- 方法调用栗子一
var age = 18;
var p = {
age:15,
say:function(){
console.log(this.age);
}
}
p.say(); //方法调用 this=>p 15
var fn1 = p.say;
fn1(); // 函数调用 this=>window 18
方法式调用,this指向看是哪个对象调用的方法,在这里this指向的是调用say方法的p对象。再看一个稍微复杂的栗子。
- 方法调用栗子二
var clear = function () {
console.log(this.name);
}
var name = 'window';
var jerry = { j: clear, name: 'jerry' };
var tom = { c: jerry, name: 'tom' };
tom.c.j(); // jerry
找调用方法的对象只找一级,就是函数“.”前面的对象。我们看调用j函数(也就是clear函数)的对象是c。对象c是tom对象的一个属性,属性指向的是jerry对象,所以这里调用j函数的对象是jerry,因此this指向的是jerry,输出"jerry"。
三、new调用
new调用也叫构造函数调用
- 栗子1
var age = 20;
var p = {
age: 15,
say: function () {
this.age = 10;
console.log(this.age);
}
}
new p.say(); // 10
通过new方式调用就是构造函数方式调用,那么函数内部的this就是该构造函数的实例。所以这里输出10。
四、call、apply、bind(上下文方式)
call、apply、bind都是改变函数this指向的方法,call和apply用法相近。
call & apply
- 栗子1
var length = 25;
function f1(p1, p2) {
console.log(this.length);
}
var arr = [1, 2, 3];
f1(); // 25
f1.call(this); // 25
f1.call(arr); // 3
f1.apply(arr); //3
call & apply改变了函数this的指向,第一个参数如果是一个对象,意为将函数内部的this指向该对象。
- 直接调用f1()的时候,函数内的this指向的是window,所以输出length的值为25
- 调用f1.call(this),这里参数this相当于window的this,那么就是将函数的this指向全局的this。所以输出25
- 调用f1.call(arr),这里将arr这个数组作为参数,那么就是将函数的this指向该数组。函数内部this.length就相当于arr.length,输出3
- apply的用法和call相近,第一个参数都是改变函数的this指向。不同之处是如果函数具有实参
4.1 call(arr,“param1”,“param2”); 实参用逗号分割
4.2 apply(arr,[“param1”,“param2”]); 实参放到数组中
注:第一个参数如果不是对象
- 如果是null、undefined那么函数的this指向window
- 如果是数字、字符串、布尔型等,函数内部this指向对应构造函数(Number、String、Boolean)的实例
f1.call(5); // undefined this=>new Number(5)
f1.call("123"); // undefined this=>new String("123")
f1.call(true); // undefined this=>new Boolean(true)
//上述代码可以用apply替换
bind
bind也是改变函数this指向,但是在es5中才有,注意兼容问题(ie9+)。
- 栗子1
function fn2() {
console.log(this.length);
}
var length = 10;
var fnBind = fn2.bind({ length: 100 });
fnBind(); // 100
调用函数的bind方法会产生了新的函数,新函数里面的逻辑和原函数一致,唯一不同是this指向为传入的对象。
注:调用bind方法不会执行函数,而是返回新的函数,所以通常简写为:
(function fn2() {
console.log(this.length);
}).bind({length: 100})();
- 栗子2
老问题新思路,开发中我们经常会遇到下面的问题
var obj = {
age: 18,
run: function () {
var _that = this;//传统的解决方案
setTimeout(function () {
console.log(this.age);//不会输出18,输出undefined,this=>window
console.log(_that.age);//18
}, 50);
}
}
因为在setTimeout中this指向了window,所以我们通常会在setTimeout之前var _that = this;
,然后通过_that变量获取到原始指向obj对象的this。
有了bind后:
var obj = {
age: 20,
run: function () {
setTimeout((function () {
console.log(this.age);
}).bind(this), 50);
}
}
obj.run();//20
这里传入的this是指向obj的,通过执行了bind方法,匿名函数本身没有执行,只是改变了函数内部的this指向,指向了obj对象。
- 栗子3
//示例1
var obj1 = {
name: "aaa",
drink: function () {
console.log(this.name);
}
}
obj1.drink.bind({ name: "bbb" })(); // bbb
//示例2
var obj2 = {
age: 10,
run: function () {
setInterval((function () {
console.log(this.age);
}).bind(this), 1000)
}
}
obj2.run(); // 10
如果对输出都没有疑问,说明已经掌握了bind的用法。
call、apply、bind总结
- 相同点:三者都可以改变函数内this指向
- 不同点:call&apply改变this指向的同时执行了函数。bind没 有执行函数,而是创建了一个新的函数,并改变了函数内的this指向。
this总结
本文通过介绍了函数的四种调用方式讲解了js中this指向的问题。并重点介绍了call、apply、bind方法如何改变this的指向。
this是js中最重要的知识点之一,也是学习很多其他js基础知识的前置。希望大家可以通过阅读本文掌握其用法。
如文中有不足、不严谨、不正确的地方,还请指出。