作用域链this关键词

259 阅读5分钟

this关键词        

参考:阮一峰

this没有作用域的限制,嵌套函数不会从调用它的函数中继承this。如果嵌套函数作为方法调用this,this指向调用它的对象,如果嵌套函数作为函数调用,this值不是全局对象就是undefined。

this总是返回

一个对象,this就是属性或方法“当前”所在的对象。

一:由于对象的属性可以赋给另一个对象,所以属性所在的当前对象是可变的,即this的指向是可变的

var A = {
  name: '张三',
  describe: function () {
    return '姓名:'+ this.name;
  }
};

var B = {
  name: '李四'
};

B.describe = A.describe;
B.describe()
// "姓名:李四"

二:只要函数被赋给另一个变量,this的指向就会变。A.describe被赋值给变量f,内部的this就会指向f运行时所在的对象(本例是顶层对象)。

var A = {
  name: '张三',
  describe: function () {
    return '姓名:'+ this.name;
  }
};

var name = '李四';
var f = A.describe;
f() // "姓名:李四"

JavaScript 语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this就是函数运行时所在的对象(环境)。这本来并不会让用户糊涂,但是 JavaScript 支持运行环境动态切换,也就是说,this的指向是动态的,没有办法事先确定到底指向哪个对象。

使用场合:

一:全局环境

this === window // true

function f() {
  console.log(this === window);
}
f() // true

二:构造函数:指向实例对象

var Obj = function (p) {
  this.p = p;
};
var o = new Obj('Hello World!');
o.p // "Hello World!"

三:对象的方法

如果对象的方法里面包含thisthis的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this的指向。

var obj ={
  foo: function () {
    console.log(this);
  }
};

obj.foo() // obj

但是,下面这几种用法,都会改变this的指向。

// 情况一
(obj.foo = obj.foo)() // window
// 情况二
(false || obj.foo)() // window
// 情况三
(1, obj.foo)() // window

上面代码中,obj.foo就是一个值。这个值真正调用的时候,运行环境已经不是obj了,而是全局环境,所以this不再指向obj

可以这样理解,JavaScript 引擎内部,objobj.foo储存在两个内存地址,称为地址一和地址二。obj.foo()这样调用时,是从地址一调用地址二,因此地址二的运行环境是地址一,this指向obj。但是,上面三种情况,都是直接取出地址二进行调用,这样的话,运行环境就是全局环境,因此this指向全局环境。上面三种情况等同于下面的代码。

// 情况一
(obj.foo = function () {
  console.log(this);
})()
// 等同于
(function () {
  console.log(this);
})()

// 情况二
(false || function () {
  console.log(this);
})()

// 情况三
(1, function () {
  console.log(this);
})()

如果this所在的方法不在对象的第一层,这时this只是指向当前一层的对象,而不会继承更上面的层。

var a = {
  b: {
    m: function() {
      console.log(this.p);
    },
    p: 'Hello'
  }
};

var hello = a.b.m;
hello() // undefined

上面代码中,m是多层对象内部的一个方法。为求简便,将其赋值给hello变量,结果调用时,this指向了顶层对象。为了避免这个问题,可以只将m所在的对象赋值给hello,这样调用时,this的指向就不会变。

var hello = a.b;
hello.m() // Hello

使用注意点:

一:避免多层this

var o = {
  f1: function() {
    console.log(this);
    var that = this;
    var f2 = function() {
      console.log(that);
    }();
  }
}

o.f1()
// Object
// Object

二:避免数组处理方法中的this

数组的mapforeach方法,允许提供一个函数作为参数。这个函数内部不应该使用this

var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    var that = this;
    this.p.forEach(function (item) {
      console.log(that.v+' '+item);
    });
  }
}

o.f()
// hello a1
// hello a2

三:避免回掉函数中的this

var o = new Object();
o.f = function () {
函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。  console.log(this === o);
}

// jQuery 的写法
$('#button').on('click', o.f);

控制台会显示false。原因是此时this不再指向o对象,而是指向按钮的 DOM 对象,因为f方法是在按钮对象的环境中被调用的。

绑定this 的方法

JavaScript 提供了callapplybind这三个方法,来切换/固定this的指向。

一:Function.prototype.call()

函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。

var obj = {};
var f = function () {
  return this;
};

f() === window // true
f.call(obj) === obj // true

call方法可以改变this的指向,指定this指向对象obj,然后在对象obj的作用域中运行函数fcall方法的参数,应该是一个对象。如果参数为空、nullundefined,则默认传入全局对象.

func.call(thisValue, arg1, arg2, ...)

二:Function.prototype.apply()

apply方法的第一个参数也是this所要指向的那个对象,如果设为nullundefined,则等同于指定全局对象。第二个参数则是一个数组,该数组的所有成员依次作为参数,传入原函数。原函数的参数,在call方法中必须一个个添加,但是在apply方法中,必须以数组形式添加。

三:Function.prototype.bind()

var counter = {
  count: 0,
  inc: function () {
    this.count++;
  }
};

var func = counter.inc.bind(counter);
func();
counter.count // 1

代码中,counter.inc方法被赋值给变量func。这时必须用bind方法将inc内部的this,绑定到counter.

结合call方法使用,
利用bind方法,可以改写一些 JavaScript 原生方法的使用形式。


  • bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。