《You Don't Know JS》阅读理解——this

1. this的诞生

假设咱们有一个speak函数,经过this的运行机制,当使用不一样的方法调用它时,咱们能够灵活的输出不一样的name。chrome

var me = {name: "me"};

function speak() {
  console.log(this.name);
}

speak.call(me) //me

可是若是没有this, 这时咱们须要显示的传递上下文给该函数。这时必须硬性的指定上下文,代码的复杂度增长,灵活性也欠缺。app

function speak(context) {
  console.log(context.name);
}

2. this的运行机制

2.1 运行原理

When a function is invoked, an activation record, otherwise known as an execution context, is created. This record contains information about where the function was called from (the call-stack), how the function was invoked, what parameters were passed, etc. One of the properties of this record is the this reference which will be used for the duration of that function's execution.函数

当函数被调用时, 函数会建立一个activation object(执行上下文), 这个对象包括了函数在哪里被调用(调用栈),函数的调用方式,传入的参数,以及this值。this

所以,咱们能够看到,this值是在函数调用时赋值的,而不是在声明的时候。是动态的。spa

2.2 运行规则

根据this的运做原理,咱们能够看到,this的值和调用栈(经过哪些函数的调用运行到调用当前函数的过程)以及如何被调用有关。prototype

2.2.1 Default Binding(默认绑定)

当函数是被独立调用时,this值在非严格模式下为全局对象, 严格模式下为undefined.debug

var a = 1;
function foo() {
  var a = 2;
  console.log(this.a);
}

function bar() {
  debuuger;
  foo();
}

bar();

打开chrome devtool能够看到,在调用foo时,函数的调用栈为bar -> foo,调用方式是独立调用,且是在非严格模式下,此时this值指向window,输出1。code

图片描述

2.2.2 Implicit Binding(隐式绑定)

var = 1;
function foo() {
  debugger;
  console.log(this.a);
}
var obj = {
  a: 2,
  foo: foo
}

obj.foo(); //2

此时,调用foo时,函数前加上了对obj这个对象的引用,输出obj.aorm

所以,若是有上下文对象引用了函数,隐式绑定规则会指定this值为该引用对象。对象

图片描述

可是咱们再看看下面这种状况。要注意的是,bar的值是对函数foo的引用,所以此时foo的调用并无上下文对象的引用,所以应用的是default binding, 输出1。要注意这种赋值的状况。

var a = 1;
function foo() {
  debugger;
  console.log(this.a);
}
var obj = {
  a: 2,
  foo: foo
}

var bar = obj.foo;

bar(); //1

2.2.3 Explicit Binding(显式绑定)

上面两种状况,要么this值为全局对象(非严格模式),要么经过对象方法调用,this指向调用的对象。
那我想不经过对象调用,而是独立调用时又能指定this值为某个对象呢?这时,call,apply就诞生了。它的第一个参数是this值,帮助咱们明确指定函数调用时this的值。

var a = 1;
function foo() {
  debugger;
  console.log(this.a);
}
var obj = {
  a: 2
}

foo.call(obj); //2

图片描述

经过call, apply,咱们能够在调用时明确指定this值。还有一种状况是,有时候咱们但愿this值绑定在咱们给定的对象上,而函数只须要接受一些参数。特别是在第三方库中,它会提供一种方法,接收方法须要的参数,可是不但愿你意外的修改了方法的this值,这时它可能会采用bind这种硬性绑定的方法明确的指出this值。

在ES5中提供了Function.prototype.bind,它的应用场景就是帮助你predicable的绑定this值。经常使用的应用场景为包裹函数、事件绑定函数、setTimeout中绑定this

//包裹函数,用来接受参数
function multiple(num) {
  console.log(this.pen, num);
  return this.pen * num;
}

var priceMapping = {
  pen: 10
}

function calTotalPrices() {
  return multiple.apply(priceMapping, arguments);
}

var total = calTotalPrices(3);
console.log(total); //30
//事件绑定
var states = {
  clickCount: 0
}
function clickHandler() {
  this.clickCount++;
  console.log(this.clickCount);
}
button.addEventListener('click', clickHandler.bind(states));

注意:当使用显示绑定时,若是第一个参数是null, undefined,则应用默认绑定规则。为避免传入null, undefined时错误的改变了全局值,最好建立一个空对象代替null, undefined

var ø = Object.create(null);

foo.call(ø);

2.2.4 new Binding(new绑定)

明白new的运做原理:

  1. 建立一个新对象;

  2. 对象连接到[[prototype]]上;

  3. this绑定到这个新对象上;

  4. 有显式的return,返回return,不然返回这个新对象。

2.2.5 优先级

new > 显示绑定(call,apply,bind) > 隐式绑定(方法调用) > 默认绑定(独立函数调用)

function foo(sth) {
  this.b = sth;
  console.log("a:", this.a, "b:", this.b);
}

var a = "window";
var obj1 = {
  a: "obj1",
  foo: foo,
}

var obj2 = {
  a: "obj2",
  foo: foo,
}

obj1.foo("obj1"); //a: obj1 b: obj1
obj1.foo.call(obj2, "obj2"); //a: obj2 b: obj2; 显示 > 隐式
var bar = foo.bind(obj1);
new bar("new"); //new > 显示

4. 箭头函数

箭头函数中的this并不适用于以上四种规则。由于这里的this不是使用的传统this机制,而是使用的词法做用域,根据外层的做用域来决定this。应用机制不同,该this也不能经过显示绑定来修改。

5. 总结

下一次再看到this的时候,咱们问本身两个问题:

  1. where to call: 函数的调用位置是?

  2. how to call: 函数的调用方法是?应用的规则是?

  3. 应用规则4条(按优先级排序):

    • new (新建立的对象)

    • 显式绑定 (绑定到指定对象,call, apply, bind:可预测的this);

    • 隐式绑定 (调用的上下文对象,注意间接引用的错误);

    • 默认绑定 (全局对象或undefined注意函数体是否为严格模式);

相关文章
相关标签/搜索