this指向是JS开发中非常重要的语法点,本文参考多篇优质博文进行归纳整理,欢迎讨论!
目录
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对象的内部和外部有任何区别,示例:
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.foo这样表示时,是直接取出地址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命令时,它后面的构造函数依次执行下面步骤:
(1)创建一个空对象,作为将要返回的对象实例
(2)将这个空对象的原型,指向构造函数的prototype属性
(3)将这个空对象赋值给函数内部的this关键字
(4)开始执行构造函数内部的代码
根据上面的四个步骤,new会创建一个连接到构造函数的prototype的新对象,同时,this始终指向实例对象。
function Person(age, name) {
this.age = age;
this.name = name;
console.log(this); //this指向实例对象p
}
var p = new Person(17, 'Mark');
如果构造函数里返回的是一个对象(对象、数组、函数,不包含null),则this指向的就是这个返回对象,
如果返回的不是对象,this就还是指向实例对象。
五、箭头函数绑定
1. 箭头函数的this指向取决于外层作用域的this,外层作用域的this指向谁,箭头函数的this就指向谁。
也就是说,函数体内的this就是定义时所在的对象,而非调用时所在的对象。
2.一旦箭头函数的this绑定成功,也无法再次修改。
function fn () {
return () => {
console.log(this.name);
}
}
var obj1 = {
name:'Mark'
};
var obj2 = {
name: 'John'
};
//箭头函数的this绑定成功,不能再修改
var tmp = fn.call(obj1);
tmp(); //Mark
tmp.call(obj2); //Mark
//通过修改外层函数this指向,来间接修改箭头函数this指向
fn.call(obj1)(); //Mark
fn.call(obj2)(); //John
六、优先级
new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。
参考资料:
1. js 五种绑定彻底弄懂this,默认绑定、隐式绑定、显式绑定、new绑定、箭头函数绑定详解
3. JS this指向总结