对 this 指向的理解

基本上可以归为四类:
1⃣️ 全局 this 是 window
2⃣️ 函数 this 是调用者
3⃣️ 构造函数的 this 是 new 之后的新对象
4⃣️ call ,apply ,bind 的 this 是第一个参数

首先我们需要得出一个非常重要一定要牢记于心的结论,this 的指向,是在函数被调用的时候确定的。也就是执行上下文被创建时确定的。因此,一个函数中的 this 指向,可以是非常灵活的。

比如下面的例子中,同一个函数由于调用方式的不同,this 指向了不一样的对象。

var a = 10;
var obj = {
    a: 20
}

function fn () {
    console.log(this.a);
}

fn(); // 10
fn.call(obj); // 20

除此之外,在函数执行过程中,this 一旦被确定,就不可更改了。

var a = 10;
var obj = {
    a: 20
}
function fn () {
    this = obj; // 这句话试图修改this,运行后会报错
    console.log(this.a);
}
fn();
一、全局对象中的this

全局环境中的 this,指向它本身。

// 通过 this 绑定到全局对象
this.a2 = 20;

// 通过声明绑定到变量对象,但在全局环境中,变量对象就是它自身
var a1 = 10;

// 仅仅只有赋值操作,标识符会隐式绑定到全局对象
a3 = 30;

// 输出结果会全部符合预期
console.log(a1); // 10
console.log(a2); // ReferenceError: a2 is not defined
console.log(a3); // 30
二、函数中的this

在总结函数中 this 指向之前,我想我们有必要通过一些奇怪的例子,来感受一下函数中 this 的捉摸不定。

// demo01
var a = 20;
function fn() {
    console.log(this.a); // undefined
}
fn(); 
// demo02
var a = 20;
function fn() {
    function foo() {
        console.log(this.a); // undefined
    }
    foo();
}
fn();
// demo03
var a = 20;
var obj = {
    a: 10,
    c: this.a + 20,
    fn: function () {
        return this.a;
    }
}
console.log(obj.c); // NaN
console.log(obj.fn()); // 10

结论

在一个函数上下文中,this 由调用者提供,由调用函数的方式来决定。如果调用者函数 被某一个对象所拥有,那么该函数在调用时,内部的 this 指向该对象。如果函数独立调用,那么该函数内部的 this,则指向 undefined。 但是在非严格模式中,当 this 指向 undefined 时,它会被自动指向全局对象。

从结论中我们可以看出,想要准确确定 this 指向,找到函数的调用者以及区分他是否是独立调用 就变得十分关键。

// 为了能够准确判断,我们在函数内部使用严格模式,因为非严格模式会自动指向全局
function fn() {
    'use strict';
    console.log(this);
}

fn();  // fn是调用者,独立调用
window.fn();  // fn是调用者,被window所拥有

在上面的简单例子中,fn() 作为独立调用者,按照定义的理解,它内部的 this 指向就为undefined。而 window.fn() 则因为 fn 被 window 所拥有,内部的 this 就指向了window对象

三、使用call,apply显示指定this

JavaScript 内部提供了一种机制,让我们可以自行手动设置 this 的指向。它们就是 callapply。所有的函数都具有的两个方法。它们除了参数略有不同,其功能完全一样,第一个参数都为 this 将要指向的对象。

如下例子所示。fn 并非属于对象 obj 的方法,但是通过 call,我们将 fn 内部的 this 绑定为 obj,因此就可以使用 this.a 访问 obja 属性了。这就是 call/apply 的用法。

function fn() {
    console.log(this.a);
}
var obj = {
    a: 20
}

fn.call(obj); // 20

而 call 与 applay 后面的参数,都是向将要执行的函数传递参数。其中 call 以一个一个的形式传递,apply 以数组的形式传递。 这是他们唯一的不同。

🧚‍♀️ call/apply 的使用场景:
  • 将类数组对象转换为数组

    function exam(a, b, c, d, e) {
    	// 先看看函数的自带属性 arguments 什么是样子的
    	console.log(arguments); // { '0': 2, '1': 8, '2': 9, '3': 10, '4': 3 }
    	
    	// 使用call/apply将arguments转换为数组, 返回结果为数组,arguments自身不会改变
    	var arg = [].slice.call(arguments);
    	console.log(arg); // [ 2, 8, 9, 10, 3 ]
    }
    exam(2, 8, 9, 10, 3);
    
    // 也常常使用该方法将 DOM 中的 nodelist 转换为数组
    // [].slice.call( document.getElementsByTagName('li') );
    
  • 根据自己的需要灵活修改 this 指向

    var foo = {
    	name: 'joker',
    	showName: function() {
      		console.log(this.name);
    	}
    }
    var bar = {
    	name: 'rose'
    }
    foo.showName.call(bar); // rose
    
  • 实现继承

    // 定义父级的构造函数
    var Person = function(name, age) {
    	this.name = name;
    	this.age  = age;
    	this.gender = ['man', 'woman'];
    }
    
    // 定义子类的构造函数
    var Student = function(name, age, high) {
    	// use call
    	Person.call(this, name, age);
    	this.high = high;
    }
    Student.prototype.message = function() {
    	console.log('name:'+this.name+', age:'+this.age+', high:'+this.high+', gender:'+this.gender[0]+';');
    }
    new Student('xiaom', 12, '150cm').message(); // name:xiaom, age:12, high:150cm, gender:man;
    
  • 在向其他执行上下文的传递中,确保 this 的指向保持不变

    如下面的例子中,我们期待的是 getAobj 调用时,this 指向 obj,但是由于匿名函数的存在导致了 this 指向的丢失,在这个匿名函数中 this 指向了全局,因此我们需要想一些办法找回正确的 this 指向。

    var obj = {
    	a: 20,
    	getA: function() {
        	setTimeout(function() {
            	console.log(this.a)
        	}, 1000)
    	}
    }
    obj.getA();
    

    常规的解决办法很简单,就是使用一个变量,将 this 的引用保存起来

    var obj = {
    	a: 20,
    	getA: function() {
        	var self = this;
        	setTimeout(function() {
            	console.log(self.a)
        	}, 1000)
    	}
    }
    obj.getA();
    

    另外就是 借助闭包与 apply 方法,封装一个 bind 方法。

    function bind(fn, obj) {
    	return function() {
        	return fn.apply(obj, arguments);
    	}
    }
    var obj = {
    	a: 20,
    	getA: function() {
        	setTimeout(bind(function() {
            	console.log(this.a)
        	}, this), 1000)
    	}
    }
    obj.getA(); // 20
    

    当然,也可以使用 ES5 中已经自带的 bind方法

    var obj = {
    	a: 20,
    	getA: function() {
        	setTimeout(function() {
            	console.log(this.a)
        	}.bind(this), 1000)
    	}
    }
    obj.getA(); // 20
    
四、构造函数与原型方法上的this

封装对象 的时候,我们几乎都会用到 this。

function Person(name, age) {
    // 这里的this指向了谁?
    this.name = name;
    this.age = age;   
}

Person.prototype.getName = function() {

    // 这里的this又指向了谁?
    return this.name;
}

// 上面的 2 个 this,是同一个吗,他们是否指向了原型对象?

var p1 = new Person('Nick', 20);
p1.getName();

我们已经知道,this 是在函数调用过程中确定,因此,搞明白 new 的过程中到底发生了什么就变得十分重要。

🌟 通过new操作符调用构造函数,会经历以下4个阶段

  • 创建一个新的对象;
  • 将构造函数的 this 指向这个新对象;
  • 指向构造函数的代码,为这个对象添加属性,方法等;
  • 返回新对象。

因此,当 new 操作符调用构造函数时,this 其实指向的是这个新创建的对象,最后又将新的对象返回出来,被实例对象 p1 接收。这个时候,构造函数的 this,指向了新的实例对象 p1

而原型方法上的 this 就好理解多了,根据上边对函数中 this 的定义,p1.getName() 中的getName 为调用者,它被 p1 所拥有,因此 getName 中的 this,也是指向了p1



🔗 推荐链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值