js中的this以及改变this指向的三种方式(call、apply、bind)

一、认识this

1、this是什么?

  • 在 js中this是一个对象。
  • 它的值是不确定的,只有在代码运行的过程中才能确定this的值。
  • 它的值不能手动更改。

2、this的难点

  • 到处都有this:只要你可以写js代码的地方,就可以使用
  • this的值是动态变化的
  • 你可以修改this对应对象的某个属性,不能修改this自身的值,但是你可以更改this指向改变它对应的对象。

3. 确定this的值

使用this有两种大类:

1. 它在function的外部

<script type="text/javascript">
this.abc={};
console.info(this);
console.info(typeof this);
</script>
//这个this指向window

一般它会出现在function的内部。

2. 它在function的内部。(重点讨论!)

再分四种情况

1.在调用这个函数时,它不是某一个对象的属性,就是一个普通的被调用的函数。
2.函数是对象的方法。
3.如果在调用函数之前加一个new的话,会创建一个新的对象,而这个函数内部的this就会指向这个创建的对象。
4.如果它出现在对象的事件响应函数中,则它会指向这个对象。

1. 普通函数调用
function f(){
console.info(this);
console.info(this===window);
}
f();
//这个this指向window
//f()===window.f();
2. 当作对象的方法来调用
var obj={
"name":"abc",
"f":function(){
console.info(this);
console.info(this===obj);
	}
}
obj.f();
//this的值就是当前这个对象。
//此处的this指的是obj
3. 以构造器的格式使用
function F(){
this.abc=5;
this.a=Math.random();
console.info(this);
}
var f1=new F();
//当它被当作构造器来使用时,其中的this可以理解为:就是new 返回的那个对象。
//这里指的是F。
//因为所有的加在this上的属性,最终都加在了f1上了。
4. 事件回调函数
<button id="btn">按钮</button>
<script type="text/javascript">
	var btn=document.getElementById("button");
	btn.onclick=function(){
		this.className="abc";
		console.info(btn===this)
}
//this的值就是当前的对象
//这里指btn
3.练习题
1.在这里插入图片描述

func() === window.func();
上面的代码中,输出this的语句都是相同的,不同的是调用这个function的方式不同,则得到的结果中,this的值完全不同!!!

Var func = obj.f
相当于是:
在这里插入图片描述
此时,我们观察调用的方式是:
func()
这种形式是就我们说的:普通的函数调用,所以内部的this会指向window对象。
2.在这里插入图片描述
由于f1()的调用才是真正的输出this的语句,而现在f1并不属于某个对象,所以调用的格式 上就是一个普通的函数调用,所以其中的this就是window对象。

3. 小结

看这个函数如何被调用:
公式:

如果是: 对象obj.方法f(),则方法f中的this会指向这个对象obj.
“谁调用我,我就是指向谁。”

注意: 后来ES6规范中的箭头函数里面的this是在定义它时绑定的,指向它的父级作用域,而不是调用它对象,这个this的指向是不能通过call和apply改变的。

4. 认识prototype中的this(掌握)

在这里插入图片描述
上面的getmax中的this指向谁,此时并不能确定,而只有代码被调用才能确定!

我们的调用方式是:在这里插入图片描述
进一步理解:
在这里插入图片描述
Arr2.proto.getMin()这种调用也是正常的,是合法的,但它的结果却不是我们想要的。原因是:此时的调用使得getMin中的this指向 arr2.proto,而不是指向arr2。

5. 更改this的值

1.Call:更改函数内部的this的值并执行语句
2.Apply:更改函数内部的this的值并执行语句(和call的唯一区别是:它的参数列表是数组)
3.bind:更改函数内部的this的值,不执行语句。

1. 例子

在这里插入图片描述
上面的代码中,我们通过call实现了更改f内部的this的功能。
之前我们调用方法:对象.方法(); // 对象在前
现在使用call:函数.call(对象); //函数

2. call是什么(掌握)

为什么f.call可以使用?

分析:f是一个函数,也是一个对象,它的构造器是Function(f.proto === Function.prototype) ,它是属性的集合。call是不是它的属性呢?

在这里插入图片描述
call不是它的自有属性,但按原型链的规则,接下来它会去__proto__属性中去继续找call.在这里插入图片描述
所以函数f可以使用call属性。
进一步说, call属性是f.__proto__的自有属性,就相当于是Function.prototype 的自有属性.
在这里插入图片描述
只要一个function,就都可以使用call.

3. call的格式 和作用

作用:更改函数内部的this的值。

call格式
函数f.call(newObj,参数1,参数2…)
形参:
newObj :用newObj替换函数f中的this
参数1…参数n : 函数f的参数。

在这里插入图片描述

6. 为什么要改函数内部的this呢?

1. 共享
1. 问题

在这里插入图片描述
上面的两个对象obj1,ob2它有完全相同的属性:hello。这种写法会造成空间的浪费。在这里插入图片描述
函数体所占的空间是完全相同的,可想而知,如果这样的对象有100个,就是学浪费了99个。

2. 改进一步:

把共同的函数提出来,写成一个外部的function.
在这里插入图片描述

Obj.hello  ----- abc();

在这里插入图片描述
在这里插入图片描述
这样函数体就只有一份,节约空间。

注意:两种调用方式的区别:
 Abc() :这里的this会指向window
 Obj1.hello():这里的this会指向obj1.

上面的代码,再进一步:上面的两个对象中,hello这个属性是一模一样的。

这两个属性各自在自己所处的对象中占一个空间。由于它们是一样的,可以把它们省略掉。

3. 再次改进一步:

在这里插入图片描述
现在的情况是:在这里插入图片描述
现在两个对象中都没有这个hello属性,它们也不具备hello功能了,但是,我们可以随时去借用hello这个外部的独立的函数。
Hello,可以在所有的对象当中共享。
在这里插入图片描述

3. call的应用:判断当前的变量中保存的数据类型是什么?(掌握)

Var a =1;

问a此时保存数据是什么类型?

1.使用Typeof 判断数据类型

在这里插入图片描述
但是typeof并不能完全解决判断数据类型的问题,怎么办呢?

2. 用Object.prototype.totString.call

在这里插入图片描述
Object.prototype.toString :
(1)Object是一个对象,它有一个属性是prototype。这个属性的值又是一个对象,在Object.prototype这个对象中有一个属性叫toString,特殊地,它是一个方法。在这里插入图片描述

(2)由于它是一个function,则它可以使用call。
console.info(Object.prototype.toString.call(function(){}));
等价于:

var f=function(){}
console.info(Object.prototype.toString.call(f));

所以从格式上来看:Object.prototype.toString这个函数借给对象f用一下。

  • 会调用Object.prototype.toString函数
  • 同时,用f去代替函数内部的this。
    (3)Object.prototype.toString的返回值不是数组(只是看起来前后有[ ] )在这里插入图片描述
    它的返回结果是固定格式的:
    [object 当前对象的构造器的名字]
    所以,我们可以根据它来确定当前的数据的类型。
    写一个函数:
var rsf=Object.prototype.toString.call(abc);
rs=rs.substr(8);
var len=rs.length;
rs=rs.subsrt(0,len-1);
return rs.toLowerCase();
}

调用这个函数:在这里插入图片描述

3. 分析原因

每一个不同类型的数据都有toString方法 。

原因是:原型链。

以i.toString()为例,它在运行过程中,会产生一个包装对象 : var i = new Number(1);
这里用的toString就是对象i的toString。其实i中也没有tostring,但按原型链,它可以访问i.__proto__中的toString.
在这里插入图片描述
但它们的表现就完全不同:
在这里插入图片描述
对于数组而说:它会把它元素全部列出来。
对于普通对象{}来说,它只会给出一个”[object Object]”
对于函数,它会把签名列出来。

但是, Object.prototype.toString 就可以输出数据的类型,类似于”[object Object]” ,它就是一个“照妖镜”,它可以看到数据的真面目。在这里插入图片描述

7. call应用翻转字符串 (掌握)

var str = “abcdefghijklmnopqrstuvwxyz”;
目标是要把str进行翻转:”zyxwvu…a”

1. 方法一:循环

在这里插入图片描述
注意:可以使用for,和[]的不一定是数组。字符串是可以通过[]和for去访问元素的,但它绝对不是数组。

2. 方法二:借用数组的翻转方法

字符串虽然没有现在的翻转,但数组有reverse()方法。
我们现在就是想:字符串跟数组说:“把你的reverse()借我用一下。”在这里插入图片描述
找到数组的reverse方法.call(字符串)

Array.prototype.reverse.call(str)
在这里插入图片描述
出错了,reverse方法需要一个类数组对象。
下面,先把字符串转成数组:split()
在这里插入图片描述
格式:
字符串.split(分隔符),返回值就是一个数组。

  • 如果分隔符是””,则是完全打散。
  • 如果不写分隔符,则还是一个整体。

str.split("") :我爱你,我愿意为你 粉身碎骨
在这里插入图片描述
现在得到的一个翻转后的数组,现在要把数组元素拼接回去成一个字符串。
把数组拼接成字符串:用join。

在这里插入图片描述
在这里插入图片描述
把代码精简一下:
在这里插入图片描述
问题是:
如果已经把一个字符串转成了数组str.split(“”),为不什么不直接使用reverse方法呢?在这里插入图片描述
上面的代码也是可以工作的。

要借用Array.prototype.reverse方法的原因是:
(1)数组对象是可以有自己的自有属性,如果它恰好有一个自有属性也叫reverse,则会覆盖Array.prototype.reverse

var arr=[1,2,3];
arr.reverse=function(){
arr.reverse();
}

用Array.prototype.reverse也是可以修改的,但它的机率要小一些。

8. call的应用–把类数组对象转成数组 (掌握)

两个用的比较多的类数组对象:

  • Lis =document.getElementsByTagName(“li”);
  • Arguments
    在这里插入图片描述
    把lis转成数组。

1. 方法一:循环

在这里插入图片描述

2. 方法二:借用array中的slice

Var 新数组 = 旧数组.slice(起点下标,终点下标)
返回值:数组,是旧数组中的一个部分。

  • 包含起点,
  • 不包含终点
    在这里插入图片描述
    特殊点,如果不给参数,则会把所有的数组元素都输出来。在这里插入图片描述
    思路:借用Array.prototype.slice方法。在这里插入图片描述

Array.prototype.slice.call(lis),做两件事情:
(1)会执行slice方法,不给参数。
如果再给参数,如下:
var arr = Array.prototype.slice.call(lis,0,1);
arr只能包含第一个li了。
(2)把Slice中的this换成lis。

同理,对arguments对象也是如此:
在这里插入图片描述

9. Apply

apply也是用来修改function中的this。它的功能与 call是完全一样。 只是有一点不同: 它的参数列表不同:
在这里插入图片描述
特别地,如果函数f没有参数,则f.apply和f.call的用法是一样。

1. 示例

在这里插入图片描述
下面改成apply
在这里插入图片描述
另一种写法:在这里插入图片描述

2. apply的应用 求数组的最大值 (掌握)

需求:求数组的最大值。

在Math对象中,有一个max方法,它可以用来求最大值。

把Math.max这个方法用一下。

现在,我们的数据是保存一个数组中,而math.max它要求的数组是在形参列表中。

先用下call:

<script>
var arr=[1,2,3,5,4];
console.info(Math.max.call(arr,1,2,3,5,4));//5
</script>

Math.max.call(arr,1,2,3,5,4)会做两件事:
(1)执行Math.max(1,2,3,5,4)
(2)把内部的this改成arr

再改成 apply

<script>
var arr=[1,2,3,5,4];
console.info(Math.max.apply(arr,[1,2,3,5,4]));//5
</script>

由于apply的第二个是一个数组,所以,我们可以更进一步:

<script>
var arr=[1,2,3,5,4];
console.info(Math.max.apply(arr,arr));//5
</script>

理解:math.max.apply(arr,arr)会做两件事:
(1)执行Math.max(arr[0],arr[1],arr[2],…arr[arr.lenght-1])
(2)把内部的this改成arr

其实,把内部的this改成什么东西都不重要!!

var arr=[1,2,3,5,4];
console.info(Math.max.apply("abc",arr));//5
console.info(Math.max.apply({},arr));//5
console.info(Math.max.apply(null,arr));//5

由于Math.max的内部根本就没有用this,所以它并不在乎你把this改成什么!!!!

10. Bind(了解)

1. 语法和格式

用来更改函数内部的this。
格式:
var newf = f.bind(obj);

作用:
把函数f中的this改成obj对象,并返回一个新函数。

2. 示例

在这里插入图片描述
与call的区别:
(1)call改this,再执行函数
(2)bind改this,不执行函数,返回一个绑定新this的函数。
在这里插入图片描述
也可以直接在bind的后面加一个()去调用函数。

11. 小结

this无处不在,非常重要。
确定this的值只能在代码运行时才能确定。公式就是”谁调用我,我就指向谁”。
this的值可以通过call,apply,bind来进行修改。
要能够理解为什么要去修改this的值。
apply、call、bind三者都是用来改变函数的this对象的指向的;
apply、call、bind三者第一个参数都是this要指向的对象,也就是想指定的上下文;
apply、call、bind三者都可以利用后续参数传参;
bind是返回对应函数,便于稍后调用;apply、call则是立即调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值