目录
this指向虽然概念抽象,变化多端,但总结起来只有两句话:
1)this指向在函数定义的时候是确定不了的,只有函数执行或调用的时候才能确定。
2)哪个对象调用函数,函数中的this就指向哪个对象。
一、默认绑定
当函数调用时没有明显前缀,也就是单纯作为独立函数调用的时候,将对函数中的this采用默认绑定的方式,指向全局对象window。但严格模式中,函数环境下的this指向undefined,全局环境下的this指向window。
1.1 调用全局函数
function fn() {
console.log(this === window); //fn()是全局函数,this指向window
}
fn(); //true
补充1——严格模式:
function fn() {
'use strict';
console.log(this === window); //fn()是全局函数,严格模式下,this指向undefined
}
fn(); //false
补充2——ES6:
顶层对象在浏览器环境指的是window对象,在Node指的是global对象。ES5之前,顶层对象的属性和全局变量是等价的,而ES6开始规定:var命令和function命令声明的全局变量,依旧是顶层对象的属性,但let命令、const命令和class命令声明的全局变量,不属于顶层对象的属性,示例:
let name = 'Mark'; //let声明的变量不属于顶层对象window
function fn() {
console.log(this.name); //this指向window
}
fn(); //undefined
1.2 调用函数内的嵌套函数
function fn() {
function inner() {
console.log(this === window); //inner是函数内部的函数,this指向window
}
inner();
}
fn(); //true
示例:(注意与作用域的区别)
var a = 1;
console.log(this.a); //1
function fn() {
var a = 2;
console.log(this.a); //1
function inner() {
var a = 3;
console.log(this.a); //1
}
inner();
}
fn(); //1
1.3 调用对象的方法内的嵌套函数
var obj = {
fn:function() { //fn函数用到了隐式绑定
function inner(){ //inner函数没有明确的调用对象
console.log(this === window); //inner是对象的方法内的嵌套函数,this指向window
}
inner();
}
}
obj.fn(); //true
示例:(注意与调用函数方法区别)
var a = 1;
console.log(this.a); //1 this指向全局变量window
var obj = {
a: 2,
fn: function () {
var a = 3;
console.log(this.a); //2 因为是obj.fn(),调用了fn函数,因为this指向了obj,输出了obj下的a=2
function inner() {
var a = 4;
console.log(this.a); //1 未明确调用对象,this指向window
}
inner(); //没有明确调用的对象
console.log(this.a); //2 this指向obj
}
}
obj.fn();
二、隐式绑定
当函数调用时,前面有明确的调用对象,这时this就会隐式绑定到这个对象上。
var obj = {
name:'Mark',
fn: function () { //fn()的this隐式绑定到obj
console.log(this === obj); //true
console.log(this.name); //Mark
}
}
obj.fn();
fn函数并不会因为它定义在obj对象的内部和外部有任何区别,示例:
【注意】fn和obj.fn两者的区别,前者单独拿出来依然是隐式绑定,后者则隐式丢失。
function fn() {
console.log(this === obj); //true
console.log(this.name); //Mark
}
var obj = {
name:'Mark',
f:fn
}
obj.f();
如果函数调用前有多个对象,this会指向离自己最近的对象,示例:
var obj = {
name:'Mark',
f1: {
name:'John',
f2: function () {
console.log(this.name);
}
}
}
obj.f1.f2(); //John
隐式绑定丢失问题
在特定情况下会存在隐式绑定丢失问题,最常见的就是作为参数传递以及变量赋值。
(1)参数传递
var a = 1;
var obj = {
a:2,
fn:function () {
console.log(this.a);
}
}
function f2(f) {
f();
}
f2(obj.fn); //参数传递导致隐式丢失,输出1
由于JS的内存机制,函数的引用类型,obj和obj.fn储存在两个内存地址,分别为地址1和地址2。obj.fn()直接调用时,是从地址1调用地址2,因此地址2的运行环境是地址1,this指向obj,但是,obj.fn这样表示时,是直接取出地址2再进行调用,如上代码,运行环境是f2所在的全局环境,this此时应指向window。
(2)变量赋值
var a = 1;
var obj = {
a:2,
fn:function () {
console.log(this.a);
}
}
var fglobal = obj.fn;
fglobal(); //变量赋值导致隐式丢失,输出1
(3)重新建立新的隐式绑定
隐式绑定丢失并不是都会指向全局对象,还可以重新建立新的隐式绑定,示例:
var a = 1;
var obj = {
a:2,
fn:function () {
console.log(this.a);
}
}
var obj1 = {
a:3
}
obj1.fn = obj.fn;
obj1.fn(); //输出3
三、显式绑定
通过call、apply和bind方法改变this指向,称为显式绑定,其中当参数为null、undefined时,this将指向全局对象。
function fn() {
console.log( this.name );
}
var obj1 = {
name:'Mark'
}
var obj2 = {
name:'John'
}
fn.call(obj1); //Mark
fn.apply(obj2); //John
var tmp = fn.bind(obj1);
tmp(); //Mark
JS中部分方法也内置了显式绑定,示例:
var obj = {
name:'Mark'
};
[1,2,3].forEach( function (){
console.log(this.name); //Mark*3
}, obj);
call、apply和bind的区别:
function fn() {
console.log( this.name );
}
var obj1 = {
name:'Mark'
}
var obj2 = {
name:'John'
}
//new_fn是返回的新函数,bind绑定后,this指向不能再改
var new_fn = fn.bind(obj1);
new_fn(); //Mark
new_fn.call(obj2); //Mark
//fn函数不是返回的新函数,bind绑定后,this指向仍可改
fn.bind(obj1)(); //Mark
fn.call(obj2); //John
1. call、apply是立即执行函数fn,而bind是返回一个绑定了this的新函数,需要调用才能执行;
2. bind属于硬绑定,返回的新函数的this指向不能再通过call、apply或bind修改;如果多次绑定,也以第一次为准;而call、apply的绑定只适用当前调用,下次要改变this指向还要再次绑定;
3. call和apply的区别只在于参数的形式,call的参数是以散列形式,apply的参数是一个数组,因此call的性能高于apply,不需要解析数组。
另外,当需要改变this指向时,除了以上三个方法,还可以存储this指向到变量中。
function fn () {
var _this = this; //将this储存在变量中,而且不会改变定时器的指向
setTimeout( function () {
console.log(_this); //obj
console.log(this); //window,定时器指向没有改变
}, 1000)
}
var obj = {
f:fn
};
obj.f();
四、new绑定
new会创建一个连接到构造函数的prototype的新对象,同时,this始终指向实例对象。
function Person(age, name) {
this.age = age;
this.name = name;
console.log(this); //this指向实例对象p Person {age: 17, name: 'Mark'}
}
var p = new Person(17, 'Mark');
如果构造函数里返回的是一个对象(对象、数组、函数,不包含null),则this指向的就是这个返回对象,如果返回的不是对象,this就还是指向实例对象。
五、箭头函数绑定
实际上,箭头函数根本没有自己的this,因此它内部的this指向的是外层this,这个指向在定义时就已经确定。
1. 箭头函数的this指向取决于外层作用域的this,外层作用域的this指向谁,箭头函数的this就指向谁。
2.我们可以通过改变外层作用域中的this指向,来改变箭头函数中this的指向。
普通函数
function foo() {
console.log(this.id);
console.log(this);
setTimeout(function(){
console.log(this.id);
console.log(this)
},0)
}
var id = 2;
foo();
// 2 window 2 window
foo.call({id:5});
// 5 {id:5} 2 window
setTimeout回调函数的代码都是在全局作用域下执行的,因此setTimeout回调函数中的this值在非严格模式下指向window,严格模式下指向undefined。
箭头函数:
function foo() {
console.log(this.id);
console.log(this);
setTimeout(() => {
console.log(this.id);
console.log(this)
},0)
}
var id = 2;
foo();
// 2 window 2 window
foo.call({id:5});
// 5 {id:5} 5 {id:5}
由于箭头函数没有自己的this,因此箭头函数的内部this指向的是外层作用域的this(foo函数)。我们可以通过改变外层作用域的this指向(foo.call方法)从而改变箭头函数的this指向。
六、优先级
new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。
参考资料:
1. js 五种绑定彻底弄懂this,默认绑定、隐式绑定、显式绑定、new绑定、箭头函数绑定详解
3. JS this指向总结