文章目录
1、为什么使用this
this 提供了用更加优雅的方式来隐式传递一个对象的引用,使得api设计的更加简洁易于复用。如果代码越来越复杂,使用显示传递上下文对象会让代码变得越来越混乱。
2、this 的指向
我们可以确定this的并不指向自身的函数,
3、this的作用域
在任何情况下,this都是不指向函数的词法作用域
4、this到底是什么?
this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。当一个函数被调用时,会创建一个活动记录(也称执行上下文)。这个记录会包含函数调用位置、函数的调用方式、传入参数化等信息。this就是这个记录的一个属性,会在函数调用过程中用到。
5、this的四条绑定规则
1)默认绑定规则
默认绑定规则可以看作无法应用其他规则时的默认规则。最常用的函数调用类型:独立函数调用,独立函数调用就是应用默认绑定规则。
使用默认绑定规则下,this指向全局对象。注意在严格模式下不能将全局对象用于默认绑定,因此this会绑定undefined。
思考下面的代码:
2)隐式绑定规则
先思考一段代码:
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
}
obj.foo() //2
foo函数无论直接在obj中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象。然而,函数调用的位置会使用 obj 上下文来引用函数,因此可以说函数被调用时obj对象‘拥有’或者‘包含’函数的引用。所以,当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定在这个上下文对象。
还需要注意,对象属性引用链只有上一层或者说最后一层调用位置起作用。举例来说:
function foo() {
console.log(this.a);
}
var obj2 = {
a: 1,
foo: foo
}
var obj1 = {
a: 2,
obj2: obj2
}
obj1.obj2.foo() //1
隐式丢失
一个常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是它会应用默认绑定。请看下面的代码:
//显示赋值
function foo() {
console.log(this.a);
}
var obj = {
a: "a",
foo: foo
}
var a="global"
var bar = obj.foo//函数别名
bar()//"global"
/*
虽然 bar 是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,
因此这里bar()其实是一个不加任何修饰的函数的调用,
我们也可以叫做独立函数调用,因此引用默认规则绑定。
*/
//隐式赋值,参数传递是一种隐式赋值,当传入的参数是函数也会被隐式赋值,所以结果跟上个例子一样如下
function foo() {
console.log(this.a);
}
function doFoo(fn) {
fn() //调用位置
}
var obj = {
a: 2,
foo: foo
}
var a = 'global'
doFoo(obj.foo)//'global'
//如果将函数传入语言内置函数,也是会出现隐式丢失
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
}
var a = 'global'
setTimeout(obj.foo, 100)//'global'
// JavaScript环境中内置的 setTimeout()函数实现和下面的伪代码类似:
function setTimeout(fn, delay) {
fn()// <--调用位置
};
3)显示绑定
可以使用函数的 call(…) 和 apply(…) 方法,它们的第一参数是一个对象,是给this准备的,在调用函数时将其绑定到this。因为可以直接指定 this 的绑定对象,因此称之为 显示绑定。如果传入一个原始值作为this的绑定对象:字符串类型、布尔类型、数字类型,该原始值会被转换成它的对象形式:new String(…)、new Boolen(…),new number(…),这通常称为“装箱”。请看以下代码:
//call 和 apply,在this绑定是一样的,具体区别在其他参数上,这里不多讨论
function foo() {
console.log(this.a);
}
var obj = {
a: 2
}
foo.call(obj)//2
显示绑定还是无法解决绑定丢失问题。
硬绑定
显示绑定的变种:硬绑定可以解决参数丢失绑定的问题,但是如果使用硬绑定,一旦绑定,修改不了this绑定对象。请思考下面的代码:
function foo() {
console.log(this);
var obj = {
a: 2
var bar = function () {
foo.call(obj)
}
bar()//2
setTimeout(bar, 100) //2
//硬绑定的bar 不可能修改它的this
var obj2 = {
a:3
}
bar.call(obj2) // 2
4)new绑定
在JavaScript中,构造函数只是一些使用 new 操作符时被调用的函数,不属于某个类,也不会实例化一个类。
使用new 来调用函数,会执行的的操作
/*
1、创建一个全新的对象
2、这个新对象会执行 [[Prototype]] 连接。
3、这个新对象会绑定到函数调用的 this
4、如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
*/
function foo(a) {
this.a = a
}
var bar = new foo(2)
console.log(bar.a);//2
使用new 来调用函数时,会构造一个新的对象并把它绑定在函数调用中的 this上。这种绑定方法称之为 new绑定
优先级
new绑定 > 显示绑定 > 隐式绑定
绑定例外
1、被忽略的this
null、undefined 作为this的绑定对象传入 call、apply和bind,这些值在调用时会被忽略,实际应用了默认绑定规则
更安全的this
当使用call、apply、bind方法,需要忽略绑定对象的时候,如果使用 null ,可能会有一些副作用,比如在默认绑定规则下,可能会发生修改全局对象的可怕后果。所以需要一种策略来防止这种情况:可以传入一个特殊对象,把this绑定在这个对象下不会对程序有任何的副作用,可以创建一个空的非委托对象,将this限制在这个空对象中。请看下面的代码
function foo() {
console.log("a:" + a, "b:" + b);
}
var o = Object.create(null)
foo.apply(o, [2, 3])//a:2,b:3
软绑定
软绑定可以实现硬绑定的效果,但是同时可以解决硬绑定不可以修改this的缺点,从而提供函数的灵活性。
if (!Function.prototype.softBind) {
Function.prototype.softBind = function (obj) {
var fn = this
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的箭头函数则无法使用这些规则,而是根据当前的词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的this
绑定,这和es6之前的 var self= this 的机制一样