搞懂 Javascript 中的this(包含apply、call、bind)

当前位置: 首页 » 记录 » javascript » 搞懂 Javascript 中的this(包含apply、call、bind)

分类: javascript 94阅读阅读模式

this 关键字是 JavaScript 中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。this 的绑定一直是一件非常令人困惑的事,即使经验丰富的开发者有时也较难说清它的指向。

当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录(上下文)的其中一个属性,会在函数执行的过程中用到。

一、this的指向

this 总是指向执行时当前对象

JavaScript 的 this 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境

也就是说 this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式

除了使用 with 和 eval 的情况,常用的可分为以下几种:

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

1. 作为对象的方法调用

对象的方法被调用时,this 指向该对象。

  1. var obj = {
  2.     a: 1,
  3.     getA() {
  4.         alert ( this === obj ); // 输出:true alert ( this.a ); // 输出: 1
  5.     }
  6. };
  7. obj.getA();

2. 作为普通函数调用

当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的 this 总是指向全局对象。

这里注意对象的方法被单独拷贝出来后执行,那么原来的this会丢失,变成指向全局对象

  1. //在浏览器的JavaScript 里,这个全局对象是window 对象。
  2. window.name = 'globalName';
  3. var getName = function(){
  4.     return this.name;
  5. };
  6. console.log( getName() ); // 输出:globalName
  7. //或者:
  8. window.name = 'globalName';
  9. var myObject = {
  10.     name: 'sven',
  11.     getName: function(){
  12.         return this.name;
  13.     }
  14. };
  15. var getName = myObject.getName;
  16. console.log( getName() ); // globalName

3. 构造器调用

通常情况下,构造器里的 this 就指向返回的这个对象;

  • 如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,this指向返回的这个对象;
  • 如果构造器显式地返回了一个对象,则实例化的结果会返回这个对象,而不是this;
  1. //构造器里的this 就指向返回的这个对象,见如下代码:
  2. var MyClass = function(){
  3.     this.name = 'sven';
  4. };
  5. var obj = new MyClass();
  6. alert ( obj.name ); // 输出:sven
  7. var MyClass = function(){
  8.     this.name = 'sven';
  9.     return { // 显式地返回一个对象
  10.         name: 'anne'
  11.     }
  12. };
  13. var obj = new MyClass();
  14. alert ( obj.name ); // 输出:anne
  15. var MyClass = function(){
  16.     this.name = 'sven'
  17.     return 'anne'// 返回string 类型
  18. };
  19. var obj = new MyClass();
  20. alert ( obj.name ); // 输出:sven

4. call 或 apply 调用

apply和call可以动态地改变传入函数的 this

  1. var obj1 = {
  2.     name: 'sven',
  3.     getName: function(){
  4.         return this.name;
  5.     }
  6. };
  7. var obj2 = {
  8.     name: 'anne'
  9. };
  10. console.log( obj1.getName() ); // 输出: sven
  11. console.log( obj1.getName.call( obj2 ) ); // 输出:anne

二、call, apply 和 bind

简介及区别

call 和 apply作用一模一样,区别仅在于传入参数形式的不同。

apply

apply 接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组

类数组:

  1. 对象本身要可以存取属性;
  2. 对象的 length 属性可读写。

call

call 传入的参数数量不固定,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依次传入函数

bind

bind 参数类型和 call 相同,不过它不会执行函数,而是修改 this 后返回一个新的函数

  1. var func = function( a, b, c ){
  2.     alert ( [ a, b, c ] );
  3. };
  4. func.apply( null, [ 1, 2, 3 ] );
  5. func.call( null, 1, 2, 3 );

call 和 bind 是包装在 apply 上面的语法糖

  1. Function.prototype.call = function( context ){
  2.     var argus = [];
  3.     for (var i = 1; i < arguments.length; i++) {
  4.         argus.push(arguments[i]);
  5.     }
  6.     this.apply(context, argus);
  7. };
  8. Function.prototype.bind = function( context ){
  9.     var self = this// 保存原函数
  10.     // 返回一个新的函数
  11.     return function(){
  12.         // 执行新的函数的时候,会把之前传入的 context // 当作新函数体内的 this
  13.         return self.apply( context, arguments );
  14.     }
  15. };

用途

  1. 改变 this 指向
  2. 借用其他对象的方法:操作类数组(比如 arguments) 的时候,可以找 Array.prototype 对象借用方法Array.prototype.push.call(a, 'first' )

三、eval 和 with

词法作用域由写代码期间函数所声明的位置来定义,而 eval 和 with 可以在运行时修改词法作用域。

eval(..) 和 with 会在运行时修改或创建新的作用域,以此来欺骗其他在书写时定义的词 法作用域。 看起来它们能实现更复杂的功能,并且代码更具有扩展性。

但这些功能已经过时,性能也较差,使用不当容易导致难以预料的问题,已经不被提倡,严格模式下会被限制和禁止,所以不要轻易使用它们

eval

eval(..) 函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序中这个位置的代码。换句话说,可以在你写的代码中用程序生成代码并运行,就好像代码是写在那个位置的一样。

  1. function foo(str, a) {
  2.     eval( str );
  3.     console.log( a, b );
  4. }
  5. var b = 2;
  6. foo( "var b = 3;", 1 ); // 1, 3
  7. // 相当于
  8. function foo(str, a) {
  9.     var b = 3;
  10.     console.log( a, b );
  11. }

字符串的内容可以是一段动态生成的函数代码

JavaScript中 还有其他一些功能效果和eval(..)很相似。setTimeoutsetInterval 的第一个参数可以是字符串。

with

with 可以改变作用域,通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。

  1. var obj = {
  2.     a: 1,
  3.     b: 2,
  4.     c: 3
  5. };
  6. // 单调乏味的重复 "obj" 
  7. obj.a = 2;
  8. obj.b = 3;
  9. obj.c = 4;
  10. // 简单的快捷方式 
  11. with (obj) {
  12.     a = 3;
  13.     b = 4;
  14.     c = 5;
  15. }

 

 

 

相关文章

评论一下

暂无评论