JavaScript this 关键字

本文最后更新于:6 个月前

我接触过Python这门语言,Python中也是用this关键字实现了面向对象的某些概念。JavaScript中也有异曲同工之妙,所以见到this时我并不是很陌生,但是没想到JavaScript中的this的注意事项却比Python中的多得多,而且很让人头大。

JavaScript真是一门混乱的语言! 🥱

属性

指向对象

简单来说:this用来指代属性或方法当前所在的对象。

1
2
3
4
5
6
var A = {
name: '张三',
describe: function () {
return '姓名:'+ this.name;
}
};

有的语境下this可能不指代任何对象,如下函数中出现this关键字,但是在这个函数未被调用的情况下,this不指代任何对象。

1
2
3
var f = function () {
console.log(this.data);
}

这时候JavaScript中的this游离于对象之外,任意一个函数的定义中的都可能出现this关键字。this指向的对象取决于他调用的对象,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var A = {
name: '张三',
describe: function () {
return '姓名:'+ this.name;
}
};

var B = {
name: '李四'
};

B.describe = A.describe;
console.log(B.describe === A.describe);
// true
console.log(B.describe());
// "姓名:李四"

describe本为在对象A中定义的方法,将B对象的describe方法也指向内存中的相同位置,但因为调用其的对象不同,导致结果的不同。换句话也可以理解为他所处的环境(上下文),而环境的判断方式为——寻找this关键字的左邻最近对象。例如:

1
2
3
4
5
6
7
8
var f = function () {
console.log(`this`.x);
}
var obj1 = { data:"1",f: f };
var obj2 = { data:"2",f: f };

obj1.f(); //1
obj2.f(); //2

再看一个稍复杂的例子:

1
2
3
4
5
6
7
8
9
var a = {
p: 'Hello',
b: {
m: function() {
console.log(this.p);
}
}
};
a.b.m(); // undefinded

指向window

距离包含this的函数m的最左临近对象为b,所以this所指的对象为b,b中不存在属性p,所以输出undefined

但是也会出现不存在最左临近对象的情况,这时候this所指的就是顶层对象window

1
2
3
4
var f = function () {
console.log(this.x);
}
f(); //undefined

这时候可以采用“严格模式”,“严格模式”下函数中的this不允许被指为window,不然则会报错。

1
2
3
4
5
var f = function () {
'use strict'
console.log(this.x);
}
f(); //undefined

使用场合

  1. 全局环境

    1
    2
    3
    4
    5
    6
    this === window // true

    function f() {
    console.log(this === window);
    }
    f() // true
  2. 构造函数

    1
    2
    3
    var Obj = function (p) {
    this.p = p;
    };
  3. 对象中方法

    1
    2
    3
    4
    5
    6
    var obj ={
    foo: function () {
    console.log(this);
    }
    };
    obj.foo() // obj

注意

多层嵌套下的this

下面的例子有些复杂,我想了一会。

1
2
3
4
5
6
7
8
9
10
var o = {
f1: function () {
console.log(this);
var f2 = function () {
console.log(this);
}();
}
}

o.f1()

可以对其进行如下的变体:

1
2
3
4
5
6
7
8
9
10
11
var o = {
f1: function () {
console.log(this);
var f2 = function () {
console.log(this);
};
f2();
}
}

o.f1()

可以发现f2被声明定义之后调用,调用时它不存在最左临近对象,因此调用它的是window

换一种思路,f2被调用的情况不属于构造函数也不属于对象的方法,因此一定是全局环境。两种方法得出的结论是一致的。

数组中的this

与上面同理,调用其的是window,不展开说了。

1
2
3
4
5
6
7
8
9
10
11
12
13
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

回调函数中的this

1
2
3
4
5
6
7
var o = new Object();
o.f = function () {
console.log(this === o);
}

// jQuery 的写法
$('#button').on('click', o.f);

此时调用函数的对象为DOM元素,也即this的指向对象。

绑定this的方法

call()

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

call方法的参数,应该是一个对象。如果参数为空、nullundefined,则默认传入全局对象。

1
2
3
4
5
6
7
8
9
10
11
12
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方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入call方法。

1
2
3
4
5
6
var f = function () {
return this;
};

f.call(5)
// Number {[[PrimitiveValue]]: 5}

call方法还可以接受多个参数。call的第一个参数就是this所要指向的那个对象,后面的参数则是函数调用时所需的参数。

1
2
3
4
5
function add(a, b) {
return a + b;
}

add.call(this, 1, 2) // 3

apply()

apply方法的作用与call方法类似。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下:

1
2
3
4
5
6
function f(x, y){
console.log(x + y);
}

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

bind()

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

1
2
3
4
5
6
7
8
9
10
var counter = {
count: 0,
inc: function () {
this.count++;
}
};

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

bind()也可以接收多个参数,这些参数将会绑定原函数的参数。

注意&用法

  1. bind每次运行就返回一个新函数,所以下面的用法是无效的。

    1
    2
    element.addEventListener('click', o.m.bind(o));
    element.removeEventListener('click', o.m.bind(o));
  2. 与回调函数结合,解决this指向的问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var counter = {
    count: 0,
    inc: function () {
    'use strict';
    this.count++;
    }
    };

    function callIt(callback) {
    callback();
    }

    callIt(counter.inc.bind(counter));
    counter.count // 1

    若不这样做,callback调用时的this指向即为window


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!