this指向问题总结
this就是函数运行时所在的对象(环境)。这本来并不会让用户糊涂,但是 JavaScript 支持运行环境动态切换,也就是说,this的指向是动态的,没有办法事先确定到底指向哪个对象,这才是最让初学者感到困惑的地方。
由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。
普通函数
普通函数中this的指向取决于函数的调用模式
1.普通函数调用中,严格模式下this指向undefind,非严格模式下this指向window
var a = 1;
function fn() {
console.log(this); //window
console.log(this.a); //1
}
fn();
非严格模式下方法调用会发生自动装箱。若初始值是 undefined,this 值会被设为全局对象。
function Animal() {}
Animal.prototype.speak = function() {
console.log('this-原型方法', this);
return this;
}
Animal.eat = function() {
console.log('this-静态方法', this);
return this;
}
let obj = new Animal();
let speak = obj.speak;
speak(); // Window
let eat = Animal.eat;
eat(); // Window
class 体内部的代码总是在严格模式下执行
class Animal {
speak() {
console.log('this-原型方法', this)
return this;
}
static eat() {
console.log('this-静态方法', this)
return this;
}
}
let obj = new Animal();
obj.speak(); //Animal {}
let speak = obj.speak;
speak(); //undefined
Animal.eat(); //构造函数本身
let eat = Animal.eat;
eat(); //undefined
2 对象方法调用中,this指向对象
const obj = {
name: "bwf",
age: 18,
eat: function() {
console.log(this); //{name: "bwf", age: 18, eat: ƒ}
console.log(this.name + "正在吃饭"); //bwf正在吃饭
}
}
obj.eat();
function foo() {
console.log(this.a);
}
var obj2 = {
a: 2,
fn: foo
};
var obj1 = {
a: 1,
o1: obj2
};
obj1.o1.fn(); // 2 fn最终是在obj2的环境中被调用的,所以结果是2
var o = {
a: 10,
b: {
// a:12,
fn: function() {
console.log(this.a); //undefined fn是在b的环境中被调用的
}
}
}
o.b.fn();
- 如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,
- 如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象
- 如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象
var o = {
a: 10,
b: {
a: 12,
fn: function() {
console.log(this.a); //undefined
console.log(this); //window
}
}
}
var j = o.b.fn;
j();
上面例子中,虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window
3构造函数中,this指向实例对象
function Person(name, age) {
this.name = "bwf";
this.age = 20;
this.sayHi = function() {
return this.name;
}
}
var p = new Person();
console.log(p.name); //bwf
console.log(p.sayHi()); //bwf
console.log(p instanceof Person); //true
补充:
- 构造函数中默认返回this
- 构造函数中如果返回基本数据类型:number,string,undefined ,boolean则this还是指向构造函数的实例,null也是哦;如果返回是对象,数组,函数引用数据类型时,那么this就指向返回的对象。自己可以试下😯
new关键字做了什么?
4上下文调用,call apply改变this指向
- call的含义: 是函数的方法 每一个函数都有这个call方法
- call的作用: 1.call会调用这个函数 2.call 可以改变函数中this的指向
- call的参数: 第一个参数: 传什么,函数中的this就指向什么;其他参数: 和函数的形参,一一对应;
function Person() {
console.log('person-this', this)
this.name = "bwf";
this.age = 18;
}
function Student() {
console.log('student-this', this);
//不调用call Person构造函数中的this指向p实例对象;调用call后Person构造函数中的this指向stu实例对象
Person.call(this);
}
const p = new Person();
const stu = new Student();
console.log('stu', stu)
function foo() {
console.log(this);
}
function bar() {
console.log(this);
}
foo.call({
name: '小米'
}); // {name: "小米"}
const bar1 = bar.bind({
num: 123
})
bar1() // {num: 123}
补充:call,apply , bind的比较
- 都会改变函数执行时的上下文
- apply 、call 是立即调用;bind返回对应的函数,调用后才会执行
- call的第二个参数是参数列表的形式;apply的第二个参数是数组
- call,apply,bind的应用场景见下方:
//求数组中的最大值
const arr = [1, 9, 3, 4]
const res1 = Math.max(...arr)
const res2 = Math.max.apply(null, arr)
const res3 = Math.max.call(null, ...arr)
//判断变量类型
const obj = {
name: 'bwf'
}
console.log(typeof obj) //object
console.log(typeof arr) //object
console.log(typeof null) //object
console.log(Object.prototype.toString.call(obj)) //[object Object]
console.log(Object.prototype.toString.call(arr)) //[object Array]
console.log(Object.prototype.toString.call(null)) //[object Null]
//继承
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student(name, age) {
Person.call(this, name, age)
}
const s = new Student('bwf', 18)
手写bind函数:
Function.prototype.mybind = function(ctx, ...args) {
// fix
const fn = this;
return function() {
fn.apply(ctx, args);
};
};
function foo(x, y) {
console.log(this, x, y);
}
const foo1 = foo.mybind({
name: "zhangsan"
}, 10, 20);
foo1();
5 事件绑定中的this
事件绑定共有三种方式:行内绑定、动态绑定、事件监听;
<input type="button" value="按钮1" onclick="clickFun()">
function clickFun() {
console.log('input-this', this) // 此函数的运行环境在全局window对象下,因此this指向window;
}
<input type="button" value="按钮2" onclick="console.log('行内this',this)">
上面this指向input dom元素
<input type="button" value="按钮3" id="btn">
var btn = document.getElementById('btn');
btn.onclick = function() {
console.log('动态事件绑定this', this) //this指向input dom元素
}
6 window定时器中的this
var obj = {
fun: function() {
console.log('定时器中的this',this);
}
}
setInterval(obj.fun, 1000); // this指向window对象
setInterval('obj.fun()', 1000); // this指向obj对象
在上面的代码中,setInterval(obj.fun,1000) 的第一个参数是obj对象的fun ,因为 JS 中函数可以被当做值来做引用传递,实际就是将这个函数的地址当做参数传递给了 setInterval 方法,换句话说就是 setInterval 的第一参数接受了一个函数,那么此时1000毫秒后,函数的运行就已经是在window对象下了,也就是函数的调用者已经变成了window对象,所以其中的this则指向的全局window对象;obj对象中的fun并没有调用
而在 setInterval('obj.fun()',1000) 中的第一个参数,实际则是传入的一段可执行的 JS 代码;1000毫秒后当 JS 引擎来执行这段代码时,则是通过 obj 对象来找到 fun 函数并调用执行,那么函数的运行环境依然在 对象 obj 内,所以函数内部的this也就指向了 obj 对象;
那如何让定时器中对象方法中的this始终指向对象呢?
setTimeout(() => {
obj.fun()
}, 1000)
上面代码中,obj.fun放在一个匿名函数之中,这使得obj.fun在obj的作用域执行,而不是在全局作用域内执行,所以能够显示正确的值。
另一种解决方法是,使用bind方法,将obj.fun这个方法绑定在obj上面。
setTimeout(obj.fun.bind(obj), 1000)
箭头函数
箭头函数中没有this
var obj = {
name: "bwf",
foo() {
setTimeout(() => {
console.log(this);
}, 1000);
},
};
obj.foo() // obj
总结:
- 箭头函数中没有this,如果用到了this,就按照变量的查找规则沿着作用域链向上查找
- 普通函数中this的指向,需要通过确定调用模式来找
- 而箭头函数中的this指向,只需要按照变量的查找规则向上查找就行