this提供了一种优雅的方式来隐式的“传递”一个对象引用,因此可以将API设计的更加简洁并且易于复用。
1,一些误解
1.1 指向自身
明显并不是指向函数自身的;
1.2 指向函数的作用域
作用域确实和对象类似,可见的标识符都是它的属性,但是作用域“对象”无法通过js代码访问,它存在于js引擎内部;
1.3事实
this是在运行时进行绑定的,并不是在编写的时候绑定,它的上下文取决于函数调用时的各个条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(执行上下文),这个记录包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的一个属性,会在函数执行的过程中被用到;
就是说this实际上是函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用;
2 this解析
要理解this首先要理解调用位置,即函数被调用的位置,最重要的是分析调用栈,当前函数的前一个就是被调用的位置;
2.1 绑定规则
2.1.1 默认绑定
最常用的函数调用类型:独立函数调用。这条规则是无法应用其他规则时候的默认规则;
当函数是直接使用不带任何修饰的函数引用进行调用时,只能使用默认绑定;
当使用严格模式时,全局对象无法默认绑定,会绑定到undefined;,,,,,,
2.1.2 隐式绑定
指调用位置是否存在上下文对象,或者说是否被某个对象拥有;比如
function foo(){
console.log(this.a);
}
var obj = {
a:2,
foo:foo
};
obj.foo();
这个函数不属于obj,但是调用位置会使用obj的上下文来引用函数,对象属性引用链只有最后一层会影响调用位置,比如obj1.obj2.foo(),会使用obj2的作用于;
隐式对修,
一个最常见的this绑定问题是被隐式绑定的函数会丢失绑定对象,从而把this绑定到全局对象或者undefined上,比如
function foo(){
console.log(this.a);
}
var obj = {
a:2,
foo:foo
};
var bar = obj.foo;
var a = "other words";
bar();
虽然bar是obj.foo的一个引用,但是事实上它引用的是foo函数本身,因此此时的bar实际上是一个不带任何修饰的函数调用,因此使用了默认绑定;
2.1.3 显式绑定
可以使用call或者apply方法显示的绑定this;js的宿主环境有时会提供一些非常特殊的函数,他们没有这两个方法,但是绝大多数函数以及自己创建的所有函数都可以使用call()&apply()方法。这两个函数的区别是call传递参数列表,apply传递一个参数数组。
在使用call或者apply时,如果第一个参数传入了一个原始值(字符串、布尔、数字类型)来当做this 的绑定对象,这个原始值会被转换成它的对向行驶(new String(..))。这个称为“装箱”
硬绑定,
var bar = function(){
foo.call(obj);
}
ES5提供了内置的硬绑定方法,Function.prototype.bind,
var bar = foo.bind( obj );
2.1.4 new绑定
在js中,构造函数只是一些使用new操作符时被调用的函数,他们只是被new操作符调用的普通函数。所以事实上不存在构造函数,只有对函数的构造调用;
new的过程
1.创建一个新对象
2.这个新对象会被执行原型连接
3.这个新对象绑定到函数调用的this
4.如果函数没有返回其他对象,那么new表达书中的函数调用会自动返回这个新对象;
2.2 优先级
四条规则的优先级
默认绑定是在没有其他绑定时执行的,隐式绑定仅高于默认绑定;
在硬绑定(显示绑定的一种)和new绑定同时使用时,实际上new会修改硬绑定;
所以new绑定高于显示绑定;
new>显示绑定>隐式绑定>默认绑定(全局对象,严格模式绑定undefined)
2.3 例外
把null或者undefined作为this的绑定对象传递给call、apply或者bind会被忽略,执行默认绑定;
2.5 this词法
2.5.1箭头函数 =>
箭头函数不适用this的标准规则,而是根据外层(函数或者全局)作用域来决定this;
箭头函数的绑定无法被修改,和bind一样确保函数的this被绑定到指定对象,此外,其重要性还体现在它用更常见的词法作用域取代了传统的this机制
2.6 小结
要判断this 的指向,首先需要查找这个函数的直接调用位置,然后根据四条规则判断this 的绑定对象;
箭头函数不会使用标准规则,而是根据当前词法作用域来决定this,其实类似self=this;