【JS之路】经典问题之this与call、apply

导读


在JavaScript中关于this的各种问题已经是屡见不鲜了,但是当我们看到某个关于this的问题时,有时候还是不能快速、准确地构建出思路。今天这篇文章帮助你系统地构建出this的各种使用场景。

this解读


为什么会有this


熟悉JavaScript的同学都知道,执行环境是JavaScript的核心概念之一。简单介绍下,执行环境就是JavaScript中所有变量生存的环境,环境决定了变量的生命周期,以及引用关系(我能引用谁 AND 谁能引用我)。

而函数是比较特殊的,每一个函数都有自己的执行环境。反过来说,函数也可以在不同的环境下运行,所以需要一种机制,能够在函数体内快速获得当前的运行环境。this的设计目的,就是在函数内部指出当前的运行环境。


this的原理


this 总是(非严格模式下)指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。 简单来说,this对象是基于函数的执行环境绑定的。

this指向的不同场景


对象中的this


在对象中,通常用this区分对象属性和变量,这时this指向该对象

	var obj={
        name: "Li",
        getName:function(){
            console.log(this.name)
        },
        setName:function(name){
            this.name=name;
        }
    }

函数中的this


当函数不是作为某个对象属性被调用,是以普通函数被调用,this一般指向全局对象

	var name="Li";
	var getName=function(){
        console.log(this.name);
    }
    getName();  //"Li"

当函数用于特殊的作用时,会导致this的行为发生改变。例如在JavaScript中模拟构造函数

	var name="Li"
	var Person=function(){
        this.name="AAA";
    }
    var me=new Person();
	console.log(me.name); //"AAA"

当通过new关键字调用函数时,总会返回一个该函数的实例,this通常也指向这个实例对象


call和apply


ECMAScript规范中,每个函数都包含call和apply方法。他们的作用实质都是在特定的作用域调用函数,也就是改变当前函数的this指向。

不同的是,call和apply在传参形式上不同。

  • call() 参数一:函数上下文的对象 后续的参数为函数的参数列表
  • apply() 参数一:函数上下文的对象 参数二为函数的参数组成的数组
用法
  1. 改变this指向
 var obj1={
     name:"AAA"
 }
 var obj2={
     name:"BBB",
     getName:function(){
         console.log("名字:"+this.name);
     }
 }
 obj2.getName(); // 名字:BBB
 obj2.getName.apply(obj1); // 名字:AAA

这里我们把obj1作为apply的参数传入,此时函数里的this便指向了obj1,此时相当于访问obj1.name

  1. 扩充函数运行时的作用域(也是所谓的继承)
var name="AAA";
var obj={
    name: "BBB"
}
function getName(){
    console.log(this.name);
}
getName();//AAA
getName.apply(this); //AAA
getName.apply(obj); //BBB

在函数中是不存在name这个变量的,但是根据作用域链,最终会到全局作用域中寻找变量,从而显示"AAA";但是我们通过apply去显式改变执行环境,就能改变this的指向,从而获取到name属性。

  1. 调用函数

apply、call方法都会使函数立即执行,因此也可以用它们来调用函数

	function fn(){
        console.log("hello");
    }
	fn.apply(); //hello

箭头函数


箭头函数是函数的一种简写方式,它和普通函数最重要的区别就是它自身没有this值。简单来说,箭头函数在定义时,this就继承了定义箭头函数所在的环境的this

	var name="AAA";
	var obj={
         	name:"BBB",
        	getName: ()=>{
                console.log(this.name);
            }
    }
    obj.getName(); //AAA
	var a=obj.getName();
	a(); //AAA

可以看到,我们改变函数的执行环境并不会改变输出结果,因为箭头函数中的this在定义时就已经绑定到全局环境了。

再看下面这个例子:

	var name="AAA";
	function Person(){
        this.name="BBB";
        return ()=>{
            console.log(this.name);
        }
    }
	var a=new Person();
	a(); //BBB 

当我们在函数中使用箭头函数时,箭头函数的this就指向这个函数。


细说bind函数


bind函数时ES6新增的方法,它和call很相似(传参方面),但是不同的是,它会返回一个改变了上下文 this后的函数,对调用函数本身不会造成影响。

	var obj={
        name:"AAA"
    }
    function fn(){
        this.name="BBB";
        console.log(this.name);
    }
	var fn1=fn.bind(obj);
	fn1(); //AAA
	fn(); //BBB

在低版本浏览器中没有bind方法,为了兼容,我们可以自己实现一个,利用call方法

if(!Function.prototype.bind){
    Function.prototype.bind=function(){
        var self=this; //保存原函数
        context=[].shift.call(arguments) //从arguments取出第一个参数,为需要绑定的上下文环境
        args=[].slice.call(arguments) //把剩余的参数取出
        return function(){
            //返回一个函数,并且把传入bind方法的参数和使用新函数传入的参数传入
            //因为在传参方面,我们通常使用arguments便于操作,它是一个类数组,
            //我们使用apply方法更方便传参
            self.apply(context,[].concat.call(args,...arguments));
        }
    }
}

这里简单说下 [].slice和Array.prototype.slice的区别:

[].slice实际上就是一个Array的实例,然后调用slice方法,而这个方法来自于Array的prototype上,在使用上效果完全一样。

要说不同之处,也就是this的指向不同,在[].slice中,this指向这个实例;在Array.prototype.slice中,this指向的是Array.prototype。


最后


借用《你不知道的JavaScript》中的总结:

Javascript 是一个文本作用域的语言,就是说,一个变量的作用域, 在写这个变量的时候确定。 this关键字是为了在JS中加入动态作用域而做的努力。所谓动态作用域,就是说变量的作用范围,是根据函数调用的位置而定的。

this是JS中的动态作用域机制,具体来说有四种,优先级有低到高分别如下:

  • 默认的this绑定:就是说在一个函数中使用了this,但是没有为this绑定对象。这种情况下, 非严格默认,this就是全局变量Node环境中的global,浏览器环境中的window。
  • 隐式绑定:使用obj.foo()这样的语法来调用函数的时候,函数foo中的this绑定到obj 对象。
  • 显示绑定: foo.call(obj,…),foo.apply(obj,[…]),foo.bind(obj,…)
  • 构造绑定: new foo(),这种情况,无论foo是否做了绑定,都要创建一个新的对象,然后 foo中的this引用这个对象。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值