JavaScript 世界里面的函数和 this 指向

发布于 2022-10-11 12:29:50 字数 4471 浏览 81 评论 0

函数是 JavaScript 世界里的第一公民,换句话来说,就是我们如果可以精通 JavaScript 函数的使用,那么对 JavaScript 的运用可以更游刃有余了。熟悉JavaScript的人应该都知道,同样的函数,以不同的方式调用的话,受影响最大的应该是 this 。

下面我们来说说 JavaScript 函数的各种调用模式。

一、普通函数的调用模式

所谓普通函数的调用模式,也是 JavaScript 函数的最简单的一种调用模式,直接就是函数名后接一个 () 实现调用,看下面代码:

function func(){
    console.log(this === window);  //true
}
func();

上面代码,我们用 function 关键字声明了一个 func 函数,并且在函数体内打印 this===window,然后我们直接调用函数 func,我们可以看到控制台是直接打印出 true ,也就是说,函数的这种普通调用模式,函数体内的 this 是指向全局环境 window 的。不清楚这点的同学,可以能会遇到这样的一个 bug:

var color = 'gg';
var obj = {
    color : 'red',
    show : function(){
        function func1(){
            console.log(this.color);  //gg
        }
        func1();
    }
}
obj.show(); 

我们在全局环境下声明了一个变量 color 和一个对象 obj ,在对象 obj 里面我们还声明了一个 color 属性为 'red',一个 show 方法。而且在 show 方法里面呢,我们还声明了一个函数 func1 并且调用了 func1,func1 的作用是打印 this.color。

最后我们运行代码 obj.show(); 调用 obj 里面的 show 方法。不清楚函数的普通调用模式的特点的同学可能会认为此时在控制台答应出来的会是 'red' 。实际上此时在控制台答应出来的应该是 gg 。因为函数 func1 的调用模式是 普通函数调用模式(即使它是在 obj 的 show 方法里面调用的),所以此时函数体内的 this 是指向 全局环境window 的,所以就打印了全局环境下的变量 color 。

可能有些同学会问:如果我们希望 func1 函数打印出来的是 'red' 呢,应该怎么改?其实很简单,因为 obj.color 才是 'red' ,所以我们只需要把指向 obj 的 this 引入到函数 func1 里面就行了:

var color = 'gg';
var obj = {
    color : 'red',
    show : function(){
        var that = this;
        function func1(){
            console.log(that.color);  //red
        }
        func1();
    }
}
obj.show();
var color = 'gg';
var obj = {
    color : 'red',
    show : function(){
        var func1=function(){
            console.log(this.color);  //red
        }.bind(this);
        func1();
    }
}
obj.show();

在上面的代码中,因为 show 里面的 this 指向 obj 的,所以我们在 show 里面声明一个变量 that = this;用来把指向 obj 的 this 引入到 func1 中,然后再把 func1 函数体内的 this.color 改为 that.color ,此时在控制台打印出来的就是我们想要的 'red' 了。

可能现在又有同学会问:为什么 show 里面的 this 是指向 obj 的呢?这就是我们要说的 JavaScript 函数的第二种调用模式:方法调用模式

二、方法调用模式

方法调用模式,简单来说就是把一个 JavaScript 函数作为一个对象的方法来调用,当一个函数被保存为一个对象的属性是,我们就把它称为方法,例如上文的 obj 对象里的 show ,当一个方法被调用时,函数体里面的 this 就会绑定到这个对象,例如上文的 show 里面的 this 。方法调用模式也很容易辨别:obj.show(),对象名.属性名(),代码的话可以参考上文的 obj 代码 ,博主就不多写了。记住:方法的调用是可以在函数体内通过 this 访问自己所属的那个对象的。

三、构造器调用模式

博主认为构造器调用模式是相对于其他模式来说较为复杂点的调用模式了。通过关键字 new 可以把一个函数作为构造器来调用。关键字 new 可以改变函数的返回值:

function func2(name){
    this.name = name;
}

name;   //undefined

//普通函数调用模式
var foo = func2('afei');
foo;  //undefined
name;   //afei


//构造器调用模式
var bar = new func2('lizefei');
bar.__proto__ === func2.prototype;  //true
bar;  //{name:'lizefei'}
bar.name;  //'lizefei'

在上示代码中我们声明了一个函数 func2 ,分别用两种不同的调用模式去调用它。因为函数 func2 并没有显式返回值,所以作为普通函数去调用时,它什么也没有返回,所以 foo 的值是 undefined 。因为普通调用模式的 this 是指向 全局环境 window 的,所以 func2('afei'); 后,全局环境下就多了一个 name 变量且等于 'afei'。

func2 作为构造器调用时,我们可以看到,它返回的是一个对象,因为关键字 new 使得函数在调用是发生了如下的特殊变化:

  1. 创建了一个新对象,而且这个新对象是链接到 func2 的 prototype 属性的
  2. 把函数里的 this 指向了这个新对象
  3. 如果没有显式的返回值,新对象作为构造器func2的返回值进行返回(所以 bar 是 {name:'lizefei'})

这样子我们就可以看出构造器的作用:通过函数的调用来初始化新创建出来的对象。在 JavaScript 的面向对象编程里面,这个可是相当重要的。

因为在函数的声明上,在未来作为构造器调用的函数和普通函数的声明没什么区别,所以导致后来的开发者很容易因为调用模式的错误导致程序出问题。所以开发者们都默契地约定,用来做构造器调用的函数的函数名的第一个字符应该大写,例如:Person,People。这样子后来的开发者一看到函数名就知道要用构造器调用模式调用此函数了。

四、使用 apply() 和 call() 方法调用

这种调用的模式是为了更灵活控制函数运行的上下文环境而诞生的。简单的说就是为了灵活控制函数体内 this 的值。

  • apply 和 call这两个方法的第一个参数都是要传递被函数上下文的对象(简单点说就是要绑定给函数 this 的对象)。其他参数就有所不同了:
  • apply 方法的第二个参数是一个数组,数组里面的值将作为函数调用的参数;
  • call 方法,从第二个参数起(包括第二个参数),剩下的参数都是作为函数调用的参数;

让我们看看栗子:

var obj = {
    name :'afei'
}
function say(ag1,ag2){
    console.log(ag1+':'+ag2+" "+ this.name);
}
say.apply(obj,['apply方法','hello']); //apply方法:hello afei
say.call(obj,'call方法','hi'); //call方法:hi afei

正如栗子所示,我们把对象 obj 作为函数 say 的上下文来调用函数 say ,所以函数里的 this 是指向 对象 obj 的。在 apply 方法里,我们通过数组 ['apply方法','hello'] 给 say 方法传递了两个参数(apply 方法 和 hello),所以打印出来是: apply 方法:hello afei。

同理 call 也是一样,而且函数传递的方式通过上面的代码也一目了然我,博主就不多做解释了。

另外,博主还听说 apply 和 call 这两个方法除了传递参数的方式不一样,执行的速度还是 apply 比 call 要快呢。不过博主就没有实验过。

五、总结

在 JavaScript 里面,函数只要的调用模式就是这几种了(在 ES6 里面还有一种很奇怪很特殊的函数调用模式,叫做 标签模板,在这里博主也不多说了,有空另更),只要掌握了这几种主要的调用模式,那么日后再也不用担心 this 的值变来变去了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

文章
评论
84965 人气
更多

推荐作者

alipaysp_snBf0MSZIv

文章 0 评论 0

梦断已成空

文章 0 评论 0

瞎闹

文章 0 评论 0

寄意

文章 0 评论 0

似梦非梦

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文