如何正确判断this?
普通函数的this如何改变?(哪些工具函数如何工作?)
严格模式的this有什么区别
箭头函数的this是什么?
鉴于this风骚的运作方式,各大厂面试和基础书籍阅读中,对this的理解是永不过时的话题,这块骨头既然这么难啃干净,那就干脆将其大卸八块,高压锅一压,营养一锅端;话不多说,这就完全拿下的。
1 描述概念
this is all about context.
this说白了就是找老大,找拥有当前上下文(context)的对象(context object)。大致可以分为八层(也可以是六层,还有说是四层,都对都没问题),层数越高权力越大,this只会认最大的。
八层: 箭头函数>new>bind>call|apply>forEach>setTimeout(foo,100)>obj.foo()>foo()
六层: 箭头函数>new>bind>call|apply>obj.foo()>foo()
四层: new>bind>obj.foo()>foo()
2 点兵点将
咱们直接整最详细的,准备一个函数foo,首行代码控制严格模式开关,就像这样:
'use strict';var that;function foo(params) { that = this; console.log('this是:', this); }复制代码
2.1 直接调用
// 1-直接调用 window || undefined (严格模式)foo();复制代码
这是一个兜底的存在,在普通情况下this就是全局,浏览器里就是window;在use strict的情况下就是undefined。
2.2 对象调用
如果用到this的那个函数是属于某个 context object 的,那么这个 context object 绑定到this。比如下面的例子:
//2-挂载在对象上,然后执行 -> 对象let arr = [1, 2, 3]; arr.fn = foo; arr.fn();复制代码
2.3 定时器调用
这个玩儿法还有很多类似的,比如用户点击IO,宏任务响应等。
//3-定时器--windowsetTimeout(foo, 100);复制代码
对,这里始终是window,因为是任务队列里面的,当被线程推到执行调用栈,此时的调用环境就是window
2.4 工具函数(forEach)
[(1, 2, 3)].forEach(function (item) { console.warn(this, item); }); //window || undefined 3[1, 2, 3].forEach(function (item) { console.warn(this, item); }); //window || undefined 1-3复制代码
可以看到forEach这样的遍历函数,其this和第一种直接一样,根据是否是严格模式反馈兜底的结果,至于为什么不是前面的对象调用,仔细想想,不难发现原因是吧。
2.5 工具函数(call|apply)
Object.prototype.call和Object.prototype.apply,它们可以通过参数指定this,具体用法和差异在此就不赘述,有需要可以上MDN。
foo.call(12); // 12foo.call(Date()); // ... (中国标准时间)foo.call('ajflk'); // ajflkfoo.apply(/dfjlk/); // /dfjlk///注意this是不可以直接赋值的哦,this = 2会报ReferenceError。复制代码
2.6 工具函数(bind)
Object.prototype.bind,它不但通过一个新函数来提供永久的绑定,还会覆盖以上工具函数的命令。
let _obj = { name: 'demo' };let foo2 = foo.bind(_obj); foo2(); // {name: "demo"}foo2.call(_obj2); // {name: "demo"}复制代码
2.7 new 当成构造函数使用
这是一个比较容易忽略的绑定this的地方。当我们new一个函数时,就会自动把this绑定在新对象上,然后再调用这个函数。它会覆盖bind的绑定。
let _obj = { name: 'demo' };new foo(); // foo {}let foo2 = foo.bind(_obj); foo2(); // {name: "demo"}new foo2(); // foo {}复制代码
2.8 箭头函数
ES2015 的箭头函数很酷,几乎所有前端开发人员都爱不释手。其原因就是在箭头函数里,this不再妖艳,被永远封印到当前词法作用域之中,称作 Lexical this ,在代码运行前就可以确定。没有其他途径可以覆盖。
这样的好处就是方便让回调函数的this使用当前的作用域,不怕引起混淆。所以对于箭头函数,只要看它在哪里创建的就行。如果对词法作用域感兴趣可以看看这里。
let fun = () => console.log('fun this is', this); fun(); // fun this is window// new fun(); // TypeErrorvar obj1 = { name: 'obj1', funThis() {var func = () => console.log('funThis this is', this);return func(); }, }; fun.call(obj1); // fun this is windowvar funBind = fun.bind(obj1); funBind(); // fun this is windowobj1.funThis(); // funThis this is obj1var obj2 = { name: 'obj2', funThis: obj1.funThis, }; obj2.funThis(); // funThis this is obj2复制代码
箭头函数的核心就是一句话:“对于箭头函数,只要看它在哪里创建”。
3 总结
啃骨头就得啃干净,this这么看来就简单了,但是简单的东西有那么多文章来描述它定义它,那其实也就不见得是多简单,所以,咱们还是平常心。
认真揣摩,多看(书籍,mdn等)多练(coding | test)。
谁也不笨,谁也不见得多聪明,始终坚信,保持每天进步一点点,这就够了!