简介
this指向了一个对象,但并不是指向调用的函数本身,也不是指向一个”作用域”,this的定义不是在函数定义时确定,它的定义取决于函数在哪里被调用,怎么被调用。this的绑定规则一般有四条:
默认绑定
非严格模式下,若在函数调用前不加任何修饰,this默认绑定到全局对象,其他规则无法确定时,也可以运用这条规则:1
2
3
4
5
6var a = 6;
var func = function() {
return this.a;
};
console.log(func());//绑定至全局对象window
//严格模式(strict mode)下会返回undefined
隐式绑定
如果函数被一个对象调用,也就是说成为了一个对象方法并被对象访问后,this将绑定到这个对象;或者说,函数被这个对象”拥有”了:1
2
3
4
5
6
7
8
9var 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
10var 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
12var 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
9var a = 6;
var func = function() {
return this.a;
};
var foo = {
func: func, //添加至对象属性成为函数方法
a: 2
};
setTimeOut(foo.func, 1000); //6
在操作DOM元素时,经常发生这种——处理器回调函数绑定丢失的错误,有时这不一定是坏事,但大多数时候需要显式将对象绑定到this上;
显式绑定
通过Function.prototype
中的call
和apply
可以将函数的this强制绑定到指定的对象上:1
2
3
4
5
6
7
8var 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
9var 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操作符来调用函数,会自动执行下面的操作:
- 创建一个新对象;
- 对这个对象进行
[[prototype]]
连接,指向函数原型; - 将这个新对象绑定到函数的this;
- 如果这个函数没有返回其他对象,那么这个新对象将会被返回;
1 | var func = function() { |
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
30Function.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 | var foo = function() { |