简述Js中this的指向

1.背景介绍

当JavaScript代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。
对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

2.知识剖析

ECMASCRIPT 的类型分为语言类型和规范类型。
  • ECMAScript 语言类型是开发者直接使用 ECMAScript 可以操作的。其实就是我们常说的Undefined, Null, Boolean, String, Number, 和 Object。
  • ECMAScript 规范中还有一种只存在于规范中的类型,它们的作用是用来描述语言底层行为逻辑。
其中重点是便是规范类型中的 Reference 类型。它与 this 的指向有着密切的关联。

这里的 Reference 是一个 Specification Type,也就是 “只存在于规范里的抽象类型”。它们是为了更好地描述语言的底层行为逻辑才存在的,但并不存在于实际的 js 代码中。
REFERENCE 的构成,由三个组成部分,分别是:

  • base value
  • referenced name
  • strict reference

base value 就是属性所在的对象或者就是 EnvironmentRecord,它的值只可能是 undefined, an Object, a Boolean, a String, a Number, or an environment record 其中的一种。

referenced name 就是属性的名称。
举个例子:

  var foo = 1;
// 对应的Reference是:
  var fooReference = {
         base: EnvironmentRecord,
         name: 'foo',
         strict: false
    };

而且规范中还提供了获取 REFERENCE 组成部分的方法,比如 GETBASE 和 ISPROPERTYREFERENCE。

  • GetBase(reference),返回的是reference的值
  • IsPropertyReference(reference),如果 base value 是一个对象,就返回true。
  • GetValue,GetValue 返回对象属性真正的值,但是要注意:调用 GetValue,返回的将是具体的值,而不再是一个 Reference

当函数调用时,如何确定THIS的值?

1.计算MemberExpression的结果赋给ref
2.判断ref是不是一个Reference类型
a.如果ref是Reference,并且ref的base value是一个对象,那么 this 的值为 base value
如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)即为undefind
b.如果 ref 不是 Reference,那么 this 的值为 undefined
举个例子:

                    function foo() {
                       console.log(this)
                    }
                    foo(); // MemberExpression 是 foo
                    function foo() {
                       return function() {
                       console.log(this)
                      }
                    }
                    foo()(); // MemberExpression 是 foo()
                    var foo = {
                       bar: function () {
                       return this;
                       }
                    }
                    foo.bar(); // MemberExpression 是 foo.bar

所以简单理解 MemberExpression 其实就是()左边的部分。

2.判断 ref 是不是一个 Reference 类型。 关键就在于看规范是如何处理各种 MemberExpression,返回的结果是不是一个Reference类型。 举最后一个例子:

                    var value = 1;
                    var foo = {
                      value: 2,
                      bar: function () {
                        return this.value;
                      }
                    }
                    //示例1
                    console.log(foo.bar());
                    //示例2
                    console.log((foo.bar)());
                    //示例3
                    console.log((foo.bar = foo.bar)());
                    //示例4
                    console.log((false || foo.bar)());
                    //示例5
                    console.log((foo.bar, foo.bar)());

foo.bar()在示例 1 中,MemberExpression 计算的结果是 foo.bar,那么 foo.bar 是不是一个 Reference 呢? 查看规范 11.2.1 Property Accessors,这里展示了一个计算的过程,什么都不管了,就看最后一步:

Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.

我们得知该表达式返回了一个 Reference 类型!根据之前的内容,我们知道该值为:

                    var Reference = {
                      base: foo,
                      name: 'bar',
                      strict: false
                    };

接下来按照 2.1 的判断流程走:该值是 Reference 类型,然后执行IsPropertyReference(ref),如果ref的base value是一个对象则返回true,那么 this 的值为 base value 即这个foo

实例3,4,5中,MemberExpression计算结果分别为(foo.bar=foo.bar),(false||foo.var),(f00,bar,foo,bar)

根据规范,逗号操作符,逻辑或操作符和赋值操作符都会执行:

3.Let rval be GetValue(ref).

因为使用了 GetValue,所以返回的不是 Reference 类型,this 为 undefined

this 为 undefined,非严格模式下,this 的值为 undefined 的时候,其值会被隐式转换为全局对象。
结果:

                    var value = 1;

                    var foo = {
                      value: 2,
                      bar: function () {
                        return this.value;
                      }
                    }

                    //示例1
                    console.log(foo.bar()); // 2
                    //示例2
                    console.log((foo.bar)()); // 2
                    //示例3
                    console.log((foo.bar = foo.bar)()); // 1
                    //示例4
                    console.log((false || foo.bar)()); // 1
                    //示例5
                    console.log((foo.bar, foo.bar)()); // 1

因此这些this都指向了全局对象


3.常见问题

1.如何改变this的指向?


4.解决方案

答:可以使用call或者apply的方法:

                // 一个对象可以作为call和apply的第一个参数,并且this会被绑定到这个对象。
                var obj = {a: 'Custom'};

                // 这个属性是在global对象定义的。
                var a = 'Global';

                function whatsThis(arg) {
                  console.log(this.a) // this的值取决于函数的调用方式
                }

                whatsThis();          // 'Global'
                whatsThis.call(obj);  // 'Custom'
                whatsThis.apply(obj); // 'Custom'

5.编码实战


6.扩展思考

以下代码的this的指向?

                    function Foo(){
                        getName = function(){
                            console.log(1);
                            };
                        return this
                    }

                    function getName(){
                        console.log(5);
                    }

                    Foo().getName();

7.参考文献

参考一:JavaScript深入之从ECMAScript规范解读this

参考一:this


8.更多讨论

Q1:构造函数的this指向?
A1:构造函数的this指向实例对象

Q2:除了call,applay还有其他改变this指向的方法吗?
A2:还可以使用new关键字和bing()方法改变this的指向

Q3:reference是什么?
A3:reference类型是js中的规范类型,是用来描述底层行为逻辑的类型,并不存在于实际的js代码中


视频
PPT

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345