细说this的绑定和指向

this绑定的分类

  1. 默认绑定
  2. 隐式绑定
  3. 显式绑定
  4. new绑定

前置知识

this的绑定和函数声明的位置没有任何关系,只取决于函数的实际调用位置

默认绑定

js 复制代码
function foo () {
    console.log(this.a);
}
var a = 5;
foo(); // 5

分析 使用var在全局作用域(window)声明了一个a属性,这跟在window对象中声明一个a属性是等价的,就像硬币的正反两面。由于foo在全局作用域上调用,所以this指向window对象,所以结果是5。这就是默认绑定,默认绑定到window对象。但如果我们将var改为const或者let,结果会是undefined,因为const和let声明的变量并不会挂在window对象上。

隐式绑定

当函数调用有上下文时,应用的就是隐式绑定,此时this一般指向该上下文对象。

js 复制代码
function foo () {
    console.log(this.a);
}

const obj = {a: 'obj:a', foo: foo};
var a = 'global:a'

obj.foo(); // obj:a

分析 obj.foo()虽然是在全局环境调用函数的,但foo的调用上下文是obj,或者说是obj持有了foo,所以此时的this指向了obj上下文对象。

为了加深印象,我们再举个🌰:

js 复制代码
function foo() { 
    console.log(this.a); 
} 
var obj2 = { a: 42, foo: foo, }
var obj1 = { a: 2, obj2: obj2 } 
obj1.obj2.foo(); // 42, obj2为foo的直接上下文

分析 由代码可知,obj1包含了obj2,obj2包含了foo,但最后调用foo时,obj2为foo的直接上下文,此时this指向了obj2,它并不会受obj1的影响。

隐式丢失

但隐式绑定在一些场景下会丢失绑定的上下文对象,从而应用了默认绑定。

举个🌰:

js 复制代码
function foo() { 
    console.log(this.a);
} 
var obj = { a: 2, foo: foo };
var bar = obj.foo;
var a = "oops, global"; // a是全局对象的属性 
bar(); // "oops, global"

分析 由于obj.foo的函数引用赋值给了bar,所以实际上bar是foo函数的函数别名,因此,在全局环境调用bar实际上跟在全局环境直接调用foo函数得到的结果是一样的。this都指向了全局window对象。

显式绑定

通过call,apply等来实现显式绑定

js 复制代码
function foo() { 
    console.log(this.a);
}
var obj = { a: 2 };

var a = 'global';
foo.call(obj); // 2

分析 显式绑定就相对容易理解很多,往call或者apply传递的第一个参数,实际上就是此函数调用的上下文对象,所以this会指向这个对象。

被忽略的this

如果往call或者apply传递一个null(空上下文),此时this会指向全局对象

举个🌰:

js 复制代码
function foo() { 
    console.log(this.a);
}

var a = 2;
foo.call(null); // 2 使用了默认绑定

new绑定

new绑定是js中绑定优先级最高的绑定规则,也是在js继承中常见的。

js 复制代码
function foo(a) { 
    this.a = a;
}

var bar = new foo(2);
bar.a // 2;

分析 由于new的过程实际上内部会将构造函数的this指向实例对象,所以this会指向bar。

this绑定优先级

那这四种this绑定,优先级怎么排列呢?

答案是: new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

this绑定的判断规则

当我们的代码中同时出现这四种绑定规则中的两种及以上时,我们应该怎么正确判定this的指向呢?

规则如下

1.函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象,因为new绑定的优先级最高!

2.函数是否通过call,apply(显示绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。

3.函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是该上下文对象。

4.如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象

箭头函数中的this绑定

箭头函数不会使用this的四条标准的绑定规则,而是根据当前的词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的this绑定,而且无法修改

js 复制代码
function foo() { 
    // 返回一个箭头函数 
    return (a) => {
        // this继承自foo() 
        console.log(this.a); 
    };  
}
var obj1 = { a:2 }; 
var obj2 = { a:3 }; 
var bar = foo.call(obj1); 
bar.call(obj2); // 2 不是3!!

一些建议

在实际的项目开发中,为了保证代码风格的统一性和便于理解,我们不应混用this绑定的四种规则语法和箭头函数!

总结

this的绑定指向一直是一个令人难以理解的东西,特别是在阅读一些框架源码或者工具函数的时候。为了提升阅读源码的个人能力,了解和掌握this的规则势在必行!通过阅读此篇文章,我们可以很容易掌握this的绑定规则有哪些,以及他们的使用方式和优先级排列。

参考资料

《你不知道的Javascript-上卷》

相关推荐
susu1083018911几秒前
前端css样式覆盖
前端·css
学习路上的小刘2 分钟前
vue h5 蓝牙连接 webBluetooth API
前端·javascript·vue.js
&白帝&2 分钟前
vue3常用的组件间通信
前端·javascript·vue.js
小白小白从不日白13 分钟前
react 组件通讯
前端·react.js
罗_三金23 分钟前
前端框架对比和选择?
javascript·前端框架·vue·react·angular
Redstone Monstrosity30 分钟前
字节二面
前端·面试
东方翱翔37 分钟前
CSS的三种基本选择器
前端·css
Fan_web1 小时前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html
yanglamei19621 小时前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
千穹凌帝1 小时前
SpinalHDL之结构(二)
开发语言·前端·fpga开发