首先有一句大家都明白的话,我还要在强调一遍,this是函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。
这句话很重要,这是理解this的基础。
而在讲解this之前,先要理解一下作用域的相关概念。
【词法作用域】和【动态作用域】
通常来说,作用域一共有两种主要的工作模型。
•词法作用域
•动态作用于
词法作用域是大多数编程语言所采用的模式,而动态作用域任有一些编程语言再用,例如bash脚本。
而JavaScript就是采用词法作用域,也就是在编程阶段,作用域已经明确确定下来了。
思考下列代码:
function foo(){ console.log(a);//2 } function bar(){ let a = 3; foo(); } let a = 2; bar();
因为JavaScript用的是词法作用域,自然foo()声明阶段,就已经确定了变量a的作用域了。
倘若,javascript是采用的动态作用域,foo()中打印的将是3
function foo(){ console.log(a);//输出的是3(不是2) } function bar(){ let a= 3; foo(); } let a = 2; bar();
而JavaScript的this机制跟动态作用域很相似,是在运行时在被调用的地方动态绑定的。
this的四种绑定规则
•默认绑定
•隐式绑定
•显式绑定
•new绑定
默认绑定
这是最直接的一种方式,就是不加任何修饰符直接调用函数,如:
function foo(){ console.log(this.a);//输出:2 } var a = 2; foo();
使用var声明的变量 a,被绑定到全局对象中,如果是浏览器,则是在window对象。
foo()调用时,引用了默认绑定,this指向了全局对象。
隐式绑定
这种情况会发生在调用位置存在【上下文对象】的情况,如:
function foo(){ console.log(this.a); } let obj1={ a:1, foo } let obj2={ a:2, foo } obj1.foo();//输出:1 obj2.foo();//输出:2
当函数调用的时候,拥有上下文的时候,this会被绑定到该上下文对象。正如上面的代码,obj1.foo()被调用时,this被绑定到了obj1,而obj2被调用时,this被绑定到了obj2.
显式绑定
这种就是使用function.prototype 中的三个方法 call(),apply(),bind()了。
这三个函数都可以改变this指向到指定的对象,不同之处在于,call() 和 apply() 是立即执行函数,并且接受的参数的形式不同:
•call(this,arg1,arg2,...)
•apply(this,[arg1,arg2,..])
而bind则是创建一个新的包装函数,并且返回,而不是立即执行
•bind(this,arg1,arg2,...)
apply()接收参数的形式,有助于函数嵌套函数的时候,把arguments变量传递到下一层函数中。
思考下面代码:
function foo(){ console.log(this.a);//1 bar.apply({a:2},arguments);//5 } function bar(b){ console.log(this.a + b); } var a = 1; foo(3);
上面代码中,foo()内部的this遵循默认绑定规则,绑定到全局变量中。而bar在调用的时候,调用了apply()函数,把this绑定到了一个新的对象中{a:2},而且原封不动的接收foo()就收的参数。
new绑定
最后一种,则是使用new操作符产生的this绑定,在理解new操作符对this的影响,首先要理解new的原理。
在JavaScript中,new操作符并不像其他面向对象的语言一样,而是一种模拟出来的机制。在JavaScript中,所有的函数都可以被new调用,这时候,这个函数一般会被成为构造函数,实际上并不存在所谓的构造函数,更确切的理解应该是对函数的构造调用。
使用new来调用函数,会自动执行下面操作:
创建一个全新的对象
这个新对象会被执行[[prototype]]连接
这个新对象会被绑定到函数调用的this。
如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
所以如果new是一个函数的话,会是这个样子的:
function New(constructor,...args){ let obj={}; Object.setPrototypeOf(obj,constructor.prototype); return constructor.apply(obj,args)||obj; } function Foo(a){ this.a=a; } New(Foo,1);
所以在使用new来调用函数的时候,我们会构造一个新对象,并把它绑定到函数调用中的this上。
优先级
如果一个位置发生了多条改变this的规则,那么优先级是如何呢?
看几段代码:
//显式绑定 > 隐式绑定 function foo(){ console.log(this.a); } let obj1 = { a:2, foo } obj1.foo();//输出:2 obj1.foo.call({a:1});//输出:1
这说明显式绑定的优先级大于隐式绑定。
function foo(a){ this.a=a; console.log(a); } let obj1={}; let bar = foo.bind(obj1); console.log(bar); console.log(obj1) bar(2); console.log(obj1);//输出:{a:2} let obj2=new bar(3); console.log(obj1);//输出:{a:2} console.log(obj2);//输出:{a:3}
这说明new绑定的优先级大于显式绑定,而默认绑定优先级毫无疑问是最低的,所以优先级顺序为:
new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
所以 this到底是什么
this并不是编写时候绑定的,而是在运行时绑定的,它的上下文取决于函数调用时的各种条件。
this的绑定和函数声明的位置没有任何关系,只取决于函数调用时的各种条件。
当一个函数被调用时会创建一个执行上下文,这个上下文会包含函数在哪里被调用(调用栈),函数的调用方式,传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。
原文:https://www.cnblogs.com/llld/p/10826234.html