手记

译文:理解javaScript中this的另一种方法

本文为译文,原文为 A different way of understanding this in JavaScript
原文地址:http://2ality.com/2017/12/alternate-this.html

理解javaScript中this的另一种方法

在这篇博文中,我将通过不同以往的方法来解释javaScript中的this:假设箭头函数是真正的函数,而普通函数是方法的特殊构造。我认为这样会使得this更加容易理解---来试一试吧

1. 两种类型的函数

在这篇博文里,我们关注两种不同的函数:

普通函数:function () { }

箭头函数: ()=> { }

1.1 普通函数

通过以下的方式创建一个普通的函数:

function add(x, y) {  return x + y;

}

每个普通函数在被调用时都填充了一个隐式参数this,换句话说,下面的两个表达式是等价的(在严格模式下):

add(3,5);

add(underfined, 3, 5);

如果你嵌套不同函数,this是隐式的

function outer(){function inner() {console.log(this);  // underfined}console.log(this);      //outer;inner();

}

outer.call(‘outer‘);

内部的inner(),this并没有指向outer的this,因为inner()拥有自己的this。

如果this是一个显示参数,这段代码将变成如下所示:

function outer(_this){function inner(_this) {console.log(_this);  // underfined}console.log(_this);      //outer;inner(underfined);

}

outer.call(‘outer);

请注意inner()对outer中的this的影响就像变量在嵌套作用域中所启到的作用一样:

const _this = outer;

console.log(_this);        //‘outer’{const _this = undefined;

console.log(_this);    //  undefined}

由于普通函数总是存在着一个隐式参数this,因此‘方法’这个名字应该更适合它们。

1.2箭头函数

如下创建一个箭头函数(我使用了一个代码块以至于它更像一个函数声明)

const add = (x, y){const inner = () = > {return x+y;

}

}

如果你在一个普通函数里内部嵌套一个箭头函数,this 是不会受到影响的

function outer(){const inner = () => {console.log(this);  // outer}console.log(this);      //outer;inner();

}

outer.call(‘outer);

由于箭头函数的这种表现,我偶尔也把它称之为‘真正的函数’。

比起普通函数,箭头函数与大多数的编程语言中的函数更加相似。

请注意箭头函数的中的this不会因为使用了.call()而受影响的,或者换句话说,它在被创建的时候就已经被固定在箭头函数所包含的作用域里了,例如:

function ordinary() {const arrow = ()=> this;console.log(arrow.call(‘goodbye’));  //hello}

Ordinary.call(‘hello’);

1.3做为方法的普通函数

如果一个普通函数是一个对象的属性值,那么这个普通函数就变成了一个方法。

const obj = {prop: function () {}

}

访问一个对象的属性的方法之一是通过.点操作符,这个操作符有两种不同模式:

获取和设置属性:obj.prop

调用方法:obj.prop(x, y)

第二个等价于:

obj.prop.call(this, x, y)

可以看出,当普通函数被调用时this再一次地被填充进去了。

JavaScript里有特殊的,更便利的语法来定义方法:

const obj = {

prop() {}

}

2. 公共的缺陷

让我们透过刚刚所学的知识来看一看一些公共的缺陷。

2.1缺陷:访问回调中的this(Promises)

一旦异步函数cleanupAsync()执行完毕,请思考以下基于Promise的代码,在该代码中我们打印了“Done”。

// Inside a class or an object literal:PerformCleanup() {

CleanupAsync().then(function (){

  This.logStatus(‘Done’);  // (A)})

}

问题在于this.LogStatus()在执行到A行时就报错了,因为这时的this没有指向我们所预想的那个this。PerformCleanup()this受到了回调函数中的this的影响。换句话说:当我们想要使用一个箭头函数的时候使用了一个普通函数,如果我们改成下面的写法,那问题就解决了:

// Inside a class or an object literal:PerformCleanup() {

CleanupAsync().then( () => {

This.logStatus(‘Done’);

})

}

2.2缺陷:访问回调中的this(.map)

类似的,下面的代码也将会在A行中失效,因为回调又影响到了prefixName方法中的this。

// Inside a class or an object literal:prefixNames(names) {    return names.map(function (name) {        return this.company + ': ' + name; // (A)

    });

}

我们再一次使用箭头函数来修改一下:

// Inside a class or an object literal:prefixNames(names) {    return names.map(      name => this.company + ': ' + name);

}

2.3缺陷:使用方法作为回调

下面是一个UI组件的类

class UiComponent {    constructor(name) {        this.name = name;        const button = document.getElementById('myButton');

        button.addEventListener('click', this.handleClick); // (A)

    }

    handleClick() {        console.log('Clicked '+this.name); // (B)

    }

}

在A行中,UiComponent 注册了一个click事件处理程序。如果这个处理程序被触发,你会得到一个错误提示:

TypeError: Cannot read property 'name' of undefined

为什么呢?在A行,我们使用了一个正常的点操作符,而不是一个特殊的方法来调用这个点操作符,因此,存储在handleClick中的函数变成了处理程序,也就是说,大致发生了以下这些事情:

const handler = this.handleClick;

handler();    //相当于: handler.call(undefined);

结果,this.name就在B行处报错了。

那我们该怎么修正它呢?问题在于,调用方法的点运算符不仅仅是先读取属性然后调用结果的组合。它做得更多。因此,当我们抽出一个方法时,我们需要自己提供缺失的部分,并通过函数方法.bind()(line A)为this填充一个固定值:

class UiComponent {    constructor(name) {        this.name = name;        const button = document.getElementById('myButton');

        button.addEventListener(            'click', this.handleClick.bind(this)); // (A)

    }

    handleClick() {        console.log('Clicked '+this.name);

    }

}

现在,this是固定的了,并且不会因为使用了正常的函数来调用而受到影响了。

function returnThis() {    return this;

}const bound = returnThis.bind('hello');

bound(); // 'hello'bound.call(undefined); // 'hello'

3. 保持安全的规则

避免问题的最简单方法是避免使用普通函数,并且始终使用方法定义或箭头函数。

然而,我更倾向于函数声明的语法。函数提升在有些时候也是有用的。如果你不在里面引用this,你可以安全地使用它们。这里有一个ESLint规则,可以帮助你。

31.不要把this当做参数来使用

一些API通过this来提供参数信息。我不喜欢那样,因为它阻止了你使用箭头函数,并违背了最初提到的简单经验法则。

来看下面的一个例子:beforeEach()函数将API对象通过this传递给它的回调函数。

beforeEach(function () {    this.addMatchers({ // access API object

        toBeInRange: function (start, end) {

            ···

        }

    });

});

重写这个函数很容易:

beforeEach(api => {

    api.addMatchers({

        toBeInRange(start, end) {

            ···

        }

    });

});

4. 进一步阅读



作者:生物股长_pld
链接:https://www.jianshu.com/p/40238ef1b721


0人推荐
随时随地看视频
慕课网APP