关于javascript:js函数this指向问题解析

45次阅读

共计 5251 个字符,预计需要花费 14 分钟才能阅读完成。

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()() // 输入 b
obj.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,输入 c
const f = obj.c.f
f() // 运行时,绑定运行环境 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()() // 输入 a
obj.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.c
const func = obj.c.f
func.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()) 后面没有任何内容,因而环境为 global
const 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})()

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

正文完
 0