资讯专栏INFORMATION COLUMN

关于this的知识归纳(通俗易懂版)

enda / 3314人阅读

摘要:于是决定写个归纳。如果懂了,那么下面的例子也就会做了已知调用函数的对象是,所以指向,即。相当于在全局作用域声明了变量,并且赋值。实际上,调用函数的是关键字。使用来调用函数,即函数的构造调用时,我们会构造一个新对象,并把它绑定到调用中的上。

对this的理解,我一直都是用一句话概括:谁调用它,它就指向谁。

好像也没有什么问题,但仔细看了<你不知道的JavaScript>这本书和网上一些文章后,发现this的原理还挺讲究的。于是决定写个归纳。
(对了,无知的我实在没想到原来bind也和this扯上关系。。)

this的绑定规则有4种。分别是:
1、默认绑定
2、隐式绑定
3、显示绑定
4、new绑定

需要明确:this的值虽然会随着函数使用场合的不同而发生变化,但有一个原则,它指向的是调用它所在的函数的那个对象。

1、默认绑定(纯函数调用)
function test(){
    console.log(this.a);
}
var a = 1;
test();

当调用test()时,因为应用了this的默认绑定,this.a被解析成全局变量a,this指向全局对象window。所以结果为1。

怎么知道应用了默认绑定呢?当前test()是直接使用不带任何修饰的函数引用进行调用的。这个简单来说就是没有任何前缀啊等东西,很纯粹!而其调用位置是全局作用域,更能确定除了默认绑定,无法应用其他规则了。

如果懂了,那么下面的例子也就会做了

function test(){
    this.a = 2;
    console.log(a);
}
test();

已知调用函数test()的对象是window,所以this指向window,即this.a===window.a。由于window可以省略,因此简写成a。
相当于在全局作用域声明了变量a,并且赋值a=2。
this.a = 2
-->window.a = 2
-->a = 2
所以结果为2。

可能说得太啰嗦了,直接说下一种绑定。

2、隐式绑定(作为方法调用)

通俗地说就是一个函数,被当作引用属性添加到了一个对象中了,然后以 “对象名.函数名()” 形式进行调用,这时如果函数引用有上下文对象,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。

下面看下例子。

function test(){
    console.log(this.a);
}
var obj = {
    a:3,
    test:test
};
obj.test();

对象obj中包含两个属性a和test,其值分别是3和一个函数test()。
obj.test()表示函数引用时有上下文对象(也就是这里的obj)。
根据隐式绑定规则,会把test()中的this绑定到这个上下文对象,即this被绑定到obj,this.a===obj.a。
所以结果为3。

如果懂了,那么下面的例子也就会做了

 function test(){
     console.log(this.a);
 }
 var obj2 = {
     a:4,
     test:test
 };
 var obj1 = {
     a:400,
     obj2:obj2
 }
 obj1.obj2.test()

看起来复杂了些,不过只要记住下面这个准则就可以了。

对象属性引用链中,只有上一层或者说最后一层在调用位置中起作用

所以只有obj2这个上下文对象生效,结果为4。

隐式绑定的番外篇——隐式丢失

这个网上貌似很少提及,隐式丢失,通俗来讲就是“变low”了!
原本应用隐式绑定的,因为丢失绑定对象,变回应用默认绑定了!把this绑定到全局对象或undefined。

当对象将其引用属性给了新的引用,再次调用这个新的引用时,原本this指向的对象就会改为指向window。

到底在说什么,看个例子。

 function foo(){
     console.log(this.a);
 }
 var obj = {
     a:5,
     foo:foo
 };
 var bar = obj.foo; //这句就是关键
 var a = "oops, global";
 bar(); // "oops, global"

b引用了test()函数本身,此时的b()是一个不带任何修饰的函数调用,因此应用了默认绑定。

如果懂了,那么下面的例子也就会做了

 function foo(){
     console.log(this.a);
 }
 function doFoo(fn){
     fn();
 }
 var obj = {
     a:5,
     foo:foo
 }
 var a = "oops, global";
 doFoo(obj.foo); // "oops, global"

其实和上面的没什么区别,只是这里把函数作为参数传递了,参数传递是一种隐式赋值。此时的this指向的是调用它的函数的对象即全局对象,因此应用了默认绑定。

3、显示绑定(apply/call调用)

回顾一下隐式绑定,其关键是把函数当作引用属性添加到了对象中,通过这个属性间接引用这个函数,把this简介(隐式)绑定到这个对象上。
如果不想在对象内部包含函数引用,而想简单粗暴地在某个对象强制调用一个函数,这时就用到了函数的call()和apply()方法。
call和apply,以往我单纯理解为“控制this的指向”,现在才发现是原来是this绑定规则中的一种。

call和apply区别
apply接收的是数组参数,call接收的是连续参数。所以当传入的参数数目不确定时,多使用apply。
(tips:这里推荐个方法记忆apply和call各自接收的参数:apply为a开头,数组Array也是a开头,所以apply接收的是数组参数)

看下面例子。

function test(){
    console.log(this.a);
}
var obj = {
    a:6
};
test.call(obj);

当调用test时强制把它的this绑定到obj上。所以this.a===obj.a,结果为6。
注意:后续参数传入的是原始值的话,会被转换成它的对象形式(如字符串类型-->new String()如此类推)

这次我们不用call,用apply。

var a = 0;
function test(){
    console.log(this.a);
}
var obj = {};
obj.a = 7;
obj.m = test;
obj.m.apply();
obj.m.apply(obj);

同样的,对象obj包含了两个属性a和m,m引用了函数test()。
obj.m.apply(obj)即把test()这个函数中的this绑定到对象obj上。即this指向的是obj。所以this.a===obj.a。
“apply没有参数怎么办,基本语法都是包含参数的啊,至少给个对象,让this有个指向啊。”
apply定义了当没有参数时,全局对象会自动默认成为其第一个参数,apply()等价于apply(window)。
因此obj.m.apply(),test()中的this就指向了全局对象window,结果为0。

显示绑定的番外篇——硬绑定

干嘛用的?解决前面提到的隐式丢失问题。
回顾当初隐式丢失的第二个例子。

 function foo(){
     console.log(this.a);
 }
 function doFoo(fn){
     fn();
 }
 var obj = {
     a:5,
     foo:foo
 }
 var a = "oops, global";
 doFoo(obj.foo); // "oops, global"

将其改一下变成:

 function foo(){
     console.log(this.a);
 }
 function doFoo(fn){
     fn.call(obj);
 }
 var obj = {
     a:5,
     foo:foo
 }
 var a = "oops, global";
 doFoo(obj.foo);

依旧是创建了doFoo()这个函数,但在其内部手动调用了 obj.foo.call(obj),
把foo()强制绑定到了obj对象,之后无论如何调用doFoo(),它总会手动在obj上调用foo。

对于硬绑定,ES5提供了一个内置方法Function.prototype.bind,我们把上面的例子再改!

 function foo(){
     console.log(this.a);
 }
 function doFoo(fn){
     fn.bind(obj);
 }
 var obj = {
     a:5,
     foo:foo
 }
 var a = "oops, global";
 doFoo(obj.foo);

一执行,我的天!啥也没有啊??
这就对了,这是bind的“效果”,也是和apply、call的主要区别——延迟调用。
也就是说bind其实只是函数的引用,要想执行需要进行回调,即fn.bing(obj)(),
此时就能输出5了。

好,现在把隐式丢失的第一个例子改动

 function foo(){
     console.log(this.a);
 }
 var obj = {
     a:5,
     foo:foo
 };
 var bar = obj.foo.bind(obj); 
 var a = "oops, global";
 bar(); //5

为了能证明bind的特点,函数在回调时执行,把bind改成call,最后3行代码变成

var bar = obj.foo.call(obj); 
var a = "oops, global";
bar(); //Uncaught TypeError: bar is not a function

结果报错了,对,因为call和apply都是绑定后立刻执行的,都执行完了,bar就只是一个没有赋值的变量而已。

总结下“显示绑定三人组”:

 共同点:
     1、都用于控制this指向;
     2、第一个参数都是this需要指向的对象,也就是上下文;
     3、都可以后续参数传递;
     4、没有任何参数时,this都指向全局对象window              
 区别:
     1、call、apply绑定后立刻执行,bind是延迟执行。换言之,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,就使用bind()方法吧。
     
4、new绑定(作为构造函数调用)

什么是构造函数,一个函数被new调用时,该函数就是构造函数。
(变身前:普通函数;使用地摊货new变身器,变身后:构造函数)

function test(){
    this.x = 8;
}
var obj = new test();
console.log(obj.x);

你可以简单粗暴理解为:就相当于test()被对象obj调用,其this指向obj。
但貌似很不规范。。

实际上,调用函数的是new关键字。

使用new来调用函数,即函数的“构造调用”时,我们会构造一个新对象,
并把它绑定到test()调用中的this上。所以在代码中,this就指向了新对象obj

最后总结

判断this的4种规则根据优先级从高到低排序如下:

1、函数在 new 中调用,this绑定的是这个新对象
2、函数通过 call、apply或bind 调用,this绑定的是指定对象
3、函数在某上下文对象中调用,this绑定的是这个上下文对象
4、以上都不是,使用默认绑定。(在全局作用域调用,this绑定window对象)

关于this书中还提及到很多其他知识,例如软绑定、this词法箭头函数等,就不归纳到这里了

归纳前看过的相关书籍或文章:
1、<你不知道的JavaScript>(上)第二部分第二章 this全面解析
2、阮一峰的网络日志-JavaScript的this用法
http://www.ruanyifeng.com/blo...
3、chanzen的个人博客-call,apply,bind用法和意义
http://ovenzeze.coding.me/use...
4、脚本之家-JS中的this变量的使用介绍
http://www.jb51.net/article/4...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/82958.html

相关文章

  • 写技术博客那点事

    摘要:从现在开始,养成写技术博客的习惯,或许可以在你的职业生涯发挥着不可忽略的作用。如果想了解更多优秀的前端资料,建议收藏下前端英文网站汇总这个网站,收录了国外一些优质的博客及其视频资料。 前言 写文章是一个短期收益少,长期收益很大的一件事情,人们总是高估短期收益,低估长期收益。往往是很多人坚持不下来,特别是写文章的初期,刚写完文章没有人阅读会有一种挫败感,影响了后期创作。 从某种意义上说,...

    ddongjian0000 评论0 收藏0
  • 写技术博客那点事

    摘要:从现在开始,养成写技术博客的习惯,或许可以在你的职业生涯发挥着不可忽略的作用。如果想了解更多优秀的前端资料,建议收藏下前端英文网站汇总这个网站,收录了国外一些优质的博客及其视频资料。 前言 写文章是一个短期收益少,长期收益很大的一件事情,人们总是高估短期收益,低估长期收益。往往是很多人坚持不下来,特别是写文章的初期,刚写完文章没有人阅读会有一种挫败感,影响了后期创作。 从某种意义上说,...

    NSFish 评论0 收藏0
  • 通俗易懂理解ES6 - ES6变量类型及Iterator

    摘要:迭代器在原有的数据结构类型上新增了两种类型,我们在使用的时候还可以通过自由组合的形式使用这些结构类型达到自己想要的数据结构,这就需要一种统一的接口机制供我们调用处理不同的数据结构。 引言 万丈高楼平地起,欲练此功,必先打好基本功: ) 在了解 ES6 新增的变量类型前,我们必须先知道 JavaScript 在ES6之前,有如下六种基本数据类型:Null、Undefined、Number...

    Keven 评论0 收藏0
  • 即将立秋《课多周刊》(第2期)

    摘要:即将立秋的课多周刊第期我们的微信公众号,更多精彩内容皆在微信公众号,欢迎关注。若有帮助,请把课多周刊推荐给你的朋友,你的支持是我们最大的动力。课多周刊机器人运营中心是如何玩转起来的分享课多周刊是如何运营并坚持下来的。 即将立秋的《课多周刊》(第2期) 我们的微信公众号:fed-talk,更多精彩内容皆在微信公众号,欢迎关注。 若有帮助,请把 课多周刊 推荐给你的朋友,你的支持是我们最大...

    MRZYD 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<