1、class中的this
上图的点分析理解:
1、在logger这个实例对象中的printName方法的this自然的指向logger本身(又或者说指向其构造函数本身的内存空间(执行的上下文?)?)
2、从对象中取出来(以es6解构的形式或者以赋值的形式)的本质是,在当前执行上下文(比如说全局window类)中声明一个新的同名对象,再将原实例对象中的匿名函数的地址传给window对象(这么一看,window本质上也是一个巨大的[Window]类的实例的对象,因为它的很多特性都符合一个类的特性),然后,该匿名函数就被挂载到window下面了
3、"this指向该方法运行时所在的环境",执行的该方法已经被挂载在新的实例对象上了,这时候执行的不再是原对象中方法,而是新的实例对象上的同名方法了
4、当上级方法已经被挂载到新的执行上下文后,其内部的执行语句中的this,这个变量在当前的执行上下文(比如说Window类)中并没有被定义,所以this本身是undefined的,我感觉此时和class的严格模式本质上没啥关系(还是有点关系的,关系在于,全局中有class,那么整个全局就都处于严格模式了,所以这时候全局环境中的this指向undefined,而全局环境中没有class即没有任何严格模式存在,this指向全局对象window,与[3.2]有点关系,此外,在node环境中的global是严格模式的,所以在global中this也是undefined),因为"this.A"的这种语法中的this本身就是在调一个变量,这样子的话,区别在于,在Window这个全局类中,没有内置this变量,所以在这里执行主函数的时候,内部调用的this这个变量在当前作用域链上找不到,所以结果是未定义不存在的(不是这样的,window中也是有this的,那这样的话,"全局环境"本质上是一个大型的[window]类不就对上了?这样的话,全局环境实际上是指[Window]这个类,window是它的实例,可以通过给window增加属性的方式给全局环境增加变量和属性,这不就又对上了?!)。那么由此也可以推理,Window全局类和class or function类的一个区别,在class or function类中内置了"this"这个变量,且为其储值为实例对象,而Window中没有?(在[3.2]中证明了全局中还是有this这个东西的,只不过在严格模式下其未被显式注册(or声明),所以为undefined,undefined本身也是未定义的意思,这不就对上了?)
感觉this代指的不只是一个实例对象,更像是当前这个构造函数的内部变量环境,那么全局环境(=全局执行栈=全局执行上下文="window对象的构造函数[Window]内部作用域"(没有"window的构造函数[Window]"的说法,是我为了自己理解this这个东西构造的一个把手))就是一个大型的构造函数内部作用域,其内在的一些特性和普通的类很相似(私以为几乎一模一样)
5、我理解this本身应该也是代指一块内存空间,this本身也是一个变量,这个变量存储的值是一个类的实例在内存中的地址字符串(如ox12fe)or一个基础类型数据(如number、string……)
2、class中的this指向固定在实例对象的处理方案
1、构造方法中绑定this:
图文解析:
1、bind函数内部,将该函数绑定在全局的变量fn中,再返回一个匿名函数,该匿名函数本身指向调用的上下文,即全局环境window,但是真正执行的目标函数fn指向(在apply中已经绑定在新的实例对象上了,比如说这里的当前构造函数的this),所以这么几个转手下来,核心点还是apply中将同名函数直接挂载在当前构造函数的内部作用域内,那么实际上全局解构or赋值得到同名函数变量执行的是外部包裹的匿名函数,但是这个匿名函数内部执行的是挂载在构造函数上的主函数,主函数已经挂载构造函数中(的实例对象上->这个替换一下也相当于其实是在class中定义了这个函数,才能体现在实例对象的调用链上),所以,实际上主函数的执行环境已经进入到构造函数内部了,在构造函数内部中this就是class内置的this变量,即实例本身 -> 这样的话,可不可以推理,class类就相当于是一个小型的封闭的执行上下文环境?所以主函数内部的this.A()中的this在作用域链上是可以找到的,A也是存在于当前环境变量中的,所以A就能顺利执行
所以这本质上,是不是一个执行上下文的替换?
需不需要和内存联系起来呢?这个暂时还想不到呢
2、在上文提到的"挂载到一个实例对象上" => "挂载到该实例对象的构造函数的内置变量this对象中" ,而一个函数内部,本身也是一个小型的执行上下文、执行环境,存在自己的作用域链,而且,与内存联系起来的话,一个函数也是一个对象,对象内部是一些执行语句,执行语句在内存中是写死的,只有当它们被压入执行栈中,才会根据作用域链去找对应的变量的值,包括在构造函数中定义的方法函数中的this,本质也是一个代码符号,也是一个变量,如果其被压入的执行环境中没有对应的this变量,那就会报错咯
3、那这样子的话,其他的在给显式的对象增加属性和方法,本质上,是在给该对象的构造函数中添加属性方法,调用该属性的方法(obj.abc())时,会自发的首先在构造函数的作用域链内找变量,挂载在对象中的方法,虽然实际上也是挂载一个引用地址,但是执行的时候,还是会把引用地址指向的内存中的代码压入执行栈,这些代码,应该全部是"真·代码"。
4、那么这样想的话,对象这个数据类型,虽然表现为一个键值对的形式,但是其实它指向一个完整的构造函数的内部作用域!显式对象其实只是这个构造函数的输出结果,那每一次的赋值和解构赋值,都只是将内存地址从一个构造函数转移到另一个构造函数的内部作用域中。
2、使用箭头函数
图文解析;
1、箭头函数的特性 -> "箭头函数内部的this总是指向定义时所在的对象",这句话的含义应该是指,箭头函数的内存地址只挂载在定义时的那个作用域中,并且不会被转移,所以,定义的时候,应该是将主函数使用箭头函数来定义,这样主函数内部调用的this.a()函数的this还是能找到的,这样的话,名义是引用的匿名函数,但是实际上是引用的obj.main,执行的时候也是执行的obj.main
3、使用proxy
图文解析:
1、我需要补充一下相关知识点 proxy和WeakMap
暂停
3、对象属性引用链上的this
1、只有上一层或者说最后一层在调用位置中起作用
图文解析:
1、根据上文的推理,foo函数挂载在obj2的构造函数内,然后obj2对象又被挂载在obj1的构造函数内,这两个构造函数中的this变量本质上是不同的,且其作用域某种程度上是隔离的,所以"obj1.obj2.foo()"的执行顺序,是先将指针指向obj1中的obj2同名属性的obj2对象的内存地址(即obj2的构造函数内部),再执行obj2中的foo函数。
2、这个过程只是将obj2的内存地址挂载在obj1对象的同名属性变量上,并没有将函数foo挂载到obj1的构造函数中,所以foo依然在obj2对象的构造函数的内部作用域中,所以执行的时候,foo函数的执行代码会自动去找它的上级作用域
3、那由此是否又可以推测,以字面量的形式生成的不同的字面量对象,也应该是有其自己的构造函数的,而且每一个字面量的构造函数都是不同,只不过没有明文存在,但是其在内存中的表现应该和一个命名的构造函数是相同的,new出来的对象和字面量对象的差别在于,new出来的对象的属性和方法在构造函数中明文定义并且还有其他附加属性和静态方法之类的,字面量对象的方法和属性则省掉了这个步骤,但是其在内存中的表现应该是一样的,但是字面量对象在内存中的“构造函数”的内容更"轻量",这样的话,是不是也可以把字面量生成的变量当成一种构造函数变量的语法糖?
4、刚刚又想到既然全局作用域本质上是一个超级无敌大的[Window]类,那么
2、this在全局中是为undefined还是指向window
图解:
1、表示在全局Window中以字面量的形式定义了一个对象,当前并没有处在严格模式下,所以全局环境中的this指向了全局变量window
2、表示当var一个bar变量(相当于在全局环境中注册了一个全局属性:window.bar),并将foo函数挂载到这个变量上时(bar = obj.foo本质上也只是把函数对象foo的堆内存引用地址转而赋值给变量bar,obj.foo本身存储的也是foo这个函数对象的堆内存引用地址, bar执行的时候,自己会根据堆内存引用地址把foo函数中的静态执行代码压入执行栈中)
3、表示foo函数中的执行代码在执行的时候会被压入全局执行环境的中(在2的时候已经将命名函数foo的内存地址赋值给变量bar了,这时候再执行的不是foo(),而是bar(),说明foo or bar 都应该只是该函数对象的堆内存引用地址的"代号"?是的,因为foo定义在全局环境中的时候,不改变引用该函数的变量,直接调foo()执行结果也是一样的),因此能够顺着作用域链找到全局环境中的this,所以此时this === window,在window中通过var已经注册了一个变量a(变量实际上是window对象中的属性),所以this能找到,this.a也能找到
拓展4、这里和"严格模式"有啥区别?区别在于,当前这个obj变量是字面量生成的,当前的全局环境不是严格模式,所以this指向window,而不是undefined
3、丢失绑定(默认绑定)
丢失绑定的含义应该是指,this原来指向定义or初次赋值时的对象的构造函数作用域内,后来指向了被调用时的this,比如说window or 其他实例对象,但是这玩意儿名字叫丢失绑定时以初始定义和初始赋值时而言的丢失,本质是函数对象的地址被挂载在另外一个构造函数作用域(即函数的执行代码被压入到另一个私有作用域、执行环境、执行上下文中去执行了),上面的那种情况也属于丢失绑定的一种情况,因为foo本来挂载在obj上,后来foo函数的地址被赋值给了window中的bar,所以bar也能调用了,也能,注意这个词,因为再次执行obj.foo()也是可以执行的,这里的this会指向obj而已,而且输出的也是42
这种情况有,还有"回调函数",包括setTimeout中的回调函数,区别在于前者的回调函数还有可能看调用回调函数的作用域是哪一个,即主函数挂载在哪个实例对象的构造函数中,setTimeout中的回调函数直接全部指向window,因为setTimeout函数执行的本来也是到了合适的时候把回调函数压入全局执行栈,所以这玩意儿的this最后指向全局很正常
4、绑定生效的优先级
new绑定 > 显示绑定(apply、call、bind) > 隐式绑定 > 默认绑定
5、到底有没有[Window]这个类?
实践:
1、快速生成一个html模板:1:!(英文)+tab 2: html:5+tab
2、写入一个闭包
在上图的Local中的this和Global的值都是一个"Window",因此我合理怀疑,全局变量window对像的构造函数[Window]是真实存在的
干脆打印了一下,虽然不知道f Window()里面的静态执行代码是啥,但是[Window]这个东西好像是真的存在,其引用地址还被赋值给this,那么这是不是也应证了,this本质是一个构造函数(的内部作用域)?window本身是一个Window类的实例对象?
2022-3-23
后记:仔细翻阅红宝书之后,在书中已经明确说明,this指向函数执行的执行环境,而执行环境本质上是一个“变量对象”,即一个堆内存地址,相当于Window,或者说全局执行环境,是一个和普通对象同级的堆内存空间,而执行栈(或者说红宝书所用的执行流),应该是js引擎的执行空间,而不是静态代码的内存地址,栈内存纯纯指这个执行空间中临时存储的数据,包括全局环境在内,都是将堆内存数据推入这个执行空间,所以,所有的执行代码,本质上,都是对数据(基本类型和对象、数组等)的处理,保存一些数据,引入一些数据,改变这些数据,删除这些数据,本质上是对js引擎创造的执行空间的数据的处理(有点点类似于数据结构与算法的处理原理,说来说去,一切的原型和起点都是算法),而js引擎的执行空间里的这些数据,就是浏览器页面中展示的页面和功能的基础,在这整个流程中,还会调用到浏览器的其他的线程和方向,这才是整个全貌
本笔记来源于以下两篇文章的学习和思考:
引用参考1:es6.ruanyifeng.com/#docs/class
引用参考2:juejin.cn/post/699497…