this指向问题

根底定义类的货色最好还是查看正规文档,网上文章有诸多谬误,容易误导人

相干内容所在仓库地址:
https://github.com/goblin-pitcher/steel-wheel-run/blob/master/%E6%98%93%E5%BF%98%E7%9F%A5%E8%AF%86%E7%82%B9%E6%B1%87%E6%80%BB/this%E6%8C%87%E5%90%91%E7%9B%B8%E5%85%B3.md

this指向相干定义

这里次要探讨function函数和箭头函数的this相干定义:

  • function函数,参考mdn官网文档(this)

    • 在绝大多数状况下,函数的调用形式决定了 this 的值(运行时绑定)。
    • this 不能在执行期间被赋值,并且在每次函数被调用时 this 的值也可能会不同。
  • 箭头函数,参考mdn官网文档(箭头函数)

    • 箭头函数不会创立本人的this,它只会从本人的作用域链的上一层继承 this

如何了解

箭头函数的this

了解箭头函数的this,最好的形式是查看babel是如何对其进行本义的,示例如下:

原代码:

const name = 'a'const obj = {  name: 'b',  f: ()=> {      console.log(this.name)  },  f0(){    return ()=>{        console.log(this.name)    }  }}

本义为es5的代码:

var _this = void 0;var name = 'a';var obj = {  name: 'b',  f: function f() {    console.log(_this.name);  },  f0: function f0() {    var _this2 = this;    return function () {      console.log(_this2.name);    };  }};

能够发现,箭头函数的本义,即是在其所在下层作用域中定义_this对象,再将箭头函数外部this替换为_this

联合箭头函数this的定义,箭头函数不会创立本人的this,它只会从本人的作用域链的上一层继承 this,咱们能够不把箭头函数中的this当作一个一般对象,例如将上述代码改成如下写法

const _context = void 0;const name = 'a';const obj = {  name: 'b',  f: function f() {    console.log(_this.name);  },  f0: function f0() {    const _context = this;    return function () {      console.log(_context.name);    };  }};obj.f0()() // 输入bobj.f0.call({name: 123})() // 输入123

将箭头函数本义成上述es5写法,同样可正确实现其成果,这样箭头函数this指向问题就变成了一个闭包问题

对于obj.f0()(),当执行obj.f0()时,会将给_context赋值obj对象,执行箭头函数时,先从其作用域链中获取_context,再打印_context.name,因而输入obj.name,即b
obj.f0.call({name: 123})()输入123也是同理

function函数的this

如文档定义所说,function函数的this在运行时绑定,惯例状况下很容易了解,例子如下

const name = 'a'const obj = {  name: 'b',  c: {    name: 'c',    f(){        console.log(this.name)    }  }}obj.c.f() // f运行时,绑定运行环境obj.c,输入cconst f = obj.c.ff() // 运行时,绑定运行环境global,输入a

惯例状况,例如<span style="color: red">obj.c</span>.f(),咱们将f后面的对象作为运行环境(标红局部),后面没有就将运行环境视为global。

易错例子
const name = 'a'const obj = {  name: 'b',  c: {    name: 'c',    f(){      return function() {        console.log(this.name)      }    }  }}obj.c.f()() // 输入aobj.c.f.call({name: 'xxx'})() // 输入a

这个例子最容易蛊惑的中央是,obj.c.f()生成了一个function,生成的function会不会以obj.c.fobj.c作为运行时的环境,继而输入对应name。

个人见解

function函数的this在运行时绑定,咱们无妨将一个function函数的执行分成两部:

  1. 查找其运行环境,绑定this
  2. 执行函数

按上述了解,obj.c.f()的执行用代码表述,后果如下

// obj.c.f()const env = obj.cconst func = obj.c.ffunc.call(env)

依照此思路,再看obj.c.f()()的执行。首先,所有运算都有各自的优先级,例如

const a = 1;console.log(a||3+1) // 输入a的后果,即1

对于a||3+1,加法优先级高于或,即先执行了3+1,再执行a||4,a为真,即输入a的值

同理,对于obj.c.f()(),可看作如此构造(obj.c.f())(),将(obj.c.f())看作一整个办法,当(obj.c.f())()执行时,(obj.c.f())后面没有环境,因而(obj.c.f())()执行环境为global。转换成代码如下

// obj.c.f()()// 首先执行了(obj.c.f()),生成了一个function// 开始查找(obj.c.f())的执行环境,能够看到(obj.c.f())后面没有任何内容,因而环境为globalconst env = global(obj.c.f()).call(env)
回头再看箭头函数

利用babel本义的代码了解箭头函数,无疑不会出问题。但还是以箭头函数定义去了解一下。

箭头函数不会创立本人的this,它只会从本人的作用域链的上一层继承 this。联合定义,依照下面的剖析形式剖析以下代码

const name = 'a'const obj = {  name: 'b',  f(){    return ()=>{        console.log(this.name)    }  }}/** * 本来式子obj.f()(),剖析如下: * const env0 = obj; * const f0 = obj.f * const f1 = f0.call(env0) * // f1为箭头函数,因而其作用域应用其上一层 * const env1 = env0 * f1.call(env1) // 输入obj.name, 即b */obj.f()() // b

综合练习

联合上述对于function函数和箭头函数this指向的了解,解析一下多层嵌套函数的输入(正文为辅助剖析内容)

const name = 'a'const obj = {  name: 'b',  c: {    name: 'c',    f(){      console.log('==>0::', this.name)      return function() {        // const _context = this        console.log('==>1::', this.name)        return () =>{          // 将其视作一般函数后,打印 _context.name          console.log('==>2::', this.name)          return function(){              // const _context = this            console.log('==>3::', this.name)            return () => {              // 将其视作一般函数后,打印 _context.name              console.log('==>4::', this.name)            }          }        }      }    }  }}/** * 原始式子为obj.c.f()()()()(), 解析如下: * const env0 = obj.c * const f0 = obj.c.f * // 式子优化为(f0.call(env0))()()()() * const f1 = f0.call(env0) // 输入 ==>0::c * const env1 = global * // 式子优化为(f1.call(env1))()()() * const f2 = f1.call(env1) // 输入 ==>1::a * let _context = env1 // f2为箭头函数,因而_context赋值父级环境env1 * const env2 = _context; * // 式子优化为(f2.call(env2))()() * const f3 = f2.call(env2) // _context==env1==global, 输入 ==>2::a * const env3 = global; * // 式子优化为(f3.call(env3))() * const f4 = f3.call(env3) // 输入 ==>3::a * _context = env3 * const env4 = _context; * f4.call(env4) // _context==env1==global, 输入 ==>4::a *  * 总结,输入如下: * ==>0::c * ==>1::a * ==>2::a * ==>3::a * ==>4::a */obj.c.f()()()()()/** * 原始式子为obj.c.f().call({name: 1})()()(), 解析如下: * const env0 = obj.c * const f0 = obj.c.f * // 式子优化为(f0.call(env0)).call({name: 1})()()() * const f1 = f0.call(env0) // 输入 ==>0::c * const env1 = {name: 1} * // 式子优化为(f1.call(env1))()()() * const f2 = f1.call(env1) // 输入 ==>1::1 * let _context = env1 // f2为箭头函数,因而_context赋值父级环境env1 * const env2 = _context; * // 式子优化为(f2.call(env2))()() * const f3 = f2.call(env2) // _context==env1=={name: 1}, 输入 ==>2::1 * const env3 = global; * // 式子优化为(f3.call(env3))() * const f4 = f3.call(env3) // 输入 ==>3::a * _context = env3 * const env4 = _context; * f4.call(env4) // _context==env1==global, 输入 ==>4::a *  * 总结,输入如下: * ==>0::c * ==>1::1 * ==>2::1 * ==>3::a * ==>4::a */obj.c.f().call({name: 1})()()()/** * 原始式子为obj.c.f.call().call({name: 1}).call({name:2}).call({name: 3})(), 解析如下: * const env0 = global // call没参数,视作global * const f0 = obj.c.f * // 式子优化为(f0.call(env0)).call({name: 1}).call({name:2}).call({name: 3})() * const f1 = f0.call(env0) // 输入 ==>0::a * const env1 = {name: 1} * // 式子优化为(f1.call(env1)).call({name:2}).call({name: 3})() * const f2 = f1.call(env1) // 输入 ==>1::1 * let _context = env1 // f2为箭头函数,因而_context赋值父级环境env1 * // 留神这里,即便执行.call({name: 2}), env2仍是_context,因为箭头函数中this替换成了_context * const env2 = _context; * // 式子优化为(f2.call(env2)).call({name: 3})() * const f3 = f2.call(env2) // _context==env1=={name: 1}, 输入 ==>2::1 * const env3 = {name: 3}; * // 式子优化为(f3.call(env3)).call(env3)() * const f4 = f3.call(env3) // 输入 ==>3::3 * _context = env3 * const env4 = _context; * f4.call(env4) // _context==env1==global, 输入 ==>4::3 *  * 总结,输入如下: * ==>0::a * ==>1::1 * ==>2::1 * ==>3::3 * ==>4::3 */obj.c.f.call().call({name: 1}).call({name:2}).call({name: 3})()

以上内容先写的剖析,再在浏览器中运行,后果正确。依照此思路一步步剖析,即便嵌套再简单,也能一步步剖析出对应的后果。