共计 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.f
或obj.c
作为运行时的环境,继而输入对应 name。
个人见解
function 函数的 this 在 运行时绑定,咱们无妨将一个 function 函数的执行分成两部:
- 查找其运行环境,绑定 this
- 执行函数
按上述了解,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})()
以上内容先写的剖析,再在浏览器中运行,后果正确。依照此思路一步步剖析,即便嵌套再简单,也能一步步剖析出对应的后果。