彻底理解JavaScript中this指向

JavaScript中this的指向

1. 普通函数调用

严格模式下,this会绑定到 undefined ,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象 window。

  • 非严格模式

    非严格模式中,普通函数中的this绑定到全局对象,类似于:

    fn.call(window);
    fn.apply(window);
    
    // 非严格模式
    var name = 'name1';
    var fn = function(){
    	console.log(this === window); // true
        console.log(this.name); // 'name1'
    }
    fn();
    

    你可能会误以为fn()是window调用的,所以this指向window。
    事实上,并非如此。
    上面代码中这是因为在ES5中 用var声明的,全局变量是挂载在顶层对象(浏览器是window)中,
    所以fn() 相当于是window.fn()name相当于window.name

    使用ES6的let关键字来声明:

    // 非严格模式
    let name = 'name2';
    let fn = function(){
        console.log(this === window); // true
        console.log(this.name); // undefined      window.name = undefined
    }
    fn()
    

    这个例子中 let没有给顶层对象中(浏览器是window)添加属性,window.namewindow.fn都是undefined

.

  • 严格模式

    严格模式中,普通函数中的 this 则表现为 undefined,类似于:

    fn.call(undefined);
    fn.apply(undefined);
    
    // 严格模式
    'use strict'
    var name = 'window';
    var fn = function(){
        console.log(this); // undefined
        console.log(this.name); // 报错   因为this是undefined
    }
    fn();
    

2. 对象中的函数(方法)调用模式

this指向最后调用它的对象,也就是看它执行的时候是谁调用的

var name = 'window';
var fn = function(){
    console.log(this.name);
}
var student = {
    name: 'xixi',
    fn: fn,
    other: {
        name: 'other',
        fn: fn,
    }
}

student.fn(); // 'xixi' => fn方法被student调用,this指向student
student.other.fn(); // 'other' => fn方法被other调用,this指向other
// 用call类比则为:
student.fn.call(student); // 'xixi' => 通过call改变this指向,指向到student
// 用call类比则为:
student.other.fn.call(student.other); // 'other' => 通过call改变this指向,指向到student中的other

但往往会有以下场景,把对象中的函数赋值成一个变量了。
这样其实又变成普通函数了,所以使用普通函数的规则(默认绑定)。

var studentFn = student.fn;
studentFn(); // 'window'
// 用call类比则为:
studentFn.call(undefined); // 'window'

3. call、apply、bind 调用模式

JavaScript中call,apply,bind方法的总结

call()、apply()、bind() 都是用来重定义 this 这个对象的(改变this指针)

  • 三者都可以改变函数的 this 对象指向

  • 三者第一个参数都是 this 要指向的对象,如果如果没有这个参数或参数为 undefined 或 null,则默认指向全局 window

  • 三者都可以传参,apply参数是数组,call、bind参数是arguments对象,且 apply 和 call 是一次性传入参数,而 bind 可以分为多次传入。

  • bind 是返回绑定 this 之后的函数,便于稍后调用;apply 、call 则是立即执行 。

  • bind()会返回一个新的函数,如果这个返回的新的函数作为构造函数创建一个新的对象,那么此时 this 不再指向传入给 bind 的第一个参数,而是指向用 new 创建的实例

fun.call(thisArg, arg1, arg2, ...)

fun.apply(thisArg, [arg1, arg2, ...])

fun.bind(thisArg, arg1, arg2, ...)()

在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。

根据参数thisArg的描述,可以知道,call就是改变函数中的this指向为thisArg,并且执行这个函数,这也就使JS灵活很多。
严格模式下,thisArg是原始值是值类型,也就是原始值。不会被包装成对象。举个例子:

var doSth = function(name){
    console.log(this);
    console.log(name);
}
doSth.call(2, 'xixi'); // Number{2}, 'xixi'   this指向被包装成对象的2


var doSth2 = function(name){
    'use strict';
    console.log(this);
    console.log(name);
}
doSth2.call(2, 'xixi'); // 2, 'xixi'   this指向原始值2

4. 构造函数new调用模式

通过构建函数new关键字生成一个实例对象,此时this指向这个实例对象

function Student(name){
    this.name = name;
    console.log(this); // Student {name: 'xixi'}   this指向这个实例对象
}

var result = new Student('xixi'); // new关键字改变了this的指向,指向这个实例对象
console.log(result); // Student {name: 'xixi'}
console.log(result.name); // 'xixi'

注意:

  1. new过程遇到return一个对象,此时this指向为返回的对象

    function Student(name){
        this.name = name;
        // return function fn(){}; // 如果返回函数fn,则result是fn(){}
        return {}; // 如果返回对象{},则result是对象{}
    }
    
    var result = new Student('xixi');
    console.log(result); // {}   返回对象是{},则result就是{}
    console.log(result.name); // undefined
    
  2. 注意的是null虽然也是对象,但是此时new仍然指向实例对象

    function Student(name){
        this.name = name;
        return null; 
    }
    
    var result = new Student('xixi');
    console.log(result); // Student {name: 'xixi'}    此时new仍然指向实例对象
    console.log(result.name); // xixi
    

5. 原型链中的调用模式

function Student(name){
    this.name = name;
}
var s1 = new Student('xixi');
Student.prototype.fn = function(){
    console.log(this.name);
}
s1.fn(); // 'xixi'       fn方法被s1调用,此时fn中的this指向s1

会发现这个似曾相识。这就是对象上的方法调用模式。自然是指向生成的新对象。 如果该对象继承自其它对象。同样会通过原型链查找。 上面代码使用 ES6中class写法则是:

class Student{
    constructor(name){
        this.name = name;
    }
    fn(){
        console.log(this.name);
    }
}
let s1 = new Student('xixi');
s1.fn();

ES6的class也是通过构造函数模拟实现的,是一种语法糖。


6. ⚡️箭头函数调用模式

箭头函数不绑定自己的 this,它捕获其所在上下文的 this 值,作为自己的 this 值。无论箭头函数在哪里被调用,或者它如何被调用,它的 this 都由创建时决定

箭头函数的特点:

  1. 没有自己的this、super、arguments和new.target绑定。
  2. this由创建时决定,指向定义时的上下文,通常是全局对象(在浏览器中是 window)
  3. 不能使用new来调用。
  4. 没有原型对象。
  5. 不可以改变this的绑定。
  6. 形参名称不能重复。

箭头函数中没有this绑定,必须通过查找作用域链来决定其值。
如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this,否则this的值则被设置为全局对象。 比如:

var name = 'window';
var student = {
    name: 'xixi',
    fn: function(){
        var arrowFn = () => { // 1.箭头函数被非箭头函数包含
            console.log(this.name); // 则this绑定的是最近一层非箭头函数的this
        }
        arrowFn();
    },
    arrowFn2: () => {// 2.箭头函数没有被非箭头函数包含
        console.log(this.name); // 因为箭头函数的 this 指向定义时的上下文,通常是全局对象(在浏览器中是 window)  
    }  
};  
  
student.fn(); // 'xixi'   this指向student 
student.arrowFn2(); // 'window'   this指向window

其实就是相当于箭头函数外的this是缓存的该箭头函数上层的普通函数的this。
如果没有普通函数,则是全局对象(浏览器中则是window)。

也就是说无法通过call、apply、bind绑定箭头函数的this(它自身没有this)。
而call、apply、bind可以绑定缓存箭头函数上层的普通函数的this。比如:

var student = {
    name: 'xixi',
    fn: function(){
        console.log(this.name);
        return () => {
            console.log('arrowFn:', this.name);
        }
    }
}
var person = {
    name: 'person'
}
student.fn().call(person); // 'xixi'  'arrowFn:' 'xixi'
student.fn.call(person)(); // 'person' 'arrowFn:' 'person'

优先级

new 调用 > call、apply、bind 调用 > 对象上的函数调用 > 普通函数调用


总结

如果要判断一个运行中函数的 this 绑定, 就需要找到这个函数的直接调用位置。 找到之后
就可以顺序应用下面这四条规则来判断 this 的绑定对象:

  1. new 调用:绑定到新创建的对象,注意:显示return函数或对象,返回值不是新创建的对象,而是显式返回的函数或对象。
  2. call、apply、bind调用:严格模式下,绑定到指定的第一个参数。非严格模式下,null和undefined,指向全局对象(浏览器中是window),其余值指向被new Object()包装的对象。
  3. 对象上的函数调用:绑定到那个对象。
  4. 普通函数调用: 在严格模式下绑定到 undefined,否则绑定到全局对象。

ES6 中的箭头函数:不会使用上文的四条标准的绑定规则, 而是根据当前的词法作用域来决定this, 具体来说, 箭头函数会继承外层函数,调用的 this 绑定( 无论 this 绑定到什么),没有外层函数,则是绑定到全局对象(浏览器中是window)。 这其实和 ES6 之前代码中的 self = this 机制一样。

DOM事件函数:一般指向绑定事件的DOM元素,但有些情况绑定到全局对象(比如IE6~IE8的attachEvent)。



1. 函数调用

this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的

function a() {    
	var name = 'Tom'
	console.log(this.name); //undefined    (window下没有name这个属性)
	console.log(this);      //window 
}    
a();     // 相当于window.a(),也就是window调用了a()方法,所以a方法中的this指向了window 



var b  = {
	say:{
		name: 'Tom',
		fn:function(){
			console.log(this.name);  //Tom  (say下有name这个属性)
		}
	}
};    
b.say.fn();  // say调用了fn方法,所以fn方法中的this指向了say



var c  = {
	name: 'Tom',
	say:{
		fn:function(){
			console.log(this.name);  //undefined  (say下没有name这个属性)
		}
	}
};    
c.say.fn();	// say调用了fn方法,fn方法中的this指向了say,但say中没有name属性



var name = 'Jon'
var d  = {
	name: 'Tom',
	say:{
		fn:function(){
			console.log(this.name);  // Jon  (this指向window)
		}
	}
};    
var j = d.say.fn; //fn虽然被say引用,但是没有被执行调用,所以this不是指向的say
j() // 而是指向调用的j()方法的window



var name = "The Window";  
var object = {    
    name: "The Object",
    getNameFunc: function () {      
        return function () {        
            return this.name;      
        };    
    }  
};  
alert(object.getNameFunc()());  //The Window
//匿名函数的自我执行,没有被上级对象调用,所以this指向window 



var name = "The Window";  
var object = {    
    name: "The Object",
    getNameFunc: function () {      
        var that = this;      
        return function () {        
            return that.name;      
        };    
    }  
};
alert(object.getNameFunc()());   //The Object
//在getNameFunc方法中将this绑定到that对象上,而that属于getNameFunc函数的内部变量,而这个函数的执行域是object。
//所以结果是"The Object"  



var obj = {
   say: function () {
     setTimeout(function () {
       console.log(this == window)
     });
   }
 }
obj.say(); //true

.

2. 箭头函数

箭头函数的this指向是根据当前的词法作用域来决定。
具体来说, 箭头函数会继承外层函数,调用的 this 绑定( 无论 this 绑定到什么),没有外层函数,则是绑定到全局对象(浏览器中是window)。

var obj = {
   say: function () {
     setTimeout(() => {
       console.log(this)
     });
   }
 }
 obj.say(); // this指向obj
 // 箭头函数没有自己的this, 它的this是继承而来; 
 // 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window

构造函数中的this

function Fn(){
    this.user = "bing";
}
var a = new Fn();    //new关键字可以改变this的指向,将这个this指向实例化对象a
console.log(a.user); //bing

为什么this会指向a?首先new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。


如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。

function fn() {
    this.user = 'bing';
	return {user:'xia'};//返回的是对象  
}
var a = new fn;
console.log(a.user);    //xia


function fn() {
    this.user = 'bing';
	return function(){};//返回的是对象  
}
var a = new fn;
console.log(a.user);    //undefined


function fn() {
    this.user = 'bing';
	return false;      //返回的是布尔值 
}
var a = new fn;
console.log(a.user);   //bing


function fn() {
    this.user = 'bing';
	return 1;          //返回的是数字  
}
var a = new fn;
console.log(a.user);   //bing


function fn() {
    this.user = 'bing';
	return 'xia';     //返回的是字符串
}
var a = new fn;
console.log(a.user);  //bing


function fn() {
    this.user = 'bing';
	return undefined; //返回的是undefined 
}
var a = new fn;
console.log(a.user);  //bing


function fn() {
    this.user = 'bing';
	return null;      //返回的是null
}
var a = new fn;
console.log(a.user);  //bing

改变this指向

我们在js中可以使用document.write()方法向网页中输入文本内容。如

document.write("test"); 网页中就会被加入’test’文本。

但是如果我们像下面这么写呢?

var myWrite = document.write;
myWrite("test");  //报错

上面的代码首先是我将document.write方法赋值给myWrite方法。然后使用myWrite()方法向网页中输入文本内容。但是这里会报错,为什么呢?

原因就在这个write方法的this指针的域被改变啦!

document.write()方法的this是指向document的,所以可以向网页中输入文本内容。

但是我们将document.write方法赋值给myWrite对象,然后在调用myWrite()方法。调用myWrite()方法的对象域是全局变量window,相当于window.myWrite()。此时this指向window,而不指向document.所以会报错。

遇到上面的this指向被改变了,该怎么办呢?

这时候就要设置this的指针指向了,JavaScript有三个方法bind()、call()、apply()。

1.使用bind方法

a.bind(b); 就是将a()方法的this指针的作用域绑定到b对象的作用域上,也就是现在a的this指针的作用域就是b

如是上面的代码就可以改成:

var myWrite = document.write;  
myWrite.bind(document)("test"); //此时的this指针指向了bind()的document,网页中正常输出test。

2.使用call方法

call(a, b);: a是当前方法需要指向的域,后面的b是当前方法的参数,可以传多个参数,多个参数直接用逗号隔开即可,如 :call(a, b, c, d);, b, c, d都是方法的参数(arguments)。

var myWrite = document.write;  
myWrite.call(document, "test"); //此时的this指针指向了bind()的document,网页中正常输出test。   

3.使用apply方法

apply(a, b); :a是当前方法需要指向的域,后面的b是当前方法的参数,可以传多个参数,多个参数需要使用数组来传入。如apply(a, [b, c, d]);,b、c、d为参数(数组)。

var myWrite = document.write;  
myWrite.apply(document, "test"); //此时的this指针指向了bind()的document,网页中正常输出test。 

具体请看: JavaScript中call,apply,bind方法的总结

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫老板的豆

你的鼓励将是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值