最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • this使用细节注意与三个固定切换方法

    正文概述 掘金(岁秋)   2021-02-07   405

    文章结构图:
    this使用细节注意与三个固定切换方法

    1.this回忆

    浏览器(解析器)调用函数时,每次都会向函数内部传递隐含的参数,不是普通的写好的实参传递给形参那种。
    这两个隐含的参数就是函数的上下文对象 this封装实参的对象 arguments

    this指向的是一个对象,即this的类型就是object。这个对象称为函数执行的上下文对象。
    粗暴总结下(可自行编程实践下),根据函数的调用方式不同,this会指向不同的对象。

    • 调用方式一:以全局函数的方式调用时,this永远是Window。
    • 调用方式二:以方法(即对象内的函数)的方式调用时,this就是调用方法的那个对象。
    • 调用方式三:在以构造函数方式调用时(使用构造函数创建一个新对象),该新对象就是函数的this
    • 调用方法四:在使用函数对象的方法call()和apply()时,this是绑定的对象,fun.call(obj);this是obj

    2. this使用注意点

    2.1 避免多层this

    问题提出:见下面多层this代码

    var data=123;
    var A={
        data:789,
        f1:function(){
            console.log("第一层",this.data);
            var f2=function(){
                console.log("第二层",this.data);
                var f3=function(){
                    console.log("第三层",this.data);
                }();
            }();
        }
    }
    A.f1();
    //输出:
    第一层 789   //表示f1的this指向A
    第二层 123	//表示f2的this指向window
    第三层 123	//表示f3的this指向window
    

    为什么第一层的this是指向其调用方法的对象,而第二层以后就是指向第三层了呢?思考一下!同时可以运行上面代码试试(注意在node下运行,全局环境是global,而不是window,会得不到上面结果,使用浏览器运行)

    问题分析:原因是其实际执行的代码和你说想像的不一样,看下面等价代码

    //这里为了简化,就将原来三层换为现在两层
    var data=123;
    var temp=function(){
        console.log("第二层",this==global);
    };
    var A={
        data:789,
        f1:function(){
            console.log("第一层",this.data);
            var f2=temp();
        }
    }
    A.f1();
    //输出
    第一层 789
    第二层 123
    

    可以看出,在f1函数执行时,其中的f2函数的上下文对象其实已经改变了,其的上层对象已经变为了全局对象window。

    解决方案

    • 解决方案一:固定指向外层的this。方法在下面的代码中实现。
    var data = 123;
    var A = {
        data: 789,
        f1: function () {
            console.log("第一层", this.data);
            var that = this;
            var f2 = function () {
                console.log("第二层", that.data);
            }();
        }
    }
    A.f1();
    //输出:
    第一层 789
    第二层 789
    

    由于this的指向不确定,故固定了指向外层的this为that变量,然后在内部使用that,阻止了this指向的改变。(该方法非常常见,一定掌握

    • 解决方案二:使用严格模式('use strict'声明使用严格模式)
    var data = 123;
    var A = {
        data: 789,
        f1: function () {
            console.log("第一层", this.data);
            var f2 = function () {
                'use strict';
                console.log("第二层", this.data);
            }();
        }
    }
    A.f1();
    //输出:
    TypeError: Cannot read property 'data' of undefined
    

    使用严格模式就防止了其不明的指向发生。

    2.2 避免数组处理方法中的this

    问题提出:见下面forEach方法参数函数中this使用的代码

    var A ={
        Greetings:"happy new year",
        friends:['张三',"李四"],
        fun:function(){
            this.friends.forEach(function(item){
                console.log(this.Greetings,item);
            });
        }
    }
    A.fun();
    //输出
    undefined 张三
    undefined 李四
    

    上面的问候语为什么没有输出,而只输出了被问候的朋友?

    问题分析:上面问候语没有出来,但是被问候的朋友出来了,说明this.friends的this指向了对象A,而forEach参数函数里的this指向了全局,而全局并没有friends数组,所以输出undefined。 所以到这里大家也能猜到了,这里的原因和上面多次this一样,是内部的this不指向外部而是指向了顶层对象。

    解决方案

    • 解决方案一:使用中间变量固定this

    看代码

    var A ={
        Greetings:"happy new year",
        friends:['张三',"李四"],
        fun:function(){
            var that=this;//中间变量绑定this
            this.friends.forEach(function(item){
                console.log(that.Greetings,item);
            });
        }
    }
    A.fun();
    //输出
    happy new year 张三
    happy new year 李四
    
    • 解决方案二:使用forEach方法的第二个参数
    var A ={
        Greetings:"happy new year",
        friends:['张三',"李四"],
        fun:function(){
            this.friends.forEach(function(item){
                console.log(this.Greetings,item);
            },this);//第二个参数为this
        }
    }
    A.fun();
    

    forEach(callback,thisArg),有两个参数,因为 thisArg 参数(this)传给了 forEach(),每次调用时,它都被传给 callback 函数,作为它的 this 值。 此外,map方法也是类似的

    2.3 避免回调函数中的this

    回调函数里的this也会改变指向。 举一个jQuery里回调函数的例子;

    var A =new Object();
    A.f=function(){
        console.log(this===A);
    }
    $('#button').on('click',A.f);
    //点击按钮后显示false
    

    原因是这里的this指向的不是A对象,而是指向按钮的DOM对象,因为f方法是在按钮对象的环境中被调用的。
    解决方法:使用apply(),call()或bind(),下一段粗略介绍其用法。

    3. this的固定操作

    JavaScript提供了call,apply,bind三个方法来切换或固定this的指向,下面就介绍下这三个方法的使用。

    3.1 Function.prototype.call()

    直接上代码

    var n=123;
    
    var obj={
        n:456
    };
    var f=function(){
        return this;
    }
    console.log(f()===window);//输出true
    //没有使用call时this指向的是window
    console.log(f.call(obj)===obj);//输出true
    //使用了call后,call方法指定this对象为obj
    var A=function(){
        console.log(this.n);
    }
    //上面的函数使用了this,this原来是window
    //使用call方法传入指定的this。
    A.call(obj);//456
    A.call();//123
    A.call(null);//123
    A.call(undefined);//123
    A.call(window);//123
    //上面表示了当call方法的参数为空,null,undefined时其传入的时window对象。
    

    call方法另外的妙用:

    • call方法可以有多个参数,第一个参数为this,第二个开始为函数调用时所需的参数。
    var add = function(a,b){
    return a+b;
    }
    console.log(add.call(this,2,3));
    //输出5,表示后面的2,3对应形参a,b。
    
    • 使类数组能够使用数组的方法

    类数组就是和数组类数的对象(例如arguments对象),其中值以键值对形式存在,拥有length属性,但不拥有数组的其他方法。

    var a = {'0':'a', '1':'b', '2':'c', length:3};  
    // An array-like object
    Array.prototype.join.call(a, '+');  
    // => 'a+b+c'
    Array.prototype.slice.call(a, 0);   
    // => ['a','b','c']: true array copy
    Array.prototype.map.call(a, function(x) { 
        return x.toUpperCase();
    });                                 
    // => ['A','B','C']:
    

    3.2 Function.prototype.apply()

    apply方法与call方法非常相似,在绑定this方面一模一样,重复的性质这里就不再介绍,这里说说其的不同点。
    其第二个参数时一个数组,call时一个一个的去传参数,apply则必须以数组形式传递参数,将所有实参组成一个数组,否则报错。

    var add = function(a,b){
    return a+b;
    }
    console.log(add.apply(this,[2,3]));
    //输出5
    

    apply方法妙用:

    • 计算出数组中最大元素(正好弥补js中没数组计算最大值的空缺)
    var arr=[1,2,3,4,5,6,7,8,9];
    var Max=Math.max.apply(null,arr);
    console.log(Max);//输出9
    
    • 将数组中的空元素变为undefined,forEach方法会跳过空元素但不会跳过undefined。利用Array构造函数将其参数变为数组,空元素则会变为undefined。
    var arr=[1,,2,,3,,4];
    function print(i){
    	console.log(i);
    }
    arr.forEach(print);
    //输出:
    // 1
    // 2
    // 3
    // 4
    Array.apply(null,arr).forEach(print);
    //输出
    // 1
    // undefined
    // 2
    // undefined
    // 3
    // undefined
    // 4
    
    • 将类数组转化为真数组

    类数组就是和数组类数的对象(例如arguments对象),其中值以键值对形式存在,拥有length属性,但不拥有数组的其他方法。
    使用slice方法,可以将一个拥有length属性的对象转为真正的数组,没有length属性就只会返回一个空数组,默认length为0.

    Array.prototype.slice.apply({0:1,length:1});
    //[1]
    Array.prototype.slice.apply({0:1});
    //[]
    Array.prototype.slice.apply({0:1,length:2});
    //[1,undefined]
    Array.prototype.slice.apply({length:1});
    //[undefined]
    

    3.3 Function.prototype.bind()

    bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。使用与部分特性和call()方法差不多,但值得注意的是其会返回一个新函数
    应用示例:

    var demo={
    	friends:1,
    	who:function(){
    	console.log("张三和李四");
    	this.friends++;
    	}
    };
    var fun=demo.who.bind(demo);
    console.log(demo.friends);//输出1
    fun();//输出张三和李四
    console.log(demo.friends);//输出2
    

    上面代码demo里的who()方法赋值给了fun(),这时用bind()方法将who()内部的this绑定到了demo。根据上面两次friends值得输出,也能看出返回的新函数与对象里方法其实是同一个。

    bind()方法妙用:
    可以对一个函数预设初始参数

    function a() {
    	return Array.prototype.slice.call(arguments);
    	 //将类数组转换成真正的数组
    }
    var b = a.bind(a, 15, 20)
    console.log(b()); //输出[ 15, 20 ]
    var s = b(25, 30);
    console.log(s); //输出[ 15, 20, 25, 30 ]
    

    上面的代码就是为函数预设了两个参数15和20,其都在类数组arguments里。

    最后,补充一点,在es6中,可以使用箭头函数的方式来规避this指向的问题

    var data = 123;
    var A = {
        data: 789,
        f1: function () {
            console.log("第一层", this.data);
            var f2 = (()=> {
                console.log("第二层", this.data);
            })();
        }
    }
    A.f1();
    //输出:
    第一层 789
    第二层 789
    //对比2.1节.就会发现使用箭头函数后this指向不在是全局对象了
    

    箭头函数的this永远指向其父作用域,任何方法都改变不了(就是没有es5中this那么"灵活"?),包括call,apply,bind。 这是由于this在箭头函数中已经按照词法作用域绑定了,所以,用call或者apply调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略,只能用于传参数。

    这就是我在阅读this相关介绍时总结的知识点了,欢迎点赞收藏!


    起源地下载网 » this使用细节注意与三个固定切换方法

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元