加深对Javascript this的理解

首先有一句大家都明白的话,我还要在强调一遍,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就是这个记录的一个属性,会在函数执行的过程中用到。

猜你喜欢

转载自www.cnblogs.com/llld/p/10826234.html