JS中函数的this指向(一)

简介: JS中函数的this指向

为什么需要this?

  • 在常见的编程语言中,几乎都有this这个关键字(Objective-C中使用的是self),但是JavaScript中的this和常见的面向对象语言中的this不太一样:
  • 常见面向对象的编程中,比如Java、C++、Swift、Dart等等一系列语言中,this通常只会出现在类的方法中
  • 也就是你需要一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象
  • 但是JavaScript中的this更加灵活,无论是它出现的位置还是它代表的含义
  • 我们来编写一个obj的对象,看有this跟没有this的区别

this的作用

使用this

var obj = {
    name:"小余",
    eacting:function(){
        console.log(this.name + "在吃东西");
    },
    runing:function(){
        console.log(this.name + "在跑步");
    },
    studying:function(){
        console.log(this.name + "在学习");
    }
}
obj.eacting()
obj.runing()
obj.studying()
//小余在吃东西
//小余在跑步
//小余在学习

不使用this的弊端

不使用this,一样是可以打印出来的,从某些角度来说,开发中没有this,很多问题我们也是有解决方案的


但是如果使用this的话,我们就不需要修改对象内部的代码了

var obj = {
    name:"小余",
    eacting:function(){
        console.log(obj.name + "在吃东西");
    },
    runing:function(){
        console.log(obj.name + "在跑步");
    },
    studying:function(){
        console.log(obj.name + "在学习");
    }
}
obj.eacting()
obj.runing()
obj.studying()
//小余在吃东西
//小余在跑步
//小余在学习

但是这种解决方案是有弊端的,当我们要使用多个对象的时候,对象里面使用到的名字还得一个个替换掉,例如obj.name就得替换成xiaoman.name


所以没有this会让我们编写代码非常不方便

var obj = {
    name:"小余",
    eacting:function(){
        console.log(obj.name + "在吃东西");
    },
    runing:function(){
        console.log(obj.name + "在跑步");
    },
    studying:function(){
        console.log(obj.name + "在学习");
    }
}
var xiaoman = {
    name:"小满",
    eacting:function(){
        console.log(xiaoman.name + "在吃东西");
    },
    runing:function(){
        console.log(xiaoman.name + "在跑步");
    },
    studying:function(){
        console.log(xiaoman.name + "在学习");
    }
}
obj.eacting()
obj.runing()
obj.studying()

this在全局作用域指向什么

全局中的this是非常特殊的,因为大多数情况下this都是出现在函数中的


  • this在全局作用域下
  • 浏览器:window
  • Node环境:{}空对象


  • 但是,开发中很少直接在全局作用域下去使用this,通常都是在函数中使用
  • 所有的函数在被调用时,都会创建一个执行上下文
  • 这个上下文中记录着函数的调用栈、AO对象等
  • this也是其中一条记录
  • this是动态绑定的。动态绑定就是等到我们函数即将执行的时候才会确定绑定上去,而不是解析的时候确定的
  • 有着比较多的绑定规则,在不同规则下的绑定情况都不大一样


//打印出来的是两个一模一样的window
console.log(this);
console.log(window);

node环境下为什么是空对象

文件在要被node执行的时候,我们的文件会被node当作一个模块module -> 加载 ->编译 -> 将所有代码放在一个函数里面 -> 执行这个函数,执行了一个apply


  • function foo(){xxx},执行的时候我们不使用foo(),而是foo.apply("小余"),则"小余"会替代掉xxx的内容

同一个函数的this的不同

  • this指向什么,跟函数所处的位置是没有关系的
  • 跟函数被调用的方式有关系

我们先来看一个让人困惑的问题:


  • 定义一个函数,我们采用三种不同的方式对他进行调用,它产生了三种不同的结果


这个的案例可以给我们什么样的启示:


  1. 函数在调用时,JavaScript会默认给this绑定一个值
  2. this的绑定和定义的位置(编写的位置)没有关系
  3. this的绑定和调用方式以及调用的位置有关系
  4. this是在运行时被绑定的

function foo(){
    console.log(this);
}
//1.直接调用这个函数
foo()
//2.创建一个对象,对象中的函数指向foo
var obj = {
    name:"小余",
    foo:foo
}
obj.foo()
//3.apply调用
foo.apply("XiaoYu")

结果如下:

image.png

this到底是怎么样的绑定规则

  • 绑定一:默认绑定
  • 绑定二:隐式绑定
  • 绑定三:显示绑定
  • 绑定四:new绑定

规则1:默认绑定

  • 什么情况下使用默认绑定呢?独立函数调用
  • 独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用
  • 这种情况下,this指向的就是window
//案例1
//函数在被调用的时候,没有被绑定在任何的对象上面,也没有使用apply等方式调用
function foo(){
    console.log(this);
}
foo()

另外的一种情况,这种情况下,函数也是独立调用的。不是XXX.foo()之类的调用也是独立调用

//案例2
function foo1(){
    console.log("foo1",this);
}
function foo2(){
    console.log("foo2",this);
    foo1()
}
function foo3(){
    console.log("foo3",this);
    foo2()
}
foo3()

结果如下:

image.png

要注意,案例3是定义的时候有绑定到对象上面,但是当他在执行的时候,我们执行的是fn,根据之前学的内存里的执行过程,我们知道fn此时执行的就是obj里面的function本身,那fn是独立调用的其实就证明了函数也是独立调用的,那答案就应该指向window


this指向什么,跟函数所处的位置是没有关系的,跟调用的位置才有关系

//案例3
var obj = {
    name:"小余",
    foo:function(){
        console.log(this);
    }
}
var fn = obj.foo
fn()
//答案是指向window,而不是obj

跟案例3只有一点点的小变化,只是将foo移到了obj外面,然后在obj内部进行引用了,本质上没有变化

//案例四
function foo(){
    console.log(this);
}
var obj = {
    name:"小余",
    foo:foo
}
var bar = obj.foo
bar()
//window

之前用过的非常熟悉的案例

//案例5
function foo(){
    function bar(){
        console.log(this);
    }
    return bar
}
var fn = foo()
fn()
//这个时候fn函数调用时返回window,非常熟悉的,fn调用的时foo函数里面的bar函数,并没有扯到foo函数上,属于独立调用。然后就是闭包调用必然指向window这种结论是错误的,换种调用方式,就会发生改变了
var obj = {
    name:"大余",
    age:fn
}
obj.age()//隐式绑定
//这种调用方式,js引擎会将obj绑定到我们age的函数内部
//看,此时外面又创建了一个obj对象,里面的age指向就是我们刚刚认定的闭包的fn或者说bar函数,调用顺序是age -> fn -> bar,但此时我们再调用age的时候,返回的结果不再是window了,因为我们输出前的那一刻的调用方式已经发生了变化,此时不再是函数独立调用

规则2:隐式绑定

  • 另外一种比较常见的调用方式是通过某个对象进行调用的
  • 也就是它的调用位置中,是通过某个对象发起的函数调用
//案例1
function foo(){
    console.log(this);
}
var obj = {
    name:"狗洛",
    foo:foo
}
obj.foo()

返回结果:

image.png

我们通过这种在一开始就演示过的代码中可以看到,obj里面的eacting跟running函数的this是可以指向函数的父级作用域的,也就是obj函数


  • 因为我们在调用的时候,是通过obj.eating的方式,将obj绑定到eating里面,所以this指向会指到obj上面
  • object对象会被js引擎绑定到fn函数中的this里面
//案例2
var obj = {
    name:"小余",
    eating:function(){
        console.log(this.name + "在吃东西");
    },
    running:function(){
        console.log(this.name + "在跑步");
    }
}
obj.eating()
obj.running()

如果我们将obj跟eating的绑定关系解除掉,再调用eating函数的时候,他的this的指向就会出现问题

var fn  = obj.eating
fn()
obj.running()

吃东西的this指向出现了问题,this.name的结果出不来了,因为我们在调用的时候已经将obj跟eating函数的关系给去除掉了,obj没有绑定到eating里面了,所以就指向不到了obj里面的内容了

image.png

通过案例3,我们调用obj2中的bar属性,obj2.bar属性调用obj1中的foo函数。

//案例3
var obj1 = {
    name:"obj1",
    foo:function(){
        console.log(this);
    }
}
var obj2 = {
    name:"obj2",
    bar:obj1.foo
}
obj2.bar()
//node环境返回结果:{ name: 'obj2', bar: [Function: foo] }

控制台打印结果:

image.png

那此时这个this是绑定到了谁身上,我们通过结果可以看到是obj2的身上,首先我们bar是调用到了obj1中的foo函数身上,但是最后我们执行的时候,是通过obj2来进行执行的,所以obj2就被绑定到了foo函数里面去了,所以此时foo函数控制台打印this的结果才会是obj2里的内容

image.png

规则3:显示绑定

  • 隐式绑定有一个前提条件:
  • 必须在调用的对象内部有一个对函数的引用(比如一个属性);
  • 如果没有这样的引用,在进行调用时,会报找不到该函数的错误
  • 正是这个引用,间接的将this绑定到了这个对象上

通俗的说就是:以上面obj1、obj2的例子来说,我们obj2如果想要调用obj1里的函数的话,我们就得想办法把obj1里的这个函数放到obj2里的属性里面,然后使用obj2对bar进行一个引用,然后我们才能用obj2.bar进行一个调用

  • 如果我们不希望在对象内部包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?
  • JavaScript所有的函数都可以使用call和apply方法(这个和Prototype有关)
  • 它们的区别是:第一个参数是相同的,后面的参数apply为数组,call为参数列表
  • 这两个函数的第一个参数都要求是一个对象,这个对象的作用是什么呢?就是给this准备的
  • 在调用这个函数时,会将this绑定到这个传入的对象上

apply、call、bind的使用

call函数

函数上面有call方法,所以当我们使用foo.call()的时候,他也会去帮我们调用函数。JavaScript内部已经帮我们实现了一个call函数了

function foo(){
    console.log("函数被调用了");
}
foo()
foo.call()
//调用的结果是一样的
// 函数被调用了
// 函数被调用了

apply函数

跟call函数同理的,函数上同样有apply函数,一样是JavaScript内部替我们实现的

function foo(){
    console.log("函数被调用了");
}
foo()
foo.apply()
// 函数被调用了
// 函数被调用了

call函数与apply函数的区别

  • 它们两者传参的方式不大一样
  • call是依次传的
  • apply是以数组的形式传的


  • call和apply在执行函数时,是可以明确的绑定this,这个绑定规则称之为显示绑定
function sum(num1,num2){
    console.log(num1+num2,this)
}
sum.call("call",20,30)//后面的参数是以逗号来做一个分割的,依次挨个传就行
sum.apply("apply",[20,30])//后面的参数是以数组存在的
//50 [String: 'call']
//50 [String: 'apply']

直接调用与call、apply的不同

  • 如果说foo()直接调用跟call、apply的结果是一样的,为什么不全部使用直接调用呢?
  • 首先,它们的this绑定是不一样的
  • foo()直接调用,指向的是全局对象(window)
  • call、apply可以手动指定我们所指向的this是谁,很多时候我们使用这两个的目的也就是这个
function foo(){
    console.log("函数被调用了",this);
}
var obj = {
    name:"班花姐姐"
}
foo()
foo.apply("小余")
foo.call(obj)

返回结果如下:

image.png

显示绑定 -- bind

当我们要重复使用多次绑定的时候,反复调用call或者apply,往里面填写重复的参数的时候,就会显得比较累赘,这个时候我们就可以使用bind函数来替代,bind函数是会返回一个值的,这个时候我们就可以声明一个函数(这个函数也会重新开辟一个堆空间来进行存放的)来接收他,然后直接调用这个声明的函数就可以了


  • 然后我们可以看到,我们调用newFoo函数的时候是独立调用的,这个时候应该是指向window才对,但是我们已经用bind将newFoo的指向明确固化到"小余"上面了,这个时候规则就会冲突,显示绑定bind函数的优先级高于默认绑定
function foo(){
    console.log(this)
}
// foo.call("小余")
// foo.call("小余")
// foo.call("小余")
// foo.call("小余")
// foo.call("小余")
//默认绑定和现实绑定bind冲突
var newFoo = foo.bind("小余")
newFoo()//是不是比每次都写foo.call("小余")方便一些?

我们将foo.bind赋值给了newFoo,又声明了bar来接收了foo函数,我们的目的是为了对比它们的this指向问题


  • 第一个对比的是bar函数跟foo函数,很明显,是直接赋值的关系,它们是一样的(包括了this也指向一样的地方),返回true
  • 第二个对比的是newFoo函数和foo函数,这里它们的不同在于newFoo接收的并不是foo函数本身,唯一的变量是被bind修改了this指向的foo函数。经过对比,它们是不相等的,返回的是false,也证明了一点:bind函数会返回新的内容,但不会修改原本函数的this(他们指向的不是同一块内存空间,而是不相干的两处地方,不然此时foo与newFoo的对比就该返回true了
function foo(){
    console.log(this)
}
// foo.call("小余")
// foo.call("小余")
// foo.call("小余")
// foo.call("小余")
// foo.call("小余")
//默认绑定和现实绑定bind冲突
var newFoo = foo.bind("小余")
var bar = foo
console.log(bar === foo);//true
console.log(newFoo === foo);//false

bind函数和callapply函数都可以用来改变函数的调用对象。但是它们之间有一些微妙的差别。


下面是这三个函数的一些基本区别:


  • bind函数会创建一个新函数,其中调用对象被固定为指定的值。而callapply函数则是立即调用函数,并改变调用对象。
  • bind函数可以在调用时指定函数的参数,而callapply函数则需要在调用时传入所有的参数。
  • bind函数返回的是一个新的函数,而callapply函数则是立即执行函数。


JS中函数的this指向(二)https://developer.aliyun.com/article/1470344

目录
相关文章
|
6天前
|
JavaScript 前端开发 Java
[JS]同事:这次就算了,下班回去赶紧补补内置函数,再犯肯定被主管骂
本文介绍了JavaScript中常用的函数和方法,包括通用函数、Global对象函数以及数组相关函数。详细列出了每个函数的参数、返回值及使用说明,并提供了示例代码。文章强调了函数的学习应结合源码和实践,适合JavaScript初学者和进阶开发者参考。
15 2
[JS]同事:这次就算了,下班回去赶紧补补内置函数,再犯肯定被主管骂
|
4天前
|
前端开发 JavaScript 开发者
除了 Generator 函数,还有哪些 JavaScript 异步编程解决方案?
【10月更文挑战第30天】开发者可以根据具体的项目情况选择合适的方式来处理异步操作,以实现高效、可读和易于维护的代码。
|
18天前
|
JavaScript 前端开发
JavaScript 函数语法
JavaScript 函数是使用 `function` 关键词定义的代码块,可在调用时执行特定任务。函数可以无参或带参,参数用于传递值并在函数内部使用。函数调用可在事件触发时进行,如用户点击按钮。JavaScript 对大小写敏感,函数名和关键词必须严格匹配。示例中展示了如何通过不同参数调用函数以生成不同的输出。
|
21天前
|
存储 JavaScript 前端开发
JS函数提升 变量提升
【10月更文挑战第6天】函数提升和变量提升是 JavaScript 语言的重要特性,但它们也可能带来一些困惑和潜在的问题。通过深入理解和掌握它们的原理和表现,开发者可以更好地编写和维护 JavaScript 代码,避免因不了解这些机制而导致的错误和不一致。同时,不断提高对执行上下文等相关概念的认识,将有助于提升对 JavaScript 语言的整体理解和运用能力。
|
2月前
|
JavaScript 前端开发 安全
JavaScript函数详解
JavaScript函数的详细解析,包括函数的定义和调用方式(如一般格式、匿名函数、构造函数、自调用函数、箭头函数和严格模式)、函数参数(arguments对象、可变参数、默认参数值)、闭包的概念和应用实例。
JavaScript函数详解
|
29天前
|
JavaScript 前端开发
js教程——函数
js教程——函数
24 4
|
29天前
|
存储 JavaScript 前端开发
js中函数、方法、对象的区别
js中函数、方法、对象的区别
15 2
|
1月前
|
JavaScript 前端开发 Java
【javaScript数组,函数】的基础知识点
【javaScript数组,函数】的基础知识点
23 5
|
1月前
|
JavaScript 前端开发
Node.js 函数
10月更文挑战第5天
19 3
|
1月前
|
前端开发 JavaScript
探索JavaScript函数基础
探索JavaScript函数基础
17 3