涅槃重生(一):JS中this的那些事

前言

经历了一年多的各种尝试之后,最终还是决定回到程序员的行列中来。一年中的许许多多事情不是由我自己主动结束就是最终走向了失败幻灭。

从小开始教科书就教育我们做人不能三心二意、眼高手低,成功露出水面的诀窍也只能是他们在水面下苦苦构筑的巨大冰山。回想从一开始起,就是以工程化为目的,奉行拿来主义,大部分情况下不知道自己放置于工程中的部分代码所引用的库中究竟是用什么手法达到我想要的目的的。

故以此为契机重拾博客写作,记录我重新学习的全过程,计划中的学习内容当然不仅于前端。苦思冥想很久,决定以昨天面试官问的JS中的this问题作为系列博客的第一篇吧!

JS中为什么要有this

根据红宝书介绍,在函数的内部有两个特殊的对象: arguments 和 this,其中this引用的是函数据以执行的环境对象(也译为执行上下文)。我们来看一个红宝书中的例子:

//例一

window.fruit = "apple";
var a = {fruit: "orange"};

function say(){
    alert(this.fruit);
}

say();    //"apple"

a.say = say;
a.say();  //"orange"    

例子中的 say() 运行与全局域下,默认指向 window 对象,相当于执行 window.say(),所以这时候 this.fruit 指向 window.fruit。而 a.say() 执行时此时 say() 执行于a对象之中,所以 this 对象指向 a 对象,这个时候当然输出的就是 a.fruit 的值了。

通过这个例子我们已经简单了解了 this 是什么了,那么它是怎么在我们的日常编程中起到举足轻重的作用呢?

我们通过一个打印字符例子来看一看:

//例二

//实现打印字符
var str = "Hello"

function PrintA(){
    if(){
        console.log(this.str);
    }else{
        console.log("str not exist");
    }
}

PrintA();//"Hello"

我们通过调用 this 获取到了上下文对象,输出了其中 str 字段的值。这样我们的函数的返回值就不是固定的值了,而是由执行时候上下文的具体数据决定,大大提高了函数的灵活性!

This的具体指向判定

This 的具体指向一直是各路面试的重点问题,下面我带大家来捋一捋 this 指向的原理
这里有一个很重要的原理请大家务必谨记!

This的指向在创建时无法确定,只有具体调用时才能决定

This 的指向共有以下几种情况:
1、如果一个函数有 this,且未被上一级对象所调用,那么这个 this 指向顶层对象,一般为window。
2、如果一个函数有 this,且被上一级对象调用,那么这个 this 指向上一级对象。
3、如果一个函数有 this,这个函数被包含于多层对象中,尽管是被最外层对象所调用,但this仍指向上一级对象。

在理解这几种情况之前,需要先明确我们的函数本质上也是顶层对象的一个属性,我们常用的 alert 函数其实也是 window 对象的一个属性,完整写法应为 window.alert(),若 alert 中存在 this,那么很明显应该指向 window 对象。

在例一中已经有1、2的情况了,那么3又是什么情况会出现呢?

//例三

//本例 this指针指向上一级对象即 b,但 b中无 a属性
//故结果为 undefined
var sample = {
    a: 10,
    b: {
        fn: function() {
            console.log(this.a);
        }
    }
}

sample.b.fn();//undefined

以上举的都是比较简单的 this 的应用,下面我们来看看实际应用中 this 都是如何指向的。

不同调用方式的 This 指向

一般地,函数调用有以下几种方式:

  • 普通函数调用
  • 以方法的形式调用
  • 构造函数调用
  • 函数中return对象
  • 箭头函数调用(ES6)

下面来看看几种调用的具体例子

普通函数调用

function Normal(){
    this.number = 10;
    console.log(this); //window
    console.log(this.number);//10
}

Normal(); //window 10

以方法的形式调用

var str = "hello";
var obj = {
    str = "hi",
    fn: function(){
        console.log(this.str); 
    }
}

obj.fn(); //"hi"

构造函数调用

function Example(num){
    this.num = num;
}

var num = Example(10);

console.log(num.num); //undefined
console.log(window.num); //10

函数中含return一个对象
*NULL对象仍指向原函数实例

//此时this指向返回的对象
function Obj1(){
    this.user = "LiMing";
    console.log(this); //{user: "LiMing"}
    return {};
}

var a = new Obj1();
console.log(a.user); //undefined
function Obj2(){
    this.user = "LiMing";
    console.log(this);//{user: "LiMing"}
    return 1;
}

var a = new Obj2();
console.log(a.user); //LiMing
function Obj3(){
    this.user = "LiMing";
    console.log(this); //{user: "LiMing"}
    return undefined;
}

var a = new Obj3();
console.log(a.user); //LiMing

箭头函数调用

//箭头函数this始终指向其定义时的上下文对象
//箭头函数this一旦定义无法改变
function Person(){
    this.age = 0;       //此this指向全局
    setInterval(()=>{
        this.age++;     //此this指向构造函数的变量,所以等于指向全局
    },1000);
}

var person = new Person;

This 指向修改

call&apply
值得注意的是,call 方法和 apply 方法作用完全相同,只是接收的参数格式不同
fun.apply( this指向, [一个数组或类数组的带下标的集合])
fun.call( this指向, ...随便传)

来个例子

var obj1 = {name: "John"};
var obj2 = {name: "Clancy"};

this.name = "Jenny";

var getName = function(){
    console.log(this.name);
}

getName(); //Jenny
getName.call(obj1); //John
getName.apply(obj2); //Clancy

Function.prototype.bind()

bind方法会创建一个新函数,当调用这个新函数时,新函数会以创建他时的第一个参数为 this ,后面传入的参数当做是原函数的参数进行调用。
这是 API 上对于 bind 方法的解释,也就是用于改变函数内部的 this 指向。下面来个例子

var name = "John";

function Person(name){
    this.name = name;
    this.sayName = function(){
        setTimeout(function(){
            console.log(this.name);
        },bind(this),500) //此时该匿名函数this始终指向Person对象
    }
} 

var person = new Person("Clancy");
person.sayName(); //"Clancy"

Eval方法
该函数执行时,this 会绑定到当前作用域的对象上
这个方法由于性能问题所以基本不考虑了,但是还是以防万一面试的时候被问到

var name = "John";
var person = function(){
    name: "Clancy",
    sayName: function(){
        eval(this.name);
    }
}

person.sayName(); //"Clancy"
var a = person.sayName();
a(); //"John"


以上便是关于 this 的所有总结了,其中有些地方还是需要细细琢磨。
最近获得的最大的人生经验,就是要温故而知新,很可能有些你学过的知识你并没有真正的掌握。
所以偶尔复习一下才是上上之策。


Clancy Lin
2019.1.8
I can be whatever I want to be