Mmear's 😘.

JavaScript中的this

字数统计: 1.5k阅读时长: 6 min
2018/04/02 Share

简介

this指向了一个对象,但并不是指向调用的函数本身,也不是指向一个”作用域”,this的定义不是在函数定义时确定,它的定义取决于函数在哪里被调用,怎么被调用。this的绑定规则一般有四条:

默认绑定

非严格模式下,若在函数调用前不加任何修饰,this默认绑定到全局对象,其他规则无法确定时,也可以运用这条规则:

1
2
3
4
5
6
var a = 6;
var func = function() {
return this.a;
};
console.log(func());//绑定至全局对象window
//严格模式(strict mode)下会返回undefined

隐式绑定

如果函数被一个对象调用,也就是说成为了一个对象方法并被对象访问后,this将绑定到这个对象;或者说,函数被这个对象”拥有”了:

1
2
3
4
5
6
7
8
9
var a = 6;
var func = function() {
return this.a;
};
var foo = {
func: func, //添加至对象属性成为函数方法
a: 2
};
console.log(foo.func());//2

绑定丢失

隐式绑定存在的一个隐患是绑定丢失,它会丢失调用它的对象,导致应用默认规则,this将绑定到全局对象上:

1
2
3
4
5
6
7
8
9
10
var a = 6;
var func = function() {
return this.a;
};
var foo = {
func: func, //添加至对象属性成为函数方法
a: 2
};
var bar = foo.func;
console.log(bar());//6 绑定丢失了

虽然bar是foo.func的一个引用,但它引用的是func函数本身,所以此时调用bar和普通地调用func一样,因此应用了默认绑定。还有一种情况发生在将函数作为回调函数传递时:

1
2
3
4
5
6
7
8
9
10
11
12
var a = 6;
var func = function() {
return this.a;
};
var foo = {
func: func, //添加至对象属性成为函数方法
a: 2
};
var bar = function(fn) {
fn();
};
bar(foo.func); //6 绑定又丢失了

参数传递其实就是一种隐式的赋值,因为JS中函数传参只有值传递,fn其实是一个对func的引用,所以绑定还是丢失了。这种情况在JS内置函数中(比如setTimeout)中经常出现:

1
2
3
4
5
6
7
8
9
var a = 6;
var func = function() {
return this.a;
};
var foo = {
func: func, //添加至对象属性成为函数方法
a: 2
};
setTimeOut(foo.func, 1000); //6

操作DOM元素时,经常发生这种——处理器回调函数绑定丢失的错误,有时这不一定是坏事,但大多数时候需要显式将对象绑定到this上;

显式绑定

通过Function.prototype中的callapply可以将函数的this强制绑定到指定的对象上:

1
2
3
4
5
6
7
8
var a = 6;
var func = function() {
return this.a;
};
var foo = {
a: 2
};
func.call(foo); //2

call 和 apply的区别在于第二个参数的不同,apply接收一个数组并自动将其展开,call只能接收独立的多个参数.

但这并不能解决我们之前提到的绑定丢失的问题,除非我们把要绑定的对象和函数一起传进去,这就是bind函数存在的意义:

1
2
3
4
5
6
7
8
9
10
11
12
13
//创建一个简单的bind函数
var a = 6;
var bind = function(fn, obj) {
return function() {
return fn.call(obj);
};
};
var obj = {a: 1};
var func = function() {
return this.a;
}
var bar = bind(func, obj);
console.log(bar()); //1

因为这个函数十分常用,所以ES5在Function.prototype中实现了这个bind方法,它返回一个硬绑定的新函数,这个函数会将你指定的参数设置为this的上下文并调用原始函数:

1
2
3
4
5
6
7
8
9
var add = function(a, b) {
return (this.num + a + b);
}
var obj = {
num: 6;
}
var add_in = add.bind(obj, 3);//传入一个对象和预设参数值a,柯里化
var result = add_in(4);//传入参数b
console.log(result); //6 + 3 + 4 = 13

new绑定

JavaScript中的new操作符看起来和普通的面向对象语言一样,使用new操作符可以调用一个”构造函数”并返回一个实例;实际上,在JS中,所有函数都可以使用new操作符,这个操作不会实例化什么类,它只是单纯地执行一系列操作的集合,然后返回一个对象罢了;

使用new操作符来调用函数,会自动执行下面的操作:

  1. 创建一个新对象;
  2. 对这个对象进行[[prototype]]连接,指向函数原型;
  3. 将这个新对象绑定到函数的this;
  4. 如果这个函数没有返回其他对象,那么这个新对象将会被返回;
1
2
3
4
5
var func = function() {
this.a = 2;
}
var obj = new func();
console.log(obj.a); //2

bind和softbind

记一下这两个函数的详细功能实现,softBind可以改变默认规则的绑定对象,还保留了隐式绑定和显式绑定的能力,比bind更灵活:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Function.prototype.bind = function(oThis) {
if(typeof this !== 'function') {
throw new TypeError(
"Function.prototype.bind - what is trying" + " to be found is not callable"
);
}
var aArgs = Array.prototype.slice.call(arguments, 1); //柯里化
var fn = this; //要绑定的函数
return function() {
aArgs = aArgs.concat(Array.prototype.slice.call(arguments));
fn.apply(oThis, aArgs);
};
};

/*-----------------------------softBind-----------------------------------------*/
Function.prototype.softBind = function(obj) {
var aArgs = Array.prototype.slice.call(arguments, 1); //柯里化
var fn = this;
var bound = function() {
aArgs = aArgs.concat(Array.prototype.slice.call(arguments));
return fn.apply(
//this的含义根据softBind函数的调用规则决定
!this || this === (window || global) ? obj : this
),
aArgs
);
}
bound.prototype = Object.create(fn.prototype); //防备将来对原函数可能的属性查找
reurn bound;
};

softBind会对指定的函数进行封装,执行时先检查当前的this,如果this绑定到全局对象或者undefined(即默认规则),就会把指定的默认对象绑定到this,否则不会修改this;此外,这段代码还支持可选的柯里化;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var foo = function() {
return this.name;
}
obj1 = {name: "obj1"};
obj2 = {name: "obj2"};
obj3 = {name: "obj3"};

var default = foo.softBind(obj1); //将obj1设为默认对象
default(); //obj1,此时bound中检查到this为全局对象,改变this为obj1
obj2.foo = foo.softBind(obj);
obj2.foo();//obj2,此时bound中检查到this为obj2,this不改变 (隐式绑定)

default.call(obj3);//obj3 (显式绑定)
setTimeout(obj2.foo, 100); //obj1 (绑定丢失的情况)
CATALOG
  1. 1. 简介
  2. 2. 默认绑定
  3. 3. 隐式绑定
    1. 3.1. 绑定丢失
  4. 4. 显式绑定
  5. 5. new绑定
  6. 6. bind和softbind