this
关于this
,其实this
是提供了一种更优雅的方式来隐式“传递”一个对象引用。灵活的应用this
,可让咱们在写内容较多的代码时,不会显得愈来愈混乱。bash
this
根据英语的语法角度来推断的话,this
能够理解为指向函数自身。可是在实际中并非如此。app
function foo(num) {
console.log( "foo: " + num );
// 记录 foo 被调用的次数
this.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
if (i > 5) {
foo( i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log( foo.count );// 0
console.log(count); //NaN
复制代码
上述代码中,咱们企图用this.count
指向foo
函数自己,以此在其做用域建立一个count
属性。可是是代码的实际结果告诉咱们,这样写并不能作到咱们所想要的结果。函数
这种写法,最后会使代码在无心中建立了一个全局变量count
,其值为NaN
。oop
若是要从函数对象内部引用其自身,只使用
this
是不够的。通常而言,须要一个指向函数对象的词法标识符来引用它。ui
对于上述状况,其实有许多解决方式。可是既然咱们的主题是this
,那就用this
的方式来解决。this
function foo(num) {
console.log( "foo: " + num );
// 记录 foo 被调用的次数
// 注意,在当前的调用方式下(参见下方代码),this 确实指向 foo
this.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
if (i > 5) {
// 使用 call(..) 能够确保 this 指向函数对象 foo 自己
foo.call( foo, i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log( foo.count ); // 4
复制代码
这个方式,是强制this
指向foo
函数对象。编码
this
的做用域对于this
,还有一种误解。就是this
指向函数的做用域。spa
可是实际上,this
在任何状况下都不指向函数的词法做用域。prototype
举个例子:code
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo(); // ReferenceError: a is not defined
复制代码
上述方法,其实在有必定代码基础的人眼里,会以为很奇怪。由于咱们要调用bar()
方法的话,这个this
其实是多余的。
而且,this
这个操做并不能跨做用域引用变量。
切记,不要将
this
和词法做用域混合使用。
What is 'this'
在标题上玩了一下英语的谐音梗,哈哈哈,但愿不要介意。
上面讲述了那些以后,咱们须要了解到的是,this
究竟是一个什么样的机制。
其实,this
的绑定和函数声明的位置,没有半毛钱关系,半分钱也没有。它只取决于函数的调用方式。
当一个函数被调用时,会建立一个执行上下文。它会包含函数在哪里被调用、调用方法和传参等信息。this
就是其中的一个属性,在函数执行过程当中会用到。
this
的全面解析在了解this
的绑定规则以前,咱们须要知道了解调用位置。
调用位置实际上就是指咱们调用对象的词法标识符的位置。
this
的绑定规则有四条。
最经常使用的函数调用类型:独立函数调用。这种方法就可使用默认绑定。
function foo(){
console.log(this.a);
}
var a = 2;
foo(); //2
复制代码
咱们能够发现,咱们在调用foo()
时,其内部的this
指向了全局对象,这是由于应用了this
的默认绑定。
代码中,
foo()
是直接使用不带任何修饰的函数引用进行调用,所以只能使用默认绑定,没法应用其余规则。
须要注意的是,上述状况只有在非严格模式下 使用。
function foo() {
"use strict";
console.log( this.a );
}
var a = 2;
foo(); // TypeError: this is undefined
复制代码
隐式的使用,须要考虑调用位置是否有上下文对象。
function foo(){
console.log(this.a);
}
var obj = {
a:2,
foo:foo
};
obj.foo();
复制代码
咱们在使用了obj.foo()
的方式来调用函数时,函数foo()
其实不属于obj
对象。可是这种方式,让函数的落脚点在obj
对象内,this
指向的是obj
对象内,所以能够调用到obj
内部的变量。
隐式丢失 在使用this
绑定的时候,有个常见的问题,就是被隐式绑定的函数会丢失绑定对象。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global 复制代码
上述代码,在使用过程当中,使用bar
引用了obj.foo
,可是实际上,引用的仍是函数自己。因此,此时 bar()
实际上是一个不带任何修饰的函数调用 ,所以应用了默认绑定。
还有另外一种状况:
function foo() {
console.log( this.a );
}
function doFoo(fn) {
// fn 其实引用的是 foo
fn(); // <-- 调用位置!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // a 是全局对象的属性
doFoo( obj.foo ); // "oops, global"
复制代码
咱们在使用
this
的时候,应该严格注意避开相似状况。由于咱们没法控制回调函数的执行方式,从而致使没法正确的使用咱们想要使用的绑定方式。
JavaScript
中的“全部”函数都有一些有用的特性,能够用来解决这个问题。具体点说,可使用函数的 call(..)
和apply(..)
方法。严格来讲,JavaScript
的宿主环境有时会提供一些很是特殊的函数,它们并无这两个方法。可是这样的函数很是罕见,JavaScript
提供的绝大多数函数以及你本身建立的全部函数均可以使用 call(..)
和 apply(..)
方法。
显示绑定主要分为两种。
硬绑定 硬绑定其实就是使用 call(..)
和 apply(..)
方法。
call
:
function foo() {
console.log( this.a );
}
var obj = {
a:2
};
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// 硬绑定的 bar 不可能再修改它的 this
bar.call( window ); // 2
复制代码
apply
:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a:2
};
var bar = function() {
return foo.apply( obj, arguments );
};
var b = bar( 3 ); // 2 3
console.log( b ); // 5
复制代码
因为硬绑定是一种很是经常使用的模式,因此在
ES5
中提供了内置的方法Function.prototype. bind
,它的用法以下:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a:2
};
var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5
复制代码
bind(..)
会返回一个硬编码的新函数,它会把参数设置为 this
的上下文并调用原始函数。
API
调用的上下文第三方库的许多函数,以及JavaScript
语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,一般被称为“上下文”(context
),其做用和 bind(..)
同样,确保你的回调函数使用指定的 this
。
实际上该种方法也是经过
call()
或者apply()
来实现的显式绑定,只不过不须要咱们本身来写。
在传统的面向类的语言中,“构造函数”是类中的一些特殊方法,使用 new
初始化类时会 调用类中的构造函数。一般的形式是这样的:
something = new MyClass(..);
复制代码
JavaScript
也有一个 new
操做符,使用方法看起来也和那些面向类的语言同样,绝大多数开发者都认为 JavaScript
中 new
的机制也和那些语言同样。然而,JavaScript
中 new
的机制实际上和面向类的语言彻底不一样。
实际上, 在
JavaScript
中并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
使用 new
来调用函数,或者说发生构造函数调用时,会自动执行下面的操做。
this
。new
表达式中的函数调用会自动返回这个新对象。function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
复制代码
使用 new
来调用 foo(..)
时,咱们会构造一个新对象并把它绑定到 foo(..)
调用中的 this
上。 new
是最后一种能够影响函数调用时 this
绑定行为的方法,咱们称之为 new
绑定。
上述说了四种绑定方式,在实际开发中,确定会遇到多种绑定交叉的状况。那咱们就须要知道,绑定的优先级如何,才能够预知到代码结果。
判断this
如今咱们能够根据优先级来判断函数在某个调用位置应用的是哪条规则。能够按照下面的 顺序来进行判断:
new
中调用( new
绑定)?若是是的话 this
绑定的是新建立的对象。var bar = new foo()
复制代码
call
、 apply
(显式绑定)或者硬绑定调用?若是是的话, this
绑定的是指定的对象。var bar = foo.call(obj2)
复制代码
this
绑定的是那个上 下文对象。var bar = obj1.foo()
复制代码
undefined
,不然绑定到全局对象。var bar = foo()
复制代码
就是这样。对于正常的函数调用来讲,理解了这些知识你就能够明白 this
的绑定原理了。
凡是规则,都会有出现意外的状况。this
绑定也不例外。
function foo() {
console.log( this.a );
}
var a = 2;
foo.call( null ); // 2
复制代码
当咱们把null
或者undefined
传入call
、apply
或者bind
时,这些值在调用时会被忽略,从而去应用默认绑定规则。
当咱们建立一个函数的“间接引用”时,调用这个函数就会应用默认绑定规则。
简介引用最容易在赋值时发生。
function foo() {
console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2
复制代码
赋值表达式 p.foo = o.foo
的返回值是目标函数的引用,所以调用位置是 foo()
而不是 p.foo()
或者 o.foo()
。因此这里会应用默认绑定。
对于默认绑定来讲,决定
this
绑定对象的并非调用位置是否处于严格模式,而是 函数体是否处于严格模式。若是函数体处于严格模式,this
会被绑定到undefined
,不然this
会被绑定到全局对象。
前面有提到一种绑定方式,叫作硬绑定,因此通常相对的,其实也有一种叫作软绑定的方式。
使用硬绑定会下降函数的灵活性,以后没法再使用隐式绑定或者显式绑定来修改
this
。可是软绑定 ,能够保留隐式绑定和显示绑定修改this
的能力。
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this;
// 捕获全部 curried 参数
var curried = [].slice.call( arguments, 1 );
var bound = function() {
return fn.apply(
(!this || this === (window || global)) ?
obj : this
curried.concat.apply( curried, arguments )
);
};
bound.prototype = Object.create( fn.prototype );
return bound;
};
}
复制代码
this
词法在ES6
中,介绍了一种特殊的函数类型:箭头函数。
箭头函数并非使用 function
关键字定义的,而是使用被称为“胖箭头”的操做符 => 定 义的。箭头函数不使用 this
的四种标准规则,而是根据外层(函数或者全局)做用域来决 定 this
。
function foo() {
// 返回一个箭头函数
return (a) => {
//this 继承自 foo()
console.log( this.a );
};
}
var obj1 = {
a:2
};
var obj2 = {
a:3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是 3
复制代码
foo()
内部建立的箭头函数会捕获调用时 foo()
的 this
。因为 foo()
的 this
绑定到 obj1
,bar
(引用箭头函数)的 this
也会绑定到 obj1
,箭头函数的绑定没法被修改。
箭头函数能够像
bind(..)
同样确保函数的this
被绑定到指定对象,此外,其重要性还体 如今它用更常见的词法做用域取代了传统的this
机制。
要判断this
绑定,就须要先找到函数的直接调用位置。
以后根据规则来判断this
的绑定对象:
1. `new`调用
复制代码
2.call
、apply
或者bind
3.上下文对象调用 4.默认绑定
ES6
中的箭头函数并不会使用四条标准的绑定规则。而是根据当前的词法做用域来决定this
。箭头函数会继承外层函数调用的this
绑定。