JavaScript 究竟是“这”还是“这”(详解 this )???

817 阅读2分钟

前言:

如果你有其他语言的开发经验并未对 JavaScript 进行深入了解,也许会认为两者中的 this 相差无几……

好吧!不管你是不是,文章已写,求你看还不行 🤣?

令人困惑的 this

思考下列代码:

function say(){
    console.log("Hello,world!");
    this.count++;
}
say.count = 0;
var count = 0;
for(var i = 0;i < 10;i++)
    say();
console.log(say.count);
console.log(count);

通常而言,我们会认为 this 应该指向包含他的函数对象。所以,代码中的 this.count 应该是 say.count,而非 var count ,所以最终输出为 10 ,0;

然而呢?

捕获.PNG

say.count 居然从未增加!可见,this 并非总是指向函数自身。这其中必有什么“内幕”,等待我们去揭开!

细心的你大概发现了,this.count 指向了全局变量 count,说明,this 并没错,错的是我们对它的理解。

this 从何而来?

事实上,this 实在运行时被确定的:

当一个函数被调用时,函数的执行上下文将创建,其中包含了 this。(执行上下文)。而执行上下文中的 this 绑定取决于函数的调用方式,如果被一个对象调用,那么 this 指向这个对象。

简明地说,函数被 调用,那么 就是 this(函数中的)。

调用的位置到底是哪

你是否已经“夜郎自大”了?请接受好 JavaScript 的毒打!

function hello(){
    console.log("Hello,world!");
    this.count++;
}
function say(){
    hello();
}
say.count = 0;
var count = 0;
for(var i = 0;i < 10;i++)
    say();
console.log(say.count);
console.log(count);

“啊,就这???”—— hello 调用在 say 里面,所以 this.count++say.count++ 而不是 count++

嗯嗯,很不错……你猜对个毛!

捕获.PNG

“你才傻了吧,教我错的!你自己说的 this 取决于调用位置。”

别急,调用位置虽然确实是函数的调用位置。但这个调用位置并非仅仅包含调用函数就是其调用位置了。这得解释调用栈:

调用栈是解释器(比如浏览器中的 JavaScript 解释器)追踪函数执行流的一种机制。当执行环境中调用了多个函数时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体中又调用了哪个函数。

看调用 hello 的地方:hello 是直接被访问到的(中间没有经过任何函数对象)。所以,调用它的还是全局对象,它的执行上下文还是全局执行上下文。

也就是说,如果我们要让 this 指向 say,应该通过 say 去访问 hello?:

function hello(){
    console.log("Hello,world!");
    this.count++;
}
function say(){
    say.helloFun();//此时是 say-> helloFun
}
say.helloFun = hello;//指向 hello
say.count = 0;
var count = 0;
for(var i = 0;i < 10;i++)
    say();
console.log(say.count);
console.log(count);

果然,this 终于指向 say 了:

捕获.PNG

在进行更深入的了解之前,是时候接受终极考验了;

function hello(){
    console.log("Hello,world!");
    this.count++;
}
function say(){
    say.helloFun();
}
say.count = 0;
var count = 0;
for(var i = 0;i < 10;i++)
    (say.helloFun = hello)();
console.log(say.count);
console.log(count);

say.count 是 10,还是 0 ?答案在最后。

this 的绑定规则

默认绑定

在任何函数体外,this 指向全局对象(如浏览器是 window )。

在函数内部,this 指向函数被调用的位置。但值得注意的是,严格模式下,如果函数不被任何函数调用,this 将保持为 undefined(不默认指向 window )。

隐式绑定

也就是前面所说: this 取决于调用的位置。

捕获.PNG

但有些情况会“解除”隐式绑定——我们经常会希望把一个函数对象传给另一个函数去执行:

捕获.PNG

然而并没得到预期的结果。细细分析其实很好理解:fun 方法接收到的参数实际上还是指向 say 函数,其中并未经过 obj 对象。

显示绑定

我们会希望,obj 里的 say 函数只得绑定 obj,JavaScript 中的 call 方法可以满足我们的需求:

var obj = {
    count :0,
    say:function(){
        console.log("Hello,world!")
        this.count++;
    }
}

function fun(say){
    say.call(obj);
}
var count = 0;
for(var i = 0;i < 10;i++)
    fun(obj.say);
console.log(obj.count);
console.log(count);

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

fun 函数中,通过 say.call(obj); 强制把 saythis 绑定到了 obj 上。

我们可以通过 bind 函数快速做到绑定:

捕获.PNG

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

new 绑定

当我们使用 new 来调用函数时,会发生以下操作:

  1. 创建一个空的 JavaScript 对象(即{});
  2. 链接该对象(设置该对象的 constructor )到另一个对象 ;
  3. 将 步骤1 新创建的对象作为 this 的上下文 ;
  4. 如果该函数没有返回对象,则返回 this 。

所以以下代码就不足为奇了:

捕获.PNG

绑定顺序

有时多种的绑定规则会运用在同一个对象上,这时的 this 绑定遵循以下规则:

  1. 由 new 调用则绑定到新创建的对象。( new 绑定)
  2. 由 call 、apply 或 bind 调用则绑定到指定的对象。(显示绑定)
  3. 由上下文对象调用则绑定到该上下文对象。(隐式绑定) 4、无法套用任何规则:绑定到全局对象,严格模式下为 undefined(默认绑定)。

你也许会发现:隐式绑定和默认绑定在非严格模式下结果是一样的。

总结

我写的太好了,还用总结???

不吹了,点个赞bie~👍!