this的指向是调用时决定的,而不是创建时决定的。(和词法作用域做区分)
this永远指向 最后 调用它的对象!
this指向
全局上下文
在全局执行上下文中this都指代全局对象。
浏览器中this等价于window对象
函数上下文
在函数内部,this的值取决于函数被调用的方式。
直接调用
this指向全局变量。
function foo(){
return this;
}
console.log(foo() === window); // true
作为对象的一个方法
this指向调用函数的对象。
var person = {
name: "axuebin",
getName: function(){
return this.name;
}
}
console.log(person.getName()); // axuebin
这里有一个需要注意的地方
var name = "xb";
var person = {
name: "axuebin",
getName: function(){
return this.name;
}
}
var getName = person.getName;
console.log(getName()); // xb
发现this又指向全局变量了,这是为什么呢?
还是那句话,this的指向得看函数调用时。第9行在全局环境调用的。
作为一个构造函数
this被绑定到正在构造的新对象。
通过构造函数创建一个对象其实执行这样几个步骤:
- 创建新对象
- 将 this 指向这个对象
- 给对象赋值(属性、方法)
- 返回 this
所以this就是指向新创建的这个对象上。
function Person(name){
this.name = name;
this.age = 25;
this.say = function(){
console.log(this.name + ":" + this.age);
}
}
var person = new Person("axuebin");
console.log(person.name); // axuebin
person.say(); // axuebin:25
作为一个DOM事件处理函数
this指向触发事件的元素
var ele = document.getElementById("id");
ele.addEventListener("click",function(e){
console.log(this);
console.log(this === e.target); // true
})
测试(好好看一下)
// demo01
var a = 20;
function fn() {
var a = 10;
console.log(this.a);
}
fn();
// demo02
var a = 20;
function fn() {
var a = 10;
function foo() {
var a = 5;
console.log(this.a);
}
foo();
}
fn();
// demo03
var a = 20;
var obj = {
a: 10,
c: this.a + 20,
fn: function () {
return this.a;
}
}
console.log(obj.c);
console.log(obj.fn());
console.log(window.obj.fn());
答案:
demo1、demo2 都是20
demo3 第一个是40,第二个是10,第三个也是10
解析:
在一个函数上下文中,this 由调用者提供,由调用函数的方式来决定。
如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的 this 指向该对象。
如果函数独立调用,那么该函数内部的 this,则指向 undefined。在非严格模式中,当 this 指向 undefined 时,它会被自动指向全局对象。
重点是在哪里调用,关注(),还要关注最后调用的对象是哪个
demo1、demo2 的 fn() 是独立调用,调用的 this 指向全局对象。
demo3 的 obj.c 属性使用 this.a + 20 来计算。没有 function 字样单独的 {} 不会形成新的作用域,因此这里的 this.a,由于并没有作用域的限制,仍然处于全局作用域之中。
而 obj.fn() 中fn()是函数,调用对象是obj,所以 this 指向 obj;
而 window.obj.fn() 实质和 obj.fn() 相同,最后调用者为 obj。
再来看一个例子
var a = 20;
var foo = {
a: 10,
getA: function () {
return this.a;
}
}
console.log(foo.getA());
var test = foo.getA;
console.log(test());
foo.getA() 中,getA 是调用者,他不是独立调用,被对象 foo 所拥有,因此它的 this 指向了 foo。
而 test() 作为调用者,尽管他与 foo.getA 的引用相同,但是它是独立调用的,因此 this 指向 undefined,在非严格模式,自动转向全局 window。
稍微修改一下代码就可以理解了:
var a = 20;
function getA() {
return this.a;
}
var foo = {
a: 10,
getA: getA
}
console.log(foo.getA());
var test = foo.getA;
console.log(test());
同理:
var obj1 = {
name: 'obj1',
fn: function () {
console.log(this.name);
}
};
var obj2 = { name: 'obj2' };
var newFn = obj1.fn;
obj2.fn = newFn;
obj2.fn();
上面代码实质就是 obj2 调用自己对象的 fn 函数
最后来一个:
function foo() {
console.log(this.a)
}
function active(fn) {
fn(); // 真实调用者,为独立调用
}
var a = 20;
var obj = {
a: 10,
getA: foo
}
active(obj.getA);
改变 this 指向
改变 this 的指向我总结有以下几种方法:
- 使用
call、apply、bind - 使用 ES6 的箭头函数
- 在函数内部使用
_this = this - 构造函数 new 实例化一个对象
call apply bind
this指向绑定的对象上。
call: fn.call(target, 1, 2)apply: fn.apply(target, [1, 2])bind: fn.bind(target)(1,2)
var person = {
name: "axuebin",
age: 25
};
function say(job,word){
console.log(this.name+":"+this.age+":"+job+":"+word);
}
say.call(person,"FE","HH"); // axuebin:25:FE:HH
say.apply(person,["FE","HH"]); // axuebin:25:FE:HH
var bindFn = say.bind(person,"FE","HH")
bindFn()
call和apply从this的绑定角度上来说是一样的,唯一不同的是它们的第二个参数。
call是每个参数一个一个传入,apply传入参数数组。
bind是返回一个函数可稍后执行,而call、apply是立即调用,传参和call类似。
箭头函数
箭头函数不会创建自己的this ,它 只会从作用域链的上一层继承this
箭头函数常用在回调函数中,例如定时器
var name = "windowsName";
var a = {
name: "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout(function () {
this.func1()
}, 100);
},
func3: function () {
setTimeout(() => {
this.func1()
}, 100);
},
func4: function () {
that = this; // 用 that 保存 this
setTimeout(function () {
that.func1()
}, 100);
}
};
a.func2() // this.func1 is not a function
a.func3() // Cherry
a.func4() // Cherry
不使用箭头函数会报错,因为最后调用 setTimeout 的对象是 window, window 中并没有 func1 函数。
var a = 'global'
function foo() {
var a = 'foo'
setTimeout(() => {
console.log(this.a);
},1000)
}
var obj = {
a: 'obj'
}
foo()
foo.call(obj);
用箭头函数输出 global obj,不用的话是 global global
在函数内部使用 that = this
将调用这个函数的对象保存在变量 that 中,然后在函数中都使用这个 that
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
var that = this;
setTimeout(function() {
that.func1()
},100);
}
};
a.func2() // Cherry
在 func2 中,设置var that = this,这里的this指向调用 func2 的对象 a,为了防止在 func2 中的 setTimeout 被 window 调用而导致的在 setTimeout 中的this为 window。我们将this(指向变量a)赋值给一个变量that,这样,在 func2 中我们使用that就是指向对象 a 了。
this 总结
如果要判断一个函数的this绑定,就需要找到这个函数的直接调用位置。
然后可以顺序按照下面规则来判断this的绑定对象:
- 由
new调用:绑定到新创建的对象 - 由
call或apply、bind调用:绑定到指定的对象 - 由上下文对象调用:绑定到上下文对象
- 默认:全局对象
- 箭头函数不使用上面的绑定规则,根据外层作用域来决定
this,继承外层函数调用的this绑定。
手写 call apply bind
fn.call(target, 1, 2)
实现步骤:
- 判断调用对象是否为函数。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 处理传入的参数,截取第一个参数后的所有参数。
- 将函数作为上下文对象的一个属性。
- 使用上下文对象来调用这个方法,并保存返回结果。
- 删除刚才新增的属性。
- 返回结果。
// call函数实现
Function.prototype.myCall = function(context) {
// 判断调用对象
if (typeof this !== "function") {
console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
let result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的方法,context是对象,this是调用函数
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将方法删除
delete context.fn;
return result;
};
fn.apply(target, [1, 2])
和call基本一致,除了参数处理不同
// apply 函数实现
Function.prototype.myApply = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判断 context 是否存在,如果未传入则为 window
context = context || window;
// 将函数设为对象的方法
context.fn = this;
// 调用方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 将属性删除
delete context.fn;
return result;
};
fn.bind(target)(1,2)
实现步骤:
- 判断调用对象是否为函数。
- 保存当前函数的引用,获取其余传入参数值。
- 创建一个函数返回
- 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。
// bind 函数实现
Function.prototype.myBind = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
var args = [...arguments].slice(1)
var fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
};