跳至正文

JavaScript入门教程笔记(11)-this关键字

1 定义

this关键字是一个非常重要的语法点,不理解它的含义,大部分开发任务都很难完成。

无论什么场合,this总是返回一个对象。简单说,this就是属性或方法“当前”所在的对象。

var person = {
    name: 'mark',
    say: function() {
        return 'name: ' + this.name;
    }
};
person.say() // "name: mark"

上面代码中,由于this.name是在say方法中调用的,而say方法所在的当前对象是person,因此this指向person,而this. name 就是 person. name。

由于对象的属性可以赋给另一个对象,因此this的指向是可变的。

function f() {
    return 'hello: ' + this.name;
}

var A = {
    name: 'AAA',
    desc: f
};

var B = {
    name: 'BBB',
    desc: f
};

A.desc() // "hello: AAA"
B.desc() // "hello: BBB"

上面代码中,函数f内部使用了this,随着f所在对象的不同,this的指向也不同。

总结一下,JavaScript语言中一切皆对象,运行环境是对象,函数都是在某个对象之中运行,this就是函数运行时所在的对象(环境)。这本来是很清晰的概念,但是因为JavaScript支持运行环境的动态切换,所以没有办法事先确定this到底指向哪个对象,这才是最容易让人迷惑的地方。

2 实质

由于函数可以在不同的运行环境执行,需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指向函数当前的运行环境。

var f = function() {
    console.log(this.x);
}

var x = 1;
var obj = {
    f: f,
    x: 2,
};

f() // 1
obj.f() // 2

上面代码中,函数f在全局环境里执行,this.x指向全局环境的x,输出1;在obj环境里执行,this.x指向obj.x,输出2。

3 使用注意点

3.1 避免多层this

切勿在函数中包含多层this,因为this的指向不确定。

var obj = {
    f1: function() {
        console.log(this);
        var f2 = function() {
            console.log(this);
        }();
    }
}
obj.f1()
// Object
// Window

上面代码包含两层this,实际运行后,第一层this指向对象obj,第二层this指向全局对象。因为实际执行的类似于下面代码:

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

var obj = {
    f1: function() {
        console.log(this);
        var f2 = temp();
    }
}

简单的解决方法是用一个指向外层对象的变量来代替第二层this。

var obj = {
    f1: function() {
        console.log(this);
        var self = this; // 增加一个变量
        var f2 = function() {
            console.log(self);
        }();
    }
}
obj.f1()
// Object
// Object

上面代码定义了变量self,固定指向外层的this,就不会发生指向的改变了。

通过一个变量固定this的值,然后内层函数使用这个变量,是非常常见的做法,请务必掌握。

3.2 避免数组处理方法中的this

数组的map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。

var o = {
    v: 'hello',
    p: ['a1', 'a2'],
    f: function f() {
        this.p.forEach(function(item) {
            console.log(this.v + ' ' + item);
        });
    }
}
o.f()
// undefined a1
// undefined a2

上面forEach方法的回调函数中的this,跟上一段的多层this一样,内层this不指向外部,而是指向顶层对象。

解决这个问题的一个简单方法,就是前面提到的,使用中间变量固定this。

3.3 避免回调函数中的this

回调函数中的this往往会改变指向,最好避免使用。

为了解决这个问题,可以采用下面的一些方法对this进行绑定,使this固定指向某个对象,减小不确定性。

4 绑定this的方法

JavaScript提供了call、apply、bind三个方法,来切换/固定this的指向。

4.1 Function.prototype.call()

函数实例的call方法,可以指定this的运行环境(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。

call方法的参数,是一个对象。

var obj = {};
var f = function() {
    return this;
};
f() === window // true
f.call(obj) === obj // true

上面代码中,直接运行函数f时,this指向全局环境(window对象)。而call方法可以改变this指向对象obj,然后在对象obj的作用域中运行函数f。

如果call方法的参数为空、null和undefined,则默认传入全局对象。

var n = 123;
var obj = { n: 456 };

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

a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456

call方法还可以接受多个参数,后面的参数是函数调用时所需的参数。

function add(a, b) {
    return a + b;
}
add.call(this, 1, 2) // 3

4.2 Function.prototype.apply()

apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别是,它接收一个数组作为函数执行的参数,使用格式如下:

func.apply(thisValue, [arg1, arg2, ...])

原函数的参数,在call方法中必须一个个添加,但是在apply方法中,必须以数组形式添加。

function f(x, y) {
    console.log(x + y);
}

f.call(null, 1, 2) // 3
f.apply(null, [1, 2]) // 3

5.3 Function.prototype.bind()

bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。

var d = new Date();
d.getTime() 

var print = d.getTime;
print() // Error

上面代码中,print报错是因为getTime方法内部的this,绑定了Date对象的实例,赋值给变量print后,内部的this已经不指向Date对象的实例了。

bind方法可以解决这个问题

var print = d.getTime.bind(d);
print() // ok

上面代码中,bind将getTime方法内部的this绑定到d对象,因此就可以安全地将getTime赋值给其它变量了。

使用bind还可以将this绑定到其它对象。

var counter = {
    count: 0,
    inc: function() {
        this.count++;
    }
};

var obj = {
    count: 100
};

var func = counter.inc.bind(obj);
func();
obj.count // 101

上面代码中,bind将inc内部的this绑定到obj对象,因此调用func后,递增的就是obj内部的count属性。

使用bind方法有一些注意事项。

(1)每一次返回一个新函数

bind方法每运行一次,就会返回一个新函数,这可能产生一些问题。

node.addEventListener('click', obj.m.bind(obj));
node.removeEventListener('click', obj.m.bind(obj)); // Error

上面代码无法取消绑定,因为后一个bind返回的是一个匿名函数,和前一个add事件并不是同一个函数。

正确的定法是下面这样:

var listener = obj.m.bind(obj);
node.addEventListener('click', listener);
node.removeEventListener('click', listener);
(2)结合回调函数使用

回调函数是JavaScript最常用的模式之一,我们应用使用bind方法将目标函数固定到目标对象。

(3)结合call方法使用

以数组的slice方法为例。

[1, 2, 3].slice(0, 1) // [1]
等同于
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]

上面代码的两种调用方法,得到同样的结果。

注:本文适用于ES5规范,原始内容来自 JavaScript 教程,有修改。

标签:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注