this关键字是javascript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。this既不指向函数本身也不指向函数的语法作用域。
this是在函数被调用时发生的绑定,this的绑定和函数声明的位置没有任何关系,它指向什么完全取决于函数在哪里被调用。
调用位置:是函数在代码中被调用的位置,而不是声明的位置。
this的4条绑定规则
* 默认绑定
* 隐式绑定
* 显式绑定
* new绑定
优先级排序:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
默认绑定
无法应用其它规则时的默认规则
如果使用严格模式(strict mode),则不能将全局对象用于默认绑定,因此this会绑定到undefined。
最常用的函数调用类型:独立函数调用。
function foo(){
console.log(this.a)
}
var a = 2;
foo() //2 >函数调用时应用了this的默认绑定,因此this指向全局对象
'tips: 声明在全局作用域中的变量就是全局对象的同名属性。'
//严格模式 example 1
function (){
"use strict";
console.log(this.a)
}
var a = 2;
foo(); //TypeError:this is undefined
//严格模式 example 2
function foo(){
console.log(this.a)
}
var a = 2;
(function(){
"use strict";
foo(); //2
})();
隐式绑定
隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
当函数引用上下文对象时,对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。
//example 1:
function foo(){
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
obj.foo();//2
//example 2:
var obj2 = {
a: 42,
foo: foo
}
var obj1 = {
a: 2,
obj2: obj2
}
obj1.obj2.foo();//42
显式绑定
直接指定this的绑定对象称为显式绑定
常见的显式绑定方法有 call()
和 apply()
call和apply的使用方法:
function.call(thisArg, arg1, arg2, ...)
//thisArg 在 f unction 函数运行时使用的 this 值
//arg1, arg2, ... 指定的参数列表。
func.apply(thisArg, [argsArray])
//thisArg 在 f unc 函数运行时使用的this值,
//如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
//argsArray 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给'func'函数,
//如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。
二者区别
call()方法接受的是一个参数列表,而apply()方法接受的是一个包含多个参数的数组。
function foo(){
console.log(this.a)
}
var obj={
a: 2
}
foo.call(obj); //2 调用foo时强制把它的this绑定到obj上
装箱
:如果你传入了一个原始值(字符串类型、布尔类型或数字类型)来当作this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(...)、new Boolean(...)或new Number(...))
1>硬绑定
//硬绑定--显式的强制绑定
function foo(){
console.log(this.a)
}
var obj = {
a:2
}
var bar = function(){
foo.call(obj)
}
bar(); //2
//硬绑定--硬绑定的使用场景就是创建一个包裹函数,负责接收参数并返回值
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
//硬绑定--另一种使用方法是创建一个可以重复使用的辅助函数
function foo(something){
console.log(this.a,something)
return this.a + something
}
//简单的辅助绑定函数
function bind(fn,obj){
return function(){
return fn.apply(obj,arguments)
}
}
var obj = {
a:2
}
var bar = bind(foo,obj);
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); //bind(...)会返回一个硬编码的新函数,它会把你指定的参数设置为this的上下文并调用原始函数
var b=bar(3); //2 3
console.log(b); //5
bind(...)的功能之一就是可以把除了第一个参数(第一个参数用于绑定this)之外的其他参数都传给下一层的函数
2>API调用的“上下文”
第三方库的许多函数,以及Javascript语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”(context),其作用和bind(...)一样,确保你的回调函数使用指定的this。
new绑定
在Javascript中,构造函数只是一些使用new操作符时被调用的函数
。它们并不会属于某个类,也不会实例化一个类。它们只是被new操作符调用的普通函数。实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
function foo(a){
this.a = a
}
var bar = new foo(2)
console.log(bar); //2
使用new调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
1、创建(或者说构造)一个全新的对象
2、这个新对象会被执行[[Prototype]]连接
3、这个新对象会绑定到函数调用的this
4、如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
绑定例外
1、如果你把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
一种安全的做法是传入一个特殊对象,把this绑定到这个对象不会对你的程序产生任何副作用。
在Javascript中创建一个空对象最简单的方法都是Object。create(null)。Object。create(null)和{}很像,但是并不会创建Object.prototype这个委托,所以它比{}更空。
2、有可能创建一个函数的“间接引用”,在这中情况下,调用这个函数会应用默认绑定规则。
function foo(){
console.log(this.a)
}
var a =2
var o = {
a: 3,
foo: foo
}
var p = {
a: 4
}
0.foo(); //3
(p.foo = o.foo)(); //2 >>p.foo = o.foo的返回是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo()
3、软绑定 Function.prototype.softBind
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
}
}
箭头函数
之前介绍的4条规则可以包含所有正常的函数,但是在es6中介绍了一种无法使用这些规则的特殊函数:箭头函数。
- 箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。
- 箭头函数的绑定无法被修改。(new也不行)
- 箭头函数会继承外层函数调用的this绑定(无论this绑定到什么)。这其实和ES6之前代码中的self=this机制一样。
小知识点
柯里化
即Currying的音译。Currying是编译原理层面实现多参函数的一个技术。
Currying 为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数
,在某些编程语言中(如 Haskell),是通过 Currying 技术支持多参函数这一语言特性的。
判断this绑定
先找到这个函数的直接调用位置,然后顺序应用下面这4条规则判断this的绑定对象:
- 由new调用?绑定到新创建的对象。
- 由call或apply或bind调用?绑定到指定的对象。
- 由上下文对象调用?绑定到那个上下文对象。
- 默认:在严格模式下绑定到undefined,否则绑定到全局对象。
文章摘取来源:《你不知道的JavaScript上卷》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。