一、认识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则是立即调用。