JavaScript中的this/call/apply/bind的使用及区别
作者:刘亦枫
一、this
1.什么是this
this 关键字在大部分语言中都是一个重要的存在,JS中自然不例外,其表达的意义丰富多样甚至有些复杂,深刻理解this是学习JS、面向对象编程非常重要的一环。
2.this 代表什么
this代表函数(方法)执行的上下文环境(上下文,类似与你要了解一篇文章,了解文章的上下文你才能清晰的了解各种关系)。
但在 JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变。
1.在方法中,this 表示该方法所属的对象。
2.如果单独使用,this 表示全局对象。
3.在函数中,this 表示全局对象。
4.在函数中,在严格模式下,this 是未定义的(undefined)。
5.在事件中,this 表示接收事件的元素。
6.类似 call() 和 apply() 方法可以将 this 引用到任何对象。
3.绑定 this 的方法
this的动态切换,固然为 JavaScript 创造了巨大的灵活性,但也使得编程变得困难和模糊。有时,需要把this固定下来,避免出现意想不到的情况。JavaScript 提供了call、apply、bind这三个方法,来切换/固定this的指向。
4.this的指向
1.在一般函数方法中使用 this 指代全局对象
function test(){ this.x = 1; //这里this就是window console.log(this.x); } test(); // 1
JS规定,函数中的this,在函数被调用时确定,它指函数当前运行的环境。
2.作为对象方法调用,this 指代上级对象
var x =3; function test(){ alert(this.x); } var o = { x:1, m:test }; o.m(); // 1
如果函数作为对象的方法时,方法中的 this 指向该对象。
3.作为构造函数调用,this 指代new 出的对象
function test(){ console.log(this); } var o = new test(); test(); //可以看出o代表的不是全局对象
new关键词的作用是调用某个函数并拿到其中的返回值,只是调用过程稍特殊。在上面的代码实例中。test函数被new关键词调用时,内部依次执行了以下步骤:
(1)创建一个空对象。
(2)将这个空对象的原型,指向这个构造函数的prototype。
(3)将空对象的值赋给函数内部的this(this就是个空对象了)。
(4)执行函数体代码,为this这个对象绑定键值对。
(5)返回this,将其作为new关键词调用oop函数的返回值。
所以构造函数中的this,依旧是在构造函数被new关键词调用时确定其指向,指向的是当前被实例化的那个对象。
4.箭头函数中的this
箭头函数是ES6的新特性,最重要的特点是它会捕获其所在上下文的this作为自己的this,或者说,箭头函数本身并没有this,它会沿用外部环境的this。也就是说,箭头函数内部与其外部的this是保持一致的。
this.a=20 var test={ a:40, init:()=>{ console.log(this.a) function go(){ this.a=60 console.log(this.a) } go.prototype.a=50 return go } } var p=test.init() p() new (test.init())() //输出 20 60 60 60
5.改变指向
this的动态切换,固然为 JavaScript 创造了巨大的灵活性,但也使得编程变得困难和模糊。有时,需要把this固定下来,避免出现意想不到的情况。JavaScript 提供了call、apply、bind这三个方法,来切换/固定this的指向。
bind方法和apply、call稍有不同,bind方法返回一个新函数,以后调用了才会执行,但apply、call会立即执行。
二、Function.prototype.bind()
bind()方法主要就是将函数绑定到某个对象,bind()会创建一个函数,函数体内的this对象的值会被绑定到传入bind()中的第一个参数的值,例如:f.bind(obj),实际上可以理解为obj.f(),这时f函数体内的this自然指向的是obj;
示例:
function f(y, z){ return this.x + y + z; } var m = f.bind({x : 1}, 2); console.log(m(3)); //6
这里bind方法会把它的第一个实参绑定给f函数体内的this,所以这里的this即指向{x : 1}对象,从第二个参数起,会依次传递给原始函数,这里的第二个参数2,即是f函数的y参数,最后调用m(3)的时候,这里的3便是最后一个参数z了,所以执行结果为1 + 2 + 3 = 6分步处理参数的过程其实是一个典型的函数柯里化的过程(Curry)。
三、call/apply
1.定义
每个函数都包含两个非继承而来的方法:call()方法和apply()方法。
call和apply可以用来重新定义函数的执行环境,也就是this的指向;call和apply都是为了改变某个函数运行时的context,即上下文而存在的,换句话说,就是为了改变函数体内部this的指向。
2.语法
call()
调用一个对象的方法,用另一个对象替换当前对象,可以继承另外一个对象的属性,它的语法是:
Function.call(obj[, param1[, param2[, [,...paramN]]]]);
- obj:这个对象将代替Function类里this对象
- params:一串参数列表
说明:call方法可以用来代替另一个对象调用一个方法,call方法可以将一个函数的对象上下文从初始的上下文改变为obj指定的新对象,如果没有提供obj参数,那么Global对象被用于obj。
apply()
和call()方法一样,只是参数列表不同,语法:
Function.apply(obj[, argArray]);
- obj:这个对象将代替Function类里this对象
- argArray:这个是数组,它将作为参数传给Function
说明:如果argArray不是一个有效数组或不是arguments对象,那么将导致一个TypeError,如果没有提供argArray和obj任何一个参数,那么Global对象将用作obj。
3.异同
相同点
call()和apply()方法的相同点就是这两个方法的作用是一样的。都是在特定的作用域中调用函数,等于设置函数体内this对象的值,以扩充函数赖以运行的作用域。
一般来说,this总是指向调用某个方法的对象,但是使用call()和apply()方法时,就会改变this的指向,看个例子:
function add(a, b) { return a + b; } function sub(a, b) { return a - b; } console.log(add.call(sub, 2, 1));//3
为什么add.call(sub, 2, 1)的执行结果是3呢,因为call()方法改变了this的指向,使得sub可以调用add的方法,也就是用sub去执行add中的内容,再来看一个例子:
function People(name, age) { this.name = name; this.age = age; } function Student(name, age, grade) { People.call(this, name, age); this.grade = grade; } var student = new Student('小明', 21, '大三'); console.log(student.name + student.age + student.grade);//小明21大三
在这个例子中,我们并没有给Student的name和age赋值,但是存在这两个属性的值,这还是要归功于call()方法,它可以改变this的指向。
在这个例子里,People.call(this, name, age);中的this代表的是Student,这也就是之前说的,使得Student可以调用People中的方法,因为People中有this.name = name;等语句,这样就将name和age属性创建到了Student中。
总结一句话就是call()可以让括号里的对象来继承括号外函数的属性。
至于apply()方法作用也和call()方法一样,可以这么写:
People.apply(this, [name, age]);
或者这么写:
People.apply(this, arguments);
在这里arguments和[name, age]是等价的。
不同点
从定义中也可以看出来,call()和apply()的不同点就是接收参数的方式不同。
1.apply()方法接收两个参数,一个是函数运行的作用域(this),另一个是参数数组。
2.call()方法不一定接受两个参数,第一个参数也是函数运行的作用域(this),但是传递给函数的参数必须列举出来。
在给对象参数的情况下,如果参数的形式是数组的时候,比如之前apply()方法示例里面传递了参数arguments,这个参数是数组类型,并且在调用Person的时候参数的列表是对应一致的(也就是Person和Student的参数列表前两位是一致的)就可以采用apply()方法。
但是如果Person的参数列表是这样的(age,name),而Student的参数列表是(name,age,grade),这样就可以用call()方法来实现了,也就是直接指定参数列表对应值的位置Person.call(this,age,name)。
到此这篇关于JavaScript中的this/call/apply/bind的使用及区别的文章就介绍到这了,更多相关JavaScript this/call/apply/bind内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!