• 0

  • 469

  • 收藏

this使用细节注意与三个固定切换方法

云哥

关注云计算

3星期前

文章结构图:

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相关介绍时总结的知识点了,欢迎点赞收藏!

免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

云计算

469

相关文章推荐

未登录头像

暂无评论