深入理解this,bind、call

直接看this 直接看call和bind

首先放一道题:

var a={
    a:'haha',
    getA: function(){
        console.log(this.a);
    }
}
var b= {
    a:'hello'
}
var getA = a.getA;
var getA2 = getA.bind(a);
function run(fn){
    fn();
}
a.getA();//
getA();//
run(a.getA);//
getA2.call(b);//

输出是什么?

可以花几分钟先自己想想。

嘿嘿~ 嘿嘿~ 嘿嘿~ 嘿嘿~ 嘿嘿~ 嘿嘿~ 嘿嘿~ 嘿嘿~ 嘿嘿~ 嘿嘿~

公布答案:

a.getA();//haha
getA();//object
run(a.getA);//object
getA2.call(b);//haha

答对了么?

这里考察了三个点:

形参实参的理解、this的指向、call和bind对this指向的影响。

第一个我相信大家都没问题,这里主要分析后面的两个问题。

this指向问题

this是大部分刚入前端的人都会遇到的坑。 这里通过借鉴网上的文章和自己的理解来总结一下。

this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象。

我的理解是,看执行的时候谁在调用就指向谁,但这样理解this也不算完全准确。

这里有三种简单情况:

  1. 如果函数中的this没有调用它的对象,那么this指向的就是window(严格模式下这种情况的this会为空,即undefined)。
  2. 如果函数中的this被不包含子对象的对象所调用,那么this指向的就是调用它的对象。
  3. 如果函数中的this被包含多级对象的对象调用,this指向的也只是它上一级的对象,如下例。
var demoObj = {
    a:1,
    b:{
        fun:function(){
            console.log(this.a); 
        }
    }
}
demoObj.b.fun();//undefined

这里this不是指向demoObj对象,而是指向demoObj.b对象,这里找不到demoObj.b对象里的a,所以会输出undefined。

还有三种特殊情况:

  • 还是上面的例子,改一下调用函数的方式,如下。
var demoObj = {
    a:1,
    b:{
        fun:function(){
            console.log(this.a); 
        }
    }
}
var newFun = demoObj.b.fun;
newFun();//undefined

这里还是得到undefined,但是this的指向却是window,这里的undefined是因为没找到window对象里的a,才输出的undefined。 虽然函数fun是被对象b所调用,但是在将fun赋值给变量newFun的时候并没有执行,newFun的上级对象window,所以最终执行时指向的是window。

  • 构造函数用new实例对象时对this的影响。
function Fun(){
    this.name = "haha";
}
var stu = new Fun();
console.log(stu.name); //haha

这里之所以对象stu.name可以输出haha,是因为new关键字就是创建一个对象实例,这个stu对象中包含了this.name这个属性,相当于复制但却没有执行。在执行时调用这个函数Fun的是对象stu,所以this指向的就是对象stu。

用new操作符创建对象时发生的事情:

第一步: 创建一个Object对象实例。 第二步: 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)。 第三步: 执行构造函数中的代码(这里的执行并不是真的让this指向哪里,而是为这个新对象添加属性)。 第四步: 返回新生成的对象实例

原本的构造函数是window对象的方法,如果不用new操作符而直接调用,那么构造函数的执行对象就是window,即this指向了window。现在用new操作符后,this就指向了新生成的对象。理解这一步至关重要。

  • 有return的函数在new时对this的影响(正常的构造函数是没有return语句),我们先看下面的几个例子。
//例1
function Fun()  
{  
    this.name = 'haha';  
    return {};  
}
var stu = new Fun();  
console.log(stu.name); //undefined
//例2
function Fun()  
{  
    this.name = 'haha';  
    return function(){};
}
var stu = new Fun();  
console.log(stu.name); //undefined
//例3
function Fun()  
{  
    this.name = 'haha';  
    return 123;
}
var stu = new Fun();  
console.log(stu.name); //haha
//例4
function Fun()  
{  
    this.name = 'haha';  
    return undefined;
}
var stu = new Fun();  
console.log(stu.name); //haha

可以看出: 如果return的是一个对象,那么this会指向返回的对象,如果return的不是一个对象,那么this还是指向函数的实例。

但是return的是null时比较特殊。虽然null也是对象,但是this还是指向函数的实例。

//例5
function Fun()  
{  
    this.name = 'haha';  
    return null;
}
var stu = new Fun();  
console.log(stu.name); //haha

至此,this就说这么多,相信前三个输出大家都能理解了,接下来说说call和bind。

call和bind

call和apply只有参数不同,这里就只讨论call,因为call和bind参数使用方法是一样的。

  • call是动态的改变this的指向,即换个对象执行原对象方法的方法,并立即执行
  • bind是静态改变this的指向,并返回一个修改后的函数

就拿开始的题目最后一个输出来说:

如果只是使用call的话:

getA.call(b);//hello
getA.call(a);//haha

在执行到这两句时动态改变了this的指向,所以call(b)的输出hello,call(a)的输出haha。

接下来看有bind影响的:

var getA2 = getA.bind(a);

这里getA其实是a.getA大家应该理解,那么getA.bind(a)将this指向a,其实还是返回了a.getA函数赋值给了getA2。注意:其实函数没有变化,但是内部已经将this指向了a

getA2.call(b);//haha
//相当于a.getA.call(b);

之后call在动态调用时,内部的this已经指向了a,不会再指向b,因此会输出haha。

如果还不是很理解,就将绑定a改成b,如下:

var getA2 = getA.bind(b);
getA2.call(a);//hello

此时无论call里是a还是b,都会输出hello,因为内部的this已经被bind绑定指向b了。

总的来说,call方法是在调用时改变this并立即执行这个函数,bind方法可以先改变函数中的this,之后对应的函数可以在需要的时候再调用。


解决了题,再学一点点。

bind的参数可以在执行的时候再次添加,但是要注意的是,参数需要按照形参的顺序添加。例如:

var demoObj = {
    name:"haha",
    fun:function(a,b,c){
        console.log(a,b,c);
    }
}
var newFun = demoObj.fun;
var newFun2 = newFun.bind(demoObj,5);
newFun2(7,9);//5,7,9

看看应该就知道怎么用了。

这里放一个很棒的文章JavaScript 的 this 指向问题深度解析

好了,谢谢大家阅读~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

Swift基础语法

本文来自Swift中文开发组,感谢翻译者的分享。 本文将分几部分对Swift对iOS的语法做讲解。本文为第一节,主要讲解基础语法。 常量和变量 常量和变量把一个...

1956
来自专栏青玉伏案

窥探Swift之字符串(String)

  之前总结过Objective-C中的字符串《Objective-C精选字符串处理方法》,学习一门新语言怎么能少的了字符串呢。Swift中的String和Ob...

2356
来自专栏Java帮帮-微信公众号-技术文章全总结

【选择题】Java基础测试七

【选择题】Java基础测试七 86.欲构造ArrayList类的一个实例,此类继承了List接口,下列哪个方法是正确的?( B ) A、ArrayList m...

6253
来自专栏Albert陈凯

函数柯里化(Currying)和偏函数应用(部分应用函数)(Partial Application)的比较

【名词解释】Currying:因为是美国数理逻辑学家哈斯凯尔·加里(Haskell Curry)发明了这种函数使用技巧,所以这样用法就以他的名字命名为 Curr...

3835
来自专栏机器学习入门

LWC 60:736. Parse Lisp Expression

LWC 60:736. Parse Lisp Expression 传送门:736. Parse Lisp Expression Problem: You a...

2277
来自专栏老马说编程

(37) 泛型 (下) - 细节和局限性 / 计算机程序的思维逻辑

查看历史文章,请点击上方链接关注公众号。 35节介绍了泛型的基本概念和原理,上节介绍了泛型中的通配符,本节来介绍泛型中的一些细节和局限性。 这些局限性主要与Ja...

2086
来自专栏专注 Java 基础分享

解析java泛型(一)

     对于我们java中的泛型,可能很多人知道怎么使用并且使用的还不错,但是我认为想要恰到好处的使用泛型,还是需要深入的了解一下它的各种概念和内部原理。...

2126
来自专栏彭湖湾的编程世界

【算法】实现栈和队列

栈(stack) 栈(stack)是一种后进先出(LIFO)的集合类型, 即后来添加的数据会先被删除 ? 可以将其类比于下面文件的取放操作:新到的文件会被先取走...

3456
来自专栏一个会写诗的程序员的博客

函数式编程与面向对象编程[4]:Scala的类型关联Type Alias函数式编程与面向对象编程[4]:Scala的类型关联Type Alias

scala里的类型,除了在定义class,trait,object时会产生类型,还可以通过type关键字来声明类型。

1173
来自专栏阿杜的世界

Java泛型基础(一)目的泛型类总结

利用Java开发的时候,肯定会有一个类持有另一个或几个类的情况,在编写一些比较基础的组件,例如缓存操作组件,这类组件的逻辑差不多,但是希望能够处理不同的类型。

631

扫码关注云+社区

领取腾讯云代金券