划重点:js中的this、call、apply

2019-08-29 13:15:25 浏览数 (1)

在js中this有4种指向,分别为:

  • 作为对象的方法调用
  • 作为普通函数调用
  • 构造器调用
  • Function.prototype.call或Function.prototype.apply调用

1、当作为对象的方法调用时,该方法中的this就指向了该对象

代码语言:javascript复制
1var obj = {
2    name: 'nitx',
3    getName: function(){
4        console.log(this === obj)      //true
5        console.log(this.name)         //nitx
6    }
7}
8obj.getName();

2、作为普通函数调用时,函数中的this指向全局对象,在浏览器环境中,指向的就是全局对象window

代码语言:javascript复制
 1window.name = 'globalName'
 2
 3var func = function(){
 4    console.log(this.name)
 5}
 6
 7func();    //globalName
 8
 9//或者
10
11var obj = {
12    name: 'nitx',
13    getName: function(){
14        console.log(this.name)
15    }
16}
17
18var func2 = obj.getName;
19func2()     //globalName
20/*这里打印结果依然是指向全局对象的原因是: 将obj对象中的getName方法赋值给新的变量func2时,func2就是一个全局作用域中的普通函数,而非obj对象中的方法,已经与getName方法是两个完全独立的方法,拥有完全不同的作用域上下文*/

3、在构造器中调用this 先要理解js中的构造器。 除了宿主环境中的内置函数外,大多数函数都既能当普通函数,也能当构造函数。区别在于调用方法:

当函数名加括号的调用时就是普通函数,

当用new运算符调用函数时就是构造函数,并且该构造函数调用时总是会返回一个对象,即实例对象,该构造函数中的this就是指向这个返回的实例对象。

代码语言:javascript复制
1var Func = function(name, age){
2    this.name = name
3    this.age = age
4}
5var func = new Func('nitx', 30)
6console.log(func.name)  //nitx

这里还需要注意一个问题,如果构造函数显式的返回一个Object类型的对象,则new 构造函数名()的运算结果是返回这个对象,而不是原先new出来的实例对象,所以返回出来的这个对象中的this指向需要注意是指向这个返回对象的。

代码语言:javascript复制
1var Func = function(){
2    this.name = 'nitx'
3    return {
4        name: 'sxm'
5    }
6}
7var func2 = new Func()
8console.log(func2.name);    //sxm

如果构造器不显式的返回任何数据,或者返回一个非对象类型的数据,就不会出现上述问题。

4、Function.prototype.call或Function.prototype.apply调用this 通过call或apply,可以动态改变传入函数的this

代码语言:javascript复制
 1var obj1 = {
 2    name: 'nitx',
 3    getName: function(){
 4        return this.name
 5    }
 6}
 7
 8var obj2 = {
 9    name: 'sxm'
10}
11
12console.log(obj1.getName())     //nitx
13console.log(obj1.getName.call(obj2))        //sxm

对于call和apply的理解

要想理解上文第4点中的call调用改变this的具体实现原理,需要先了解call和apply的作用。

Function.prototype.call或Function.prototype.apply,它们的作用是完全一样的,都是改变函数的this指向。区别在于两者传入参数的不同。

apply接收两个参数,第一个参数指定了调用apply的函数体内this对象的指向,第二个参数是一个带下标的集合,该集合可以是数组,也可以是类数组,apply方法把这个集合中的所有元素作为参数依次传递给调用apply的函数:

代码语言:javascript复制
1var func = function(a, b, c){
2    console.log([a, b, c])
3}
4
5func.apply(null, [1, 2, 3])     //[1, 2, 3]

call方法传入的参数中,第一个参数也是指定调用call的函数体内this对象的指向,从第二个参数开始往后,每个参数被依次传入函数中。

当使用apply或call时,如果传入的第一个参数是null,则函数体内的this会指向默认的宿主对象,在浏览器中就是window,但在严格模式下,函数体内的this还是为null:

代码语言:javascript复制
 1var func = function(){  
 2    //非严格模式下,函数调用apply或call时,第一个参数设为null时,函数体内的this指向全局对象
 3    console.log(this === window)        //true
 4}   
 5
 6func.apply(null)
 7
 8var func = function(){
 9    'use strict'    //严格模式下this依然指向null
10    console.log(this === null)        //true
11}
12
13func.apply(null)

所以有时如果使用apply或call的目的不是指定函数体内的this指向,而只是借用该函数方法进行某种运算时,可以传入null来代替某个具体对象。

代码语言:javascript复制
1Math.max.apply(null, [1, 2, 3, 4, 5])       //借用Math.max方法来计算数据[1,2,3,4,5]中的最大值

再来回顾下本文重点:

this在不同的调用情况下指向也不同。

当在对象方法内调用时指向该对象;

当在普通函数内调用时指向宿主环境中的全局对象;

当在构造器中调用时分为两种情况。构造器无return返回值或返回值不为对象类型数据时,构造器中的this指向被构造器new出来的实例对象;构造器有return返回值且值为Object对象类型的数据时,this指向该构造器运算后返回出来的对象值;

当在Function.prototype.call或Function.prototype.apply情况下,前面调用apply或call的函数体内的this原有指向被更改为指向apply或call方法中的第一个参数。

关于apply或call,两者的作用完全一致,都是更改调用apply或call的函数体内的this对象指向。 区别仅在于两者的第二个参数传入不同:

代码语言:javascript复制
1func.apply(
2    [参数一:将调用apply方法的函数体内的this对象指向改为指向本参数], 
3    [参数二:传入调用apply方法的函数体内的参数集合(数组或类数组)]
4    )
代码语言:javascript复制
1func.call(
2    [参数一:将调用call方法的函数体内的this对象指向改为指向本参数], 
3    [参数二:传入调用call方法的函数体内的参数1]  //从第二个参数开始,每个参数被依次传入函数func中
4    [参数三:传入调用call方法的函数体内的参数2]
5    [参数四:传入调用call方法的函数体内的参数3]
6    ...
7    )

如果只是想通过apply或call来借用某个函数方法进行某种运算,则只需将apply或call的第一个参数设为null来代替某个具体对象。

原因?

因为在非严格模式下,此时调用apply或call的函数体内的this会指向宿主环境中的全局对象;在严格模式下此时调用apply或call的函数体内的this会指向null。

延伸应用:

理解了this、call、apply后,在实际js开发中,可以很方便的实现对象的继承

继承demo1:

代码语言:javascript复制
 1var Parent = function(){
 2    this.name = 'nitx';
 3    this.job = 'frontEnd'
 4}
 5
 6var child = {};
 7
 8console.log(child)      //空对象 {}
 9
10Parent.call(child)
11
12console.log(child)      //已继承Parent对象的child  {name: "nitx", job: "frontEnd"}

继承demo2:

代码语言:javascript复制
 1//子类继承父类的属性和方法
 2
 3//定义父类
 4var Person = function(name, job){
 5    this.name = name;
 6    this.job = job;
 7}
 8
 9Person.prototype.showName = function(){
10    console.log(this.name)
11}
12
13Person.prototype.showJob = function(){
14    console.log(this.job)
15}
16
17//定义子类
18var Worker = function(name, job, age){
19    Person.call(Worker, name, job)  //把父类Person中的this对象指向改为指向子类Worker运算new出来的实例对象
20    this.age = age;  
21}
22
23//子类从父类(父原型)继承方法有三种,会全部列出来,但我推荐使用第三种方法,原因在注释中
24
25//方法一:缺点 --子类原型与父类原型本质都是指针,各自指向内存中作为它们原型的对象,通过赋值操作会将子类原型指针指向父类原型对象,产生的问题就是如果修改子类原型方法(也叫实例方法),父类的原型方法(实例方法)也会发生同步改变
26//Worker.prototype = Person.prototype
27
28//方法二:将子类的原型指向父类的实例对象,缺点不够优雅
29//Worker.prototype = new Person()
30
31//方法三:将父类方法通过for...in...枚举进子类原型方法中
32for(var i in Person.prototype){
33    Worker.prototype[i] = Person.prototype[i]
34}
35
36Worker.prototype.showAge = function(){
37    console.log(this.age)
38}
39
40
41var p1 = new Person('sxm', 'count')
42var w1 = new Worker('nitx', 'frontend', 30)
43
44console.log(p1)
45console.log(w1)
46
47w1.showName();  //nitx
48w1.showJob();   //frontend
49w1.showAge();   //30
50
51p1.showName();  //sxm
52p1.showJob();   //count
53p1.showAge();     //error 显示没有这个方法

0 人点赞