JavaScript中的this指向问题

一、前言

深入学习的过程是 快乐 的!!!

面试问到了箭头函数和普通函数的区别,去阮一峰ES6中看相关资料,发现主要还是指出 this 的指向问题,于是去深入理解this,相关文章又提出了执行上下文,调用栈等概念,顺着这个线索学下去发现盲区越来越多。。

本文主要介绍JavaScript中 this 关键字,原文地址:js 五种绑定彻底弄懂this,默认绑定、隐式绑定、显式绑定、new绑定、箭头函数绑定详解

二、绑定方式

在ECMAScript 6之前没有箭头函数,JavaScript中绑定方式主要包括四种,分别是 默认绑定隐式绑定 , 显式绑定new绑定 几种,ES6中提出了箭头函数,因此this绑定多出了一种形式,让我们一探究竟吧

1. 默认绑定

所谓的默认绑定,指的函数在全局作用域中运行时,this默认指向window对象的情况(非严格模式),严格模式下指向undefined

如下面例子(非严格模式):


   
   
var name = "rambler"; function test() { console.log(this); // window对象 console.log(this.name) // rambler } test();

严格模式下的结果:


   
   
var name = "rambler"; function test() { "use strict" console.log(this); // undefined console.log(this.name) // Uncaught TypeError: Cannot read property 'name' of undefined } test();

严格模式 只是在ECMAScript 5中提出的用在脚本或者函数顶部的一种指令或者声明,有点类似html文档头部的 <!DOCTYPE html> 声明

在严格模式下代码有一些限制,比如未声明不能赋值,不能通过 delete删除变量和属性等,这里不一 一罗列

2. 隐式绑定

什么是隐式绑定呢,如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上,如果函数调用前存在多个对象,this指向距离调用自己最近的对象


   
   
function fun() { console.log(this.name); } let obj = { name: "test", fun: fun } obj.fun(); // test

可能这样写更容易理解?


   
   
let obj = { name: "test", fun: function() { console.log(this.name); } } obj.fun(); // test

其实上面说的多个对象调用this指向最近的对象,和这个是一个意思,例子:


   
   
let obj = { name: "外层", test: { name: "内层", fun: function() { console.log(this.name); } } } obj.test.fun(); //内层

文章原文中指出了一个问题,将是将函数引用赋值给一个变量,通过这个变量调用这个函数this将丢失

看这个例子


   
   
let test = { name: "内层", fun: function() { console.log(this.name); } } let fun = test.fun; fun(); // 没有输出 test.fun(); // 内层

将test.fun赋值给fun,在通过fun()调用this就失效

其实这里可以这样理解,test.fun是一个函数的引用,fun这个变量就是保存的这个函数的引用,直接通过fun调用,就相当于全局执行这个函数,类似这样


   
   
function fun() { console.log(this.name); } fun();

修改代码,打印this,会发现通过变量引用对象里面的函数,this是指向window对象,印证了我的说法

但是存在一个问题就是不太明白为什么打印的空,而不是undefined,因为window对象中明明没有name属性

3. 显式绑定

所谓的显式绑定就是指主动绑定函数调用时的this,主要时通过apply,call和bind进行绑定,三者的区别不是本文的重点,只将this的问题

上个简单例子


   
   
function fun() { console.log(this.name); } let obj1 = { name: "apply方式绑定" } let obj2 = { name: "call方式绑定" } let obj3 = { name: "bind方式绑定" } fun.apply(obj1); // apply方式绑定 fun.call(obj2); // call方式绑定 let newFun = fun.bind(obj3); newFun(); // bind方式绑定

通过这三中方式手动绑定this,这里需要注意的是call和apply绑定this后会自动执行,而bind绑定后返回的是一个函数,可以通过一个变量接受,并执行,或者直接在后面加上括号运行,fun.bind(obj3)()

bind是硬绑定,也就是说通过bind绑定this后,后续无论是通过bind还是apply都无法修改this指向

4. new 绑定

准确来说,js中的构造函数只是使用new 调用的普通函数,它并不是一个类,最终返回的对象也不是一个实例,只是为了便于理解习惯这么说罢了。new的过程如下:

比如我声明一个Person构造函数


   
   
function Person() { this.id = "123"; } Person.prototype.name = "test";

然后通过new关键字创建一个Person的 实例


   
   
var person = new Person

这期间都发生了什么呢?实际过程如下


   
   
// 伪代码 var person = {}; // 创建一个空对象 person.__proto__ = Person.prototype; // 将person对象的原型链指向Person的原型链 Person.call(person); // 通过call改变this指向person对象

这就很容易理解new中this绑定在谁身上了,也就是刚创建的对象

通过打印创建的person对象发现person对象有id属性,证明this确实指向person对象

5. 箭头函数绑定

箭头函数的this指向取决于外层作用域中的this,外层作用域或函数的this指向谁,箭头函数中的this便指向谁。而且一旦绑定,便无法修改,类似bind硬绑定

记住一句话, 箭头函数就是个吃软饭的,外层this指向谁,他的this就指向谁

简单例子:


   
   
var name = "111" let fun = () => { console.log(this.name); } let obj = { name: "test" } fun.call(obj);

这个箭头函数fun是在全局作用域中创建的,因此this指向window对象,所以打印的结果不是test,而是window对象的name属性,也就是111

第二个例子


   
   
function fun() { var test = () => { console.log(this.name) } return test; } var obj = { name: "test", } fun.call(obj)();

注意:箭头函数所在函数this指向谁,箭头函数的this就指向谁

上面例子最后一行,fun.call(obj)这句话通过显式绑定将fun函数的this指向obj对象,fun函数中又返回了一个箭头函数,所以箭头函数也指向obj对象,因此打印结果是 test

三、this绑定优先级问题

new无法和显式绑定同时存在,因为通过new操作符调用函数返回的是一个对象,而不是一个函数,因此无法在通过call和apply等更改this指向

例如下面的代码会报错


   
   
function Person() {}; let obj = {}; new Person().call(obj);//Uncaught TypeError: (intermediate value).call is not a function

因此有下面的结论:

显式绑定 > 隐式绑定 > 默认绑定
new绑定 > 隐式绑定 > 默认绑定

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值