你不知道的JS-上(六)

19 阅读3分钟

你不知道的 JS-上

this 和对象原型

关于 this

为什么使用 this
function identify() {
  return this.name.toUpperCase();
}

function speak() {
  var greeting = "Hello, I'm " + identify.call(this);
  console.log(greeting);
}

var me = {
  name: "kyle",
};

var you = {
  name: "Reader",
};

identify.call(me); // KYLE
identify.call(you); // READER

speak.call(me); // Hello, I'm KYLE
speak.call(you); // Hello, I'm READER

这段代码可以在不同的上下文对象(me 和 you)中重复使用函数 identify() 和 speak(),不用针对每个对象编写不同版本的函数。

如果不使用 this,那就需要给 identify() 和 speak()显示传入一个上下文对象。

function identify(context) {
  return context.name.toUpperCase();
}

function speak(context) {
  var greeting = "Hello, I'm " + identify(context);
  console.log(greeting);
}

identify(you); //READER
speak(me); //hello,I'm KYLE

然而,this 提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将 API 设计得更加简洁并且易于复用。

人们很容易把 this 理解成指向函数自身,这个推断从英语的语法角度来说是说得通的。不过 this 并不是我们所想的那样指向函数本身。

function foo(num) {
  console.log("foo:" + num);

  //记录foo被调用的次数
  this.count++;
}

foo.count = 0;

var i;

for (i = 0; i < 10; i++) {
  if (i > 5) {
    foo(i);
  }
}

// foo: 6
// foo: 7
// foo: 8
// foo: 9

//foo 被调用了多少次?
console.log(foo.count); // 0 -- ?!

显然函数 foo(){..}中的 this.count 指的不是 foo.count,只是属性名称相同。foo(i)在全局作用域被调用,因此 this 指的是全局作用域,并且对 count 属性进行 LHS,从而在全局作用域隐式地创建了 count 属性。

非严格模式下,this 指向全局对象(在浏览器中是 window,在 Node.js 中是 global)。严格模式下,this 为 undefined,那么 this.count++会报错。

如果要从函数对象内部引用它自身,那只使用 this 是不够的。一般来说我们需要通过一个指向函数对象的词法标识符(变量)来引用它。

function foo() {
  foo.count = 4; // foo指向它自身
}

setTimeout(function () {
  // 匿名函数无法指向自身
}, 10);

第一个函数为具名函数,在他内部可以使用 foo 来引用自身。

但传入 setTimeout(..)的回调函数没有名称标识符,因此无法引用自身。

我们可以强制 this 指向 foo 函数对象:

function foo(num) {
  console.log("foo:" + num);

  // 记录foo被调用的次数
  // 注意,在当前的调用方式下(参见下方代码),this确实指向foo
  this.count++;
}

foo.count = 0;

var i;

for (i = 0; i < 10; i++) {
  if (i > 5) {
    // 使用call(..)可以确保this指向函数对象foo本身
    foo.call(foo, i);
  }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

// foo 被调用多少次?
console.log(foo.count); // 4
它的作用域

对 this 强制绑定常见的误解是,this 指向函数的作用域。这个问题有些复杂,在某种情况下是正确的,但在其他情况下却是错误的。

需要明确的是,this 在任何情况下都不指向函数的词法作用域。在 JS 内部,作用域和对象类似,可见的标识符都是它的属性。但是作用域“对象”无法通过 JavaScript 代码访问,它存在于 JS 引擎内部。

function foo() {
  var a = 2;
  this.bar();
}

function bar() {
  console.log(this.a);
}

foo(); // undefined

this 的值取决于函数被调用的方式。该例子中,foo 是直接调用的(而不是作为对象的方法调用),并且没有使用严格模式。因此,foo 函数中的 this 指向全局对象。全局中没有定义 a,所有显示 undefined。

this 是什么

this 是在运行时进行绑定的,并不是在编写时,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

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