prettyEcho / deep-js

show you the most beautiful side of JavaScript
151 stars 10 forks source link

this是个淘气鬼 #5

Open prettyEcho opened 6 years ago

prettyEcho commented 6 years ago

BY 张建成(prettyEcho@github)

除非另行注明,页面上所有内容采用知识共享-署名(CC BY 2.5 AU)协议共享

🐬🐬 欢迎评论和star 🐳🐳

先说个重要的题外话:maoyanMovie是我的一个前后端分离的开源项目,该项目模仿猫眼电影移动端 APP(半模仿吧,我加了一些后端逻辑在里面,嘿嘿),涉及到前端、后端、数据库、部署一系列技术栈,对于前端来说,也算是一个全栈(暂不考虑安卓、iOS、AI)的实践吧。如果有哪位小伙伴感兴趣,给个 star🙏 ➡️ maoyanMovie

好了,还是言归正传吧。

如题,this 就是一个淘气鬼。

对此我想很多小伙伴都不会反对我,对吧?😘😘😘

在工作中我们经常被它搞得晕头转向的,一遇到 this,首先闭目自问:TND 指向谁。明明觉得它指向 a,怎么偏偏就指向了 b。真是无语。😓😓😓

就这种情况,我是不能忍的,小样,竟敢这样戏耍我们玩 JS 的人,看我不整蒙你。

于是决心写这样一篇文章,分析 this 到底是什么,所有 this 指向的场景以及如何使用 this,帮助大家彻彻底底的搞定 this,希望大家多多支持!!!

探个究竟

关于 this,我们放在嘴边的话就是:this 指向谁。很少有小伙伴会想到,this 到底是什么指向指的是什么,以及什么时候确定的指向

此时,给自己 30 秒钟的时间,想一想自己是否清楚我提的这 3 个问题。

如果觉得自己对这些问题很清楚,我觉得应该为自己小小的高兴一下。

好了,我来回答下。

变来变去不累吗

《JavaScript 高级程序设计》中说:this 引用的是函数据以执行的环境对象。因此正是由于执行的环境对象飘忽不定,才导致 this 引用的对象“变来变去”。

一、函数调用
二、非函数调用
三、 new 操作符下的 this

new 把构造函数中的 this 指向新创建的实例对象(如果不清楚可以去研究下 new 操作符具体都干了什么) function People(name) { this.name = name; } People.prototype.getName = function() { return this.name; } var person = new People('echo'); console.log( person.name ); // echo this -> person

四、特殊情况
五、箭头函数中的 this

箭头函数中的 this 可就神奇了,来来,我们一起看下。准确的说,箭头中并不存在 this 上下文(真懒,嘿嘿,和我一样),它懒得自己生成 this 上下文,直接捕获父级作用域中的 this。但是懒也有懒的好处,因为在定义的时候我们就能确定箭头函数中的 this(词法作用域),而且也不会受到严格模式、call、apply、bind 的限制。说的什么鬼东西?行,我们代码分析。

var a = 10;
var obj = {
    a: 20,
        fn: () => {
        var a = 30;
        console.log( this.a );
    }
}

obj.fn();

箭头函数 fn 向上寻找 this,首先遇到 obj,发现它不能构成作用域,也不会存在 this,直接跳过,此时遇到全局 window,发现其中的 this,真好,拿过来,那么我们结果自然就是 10. 我们想让结果是 20,怎么办,继续看。

var a = 10;
var obj = {
    a: 20,
    fn: function() {
        (() => {
            var a = 30;
            console.log( this.a );
        })();
    }
}

obj.fn();

箭头函数开始向上寻找 this,遇到函数作用域 fn,发现其中有 this 上下文,今天真走运,刚出门就找到了,直接拿过来用,哈哈。所以自然而然箭头函数中的 this 就是函数 fn 中的 this,通过上面的知识,我们知道 fn 中的 this 指向 obj,因此自然而然输出 20。很简单是吧?那么你分析下,下面这题输出什么?

var a = 10;
var obj = {
    a: 20,
    fn: function() {
        (() => {
            var a = 30;
            (() => {
                var a = 40;
                (() => {
                    console.log( this.a );
                })()
            })();
        })();
    }
}

obj.fn();

如果你的结果是 20,那么恭喜你,箭头函数中的 this 问题,对你来说已经是小菜一碟了。

总结

好累,this 指向会出现这么多种情况,谁记得住,别急,所有你能想到的我都为你准备好了。(请叫我雷锋)

this指向总结

你皮,任你皮

你不是淘气吗?你不是变来变去吗?

看我不找个绳子绑住你。

于是乎,JavaScript 给了我们三条绳子。

我们认识下它们吧。

一、call、apply

这两条绳子都可以把 this 限制到固定的对象上,只是传递参数的方式不一样。

继续看代码:

var a = 10;

function foo() {
    console.log( this.a );
}

var obj = {
    a: 20
}

foo.call(obj); // 20 this -> obj
foo.apply(obj); // 20 this -> obj

这样我们就把 this 硬生生的绑定到 obj 上了。

接下来看下两个函数的区别

function sum(a, b) {
    console.log(this.c + a + b);
}

var obj = {
    c: 10
}

sum.call(obj, 20, 30); // 60
sum.apply(obj, [20, 30]); // 60

简单说下我们实际应用

function foo(a, b){
    let arr = [].slice.call(arguments);
    console.log( arr );
}

foo(1,2);

补充 2 种方法:

  1. Array.from(arguments)
  2. [...arguments]
var People = function(name) {
    this.name = name;
}

var Programmer = function(name) {
    People.call(this, name)
}

Programmer.prototype.getName = function() {
    console.log( `${this.name} is a programmer!` );
}

var person = new Programmer('echo');

person.getName(); // echo is a programmer!

二、bind

bind 方法只是把 this 绑定到固定对象上,不会传递参数。

var people = {
    name: 'echo',
    getName() {
        setTimeout(function(){
            console.log(this.name);
        }.bind(this), 1000)
    }
}

peopler.getName(); // echo

当然我们也可以通过声明一个临时变量来暂存 this,利用作用域链实现 this 的处理,这种方法很简单,这里不给出例子了。

唯一的不足

这篇文章我认为唯一不足的地方就是,我没有涉及到 this 在 Node 环境下的情况。起初我的确把 Node 环境考虑进来了,写着写着发现出现的情况太多了,为了不给大家营造一个十分混乱的局面,我决定把所有关于在 Node 中的情况放弃了。

在这深表抱歉 !🙏🙏🙏

🤗 如果你觉得写的还不错,请关注我的 github 吧,让我们一起成长。。。

hushiking commented 5 years ago

写的很详细

FoxChouChou commented 4 years ago

怎么不继续写文章了!太可惜了