资讯专栏INFORMATION COLUMN

Know this, use this! (总结 this 的常见用法)

zorro / 1905人阅读

摘要:而当做普通函数调用的话,实际上即第种情况下,对函数普通调用,此时的指向这是正常情况下,会正确返回并且指向该对象,但是在构造函数当中,如果返回了一个对象,那么会指向返回的那个对象。

this应该是一个讨论了很久的话题了。其中,关于this的文章,在很多的博客当中也有很多介绍,但是,以前我都是一知半解的去了解它,就是看博客当中,只介绍了一些情况下的 this 的使用方式,但是也并没有自己去做过总结。刚好是在掘金当中有看到一篇关于this的一些详细文章,文末会附上链接以及英文原文,这里纯粹是自己进行一个总结,以后方便自己进行回顾以及加深印象。希望这篇文章对于你了解this有一定的帮助,文末还有一些练习题噢~希望真的对你们有帮助。(因为写项目过程中,一直被 this 坑过,却找了很久的 bug ,我真是 乐了狗)

在了解this之前,相信大家都应该会知道作用域这个知识点的存在,函数在创建之后,会构建自己的执行环境以及作用域,这是一开始就确定了。但是实际的上下文(context)环境,也可以理解为就是this,它是动态确定的,即在函数运行时才确定this所指向的对象,而非声明时所指向的对象。

关于this,总结起来,主要有以下几个途径能够被运用到。

1 对象方法中调用this

如果函数被当中对象的一个方法进行调用,则this值指向该对象。

var person = {
    name: "Alice",
    sayName: function() {
        alert("welcome " + this.name);
    }
}

person.sayName();    // this == person, alert: "welcome Alice"

在这里,函数的this指向该对象(即 person);但是有一点需要注意,就是当对象的方法被赋予给一个变量时,其则变为了函数触发,此时的this为 window 或者 undefined(严格模式下),如下:

var name = "Bob";
var person;    // 即上面的定义,此不拓展详细,直接使用

var say = person.sayName;    // this == window || undefined
say();    // "welcome Bob" || throw an error: Cannot read property "name" of undefined(...)
2 函数内部使用

在函数内部当中使用了 this,即函数被当做方法使用,不同于 1 当中作为对象的方法使用,此时调用,是在全局作用域下进行调用,即在window下进行调用,由定义可以知道,在全局作用域下声明一个函数,其自动加为window的一个属性。this此时名正言顺的会指向window,严格模式下为 undefined

function sayThis() {
    alert(this == window);    // true
}

结合第一点,函数作为对象的一个方法使用,这里存在一个小坑,即闭包,啥是闭包,这个在这里就不扯开了,最简单的理解就是 Function that returns function,如果不理解什么是闭包的话,可以去翻翻 《JavaScript 高级程序设计》第七章关于闭包的相关内容。第一点当中存在一个小坑,就是将对象的方法赋予给一个变量的时候,其变为函数触发,此时的 this 实际上是指向 window(非严格模式)。

那么,当函数中返回一个函数,此时在对象当中调用该方法,其就相当于是函数触发,此时的 this,在不做任何上下文绑定的前提之下,其指向 window(非严格模式)。

var name = "Bob",
    person = {
        name: "Alice",
        sayName: function() {
            console.log(this === person);    // true
            return function() {
                console.log(this === person);    // false
                console.log(this === window);    // true
                console.log(this.name);          // Bob
            };
        }
    };

person.sayName()();

当然,要解决这个问题的方法,很简单,就是给他绑定一个上下文。

var name = "Bob",
    person = {
        name: "Alice",
        sayName: function() {
            console.log(this === person);    // true
            return function() {
                console.log(this === person);    // true
                console.log(this === window);    // false
                console.log(this.name);          // Alice
            }.bind(this);
        }
    };

person.sayName()();
3 new 当中进行使用

我们知道在使用 new 方法创建对象的时候,会经过如下这些个过程:

创建对象,将 this 值赋予新的对象

调用构造函数,为 this 添加属性和方法

返回 this 给当前的对象

function Person(name, age) {
    this.name = name;
    this.age = age;
}

var person1 = new Person("Alice", 29);
console.log(person1.name);    // Alice

这里要记得使用 new 运算符,否则,其只能算是普通的调用,而不是创建一个新的实例对象。而当做普通函数调用的话,实际上即 第 2 种情况下,对函数普通调用,此时的 this 指向 window

function Person(name, age) {
    this.name = name;
    this.age = age;
    return this;
}

var person1 = Person("Alice", 29);
console.log(person1.name);    // Alice
console.log(window.name);     // Alice
console.log(person1 === window);    // true

这是正常情况下,this 会正确返回并且指向该对象,但是在构造函数当中,如果返回了一个对象,那么 this 会指向返回的那个对象。

function Person(name, age) {
    this.name = name;
    this.age = age;
    return {
        name: "Bob"
    };
}

var person1 = new Person("Alice");
console.log(person1.name);    // Bob
console.log(person1.age);     // undefined

题外话,类似的,联想到 var a = new Person(),则 a instanceof Person一定返回 true吗?留给你们想一想咯。

4 使用 callapplybind 改变 this

在引用类型 Function当中,函数存在两个方法属性,callapply,在 ECMAScript5当中,加入了 bind 方法。题外话,他们三者区别,应该都知道了吧,不知道的加紧补习呀。

var name = "Bob";
var person = {
    name: "Alice",
    age: 29
}

function sayName() {
    console.log(this.name);
}

sayName.call(person);    // Alice

这里是使用了 call 方法来改变了 this的执行环境,至于使用 apply,效果一样,只是二者差别在于传入参数的不同。

func.call(context, arg1, arg2, ...)
func.apply(context, [arg1, arg2, ...])

使用 bind 方法进行上下文的改变,bind 方法与 callapply有着本质的不同,其不同点是,bind()函数返回的是一个新的函数,即方法,而后两者则都是立即执行函数,使用的时候即调用了该函数,返回方法操作的结果。

并且,使用 bind()方法创建的 上下文,其为永久的上下文环境,不可修改,即使是使用 call 或者 apply方法,也无法修改 this 所指向的值。

var name = "Bob";
var person = {
    name: "Alice",
    age: 29
}

function sayName() {
    console.log(this.name);
}

var say = sayName.bind(person);
say();        // Alice
sayName();    // Bob
5 箭头函数

箭头函数并不创建其自身的上下文,其上下文 this,取决于其在定义时的外部函数。

并且,箭头函数拥有静态的上下文,即一次绑定之后,便不可再修改,即使是用了 第 4 种用途当中的改变上下文的方法,也不为之动容。

var num = [1, 2, 3];

(function() {
    var showNumber = () => {
        console.log(this === num);    // true
        console.log(this);            // [1, 2, 3]
    }
    console.log(this === num);        // true
    showNumber();                     // true && [1, 2, 3]
    showNumber.call([1, 2]);          // true && [1, 2, 3]
    showNumber.apply([1, 2]);         // true && [1, 2, 3]
    showNumber.bind([1, 2])();        // true && [1, 2, 3]
}).call(num);

由于箭头函数的外部决定上下文以及静态上下文等的特性,不太建议使用箭头函数在全局环境下来定义方法,因为不能通过其他方法改变其上下文。这很蛋疼。

function Period (hours, minutes) {  
    this.hours = hours;
    this.minutes = minutes;
}
Period.prototype.format = () => {  
    console.log(this === window);    // => true
    return this.hours + " hours and " + this.minutes + " minutes";
};
var walkPeriod = new Period(2, 30);  
console.log(walkPeriod.hours);
walkPeriod.format();    // => "undefined hours and undefined minutes"

此时的 this 实际上是指向了 window,所以 this.hours 和 this.minutes实际上没有声明的,故为 undefined

在全局环境下,还是选用 函数表达式 来进行函数的定义,可以保证正确的上下文环境

function Period (hours, minutes) {  
    this.hours = hours;
    this.minutes = minutes;
}
Period.prototype.format = function() {  
    console.log(this === walkPeriod);    // => true
    return this.hours + " hours and " + this.minutes + " minutes";
};
var walkPeriod = new Period(2, 30);  
walkPeriod.format(); // "2 hours and 30 minutes"
练习
// 练习1
var func = (function(a) {
    this.a = a;
    return function(a) {
        a += this.a;
        return a;
    }
})(function(a, b) {
    return a;
}(1, 2))

func(4) // ?

// 练习2
var x = 10,
    foo = {
        x: 20,
        bar: function() {
            var x = 30;
            return this.x;
        }
    }

console.log(foo.bar());
console.log((foo.bar)());
console.log((foo.bar = foo.bar)());
console.log((foo.bar, foo.bar)());

希望看完这篇文章,这两个练习题,你是能够做出来的呀~ 好好分析一下呗。如果不确定的话,可以在留言板上,咱们相互讨论一下呀~

参考

JavaScript This之谜 (第二个参考的译文)

Gentle explanation of "this" keyword in JavaScript

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87892.html

相关文章

  • JavaScript 原型系统变迁,以及 ES6 class

    摘要:一般我们对这种构造函数命名都会采用,并把它称呼为类,这不仅是为了跟的理念保持一致,也是因为的内建类也是这种命名。由生成的对象,其是。这是标准的规定。本文的主题是原型系统的变迁,所以并没有涉及和对原型链的影响。 概述 JavaScript 的原型系统是最初就有的语言设计。但随着 ES 标准的进化和新特性的添加。它也一直在不停进化。这篇文章的目的就是梳理一下早期到 ES5 和现在 ES6,...

    chuyao 评论0 收藏0
  • 快速掌握JavaScript面试基础知识(二)

    摘要:第一部分请点击快速掌握面试基础知识一闭包闭包由一个函数以及该函数定义是所在的环境组成。当匿名函数执行的时候,的值为。这个问题可以改用后面会介绍方法来解决,通过对每一个匿名函数构建独立的外部作用域来实现。 译者按: 总结了大量JavaScript基本知识点,很有用! 原文: The Definitive JavaScript Handbook for your next develope...

    fyber 评论0 收藏0
  • [翻译]You Don't Know JS: this & Object Prot

    摘要:引用是从匿名函数内部引用自身的唯一方法,不过,最好的方法是避免使用匿名函数,至少在那些需要引用自身的时候,使用命名函数或者表达式。 [翻译]Chapter1 this or that 第一次翻译,翻译的不好,已经再尽全力s去翻译了,如果哪里看不明点,请出门左转下边原文地址 英文原文点击这里 javascript中最令人困惑的东西就是this关键字,它在每个函数作用域中都会自动定义的一个...

    mingzhong 评论0 收藏0
  • 魔幻语言 JavaScript 系列之 call、bind 以及上下文

    摘要:那么,它到底是如何工作的呢让我们从一种更简单的实现开始实际上这种实现代码更短,并且更易读是函数原型中的一个函数,它调用函数,使用第一个参数作为参数,并传递剩余参数作为被调用函数的参数。 原文:The Most Clever Line of JavaScript 作者:Seva Zaikov 原文 最近 一个朋友 发给我一段非常有趣的 JavaScript 代码,是他在某个 开源库中...

    cuieney 评论0 收藏0
  • 《You Don't Know JS》阅读理解——this

    摘要:运行规则根据的运作原理,我们可以看到,的值和调用栈通过哪些函数的调用运行到调用当前函数的过程以及如何被调用有关。 1. this的诞生 假设我们有一个speak函数,通过this的运行机制,当使用不同的方法调用它时,我们可以灵活的输出不同的name。 var me = {name: me}; function speak() { console.log(this.name); }...

    tianren124 评论0 收藏0

发表评论

0条评论

zorro

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<